| name | managing-views |
| description | Creates, lists, deletes, and regenerates custom task visualizations. In cli / claude-desktop they are HTML files served by the local view server. In Cowork each custom view is registered as its own Live Artifact (id `waggle-view-{slug}`) that fetches data via window.cowork.callMcpTool. Use this skill whenever the user wants to create, delete, customize, or manage saved task visualizations — custom dashboards, filtered boards, or personalized views. Triggers on: "create view", "custom view", "make a dashboard", "view of", "delete view", "list views", "regenerate view", "my view", "build a board", "design visualization".
|
| user-invocable | true |
Waggle — Custom View Management
You manage custom task visualizations that users can create via natural language. The local HTML file at ~/.waggle/views/<slug>.html is the source of truth in every environment; how it reaches the user differs by execution_environment.
Output Discipline
This skill runs as a multi-step pipeline, but the user only needs its outcomes. Do not
narrate step transitions ("Now I'll...", "X done, next Y") and do not relay protocol
internals — provider detection, config/schema checks, cache state, validation plumbing,
view-server pushes. Surfacing them buries what actually matters.
Emit user-facing text only when it changes something for the user:
- a prompt or confirmation that needs their input
- an error or a warning
- an intermediate result that changes the outcome (e.g., a non-PASS quality verdict and
the gaps behind it — it explains why a task lands at a different status than expected)
- the final result summary
Session Bootstrap
Invoke the bootstrap-session skill to establish the active provider, the current user, and execution_environment. Skip if these are already set in this conversation.
Directory Setup
Custom views are stored outside the plugin directory so they survive updates:
mkdir -p ~/.waggle/views
Mode Selection
Pick the operation's transport based on execution_environment:
cli / claude-desktop → the local view server (localhost:3456) serves each ~/.waggle/views/<slug>.html at /custom/<slug>.html. Plain file create/delete/edit on disk.
cowork → each custom view is registered as a Live Artifact with id = "waggle-view-<slug>" via mcp__cowork__create_artifact. Updates go through mcp__cowork__update_artifact. Cowork has no delete API, so delete degrades to a stub-HTML replacement (see Delete below).
In both modes the local HTML at ~/.waggle/views/<slug>.html remains the canonical source — Cowork operations regenerate from it via generate-cowork-custom-artifact.sh.
Resolving the Notion-query MCP tool name (Cowork operations only)
Before any cowork operation that calls generate-cowork-custom-artifact.sh or create_artifact / update_artifact, look through your available MCP tools and find the one whose unqualified name is notion-query and that comes from the notion-extension MCP. Its full name typically looks like mcp__notion-extension__notion-query, but the exact prefix depends on the installed extension version's manifest — never hardcode it. Use that exact, full tool name as the 6th argument to the generator and as the value in the mcp_tools array when registering the artifact with Cowork. If no such tool is available, surface the failure and stop — the artifact cannot operate without it. Subsequent sections refer to this as "the resolved notion-query tool name".
Selection rule — more than one Notion query tool is normally present. A session typically exposes two unrelated families at once: the notion-extension MCP's notion-query, and the Notion connector's data-source tools (names like notion-query-data-sources, notion-fetch). Only the notion-extension one works here. Select by exact unqualified name equal to notion-query — not by prefix match, and not by "the first tool with notion and query in it", which would wrongly match notion-query-data-sources. If two tools both have the exact unqualified name notion-query, surface the ambiguity to the user and stop rather than guessing.
Operations
Create
When the user asks to create a custom view (e.g., "create a view showing blocked tasks by assignee", "make a dashboard for team progress"):
- Derive a slug from the user's description (e.g., "team progress dashboard" ->
team-progress-dashboard). See Naming Convention below.
- Generate a standalone HTML file using the Reference Template below. The template must include the
<!-- COWORK_BOOT --> marker inside <head> so Cowork-mode registration can inject the live-fetch adapter.
- Write it to
~/.waggle/views/<slug>.html.
Then, depending on execution_environment:
-
cli / claude-desktop: confirm creation and provide the URL http://localhost:3456/custom/<slug>.html. Open in the browser using platform detection (see viewing-tasks skill).
-
cowork: resolve tasksDatabaseId (and optional current_team) from headless_config. Determine the assignee to scope the view to — default to current_user.id; if the user explicitly asked for another person's view ("Alice's blocked tasks"), resolve via the looking-up-members skill and pass that Notion user ID. Also resolve the notion-query MCP tool name (see "Resolving the Notion-query MCP tool name" above) — required as the 6th argument. The generator bakes the assignee + a fixed Status != Done/Cancelled exclusion into the live-fetch adapter so the artifact's Notion query is scoped at the source:
SCRIPT=scripts/generate-cowork-custom-artifact.sh; SKILL_DIR="${CLAUDE_SKILL_DIR}"
if [ ! -d "$SKILL_DIR" ]; then _S="${PWD%%/mnt/*}"; _R="$_S/mnt/.remote-plugins"
case "$SKILL_DIR" in */plugin_*) _P="plugin_${SKILL_DIR#*/plugin_}"; SKILL_DIR="$_R/$_P"
if [ ! -f "$SKILL_DIR/$SCRIPT" ]; then _M=$(find "$_R/${_P%%/*}" -path "*/$SCRIPT" 2>/dev/null)
[ "$(printf %s | grep -c .)" = 1 ] && SKILL_DIR=; ;;
[ -f ] || { >&2; 1; }
bash \
\
\
\
\
> /tmp/waggle-view-<slug>.html
List
When the user asks to list custom views:
-
cli / claude-desktop:
ls ~/.waggle/views/*.html 2>/dev/null
Or use the API: curl -s http://localhost:3456/api/views | jq
-
cowork: call mcp__cowork__list_artifacts() and filter the response to entries where id == "waggle-tasks" (the primary dashboard) or id.startsWith("waggle-view-") (custom views). Surface each entry's id, name, and createdAt. Cowork's list_artifacts does not return the description or mcp_tools we pass on create — all metadata lives in the id.
Delete
Confirm deletion with the user before proceeding.
-
cli / claude-desktop:
rm ~/.waggle/views/<slug>.html
-
cowork: Cowork has no delete API, and id is immutable across update_artifact calls. Degrade as follows:
-
Remove the local source: rm -f ~/.waggle/views/<slug>.html.
-
Write a stub HTML to /tmp/waggle-view-<slug>-stub.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>View removed</title>
<style>body{font-family:-apple-system,sans-serif;padding:32px;color:#888;}</style>
</head>
<body>
<h2>This view has been removed.</h2>
<p>Dismiss this panel via the Cowork sidebar (✕).</p>
</body>
</html>
-
Call:
Regenerate
When the user asks to regenerate or update a custom view:
- Read the existing file at
~/.waggle/views/<slug>.html to understand what it does.
- Generate a new version incorporating the user's feedback. Keep the
<!-- COWORK_BOOT --> marker in <head>.
- Overwrite
~/.waggle/views/<slug>.html.
Then, depending on execution_environment:
-
cli / claude-desktop: the localhost server serves the updated file on next browser refresh.
-
cowork: re-run the cowork generator, then list_artifacts and dispatch on whether the artifact already exists — update_artifact if so, create_artifact if not. The fallback to create_artifact matters when the user previously deleted the view (which only sets a stub HTML) or when the local file exists but was never registered. Assignee binding caveat: Cowork's list_artifacts does not return the assigneeUserId baked into a previously-registered artifact, so the original scoping is irrecoverable at regenerate time. If the user originally scoped this custom view to someone other than themselves (e.g., Alice) and then asks for a plain "regenerate" without naming the assignee, the default below silently re-scopes to current_user.id. When in doubt, confirm with the user before defaulting — or have them re-state the assignee on the regenerate command (/managing-views regenerate <slug> for Alice). By default pass current_user.id as the 5th positional, or the Notion user ID of an explicitly-named person via the looking-up-members skill. Also resolve the notion-query MCP tool name (see "Resolving the Notion-query MCP tool name" above) for the 6th positional:
SCRIPT=scripts/generate-cowork-custom-artifact.sh; SKILL_DIR="${CLAUDE_SKILL_DIR}"
if [ ! -d "$SKILL_DIR" ]; then _S="${PWD%%/mnt/*}"; _R="$_S/mnt/.remote-plugins"
case "$SKILL_DIR" in */plugin_*) _P="plugin_${SKILL_DIR#*/plugin_}"; SKILL_DIR="$_R/$_P"
if [ ! -f "/" ]; _M=$(find -path 2>/dev/null)
[ = 1 ] && SKILL_DIR=; ;;
[ -f ] || { >&2; 1; }
bash \
\
\
\
\
> /tmp/waggle-view-<slug>.html
Reference Template
When generating a custom view HTML file, use this skeleton. Fill in the visualization logic based on the user's request. Keep the <!-- COWORK_BOOT --> marker in <head> unchanged — the Cowork generator replaces it with the live-fetch adapter; in cli / claude-desktop it stays a harmless HTML comment.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="view-name" content="VIEW_NAME_HERE">
<meta name="view-description" content="VIEW_DESCRIPTION_HERE">
<title>VIEW_NAME_HERE — Waggle</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
surface: { primary: 'rgba(28,28,30,0.8)', secondary: 'rgba(44,44,46,0.6)', tertiary: 'rgba(58,58,60,0.4)' },
label: { primary: '#ffffff', secondary: 'rgba(235,235,245,0.6)', : },
: { : , : , : , : , : , : },
}
}
}
}
Views
VIEW_NAME_HERE
Connecting...
Loading tasks...
Task Data Shape
Each task object in the tasks array has these fields:
interface Task {
id: string;
title: string;
description: string;
acceptanceCriteria: string;
status: "Backlog" | "Ready" | "In Progress" | "In Review" | "Done" | "Blocked" | "Cancelled";
blockedBy: string[];
priority: "Urgent" | "High" | "Medium" | "Low";
executor: string;
requiresReview: boolean;
executionPlan: string;
workingDirectory: string;
sessionReference: string;
dispatchedAt: string | null;
agentOutput: string;
errorMessage: string;
context: string;
artifacts: string;
repository: string | null;
startDate: string | null;
: | ;
: [];
: | ;
: | ;
: | ;
: { : ; : }[];
: | ;
: | ;
: ;
}
Naming Convention
Derive slugs from the user's description:
- "team progress dashboard" ->
team-progress-dashboard
- "blocked tasks by assignee" ->
blocked-tasks-by-assignee
- "priority heatmap" ->
priority-heatmap
Use lowercase, hyphens for spaces, remove special characters.
Design Guidelines
- Match the existing dark theme (black background, glass-morphism surfaces)
- Use the design system colors defined in the Tailwind config above
- The back link to
/selector.html is meaningful in localhost mode only; in Cowork it points to an artifact-internal path that won't resolve. The link can stay (harmless dead link in Cowork) or be hidden when window.__coworkFetch is defined.
- Include the status badge (
#sse-status) — the template's setStatus() helper renders "Live" (localhost) / "Live (Cowork)" / "Disconnected" / "Error" with consistent styling.
- Make visualizations interactive where possible (hover states, click-to-copy).
- Ensure the view is responsive.
- Render the Loading / Empty / Error triad consistently: the template ships with "Loading…" + "No tasks match this view." + "Failed to load tasks: …" states wired up. Reuse those rather than inventing per-view variants.