feat(image): --file metadata payload for annotated image cards (T-316)

`clide image show --file meta.json` reads a {path,label,description,caption}
payload so an image can carry a title and a longer description, not just a
one-line caption. ImageMessage + the image card render the richer metadata;
the bare `image show <path> [--caption]` form is unchanged. Honest userError
on a malformed/missing payload. Text annotation only (option a) — visual
marker overlays stay a follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-29 12:58:30 +02:00
co-authored by Claude Opus 4.8
parent 5f8fa6af10
commit f0fb5a5134
10 changed files with 207 additions and 17 deletions
+21
View File
@@ -8887,3 +8887,24 @@ RESOLUTION CORE DONE (2026-06-28): functional path resolution built + wired + te
DONE (2026-06-29): full D-104 surface landed + fast suite green. UI complete — Tools settings category (per-tool path field app.tools.<name> + Re-detect action tools.detect, live resolver rebuild on edit, en+nl catalogs) in new builtin.tools-settings; Problems panel flags a stale/broken pinned path. Storage is per-tool keys + a first-run marker. Commits dab0b319/0adccc5c/62418b40/bd4a8332/3be063d6/eac9612a/67edf732.', NULL, '2026-06-29 07:32:43', '2026-06-29 07:32:43.640', '2026-06-29 07:32:43.640', NULL, '0dfb6eeb1bcde2f386379f029fa4d0f6', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGYPJ6FTEPP4JK3D7D01ZSMM', 'status', 'in_progress', 'done', NULL, '2026-06-29 07:32:43', '2026-06-29 07:32:43.682', '2026-06-29 07:32:43.682', NULL, '45aeefee25353af62e4aa9f7b1f3265c', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGX26B0NAC9WRJMVB0QE6CV8', 'description', 'A conversation-pane drawing card (peer of the SVG card T-320) that renders a D2 diagram. SEPARATE card type from SVG, but the ONLY difference is a compile step in front: D2 source → compile to SVG → render through the SAME SVG widget (T-320). Blocked on T-320 for that reason.
CLI/parity (D-6): driven via the drawing-card dispatch (T-318) — ''clide draw --file pipeline.d2'' (type inferred from .d2), or template:"d2" with inline source in the drawing-card JSON. Honest userError on a d2 compile failure (bad source), mirroring image/svg validation.
UX: the rendered diagram LEADS; the d2 source folds into a collapsed ''view d2 source'' disclosure on the card (ClideCollapserCard pattern, T-305) so the code doesn''t dominate. Display-only per D-78.
Open: D2 compiler dependency — d2 is Go (there is a d2-diagram skill). Decide whether to shell out to a d2 binary (supporter-tool pattern, peer of pql) or vendor it; flag before building. Wireframe: docs/design/wireframes/conversation/d2-render-card.png.
PATH / tool-resolution dependency (2026-06-28): the d2 compile step shells out to the ''d2'' binary, so it relies on clide resolving d2 on PATH. clide already derives a login-shell PATH for every spawned tool — T-439 (consolidated the Linux PATH fix T-347), both DONE — so this is very likely already covered. ACCEPTANCE: verify clide resolves ''d2'' via that login-shell PATH on a Homebrew/linuxbrew install (d2 lives in /home/linuxbrew/.linuxbrew/bin). WATCH: a ''$SHELL -lc'' login probe can miss brew if ''brew shellenv'' is only in ~/.bashrc (login shells source .bash_profile/.profile, not .bashrc); if so, that''s a real follow-up on T-439''s probe surfaced from here. Honest userError if d2 is unresolved, hinting how to install/locate it. (The ''command -v d2'' miss seen during design was Claude Code''s own lean sandbox PATH, not clide.)', 'A conversation-pane drawing card (peer of the SVG card T-320) that renders a D2 diagram. SEPARATE card type from SVG, but the ONLY difference is a compile step in front: D2 source → compile to SVG → render through the SAME SVG widget (T-320). Blocked on T-320 for that reason.
CLI/parity (D-6): driven via the drawing-card dispatch (T-318) — ''clide draw --file pipeline.d2'' (type inferred from .d2), or template:"d2" with inline source in the drawing-card JSON. Honest userError on a d2 compile failure (bad source), mirroring image/svg validation.
UX: the rendered diagram LEADS; the d2 source folds into a collapsed ''view d2 source'' disclosure on the card (ClideCollapserCard pattern, T-305) so the code doesn''t dominate. Display-only per D-78.
Open: D2 compiler dependency — d2 is Go (there is a d2-diagram skill). Decide whether to shell out to a d2 binary (supporter-tool pattern, peer of pql) or vendor it; flag before building. Wireframe: docs/design/wireframes/conversation/d2-render-card.png.
PATH / tool-resolution dependency (2026-06-28): the d2 compile step shells out to the ''d2'' binary, so it relies on clide resolving d2 on PATH. clide already derives a login-shell PATH for every spawned tool — T-439 (consolidated the Linux PATH fix T-347), both DONE — so this is very likely already covered. ACCEPTANCE: verify clide resolves ''d2'' via that login-shell PATH on a Homebrew/linuxbrew install (d2 lives in /home/linuxbrew/.linuxbrew/bin). WATCH: a ''$SHELL -lc'' login probe can miss brew if ''brew shellenv'' is only in ~/.bashrc (login shells source .bash_profile/.profile, not .bashrc); if so, that''s a real follow-up on T-439''s probe surfaced from here. Honest userError if d2 is unresolved, hinting how to install/locate it. (The ''command -v d2'' miss seen during design was Claude Code''s own lean sandbox PATH, not clide.)
DONE (2026-06-29): d2 diagram template landed + fast suite green. Decision RESOLVED: shell out to the d2 binary (supporter-tool pattern, D-3/D-5) — not vendored. d2TemplateHandler compiles doc.source -> SVG via ''d2 - -'', resolved through the D-104 path layer (T-495); painted by the T-320 SVG renderer. CLI infers type from .d2 (and .svg renders directly). Template handlers now return DrawResult so an unresolved d2 or a compile failure surface as honest userError with an install hint. The d2 source folds into a collapsed ''view source'' disclosure (D-78). ACCEPTANCE VERIFIED LIVE: resolver found /home/linuxbrew/.linuxbrew/bin/d2 and compiled a -> b to a 13.5KB SVG end-to-end (the brew-on-linux case). Commits 64d77ec5 (core) + 5f8fa6af (disclosure).', NULL, '2026-06-29 10:23:55', '2026-06-29 10:23:55.745', '2026-06-29 10:23:55.745', NULL, '1ab9a75ee23a5332edc9e3459a92b6cd', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGX26B0NAC9WRJMVB0QE6CV8', 'status', 'backlog', 'done', NULL, '2026-06-29 10:23:55', '2026-06-29 10:23:55.781', '2026-06-29 10:23:55.781', NULL, 'ff09222d1e3a77f0a082caf302aedf07', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2AD3HPR3HXSVASVZEX8PK0', 'status', 'backlog', 'in_progress', NULL, '2026-06-29 10:41:42', '2026-06-29 10:41:42.262', '2026-06-29 10:41:42.262', NULL, '6ecda6d328b1cc8ade1d80e5f2de08f1', 2) ON CONFLICT(hash) DO NOTHING;
+52
View File
@@ -10678,3 +10678,55 @@ Storage: user-scope SettingsStore keyed by machine (D-93) — never committed. S
RESOLUTION CORE DONE (2026-06-28): functional path resolution built + wired + tested (14 dart-test cases). SupporterBinaries resolver (lib/src/env/supporter_binaries.dart): explicit override -> login-shell/process PATH -> well-known dirs INCLUDING Homebrew-on-Linux (the D-104 gap). loadSupporterBinaries: first-run auto-detect + persist (pinned, not re-probed); isStalePin flags a moved tool. Boot wiring in main.dart exposes the process-wide activeSupporterBinaries. This UNBLOCKS T-494 — the d2 template resolves d2 via activeSupporterBinaries.resolve(''d2''). REMAINING (UI follow-ons, not gating T-494): Config-tab override editor (view/edit/clear paths), a re-detect action, and Problems-panel surfacing of unresolved tools.
DONE (2026-06-29): full D-104 surface landed + fast suite green. UI complete — Tools settings category (per-tool path field app.tools.<name> + Re-detect action tools.detect, live resolver rebuild on edit, en+nl catalogs) in new builtin.tools-settings; Problems panel flags a stale/broken pinned path. Storage is per-tool keys + a first-run marker. Commits dab0b319/0adccc5c/62418b40/bd4a8332/3be063d6/eac9612a/67edf732.', 'done', 'medium', NULL, NULL, 'D-104', '2026-06-28 17:50:02.366', '2026-06-29 07:32:43.682', NULL, '70724bd4ac9c1da06f8d1c252492a4b0', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGX26B0NAC9WRJMVB0QE6CV8', 'task', '06FB2EDCBYRBDSV9V1PJ1KE3CM', 'D2 diagram card — compile d2 source to SVG, reuse the SVG render widget', 'A conversation-pane drawing card (peer of the SVG card T-320) that renders a D2 diagram. SEPARATE card type from SVG, but the ONLY difference is a compile step in front: D2 source → compile to SVG → render through the SAME SVG widget (T-320). Blocked on T-320 for that reason.
CLI/parity (D-6): driven via the drawing-card dispatch (T-318) — ''clide draw --file pipeline.d2'' (type inferred from .d2), or template:"d2" with inline source in the drawing-card JSON. Honest userError on a d2 compile failure (bad source), mirroring image/svg validation.
UX: the rendered diagram LEADS; the d2 source folds into a collapsed ''view d2 source'' disclosure on the card (ClideCollapserCard pattern, T-305) so the code doesn''t dominate. Display-only per D-78.
Open: D2 compiler dependency — d2 is Go (there is a d2-diagram skill). Decide whether to shell out to a d2 binary (supporter-tool pattern, peer of pql) or vendor it; flag before building. Wireframe: docs/design/wireframes/conversation/d2-render-card.png.
PATH / tool-resolution dependency (2026-06-28): the d2 compile step shells out to the ''d2'' binary, so it relies on clide resolving d2 on PATH. clide already derives a login-shell PATH for every spawned tool — T-439 (consolidated the Linux PATH fix T-347), both DONE — so this is very likely already covered. ACCEPTANCE: verify clide resolves ''d2'' via that login-shell PATH on a Homebrew/linuxbrew install (d2 lives in /home/linuxbrew/.linuxbrew/bin). WATCH: a ''$SHELL -lc'' login probe can miss brew if ''brew shellenv'' is only in ~/.bashrc (login shells source .bash_profile/.profile, not .bashrc); if so, that''s a real follow-up on T-439''s probe surfaced from here. Honest userError if d2 is unresolved, hinting how to install/locate it. (The ''command -v d2'' miss seen during design was Claude Code''s own lean sandbox PATH, not clide.)
DONE (2026-06-29): d2 diagram template landed + fast suite green. Decision RESOLVED: shell out to the d2 binary (supporter-tool pattern, D-3/D-5) — not vendored. d2TemplateHandler compiles doc.source -> SVG via ''d2 - -'', resolved through the D-104 path layer (T-495); painted by the T-320 SVG renderer. CLI infers type from .d2 (and .svg renders directly). Template handlers now return DrawResult so an unresolved d2 or a compile failure surface as honest userError with an install hint. The d2 source folds into a collapsed ''view source'' disclosure (D-78). ACCEPTANCE VERIFIED LIVE: resolver found /home/linuxbrew/.linuxbrew/bin/d2 and compiled a -> b to a 13.5KB SVG end-to-end (the brew-on-linux case). Commits 64d77ec5 (core) + 5f8fa6af (disclosure).', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-28 14:01:13.733', '2026-06-29 10:23:55.745', NULL, 'ed7a17c9fcf8d9759f35ec444383e2f6', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGX26B0NAC9WRJMVB0QE6CV8', 'task', '06FB2EDCBYRBDSV9V1PJ1KE3CM', 'D2 diagram card — compile d2 source to SVG, reuse the SVG render widget', 'A conversation-pane drawing card (peer of the SVG card T-320) that renders a D2 diagram. SEPARATE card type from SVG, but the ONLY difference is a compile step in front: D2 source → compile to SVG → render through the SAME SVG widget (T-320). Blocked on T-320 for that reason.
CLI/parity (D-6): driven via the drawing-card dispatch (T-318) — ''clide draw --file pipeline.d2'' (type inferred from .d2), or template:"d2" with inline source in the drawing-card JSON. Honest userError on a d2 compile failure (bad source), mirroring image/svg validation.
UX: the rendered diagram LEADS; the d2 source folds into a collapsed ''view d2 source'' disclosure on the card (ClideCollapserCard pattern, T-305) so the code doesn''t dominate. Display-only per D-78.
Open: D2 compiler dependency — d2 is Go (there is a d2-diagram skill). Decide whether to shell out to a d2 binary (supporter-tool pattern, peer of pql) or vendor it; flag before building. Wireframe: docs/design/wireframes/conversation/d2-render-card.png.
PATH / tool-resolution dependency (2026-06-28): the d2 compile step shells out to the ''d2'' binary, so it relies on clide resolving d2 on PATH. clide already derives a login-shell PATH for every spawned tool — T-439 (consolidated the Linux PATH fix T-347), both DONE — so this is very likely already covered. ACCEPTANCE: verify clide resolves ''d2'' via that login-shell PATH on a Homebrew/linuxbrew install (d2 lives in /home/linuxbrew/.linuxbrew/bin). WATCH: a ''$SHELL -lc'' login probe can miss brew if ''brew shellenv'' is only in ~/.bashrc (login shells source .bash_profile/.profile, not .bashrc); if so, that''s a real follow-up on T-439''s probe surfaced from here. Honest userError if d2 is unresolved, hinting how to install/locate it. (The ''command -v d2'' miss seen during design was Claude Code''s own lean sandbox PATH, not clide.)
DONE (2026-06-29): d2 diagram template landed + fast suite green. Decision RESOLVED: shell out to the d2 binary (supporter-tool pattern, D-3/D-5) — not vendored. d2TemplateHandler compiles doc.source -> SVG via ''d2 - -'', resolved through the D-104 path layer (T-495); painted by the T-320 SVG renderer. CLI infers type from .d2 (and .svg renders directly). Template handlers now return DrawResult so an unresolved d2 or a compile failure surface as honest userError with an install hint. The d2 source folds into a collapsed ''view source'' disclosure (D-78). ACCEPTANCE VERIFIED LIVE: resolver found /home/linuxbrew/.linuxbrew/bin/d2 and compiled a -> b to a 13.5KB SVG end-to-end (the brew-on-linux case). Commits 64d77ec5 (core) + 5f8fa6af (disclosure).', 'done', 'medium', NULL, NULL, NULL, '2026-06-28 14:01:13.733', '2026-06-29 10:23:55.780', NULL, 'bd4bf02fcf625ba819d85d2c3cd97f76', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2AD3HPR3HXSVASVZEX8PK0', 'task', '06FB2EDCBYRBDSV9V1PJ1KE3CM', 'clide image show: JSON metadata payload so images can be annotated (label/description/markers)', 'Give `clide image show` the same structured-metadata plumbing the icon card gets (T-313), so an image driven into the conversation can carry ANNOTATIONS — not just a single one-line caption.
## Why
Today image.show (lib/src/daemon/image_commands.dart) takes a `path` positional plus an optional `--caption` string, and ImageMessage (lib/builtin/claude/src/transcript_reader.dart:160) holds only `path` + optional one-line `caption`. When showing a screenshot/wireframe to discuss, one line is thin — we want to attach a title/label, a longer description, and potentially captioned markers (callouts on specific spots). This mirrors the per-entry label+description the icon card introduces.
## Deliverable
Extend image.show to accept a JSON metadata payload (via the new stdin plumbing T-315 and/or a `--file <path.json>` flag, same as T-313''s icon entries) describing the image''s annotations, e.g.:
{ "path": "docs/shot.png", "label": "HUD v3", "description": "note the cramped status row", "caption": "before the fix" }
Extend ImageMessage + the image card to render the richer metadata. Keep the existing `clide image show <path> --caption "…"` form working unchanged (back-compat); the JSON payload is additive.
## Open decision — text vs visual annotation
''Annotated'' could mean (a) richer TEXT metadata shown around the image (label + description + caption), or (b) VISUAL overlays drawn ON the image (markers/arrows/numbered callouts at coordinates). Start with (a) — straightforward extension of the current card. (b) is a bigger, clide-owned CustomPaint job (markers:[{x,y,label}] painted over the image); feasible since the image card is our own rendering, but scope it as a follow-up unless we decide we need it now. Flag which we want before building markers.
## Dependencies
- Pairs with T-315 (stdin JSON plumbing) for the piped form; can land with `--file` alone if T-315 isn''t ready.
- Parallel to T-313 (icon card) — same --file/--stdin metadata pattern, same Flutter-free handler + MessageBus publish path.
## Acceptance
- `clide image show` accepts a JSON metadata payload (via --file, and via --stdin once T-315 lands) carrying at least label + description alongside the existing caption.
- ImageMessage + the image card render the added metadata.
- The existing `image show <path> [--caption]` form is unchanged.
- Malformed payload / unknown fields fail with a clear userError, mirroring image.show''s current validation.
- Visual marker overlays are explicitly noted as a separate follow-up unless pulled in by decision.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-10 10:52:36', '2026-06-29 10:41:42.262', NULL, 'b2f5d954720a3a837142c31aaac94257', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);