Cowork D: artifact side panel with per-kind renderers #65

Merged
ryan merged 4 commits from feat/cowork-artifact-panel into main 2026-10-12 18:41:43 -04:00
Owner

Task D from docs/cowork-ui-plan.md.

What

  • Artifact panel (components/artifacts/ArtifactPanel.tsx + useArtifactPanel):
    • right-hand pane on lg: and up, a bottom sheet below that
    • tabs (shrink like browser tabs; the active one scrolls into view), collapse, loading/error/empty states
    • fetched artifacts cached by id; tabs reset when the conversation changes
  • Wiring: the timeline's Open chip opens the artifact. A tool_end carrying an artifact auto-opens it when the window is ≥ 1280 px wide (skipped if you've switched conversation). An Artifacts button lists every artifact in the conversation, so they can be reopened after a reload.
  • Renderers for all 9 v1 kinds, plus a JSON fallback for unknown kinds:
    • YNAB: summary tiles, category budget with available-vs-assigned bars and overspent rows, spending report (bars, or a monthly column chart), sortable transactions
    • Mealie: recipe (ingredients you can tick, steps, "Open in Mealie"), meal plan, shopping lists
    • Obsidian note: markdown, "Open in Obsidian"
    • search results
    • CSS only, no chart library
  • artifactService.ts for GET /api/artifacts.
  • Fixtures: one per kind in test-fixtures/cowork/artifacts/, reconciled with the shapes the backend really emits: positive spending, signed transactions, and start_date: null for open-ended searches.
  • One shared mock server (test-fixtures/cowork/mock-server.mjs) with C's timeline scenarios plus the artifact routes.

Test plan

  • yarn build; tsc --noEmit shows only the existing MessageInput.tsx error
  • Playwright against the mock server, 29 screenshots checked by eye:
    • auto-open at 1366 px
    • the Open chip at 1024 px
    • reload restoring the timeline and reopening an artifact
    • every renderer, the library, the error state, the mobile sheet
  • Coordinator review, 2 passes (reconciled with A/B's real shapes)

🤖 Generated with Claude Code

Task D from `docs/cowork-ui-plan.md`. ## What - **Artifact panel** (`components/artifacts/ArtifactPanel.tsx` + `useArtifactPanel`): - right-hand pane on `lg:` and up, a bottom sheet below that - tabs (shrink like browser tabs; the active one scrolls into view), collapse, loading/error/empty states - fetched artifacts cached by id; tabs reset when the conversation changes - **Wiring:** the timeline's **Open** chip opens the artifact. A `tool_end` carrying an artifact **auto-opens** it when the window is ≥ 1280 px wide (skipped if you've switched conversation). An **Artifacts** button lists every artifact in the conversation, so they can be reopened after a reload. - **Renderers** for all 9 v1 kinds, plus a JSON fallback for unknown kinds: - YNAB: summary tiles, category budget with available-vs-assigned bars and overspent rows, spending report (bars, or a monthly column chart), sortable transactions - Mealie: recipe (ingredients you can tick, steps, "Open in Mealie"), meal plan, shopping lists - Obsidian note: markdown, "Open in Obsidian" - search results - CSS only, no chart library - **`artifactService.ts`** for `GET /api/artifacts`. - **Fixtures:** one per kind in `test-fixtures/cowork/artifacts/`, reconciled with the shapes the backend really emits: positive spending, signed transactions, and `start_date: null` for open-ended searches. - **One shared mock server** (`test-fixtures/cowork/mock-server.mjs`) with C's timeline scenarios plus the artifact routes. ## Test plan - [x] `yarn build`; `tsc --noEmit` shows only the existing `MessageInput.tsx` error - [x] Playwright against the mock server, 29 screenshots checked by eye: - auto-open at 1366 px - the Open chip at 1024 px - reload restoring the timeline and reopening an artifact - every renderer, the library, the error state, the mobile sheet - [x] Coordinator review, 2 passes (reconciled with A/B's real shapes) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
ryan added 4 commits 2026-10-12 18:41:35 -04:00
artifactTypes.ts mirrors the contract (worker C creates the same file).
artifactService.ts wraps GET /api/artifacts and /api/artifacts/<id>
with the shared fetchWithAuth helper.

test-fixtures/cowork/artifacts/<kind>.json holds one ToolArtifact per
v1 kind. mock-server.mjs is a dependency-free Node server that serves
dist/ and speaks the contract (SSE v2 tool events, artifacts API) for
Playwright checks without Docker or OIDC.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Right-hand pane (440px, lg and up) with tabs, collapse rail and a
library of the conversation's artifacts; a bottom sheet below lg.
useArtifactPanel holds tabs/active and caches fetched artifacts by id;
tabs reset on conversation change.

Artifacts from tool_end auto-open on windows >= 1280px (newest wins,
and a panel the user collapsed stays collapsed). ChatScreen exposes
openArtifact for the activity timeline's onOpenArtifact and adds an
Artifacts button to the chat header.

One renderer per v1 kind (YNAB summary, category budget, spending
report, transactions; Mealie recipe, meal plan, shopping lists;
Obsidian note; document search) using CSS bars only, plus a JSON
fallback for unknown kinds or renderer errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Workers A/B are merged, so match what blueprints/ynab/tools.py and
conversation/agents.py actually emit:

- ynab.spending_report rows and ynab.summary.spent are positive for
  spending; bars use the positive amount and a net refund (negative
  row) sorts last with no bar.
- ynab.transactions start_date may be null (open-ended search); the
  header reads "Through Oct 12, 2026". Amounts stay signed with
  outflows as −$, and total is the net.
- Recipe image_url is never sent, search.documents has no source_ref,
  memo/age_of_money/goal_target are optional; fixtures now match
  (backend titles, ordering, float quantities, no empty fields).
- Mock server: drop the placeholder recipe image, positive month
  spending, and a "philz" open-ended transactions scenario.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One mock server now serves both the timeline scenarios and the artifact
panel's: per-kind fixtures from artifacts/<kind>.json, an "everything"
conversation with one saved tool message per kind, keyword scenarios
(recipe, budget, trend, philz, everything, broken, mystery, ...), a
500ing "broken" artifact, and small delays on /api/artifacts* so the
panel's loading states are visible. SPENDING now uses positive amounts
like the backend.

Also: renderers reuse ArtifactSourceRef, and the collapsed rail's
buttons say "Show <title>" so they don't share the timeline chip's
"Open <title>" accessible name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ryan merged commit 55c42fdfc1 into main 2026-10-12 18:41:43 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: ryan/simbarag#65