From 1194527ad5784f9ae754b21be7620010e68f9a81 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Wed, 10 Jun 2026 13:25:04 +0200 Subject: [PATCH] T-317: merge canvas work into one drawing-card epic (D-91 follow-up) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Records the T-7 -> T-317 merge on D-91's Relationship bullet (the former Tier-5 canvas+graph epic folds into the unified canvas epic; canvas pane T-322 + graph view T-323 consume the shared renderer) and the D-91 index line. Plus the changelog for this turn's board restructure: epic T-317, core engine T-318, templates T-319/320/321, re-parented T-313/315/316, T-7 cancelled as superseded, T-41 moved to T-276. Governance + .pql/changelog only — leaves concurrent lib/ work and CHANGELOG.md to the other worker on this branch. Co-Authored-By: Claude Opus 4.8 (1M context) --- .pql/changelog/ticket_deps/2026-06.sql | 1 + .pql/changelog/ticket_history/2026-06.sql | 33 ++++++++++++++++++++ .pql/changelog/ticket_idmap/2026-06.sql | 2 ++ .pql/changelog/tickets/2026-06.sql | 37 +++++++++++++++++++++++ governance/README.md | 1 + governance/decisions/architecture.md | 2 +- 6 files changed, 75 insertions(+), 1 deletion(-) diff --git a/.pql/changelog/ticket_deps/2026-06.sql b/.pql/changelog/ticket_deps/2026-06.sql index d5d4b7e5..75a74863 100644 --- a/.pql/changelog/ticket_deps/2026-06.sql +++ b/.pql/changelog/ticket_deps/2026-06.sql @@ -14,3 +14,4 @@ INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updat INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2ERREMEEF26KKHGNZBWW64', '06FB2ESCR4V6V07CRH18FBCDN8', '2026-06-10 11:12:18', '2026-06-10 11:12:18', NULL, 'c74a2c5d562157c24c11c8fa5d04799e', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2ERREMEEF26KKHGNZBWW64', '06FB2ETJQP0CT6X7W3CWZ6NS9G', '2026-06-10 11:12:20', '2026-06-10 11:12:20', NULL, 'cb20764a77d2a15290ed61a186542095', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2ERREMEEF26KKHGNZBWW64', '06FB2EV29HSK6EJ5VF50R87VC4', '2026-06-10 11:12:25', '2026-06-10 11:12:25', NULL, 'dd9434426790edcaa556221f74c431ba', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2ERREMEEF26KKHGNZBWW64', '06FB2G1WD1839Z90AQ5C0BHNV4', '2026-06-10 11:17:25', '2026-06-10 11:17:25', NULL, '5535e3d16bee2a2cdfcbeb84ea3fca99', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); diff --git a/.pql/changelog/ticket_history/2026-06.sql b/.pql/changelog/ticket_history/2026-06.sql index 6ce670ab..4164aaed 100644 --- a/.pql/changelog/ticket_history/2026-06.sql +++ b/.pql/changelog/ticket_history/2026-06.sql @@ -3085,3 +3085,36 @@ INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, chang 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 ('06FB234WP4Y6Q16A0HFW8BSXMG', 'parent_id', 'T-276', 'T-317', NULL, '2026-06-10 11:11:10', '2026-06-10 11:11:10', '2026-06-10 11:11:10', NULL, '994f7c1bd8f3f205dc7183cccf7d4329', 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 ('06FB2ACSDBDZARV3NNGYD9NYYR', 'parent_id', 'T-276', 'T-317', NULL, '2026-06-10 11:11:10', '2026-06-10 11:11:10', '2026-06-10 11:11:10', NULL, 'e0ba90285dafac31357bb999d8e12c36', 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 ('06FB16YM1Y910T07Y1Y22MGTEM', 'status', 'ready', 'done', NULL, '2026-06-10 11:16:41', '2026-06-10 11:16:41', '2026-06-10 11:16:41', NULL, '9873b3f3b5d107be1146224ac48b9b45', 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 ('06FB0TNQM7162Z26FDWXG35VH0', 'description', 'Tier 5 of the build plan: bring the canvas pane (Obsidian-style spatial whiteboard) and the graph view (force-directed view of vault links) to feature parity. + +**Canvas pane (`builtin.canvas`):** +- Render `.canvas` files (Obsidian-compatible JSON) — nodes (note, text, group, image), edges, layout state. +- Pan/zoom via `InteractiveViewer`; node selection, drag, resize. +- Edit affordances: add note from file picker, add text node, draw edge between nodes. +- Persist layout back to the `.canvas` file on disk. + +**Graph view (`builtin.graph`):** +- Force-directed layout of the current vault (or a filtered subset) — nodes are notes, edges are wikilinks. +- Hover highlights connected subgraph; click opens the note in the editor. +- Filter pane: tag include/exclude, file glob, depth-from-active. +- pql provides the link data (`pql backlinks` / `pql outlinks`); rendering owned in-app. + +Both panes use the existing `MultitabPane` (T-83) for tab management once they ship. Each lives in its own slot per D-47 (canvas in workspace, graph in context panel).', 'Tier 5 of the build plan: bring the canvas pane (Obsidian-style spatial whiteboard) and the graph view (force-directed view of vault links) to feature parity. + +**Canvas pane (`builtin.canvas`):** +- Render `.canvas` files (Obsidian-compatible JSON) — nodes (note, text, group, image), edges, layout state. +- Pan/zoom via `InteractiveViewer`; node selection, drag, resize. +- Edit affordances: add note from file picker, add text node, draw edge between nodes. +- Persist layout back to the `.canvas` file on disk. + +**Graph view (`builtin.graph`):** +- Force-directed layout of the current vault (or a filtered subset) — nodes are notes, edges are wikilinks. +- Hover highlights connected subgraph; click opens the note in the editor. +- Filter pane: tag include/exclude, file glob, depth-from-active. +- pql provides the link data (`pql backlinks` / `pql outlinks`); rendering owned in-app. + +Both panes use the existing `MultitabPane` (T-83) for tab management once they ship. Each lives in its own slot per D-47 (canvas in workspace, graph in context panel). + +MERGED (2026-06-10) into the unified canvas epic T-317 (decision D-91). The canvas rendering work consolidates onto one clide-owned canvas renderer driven by JSON; .canvas becomes an import format converted into that JSON rather than a native schema. T-7''s scope is preserved as children of T-317: canvas pane -> T-322, graph view -> T-323. This epic is cancelled as superseded; track the work under T-317.', NULL, '2026-06-10 11:17:31', '2026-06-10 11:17:31', '2026-06-10 11:17:31', NULL, 'e7d70c5b631bd6109f1b83b55c857c20', 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 ('06FB0TNQM67JSC5RKS6M9182KG', 'parent_id', 'T-7', 'T-276', NULL, '2026-06-10 11:18:12', '2026-06-10 11:18:12', '2026-06-10 11:18:12', NULL, 'df23887d88f2a45e8ad12a72a4b27407', 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 ('06FB0TNQM7162Z26FDWXG35VH0', 'status', 'backlog', 'cancelled', NULL, '2026-06-10 11:18:15', '2026-06-10 11:18:15', '2026-06-10 11:18:15', NULL, '64906dcc15f8f1b32034640a7e9aab82', 2) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/ticket_idmap/2026-06.sql b/.pql/changelog/ticket_idmap/2026-06.sql index 7eb8efd6..b674790c 100644 --- a/.pql/changelog/ticket_idmap/2026-06.sql +++ b/.pql/changelog/ticket_idmap/2026-06.sql @@ -146,3 +146,5 @@ INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_ INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2ESCR4V6V07CRH18FBCDN8', 'T-319', '2026-06-10 11:11:45', '2026-06-10 11:11:45', NULL, '12c5a57fb83444410cca98a802e9ba6f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2ETJQP0CT6X7W3CWZ6NS9G', 'T-320', '2026-06-10 11:11:55', '2026-06-10 11:11:55', NULL, '2266e362f144e4fd97403e1d1abe3eb4', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2EV29HSK6EJ5VF50R87VC4', 'T-321', '2026-06-10 11:11:59', '2026-06-10 11:11:59', NULL, 'd48b6bd9f0c8289c95448dda6865d666', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2G1WD1839Z90AQ5C0BHNV4', 'T-322', '2026-06-10 11:17:17', '2026-06-10 11:17:17', NULL, 'f33b1db4b67a0521f02c5c49fcfa8e6f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2G2KHKT5CJYR0TK1WQGMD0', 'T-323', '2026-06-10 11:17:23', '2026-06-10 11:17:23', NULL, '0738a8da475a6d8d49132d1e4e753775', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); diff --git a/.pql/changelog/tickets/2026-06.sql b/.pql/changelog/tickets/2026-06.sql index d5265dcd..d3cfd75b 100644 --- a/.pql/changelog/tickets/2026-06.sql +++ b/.pql/changelog/tickets/2026-06.sql @@ -2020,3 +2020,40 @@ Make the context block follow the standard collapser-card pattern established fo Keep the de-emphasis that D-78 calls for (muted accent, collapsed-by-default, first-line summary) — this is about giving it the same *frame* as the other cards, not the blue "you" accent. The same treatment likely applies to the sibling bare cards that share this branch (agent prompt, thinking) for consistency; scope to context first and note whether thinking/agent-prompt should follow. Acceptance: the context row sits in a framed card matching the collapser-card wireframe geometry, still muted and collapsed by default, with the toggle and counter/status slot aligned to the other cards. Update/extend the relevant golden(s).', 'done', 'medium', NULL, NULL, NULL, '2026-06-10 08:17:42', '2026-06-10 11:16:41', NULL, '5b583df100b64dbebe19c235f32e0541', 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 ('06FB2G1WD1839Z90AQ5C0BHNV4', 'story', '06FB2EDCBYRBDSV9V1PJ1KE3CM', 'Tier-5 canvas pane (builtin.canvas)', 'Tier-5 canvas PANE, absorbed from the former T-7 into the unified canvas epic (T-317, decision D-91). A full workspace pane that renders .canvas files by CONVERTING them into the drawing-card JSON (per D-91: .canvas is an import format, not a native schema) and painting via the shared canvas renderer (T-318). Scope: nodes (note, text, group, image) + edges + layout state; pan/zoom; node selection, drag, resize; edit affordances (add note from file picker, add text node, draw edge between nodes); persist layout back to the .canvas file on disk. Uses MultitabPane (T-83) for tabs and its own slot per D-47; panels are extension-shaped (D-17). Depends on the core renderer (T-318). NOTE: unlike the conversation drawing card (display-only, D-78), this pane is interactive/editable — it is a pane, not a conversation widget.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-10 11:17:17', '2026-06-10 11:17:17', NULL, 'a2f956a7f232bf7cd3d1e05dc679f1d7', 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 ('06FB2G2KHKT5CJYR0TK1WQGMD0', 'story', '06FB2EDCBYRBDSV9V1PJ1KE3CM', 'Tier-5 graph view (builtin.graph)', 'Tier-5 graph VIEW, absorbed from the former T-7 into the unified canvas epic (T-317). Force-directed layout of the vault (or a filtered subset): nodes are notes, edges are wikilinks. Hover highlights the connected subgraph; click opens the note in the editor. Filter pane: tag include/exclude, file glob, depth-from-active. pql provides the link data (pql backlinks / pql outlinks); rendering is owned in-app (CustomPaint, own-the-rendering-stack). Lives in its own slot per D-47 (context panel), uses MultitabPane (T-83). DISTINCT from the in-card graph TEMPLATE (T-321): T-321 renders a graph passed into the conversation drawing card; this is the full interactive graph pane over the whole vault. Relates to the core renderer (T-318) but has its own force-directed layout.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-10 11:17:23', '2026-06-10 11:17:23', NULL, 'cd97babed081736efcba6756ffec2ead', 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 ('06FB0TNQM7162Z26FDWXG35VH0', 'epic', NULL, 'Tier 5 — canvas and graph view', 'Tier 5 of the build plan: bring the canvas pane (Obsidian-style spatial whiteboard) and the graph view (force-directed view of vault links) to feature parity. + +**Canvas pane (`builtin.canvas`):** +- Render `.canvas` files (Obsidian-compatible JSON) — nodes (note, text, group, image), edges, layout state. +- Pan/zoom via `InteractiveViewer`; node selection, drag, resize. +- Edit affordances: add note from file picker, add text node, draw edge between nodes. +- Persist layout back to the `.canvas` file on disk. + +**Graph view (`builtin.graph`):** +- Force-directed layout of the current vault (or a filtered subset) — nodes are notes, edges are wikilinks. +- Hover highlights connected subgraph; click opens the note in the editor. +- Filter pane: tag include/exclude, file glob, depth-from-active. +- pql provides the link data (`pql backlinks` / `pql outlinks`); rendering owned in-app. + +Both panes use the existing `MultitabPane` (T-83) for tab management once they ship. Each lives in its own slot per D-47 (canvas in workspace, graph in context panel). + +MERGED (2026-06-10) into the unified canvas epic T-317 (decision D-91). The canvas rendering work consolidates onto one clide-owned canvas renderer driven by JSON; .canvas becomes an import format converted into that JSON rather than a native schema. T-7''s scope is preserved as children of T-317: canvas pane -> T-322, graph view -> T-323. This epic is cancelled as superseded; track the work under T-317.', 'backlog', 'medium', NULL, NULL, 'D-17', '2026-04-22 11:45:41', '2026-06-10 11:17:31', NULL, 'ff3683fb066e070dda96af9c5eeaf943', 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 ('06FB0TNQM67JSC5RKS6M9182KG', 'task', '06FB0TNQM5TWC00GW0P3X02HZW', 'Add images tab to context panel', 'Spec lists images as a right-panel section. No extension exists yet.', 'backlog', 'low', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-06-10 11:18:12', NULL, '17e5d66435d973f3771be7ac93104262', 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 ('06FB0TNQM7162Z26FDWXG35VH0', 'epic', NULL, 'Tier 5 — canvas and graph view', 'Tier 5 of the build plan: bring the canvas pane (Obsidian-style spatial whiteboard) and the graph view (force-directed view of vault links) to feature parity. + +**Canvas pane (`builtin.canvas`):** +- Render `.canvas` files (Obsidian-compatible JSON) — nodes (note, text, group, image), edges, layout state. +- Pan/zoom via `InteractiveViewer`; node selection, drag, resize. +- Edit affordances: add note from file picker, add text node, draw edge between nodes. +- Persist layout back to the `.canvas` file on disk. + +**Graph view (`builtin.graph`):** +- Force-directed layout of the current vault (or a filtered subset) — nodes are notes, edges are wikilinks. +- Hover highlights connected subgraph; click opens the note in the editor. +- Filter pane: tag include/exclude, file glob, depth-from-active. +- pql provides the link data (`pql backlinks` / `pql outlinks`); rendering owned in-app. + +Both panes use the existing `MultitabPane` (T-83) for tab management once they ship. Each lives in its own slot per D-47 (canvas in workspace, graph in context panel). + +MERGED (2026-06-10) into the unified canvas epic T-317 (decision D-91). The canvas rendering work consolidates onto one clide-owned canvas renderer driven by JSON; .canvas becomes an import format converted into that JSON rather than a native schema. T-7''s scope is preserved as children of T-317: canvas pane -> T-322, graph view -> T-323. This epic is cancelled as superseded; track the work under T-317.', 'cancelled', 'medium', NULL, NULL, 'D-17', '2026-04-22 11:45:41', '2026-06-10 11:18:15', NULL, '3e558a94afc3e675e673c6e9f9390855', 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); diff --git a/governance/README.md b/governance/README.md index dee9fbc8..2011a2fa 100644 --- a/governance/README.md +++ b/governance/README.md @@ -132,6 +132,7 @@ You might also want, project-permitting: - [D-88: clide-owned anchored popover + menu primitive](decisions/design.md#d-88-clide-owned-anchored-popover--menu-primitive) — _design_ - [D-89: inline pasted-image thumbnails that expand to the lightbox](decisions/design.md#d-89-inline-pasted-image-thumbnails-that-expand-to-the-lightbox) — _design_ - [D-90: clide:// deep links — paranoid allowlist + user confirmation](decisions/architecture.md#d-90-clide-deep-links--paranoid-allowlist--user-confirmation) — _architecture_ +- [D-91: Unified conversation drawing card backed by a canvas renderer](decisions/architecture.md#d-91-unified-conversation-drawing-card-backed-by-a-canvas-renderer) — _architecture_ ## Open questions diff --git a/governance/decisions/architecture.md b/governance/decisions/architecture.md index 4e2c906a..3c2fb513 100644 --- a/governance/decisions/architecture.md +++ b/governance/decisions/architecture.md @@ -466,7 +466,7 @@ Core, rendering, IPC, kernel, panel manager. - **Decision:** The conversation pane gets ONE drawing card — a clide-owned canvas renderer driven by a JSON document — instead of a growing set of one-off renderers (image card, icon card, …). The mental model is the **HTML ``**, not Obsidian's `.canvas` (Obsidian naming its feature "canvas" does not make its schema our pattern). The card's JSON can express raw **primitives** (rects/lines/text/etc. at coordinates — "draw a box at x,y, a line from x,y to x,y" works), but in the common case it runs in **template mode**: the JSON names a predefined component and the card grabs it — `image` (display + lightbox-on-click), `icon` (the multi-size glyph set, D-78/T-313), `compare-images` / before-after (two paths side by side), and — added incrementally — `svg`, `graph`, and more. Every drawn object may carry an optional **label + description**, rendered as a widget beneath it only when those fields are present in the JSON. Templates lower onto the same primitive scene the raw API accepts (hybrid: scene-graph core + high-level block sugar). The card stays **display-only** per [D-78] — selection/interaction happens in the interaction zone, not on the card. The same renderer is intended for **reuse as the `.canvas` viewer** elsewhere: a `.canvas` file is converted into this JSON rather than the card adopting Obsidian's schema natively. The already-shipped image card (T-249/T-252) is left in place for now and migrated onto this canvas once the canvas layer lands. - **Rationale:** Each new "show X in the conversation" need was trending toward another bespoke message type + widget + CLI verb. A single canvas that dispatches templates (and falls back to primitives) collapses that into one surface to build, test, and drive — new visuals become a new template/JSON shape, not a new renderer. Anchoring on HTML `` rather than `.canvas` keeps the model general (arbitrary drawing) instead of bending features to a third-party document schema, while still letting us ingest `.canvas` via conversion. Hybrid (templates over primitives) is the pragmatic middle: the common cards ship without hand-placing coordinates, but nothing is locked out of the primitive layer. Honors "own the rendering stack" — canvas is explicitly a clide-owned `CustomPaint`, not a packaged widget. - **Cost:** A real renderer with a JSON schema is more up-front design than another one-off widget, and the schema must stay coherent across the primitive and template layers as templates accrue. Templates expand over time ("we will expand the templates as we go") so the schema is deliberately open-ended; that flexibility is a standing maintenance surface. The `.canvas`→JSON converter is additional scope, deferred. Migrating the shipped image card is follow-up churn on working code. -- **Relationship:** Narrows [Q-4](../questions/architecture.md#q-4-canvas-schema-compatibility-with-obsidian) — clide's canvas is its own HTML-canvas-inspired JSON; Obsidian `.canvas` is an *import* format via conversion, not the native schema. Consumes the stdin/`--file` JSON input plumbing (T-315). Subsumes the standalone icon card (T-313) and image-annotation work (T-316) as templates of this card. +- **Relationship:** Narrows [Q-4](../questions/architecture.md#q-4-canvas-schema-compatibility-with-obsidian) — clide's canvas is its own HTML-canvas-inspired JSON; Obsidian `.canvas` is an *import* format via conversion, not the native schema. Consumes the stdin/`--file` JSON input plumbing (T-315). Subsumes the standalone icon card (T-313) and image-annotation work (T-316) as templates of this card. **Merges the former Tier-5 "canvas and graph view" epic (T-7) into one canvas epic (T-317):** the Tier-5 canvas *pane* (T-322, interactive/editable — distinct from the display-only conversation card) and graph *view* (T-323) consume the same shared renderer; T-7 is cancelled as superseded. The conversation drawing card stays display-only per [D-78]; the canvas pane is a full interactive pane. (D-17 "panels are extension-shaped" is unaffected and still governs the panes.) - **Raised by:** 2026-06-10 — user, while refining the icon-preview card (T-313): "make it all into one drawing card that receives a json input and selects based on the context inside the json what to draw … pull the entire thing closer to a dynamic canvas than a bunch of one-off renderers." Clarified the model is HTML `` (not Obsidian's), templates-over-primitives, per-object label/description, and reuse as the `.canvas` renderer; before/after comparisons, SVGs, icons, and graphs all become things you send into the card. ---