From d6ada86dad59ca52ad9b68190dbc6d6aac562139 Mon Sep 17 00:00:00 2001 From: John Tennant Date: Wed, 21 Oct 2026 13:17:32 -0400 Subject: [PATCH 01/12] Show nested Canvas tasks in Todos Co-authored-by: Simone <0828be588d5c9c4d092c5b1cf01761d5aef63d6b104807fd8e8284d517dd1a99@buzz.block.builderlab.xyz> Signed-off-by: John Tennant --- docs/plugin-architecture.md | 5 +- src/bundled/todos/Todos.module.css | 5 ++ src/bundled/todos/TodosPanel.test.tsx | 24 ++++++ src/bundled/todos/TodosPanel.tsx | 7 ++ src/bundled/todos/model.test.ts | 28 ++++++- src/bundled/todos/model.ts | 103 ++++++++++++++------------ tests/browser/todos.spec.mjs | 18 ++++- 7 files changed, 140 insertions(+), 50 deletions(-) diff --git a/docs/plugin-architecture.md b/docs/plugin-architecture.md index 0d90637b50..1fb6225e3c 100644 --- a/docs/plugin-architecture.md +++ b/docs/plugin-architecture.md @@ -442,8 +442,9 @@ The source of truth is ordinary Markdown in one root level-two `Todos` section: ordinary Markdown renders it as plain text. Canvases without it keep their existing meaning: `[ ]` is To do and `[x]`/`[X]` is Done. -Only top-level unordered checkbox items in that section are shown. Nested lists, -quotes and fenced examples are not tasks in this view. Doing and checkbox edits +Unordered checkbox items in that section are shown, including nested tasks. Each +nested task shows its parent-task path, including parents in other status groups. +Quotes and fenced examples are not tasks in this view. Doing and checkbox edits change one source byte; additions insert below the heading without rewriting other content. Duplicate Todos sections block editing until corrected in Canvas. Disabling removes diff --git a/src/bundled/todos/Todos.module.css b/src/bundled/todos/Todos.module.css index 66c8df4133..0daadb7406 100644 --- a/src/bundled/todos/Todos.module.css +++ b/src/bundled/todos/Todos.module.css @@ -108,3 +108,8 @@ flex: 1 1 16rem; margin: 0; } + +.parents { + grid-column: 1 / -1; + overflow-wrap: anywhere; +} diff --git a/src/bundled/todos/TodosPanel.test.tsx b/src/bundled/todos/TodosPanel.test.tsx index f4abf48ae8..7b68a294d6 100644 --- a/src/bundled/todos/TodosPanel.test.tsx +++ b/src/bundled/todos/TodosPanel.test.tsx @@ -94,6 +94,30 @@ const saved = () => waitFor(() => expect(screen.getByRole("status")).toHaveTextContent("Saved in Canvas"), ); +it("shows nested tasks with parent context across statuses and saves only the child checkbox", async () => { + const f = fixture(); + const content = + "## Todos\n\n- [/] Ship\n - [ ] Build\n - [x] Test\n - [ ] Publish\n\n## Keep\nUntouched.\n"; + f.canvas.read.mockResolvedValue({ ...head, content }); + render(); + const child = await screen.findByRole("checkbox", { name: "Build" }); + expect(within(child.closest("li")!).getByText("Ship")).toBeInTheDocument(); + const grandchild = screen.getByRole("checkbox", { name: "Test" }); + expect( + within(grandchild.closest("li")!).getByText("Ship / Build"), + ).toBeInTheDocument(); + await userEvent.setup().click(child); + await saved(); + expect(f.canvas.save).toHaveBeenCalledWith( + context.channelId, + content.replace("[ ] Build", "[x] Build"), + head.id, + ); + expect(screen.getByRole("checkbox", { name: "Ship" })).toHaveAttribute( + "aria-checked", + "mixed", + ); +}); it("adds and checks tasks, saves exact base, preserves other Markdown and restores saved content", async () => { const f = fixture(), user = userEvent.setup(); diff --git a/src/bundled/todos/TodosPanel.tsx b/src/bundled/todos/TodosPanel.tsx index 7c7eb19dd2..49b34fcbb2 100644 --- a/src/bundled/todos/TodosPanel.tsx +++ b/src/bundled/todos/TodosPanel.tsx @@ -563,6 +563,13 @@ export function TodosPanel({