chore(plan): file T-509 (model/effort feedback) + T-322 canvas progress

T-509 (child of the UI/UX epic T-276): investigate + improve the model
switch and effort toggle so a change is unmistakably confirmed. T-322
(canvas pane) moved to in_progress with its parser + renderer foundations
recorded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-02 22:39:16 +02:00
co-authored by Claude Opus 4.8
parent 7fa2d091bc
commit 717b854d19
2 changed files with 6 additions and 0 deletions
@@ -42,3 +42,6 @@ SCOPE NOTE (2026-06-29): currently a 17-line stub. This is a from-scratch intera
PROGRESS (2026-07-02): two foundation slices built + tested (from the 17-line stub). (1) JSONCanvas parser/model — lib/src/canvas/json_canvas.dart (2eb5d0f1): typed CanvasDoc (text/file/link/group nodes + edges with sides/ends/color/label), parse + toJson round-trip, skips unknown/malformed; pure Dart, 8 dart-test cases. (2) CustomPaint renderer — lib/builtin/canvas/src/canvas_painter.dart (035a37a1): CanvasBounds + pan/zoom CanvasViewport, group frames + edges (arrowheads) + node cards; node colours from the file (Obsidian presets/hex, NOT clide tokens per arbitrary-content rule), chrome themed; 7 tests (pixel-probe + viewport + colour). DECISION NOTE: the interactive pane paints the CanvasDoc model directly via CustomPaint (hit-testable, cheap re-paint) rather than re-lowering to SVG each frame — the ticket''s ''convert to drawing-card JSON'' path (D-91 SVG substrate) is for the display-only viewer; an editable pane needs direct model painting. Mirrors the graph pane. REMAINING (multi-session): interactive CanvasView (hit-test nodes + pan/zoom + click-select), pane shell (load .canvas from disk, workspace slot D-47, MultitabPane), CanvasExtension registration + open-.canvas routing, then edit affordances (add node/edge, drag, resize) + persist back to disk.', NULL, '2026-07-02 20:27:03', '2026-07-02 20:27:03.493', '2026-07-02 20:27:03.493', NULL, '7a661d8cc999f526e26cb1a14b7839c4', 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 ('06FJ91RYFXBJH99HJ18QTHY2B0', 'description', NULL, 'REPORTED (2026-07-02, Jeroen): can''t trust that the model switch and the effort toggle actually took effect when used — the UI feedback + positioning leave it uncertain whether the change applied. INVESTIGATE: what confirmation (if any) fires today when the model or effort is changed, and where it surfaces relative to the control. Likely-related surfaces: the Config tab controls (T-414), /effort ownership (T-412), account/model settings control. IMPROVE: give an unmistakable, well-positioned confirmation that the change landed — e.g. the control reflects the new value immediately (selected state), and/or a brief toast/inline acknowledgement near the control, not somewhere the eye isn''t. Goal: after a switch, the user is certain which model/effort is now active.', NULL, '2026-07-02 20:31:02', '2026-07-02 20:31:02.807', '2026-07-02 20:31:02.807', NULL, '5030849d297f6312073802a0641a2594', 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 ('06FB2G1WD1839Z90AQ5C0BHNV4', 'status', 'backlog', 'in_progress', NULL, '2026-07-02 20:35:12', '2026-07-02 20:35:12.739', '2026-07-02 20:35:12.739', NULL, 'd971e23e05c7547672b6989ca0a98434', 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 ('06FJ91RYFXBJH99HJ18QTHY2B0', 'description', 'REPORTED (2026-07-02, Jeroen): can''t trust that the model switch and the effort toggle actually took effect when used — the UI feedback + positioning leave it uncertain whether the change applied. INVESTIGATE: what confirmation (if any) fires today when the model or effort is changed, and where it surfaces relative to the control. Likely-related surfaces: the Config tab controls (T-414), /effort ownership (T-412), account/model settings control. IMPROVE: give an unmistakable, well-positioned confirmation that the change landed — e.g. the control reflects the new value immediately (selected state), and/or a brief toast/inline acknowledgement near the control, not somewhere the eye isn''t. Goal: after a switch, the user is certain which model/effort is now active.', 'REPORTED (2026-07-02, Jeroen): can''t trust that the model switch and the effort toggle actually took effect when used — the UI feedback + positioning leave it uncertain whether the change applied. INVESTIGATE: what confirmation (if any) fires today when the model or effort is changed, and where it surfaces relative to the control. Likely-related surfaces: the Config tab controls (T-414), /effort ownership (T-412), account/model settings control. IMPROVE: give an unmistakable, well-positioned confirmation that the change landed — e.g. the control reflects the new value immediately (selected state), and/or a brief toast/inline acknowledgement near the control, not somewhere the eye isn''t. Goal: after a switch, the user is certain which model/effort is now active.
EVIDENCE + ROOT CAUSE (2026-07-02, from a /model screenshot). The confirmation renders as a raw, unstyled line: literally ''<local-command-stdout>Set model to claude-fable-5[1m] (claude-fable-5)</local-command-stdout>'', shown under the ''you'' (user) speaker stripe. Three distinct defects in that one line: (a) the CLI''s <local-command-stdout> wrapper tag is displayed verbatim instead of being unwrapped; (b) an ANSI SGR bold code leaks through as literal ''[1m]'' (a \x1b[1m) — slash-command stdout is not ANSI-stripped; (c) it is attributed to the USER speaker, so a system/command acknowledgement masquerades as something the user typed. ROOT CAUSE: clide has NO handling for local-command-stdout at all (grep in lib/ = zero hits) — the slash-command result falls through and is rendered as plain user prose. ENTRY POINTS for the fix: inbound message parse / role attribution in lib/builtin/claude/src/transcript_reader.dart; existing ANSI-strip refs in transcript_reader.dart + claude_composer.dart. IMPROVE: detect local-command-stdout content, unwrap the tag + strip ANSI, and render it as a distinct, clearly-positioned system/command-acknowledgement (NOT the user stripe) so a /model or /effort change is an unmistakable confirmation. Affects all slash-command stdout (model, effort, etc.), so fix at the render/parse path, not per-command.', NULL, '2026-07-02 20:39:06', '2026-07-02 20:39:06.684', '2026-07-02 20:39:06.684', NULL, '02ea6e2dbb60929aed687275f5329a4f', 2) ON CONFLICT(hash) DO NOTHING;
+3
View File
@@ -43,3 +43,6 @@ INSERT INTO tickets (record_id, type, parent_record_id, title, description, stat
SCOPE NOTE (2026-06-29): currently a 17-line stub. This is a from-scratch interactive Tier-5 pane (parse .canvas -> drawing-card JSON, CustomPaint render, pan/zoom, node select/drag/resize, edit affordances, persist .canvas) — a multi-session feature, NOT template-class work. The drawing-card TEMPLATE half of epic T-317 is now complete (svg/d2/image/icon/compare/graph + stdin).
PROGRESS (2026-07-02): two foundation slices built + tested (from the 17-line stub). (1) JSONCanvas parser/model — lib/src/canvas/json_canvas.dart (2eb5d0f1): typed CanvasDoc (text/file/link/group nodes + edges with sides/ends/color/label), parse + toJson round-trip, skips unknown/malformed; pure Dart, 8 dart-test cases. (2) CustomPaint renderer — lib/builtin/canvas/src/canvas_painter.dart (035a37a1): CanvasBounds + pan/zoom CanvasViewport, group frames + edges (arrowheads) + node cards; node colours from the file (Obsidian presets/hex, NOT clide tokens per arbitrary-content rule), chrome themed; 7 tests (pixel-probe + viewport + colour). DECISION NOTE: the interactive pane paints the CanvasDoc model directly via CustomPaint (hit-testable, cheap re-paint) rather than re-lowering to SVG each frame — the ticket''s ''convert to drawing-card JSON'' path (D-91 SVG substrate) is for the display-only viewer; an editable pane needs direct model painting. Mirrors the graph pane. REMAINING (multi-session): interactive CanvasView (hit-test nodes + pan/zoom + click-select), pane shell (load .canvas from disk, workspace slot D-47, MultitabPane), CanvasExtension registration + open-.canvas routing, then edit affordances (add node/edge, drag, resize) + persist back to disk.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-10 11:17:17', '2026-07-02 20:35:12.739', NULL, '3e2c84564d3b6cf59f8c8f0e8af1b0b5', 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 ('06FJ91RYFXBJH99HJ18QTHY2B0', 'bug', '06FB0TNQM5TWC00GW0P3X02HZW', 'Model switch + effort toggle: unclear UI feedback', 'REPORTED (2026-07-02, Jeroen): can''t trust that the model switch and the effort toggle actually took effect when used — the UI feedback + positioning leave it uncertain whether the change applied. INVESTIGATE: what confirmation (if any) fires today when the model or effort is changed, and where it surfaces relative to the control. Likely-related surfaces: the Config tab controls (T-414), /effort ownership (T-412), account/model settings control. IMPROVE: give an unmistakable, well-positioned confirmation that the change landed — e.g. the control reflects the new value immediately (selected state), and/or a brief toast/inline acknowledgement near the control, not somewhere the eye isn''t. Goal: after a switch, the user is certain which model/effort is now active.
EVIDENCE + ROOT CAUSE (2026-07-02, from a /model screenshot). The confirmation renders as a raw, unstyled line: literally ''<local-command-stdout>Set model to claude-fable-5[1m] (claude-fable-5)</local-command-stdout>'', shown under the ''you'' (user) speaker stripe. Three distinct defects in that one line: (a) the CLI''s <local-command-stdout> wrapper tag is displayed verbatim instead of being unwrapped; (b) an ANSI SGR bold code leaks through as literal ''[1m]'' (a \x1b[1m) — slash-command stdout is not ANSI-stripped; (c) it is attributed to the USER speaker, so a system/command acknowledgement masquerades as something the user typed. ROOT CAUSE: clide has NO handling for local-command-stdout at all (grep in lib/ = zero hits) — the slash-command result falls through and is rendered as plain user prose. ENTRY POINTS for the fix: inbound message parse / role attribution in lib/builtin/claude/src/transcript_reader.dart; existing ANSI-strip refs in transcript_reader.dart + claude_composer.dart. IMPROVE: detect local-command-stdout content, unwrap the tag + strip ANSI, and render it as a distinct, clearly-positioned system/command-acknowledgement (NOT the user stripe) so a /model or /effort change is an unmistakable confirmation. Affects all slash-command stdout (model, effort, etc.), so fix at the render/parse path, not per-command.', 'backlog', 'medium', NULL, NULL, NULL, '2026-07-02 20:31:02.783', '2026-07-02 20:39:06.684', NULL, '8678946b438ae5cbdff677bb73949374', 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);