Renders task data as interactive HTML dashboards (Kanban, List, Calendar, Gantt). In cli / claude-desktop the dashboard is served by a local view server on localhost:3456. In Cowork the dashboard is registered as a Live Artifact that fetches Notion data directly via window.cowork.callMcpTool. Use this skill whenever the user wants to see tasks visually — board views, timelines, calendars, or any kind of task visualization or dashboard display. Read-only views only. For any write, or a scoped filter that may lead to a mutation, route through `managing-tasks` instead. Triggers on: "kanban", "list view", "show tasks", "view", "visualize", "gantt", "calendar", "board", "timeline", "display tasks", "open dashboard".
Renders task data as interactive HTML dashboards (Kanban, List, Calendar, Gantt). In cli / claude-desktop the dashboard is served by a local view server on localhost:3456. In Cowork the dashboard is registered as a Live Artifact that fetches Notion data directly via window.cowork.callMcpTool. Use this skill whenever the user wants to see tasks visually — board views, timelines, calendars, or any kind of task visualization or dashboard display. Read-only views only. For any write, or a scoped filter that may lead to a mutation, route through `managing-tasks` instead. Triggers on: "kanban", "list view", "show tasks", "view", "visualize", "gantt", "calendar", "board", "timeline", "display tasks", "open dashboard".
user-invocable
true
Waggle — Tasks Dashboard
You render the user's tasks as an interactive dashboard. The transport depends on the execution environment; the user-visible interface (Kanban / List / Calendar / Gantt) is the same in every 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 data pushes (POST /api/data). 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
the final result summary
The view server URL (http://localhost:3456) and dashboard status are this skill's final
result and must always surface.
Session Bootstrap
Invoke the skill to establish the active provider, the current user, and . Skip if these are already set in this conversation.
bootstrap-session
execution_environment
Mode Selection
Pick the transport based on execution_environment (set by bootstrap):
cowork → use Cowork Live Artifact Mode (below). localhost is not reachable from the user's browser in Cowork.
cli / claude-desktop → use Localhost Server Mode (below). The local view server hosts the dashboard at http://localhost:3456.
The two modes use the same Task data shape and the same set of view renderers (kanban / list / calendar / gantt). Only the transport and the host shell differ.
Cowork Live Artifact Mode
In Cowork, the dashboard is a single Live Artifact (id = "waggle-tasks") that bundles all four view renderers with a tab strip at the top. The artifact fetches Notion data itself via the notion-query tool from the notion-extension MCP; refresh is driven by Cowork's built-in ↻ button or the in-page refresh, both of which re-run the artifact JS.
Steps
Resolve tasksDatabaseId from headless_config (set during bootstrap). If current_team is set, capture current_team.id / current_team.name for baking into the artifact.
Determine the assignee to scope the artifact to. By default this is current_user.id (the person opening the artifact almost always wants their own open tasks, not the entire workspace). If the user has explicitly asked to view another person's tasks ("show Alice's board", "build a dashboard for the platform team lead"), resolve that person via the looking-up-members skill and use their Notion user ID instead.
Resolve the Notion-query MCP tool name. 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 5th argument to the generator below and as the value in the mcp_tools array when calling create_artifact / update_artifact. If you cannot find such a tool, surface the failure to the user and stop — the artifact cannot operate without it.
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 is
correct here; the connector's tools take different parameters and are not what the
artifact's callMcpTool bridge expects. Select by exact unqualified name equal to
notion-query — not by prefix match, not by "the first tool with notion and
query in it", which would 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.
Generate the bundled HTML. The 4th argument is the assignee Notion user ID; the 5th argument is the resolved MCP tool name from Step 3. The bundle will server-side filter to that assignee AND exclude Done/Cancelled at the Notion query layer:
SCRIPT=scripts/generate-cowork-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 "$_M" | grep -c .)" = 1 ] && SKILL_DIR="${_M%/$SCRIPT}"; fi ;;
esacfi
[ -f "$SKILL_DIR/$SCRIPT" ] || { echo"waggle: skill directory unresolved; $SCRIPT not found. Artifact not generated." >&2; exit 1; }
bash "$SKILL_DIR/$SCRIPT" \
"<tasksDatabaseId>" \
"<current_team.id or empty>" \
"<current_team.name or empty>" \
"<assignee notion user id, e.g. current_user.id>" \
\
> /tmp/waggle-tasks.html
The leading eight lines resolve the skill directory for the runtime the shell is
actually running in. This branch runs on Cowork, where the agent loop is native to
the host and Bash runs in a separate VM, so the substituted ${CLAUDE_SKILL_DIR}
path does not exist for the shell. See the provider-contract skill for the
rationale. Resolution and invocation must stay in the same Bash call. If the block
reports the directory unresolved, stop and tell the user the dashboard could not be
generated — do not hand-write the artifact HTML.
Pass an empty string for the 4th argument only if the user has explicitly asked for an unscoped view across all assignees; the bundle then shows all open tasks with an informational banner. Status exclusion (Done + Cancelled) is always applied — these terminal states are never useful in the active dashboard.
Call mcp__cowork__list_artifacts() and check whether the response includes an entry with id == "waggle-tasks".
If the artifact already exists, refresh it in place via update_artifact (don't create a duplicate):
mcp__cowork__update_artifact({
id: "waggle-tasks",
html_path: "/tmp/waggle-tasks.html",
update_summary: "[REFRESH] regenerated against latest schema / team / assignee scope",
mcp_tools: ["<the full MCP tool name you resolved in Step 3>"]
})
This re-bakes the latest databaseId / currentTeam / assigneeUserId and picks up any code changes since the last registration. The user gets the Cowork approval prompt on update.
If the artifact does not exist, register it via create_artifact:
mcp__cowork__create_artifact({
id: "waggle-tasks",
html_path: "/tmp/waggle-tasks.html",
description: "Waggle Tasks Dashboard — Kanban / List / Calendar / Gantt",
mcp_tools: ["<the full MCP tool name you resolved in Step 3>"]
})
Clean up the temp file: rm -f /tmp/waggle-tasks.html. Tell the user to open the waggle-tasks Live Artifact panel in the Cowork sidebar (or, on update, reload the existing panel).
Cowork-mode behavior
The artifact bundles Kanban / List / Calendar / Gantt; the active tab is persisted per-user in localStorage (waggle-tasks-active-tab-v1).
Each artifact reload re-fetches via the notion-extension notion-query tool resolved in Step 3 — the one baked into mcp_tools, not whichever Notion tool the session happens to list first (paginated, cap 1000 rows). The fetch is server-side filtered to the baked assigneeUserId and always excludes Status == Done / Status == Cancelled; the bundled filter-bar.js narrows further on the client. The status badge reads "Live (Cowork)" when the fetch succeeds. To switch the bound assignee, re-run /viewing-tasks with the new person's name — the skill regenerates and calls update_artifact with the new scope.
The artifact is read-only. Mutating Notion tools are deliberately not declared in mcp_tools yet; inline-edit UI will come in a later skill release and will widen mcp_tools via update_artifact.
Cold-start race (GitHub Issue #55788): on either Windows or macOS the artifact's first call to callMcpTool may fail with HTTP 400 in a cold-start state. Workaround: ask the user to invoke any Notion MCP tool from the Cowork chat once before opening the artifact, then reload the panel.
Custom user-defined views are managed by the managing-views skill and registered as separate waggle-view-<slug> artifacts.
Troubleshooting
"Cowork runtime unavailable" banner in the artifact: the cold-start race fired. Reload the panel, or have the user run any Notion MCP tool from chat first.
"Failed to load tasks: ...": open DevTools on the artifact panel (right-click → Inspect). Network tab shows the callMcpTool request; Console shows any JS errors. Verify the baked databaseId matches the active tasksDatabaseId.
Tool call failed: 400 from the artifact (with chat-mode calls succeeding) — historical observation; does not reproduce in current environments. It was reported that the Live Artifact bridge rejects extension tool prefixes containing underscores while accepting uppercase ones (mcp__EchoUpper__... worked, mcp__echo_lower_only__... returned 400). The prefix derives from the extension manifest's display_name, and a display_name containing whitespace normalizes the whitespace to _, producing such a prefix.
The stated mitigation — install notion-extension v0.5.0+, which drops display_name and yields a hyphenated mcp__notion-extension__... prefix — is already in effect: every Notion tool prefix present in a current session is hyphen-only, so the failure condition no longer arises. Keep the version floor for that reason.
The underlying claim about the bridge is untested and is recorded here as history, not as a live hazard. If a 400 does appear, check the prefix for an underscore before assuming this is the cause, and look for the more common explanations first (the cold-start race above, or a stale baked databaseId).
Dashboard shows stale data: click the Cowork built-in ↻, which re-executes the artifact JS and re-fetches.
Stale artifact (schema changed, wrong team, wrong assignee): re-run /viewing-tasks — the skill regenerates and calls update_artifact with the latest databaseId / team / assignee binding.
"My dashboard is empty / shows the wrong person's tasks": the baked assigneeUserId may not match the user's expectations. Re-run /viewing-tasks (defaults to current_user.id) or /viewing-tasks <name> to scope to someone else. To see everyone, ask for an unscoped regeneration explicitly.
Localhost Server Mode
In cli / claude-desktop, the dashboard is served from a local Hono server on http://localhost:3456. The skill ensures the server is running, pushes the current task snapshot, and opens the browser.
The resolver applies to cd exactly as it does to bash — the rule is about reaching
a bundled file from the shell, not about which command does the reaching. This mode
only runs on cli / claude-desktop, where the [ ! -d ] test is false and the branch
is skipped; the block is present so the failure mode is an explicit error rather than a
cd into a nonexistent directory. See the provider-contract skill for the rationale.
After ensuring the server is running, open the appropriate URL in the user's browser:
# macOS
open http://localhost:3456/kanban.html
# Linux
xdg-open http://localhost:3456/kanban.html
# WSL
wslview http://localhost:3456/kanban.html
Detect the platform and use the appropriate command.
Initializing Data After Start
After starting the server, push current task data so the view is populated.
Follow the Pushing Data to View Server section in the active provider's SKILL.md to:
Fetch all tasks from the data source
Format as { "tasks": [...], "updatedAt": "<ISO timestamp>", "currentTeam": { "id": "<id>", "name": "<name>" } } (include currentTeam if current_team is set from resolving-identity; omit if null)
POST to http://localhost:3456/api/data
Custom Views
Users can create custom visualizations using the managing-views skill. Custom views are served at /custom/<slug>.html.
Opening a Custom View
# macOS
open http://localhost:3456/custom/<slug>.html
# Linux
xdg-open http://localhost:3456/custom/<slug>.html
# WSL
wslview http://localhost:3456/custom/<slug>.html
Listing Custom Views
ls ~/.waggle/views/*.html
To create, delete, or regenerate custom views, use the managing-views skill.
View Features (Localhost Mode)
All views support:
Real-time updates: Connected to SSE at /api/events. Changes made via managing-tasks skill are reflected automatically.
Client-side filtering: Filter by Status, Priority, search text
Click-to-copy: Click a task to copy its ID for use in Claude Code
Dark mode: Default dark theme
Troubleshooting (Localhost Mode)
If views don't update after task changes:
Check the server is running: curl http://localhost:3456/api/health
Manually push data: use the managing-tasks skill to query tasks and POST to /api/data
Check server logs in the terminal where it's running