2026-07-28 12:11:09 -04:00

479 lines
23 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# SLICE 10 — Separate child-notebook kanban and matrix views
> **State-saving rule:** update this file and `TASKS.md` after every completed
> task and whenever work pauses. Automated checks and manual Joplin acceptance
> must be recorded separately.
## Status
**COMPLETE.** Phases 18 are implemented and validated. Automated checks and
manual Joplin acceptance passed; README, SPEC, and v1.0.0 CHANGELOG coverage are
complete.
## Goal
When a kanban or matrix block uses `scope: children` with `group: notebook`,
render a separate complete view for the root notebook and each descendant notebook that owns eligible
cards. This keeps large notebook trees browsable without nesting subsections
inside one board or matrix.
## Confirmed behavior
- Multi-view rendering requires both the literal `scope: children` keyword and
`group: notebook`.
- `scope: children` without `group: notebook` retains the current aggregated
single view.
- `this-folder`, numeric depths (including very large values), and `scope: all`
keep their current single aggregated view.
- The root notebook gets its own view for cards it owns directly.
- Every descendant notebook gets its own view for cards it owns directly.
- Cards are not rolled up into ancestor views; each card appears exactly once.
- Empty notebook views are omitted.
- If every notebook is empty, show the existing overall empty board/matrix once.
- Each view heading uses a full notebook path so duplicate titles are clear.
- Views use deterministic depth-first tree order: root first, then descendants;
siblings sort case-insensitively by title with folder ID as a stable tie-breaker.
- Each generated view contains the normal three kanban columns or four matrix
quadrants, rather than notebook subsections inside those buckets.
- Each bucket receives independent SLICE8 visible-count state and the configured
`page-size`.
- SLICE9 normal-note cards group by `parent_id` exactly like to-do cards.
- Overall scan statistics count folders and notes once. Card totals equal the sum
of all non-empty notebook groups.
## Example
```text
Projects <- root view (direct cards only)
├── Home <- separate Home view
│ ├── Repairs <- separate Repairs view
│ └── Garden <- separate Garden view
└── Work <- separate Work view
└── Release <- separate Release view
```
If `Home` has no eligible cards, its view is omitted; `Repairs` and `Garden`
still appear if they have cards.
## Architecture and data flow
```text
parse scope
children + group: notebook -> scopeDepth: Infinity + groupByNotebook: true
all other combinations -> existing depth + groupByNotebook: false
|
v
resolve root notebook + ordered scoped folder metadata
|
v
collector scans each folder once
single mode -> existing aggregate board/matrix
grouped mode -> one exact-owner board/matrix per folder
|
v
plugin payload: discriminated single or notebook-grouped result
|
v
webview
single -> current component
grouped -> repeat complete component per non-empty notebook group
(each component owns independent SLICE8 state)
```
## Data-contract decision
Use a discriminated view layout instead of returning both aggregate and grouped
copies of every card:
```ts
type KanbanLayout =
| { kind: "single"; board: KanbanBoard }
| { kind: "notebooks"; groups: NotebookKanbanGroup[] };
type MatrixLayout =
| { kind: "single"; board: MatrixBoard }
| { kind: "notebooks"; groups: NotebookMatrixGroup[] };
```
Each group carries `folderId`, `notebookPath`, its board/matrix, `cardCount`, and
item-level warnings for that notebook. Config warnings, notebook-resolution
warnings, and profile-wide soft-cap warnings remain at the overall block level.
The empty grouped case carries `groups: []` and renders one existing empty state.
## Implementation plan
### Phase 1 — Preserve the explicit grouping intent
- [x] Add `groupByNotebook: boolean` to `KanbanConfig` and `MatrixConfig`
in `src/Gtd/types.ts`.
- [x] Recognize `group: notebook` in both parsers.
- [x] Set `scopeDepth = Infinity` for literal `scope: children`, preserving
its existing recursion behavior.
- [x] Set `groupByNotebook = true` only when literal `scope: children` and
`group: notebook` appear together.
- [x] Default `groupByNotebook` to false, including `scope: children` without
the grouping option.
- [x] Keep `this-folder`, numeric depth, invalid-value fallback, and
`scope: all` parsing unchanged.
- [x] Warn and remain non-grouped when `group: notebook` is combined with
another scope, or when `group:` has an unsupported value.
- [x] Add parser tests covering the grouped combination and every non-grouped
scope form.
A boolean is deliberately preferred over a broad scope-enum refactor: existing
collectors still need `scopeDepth` and `scopeAll`, while this slice needs only
one additional rendering decision.
### Phase 1 completion record
- Kanban and matrix configs now preserve explicit notebook-grouping intent
independently of recursive scope depth.
- Existing `scope: children` dashboards remain aggregated unless they opt in
with `group: notebook`.
- Focused validation: 4 suites and 104 tests passed.
- Full validation: 14 suites and 205 tests passed.
- TypeScript, whitespace, and prohibited-reference checks passed.
- No manual Joplin test is required for this parser-only phase.
### Phase 2 — Ordered notebook metadata helper
- [x] Extend `folderScope.ts` or add a focused `folderTree.ts` helper that returns
scoped folder metadata rather than only IDs when grouping is requested.
- [x] Include `id`, `parentId`, display title, and full path for the resolved root
and every descendant.
- [x] Build paths from the actual notebook tree, including ancestors above a
`notebook:`-selected root so headings remain unambiguous.
- [x] Order root first, then perform depth-first traversal with siblings sorted
by case-insensitive title and folder ID tie-breaker.
- [x] Handle missing/empty titles with a stable documented fallback such as
`Untitled notebook` while retaining the folder ID tie-breaker.
- [x] Defend against malformed folder data (orphan parent references and cycles)
without hanging; keep every reachable scoped folder at most once.
- [x] Leave `resolveScopedFolderIds` behavior intact for calendars, Gantt, and
non-grouped views.
### Phase 2 completion record
- Added `folderTree.ts` with typed scoped-folder metadata: folder ID, parent ID,
display title, and full notebook path.
- Ordering is deterministic root-first depth-first; siblings sort by
case-insensitive display title and then folder ID.
- Notebook-selected roots retain real ancestor paths above the selected root.
- Blank titles use `Untitled notebook`.
- Missing roots return no metadata; orphan roots remain usable; visited guards
prevent cycles and duplicate traversal.
- The legacy `resolveScopedFolderIds` implementation and output remain unchanged.
- Focused validation: 4 suites and 52 tests passed.
- Full validation: 15 suites and 214 tests passed.
- TypeScript, whitespace, and prohibited-reference checks passed.
- No manual Joplin test is required for this pure metadata phase.
### Phase 3 — Grouped collector contracts
- [x] Add shared notebook-view metadata types plus `KanbanLayout` and
`MatrixLayout` discriminated unions in `src/Gtd/types.ts` or collector-local
result types where appropriate.
- [x] Refactor kanban collection so each eligible card is appended to the board
associated with the current scanned `folderId` when grouping is enabled.
- [x] Apply done-window filtering and sort each kanban group's columns using the
existing logic.
- [x] Refactor matrix collection equivalently and sort each group's quadrants.
- [x] Attach malformed-block/item warnings to the owning notebook group.
- [x] Omit groups whose final `cardCount` is zero, including groups emptied by
done-window or eligibility filtering.
- [x] Retain the current aggregate path and result behavior when grouping is off.
- [x] Compute overall `scannedFolders`, `scannedNotes`, and `cardCount` once,
without summing repeated scans or duplicating cards.
- [x] Ensure the host view note is excluded before group counts are finalized.
### Phase 3 completion record
- Added shared notebook-view metadata plus discriminated single/notebook layouts
for kanban and matrix results. The existing top-level board remains a temporary
empty alias in grouped mode so later payload consumers could migrate cleanly
without duplicating cards. The alias was removed after the Phase 5 webview
migration.
- Grouped collectors scan every scoped folder once, bucket cards by the folder
currently being scanned, apply existing filtering and sorting per group, and
preserve the aggregate collector path when grouping is disabled.
- Empty groups are omitted after filtering. Item warnings remain with their
owning non-empty group; overall folder, note, and card totals count unique
scans/cards once.
- Added exact-owner coverage for root/child/grandchild cards, mixed notes and
to-dos, host-note exclusion, empty groups, done-window removal, group-local
warnings, ordering, sorting, both matrix modes, and single-layout regressions.
- Focused validation: 4 suites and 110 tests passed.
- Full validation: 15 suites and 219 tests passed.
- Production TypeScript/webpack build and `git diff --check` passed; the package
was created at `publish/com.victorwiebe.joplin.plugin.gtd-calendar.jpl`.
- No manual Joplin test is required for this collector-contract phase; grouped
payload and visual acceptance remain in Phases 48.
### Phase 4 — Plugin payloads and warning ownership
- [x] Update `handleGetKanban` and `handleGetMatrix` in `src/index.ts` to return
the discriminated layout plus SLICE8 `pageSize`.
- [x] Keep config warnings, notebook-resolution warnings, config parse errors,
and `scope: all` soft-cap warnings at the overall block level.
- [x] Send item-level warnings inside their notebook group in grouped mode.
- [x] Preserve the current top-level warning array in single mode.
- [x] Return a structurally valid empty layout when the source note cannot be
resolved.
- [x] Keep overall statistics visible once per source block; optionally show a
compact per-notebook card count in each generated heading, but do not add
separate scan totals that could imply folders were scanned repeatedly.
### Phase 4 completion record
- Kanban and matrix handlers now send the collector's discriminated `layout`
together with normalized `pageSize`, one overall statistics object, and the
existing global configuration fields.
- Missing-source responses use the requested single/notebook discriminator and
return a structurally valid empty layout, including matrix labels and scope
metadata.
- Configuration, YAML parse, notebook-resolution, and soft-cap warnings remain
overall. Single-mode item warnings retain the top-level warning path; grouped
item warnings remain inside their exact-owner notebook group.
- A temporary legacy `board` alias kept the single-view webview working during
this phase. It was removed in Phase 5 after the webview migrated to `layout`;
grouped mode never duplicated cards into an aggregate payload.
- Added pure payload-boundary helpers and tests for all missing-source layouts
and the no-aggregate compatibility behavior.
- Focused validation: 3 suites and 83 tests passed.
- Full validation: 16 suites and 221 tests passed.
- TypeScript with `--skipLibCheck`, `git diff --check`, and the production
webpack/JPL build passed. The current artifact is
`publish/com.victorwiebe.joplin.plugin.gtd-calendar.jpl`.
- No manual Joplin test is required for this payload-only phase; rendering and
manual acceptance remain in Phases 58.
### Phase 5 — Webview component reuse
- [x] Separate the existing kanban wrapper concerns (overall title, errors,
global warnings, stats) from rendering one kanban board component.
- [x] Do the same for one matrix component.
- [x] In single mode, render exactly one component with current markup and visual
behavior.
- [x] In grouped mode, iterate groups in payload order and render one full
component per group beneath a notebook-path heading.
- [x] Render each group's item warnings adjacent to that group.
- [x] Reuse the SLICE8 incremental-list helper inside every component; closure
state must not be shared between groups or buckets.
- [x] When `groups` is empty, render the existing empty board/matrix once rather
than showing a blank block.
- [x] Add only the spacing/heading CSS required to distinguish notebook views;
preserve column and matrix layout styles.
### Phase 5 completion record
- Split kanban and matrix rendering into overall wrappers plus reusable
one-board/one-matrix component functions. Single layouts retain the existing
component markup and visual behavior.
- Notebook layouts render every non-empty group in collector order beneath its
full notebook path, with item warnings adjacent to the owning view.
- Every generated component calls the existing SLICE8 incremental-list helper
separately for each column/quadrant, giving every bucket independent closure
state and the configured `pageSize`.
- Empty notebook layouts render one ordinary empty board or matrix, preserving
the established overall empty presentation.
- Removed the temporary top-level `board` payload alias. The webview consumes
`layout` directly, retaining only a defensive fallback for stale payloads.
- Added only shared notebook-view spacing and heading styles; existing board,
matrix, card, and batching styles remain unchanged.
- Full validation: JavaScript syntax, TypeScript with `--skipLibCheck`,
`git diff --check`, 16 suites/220 tests, and the production webpack/JPL build
all passed. The current artifact is
`publish/com.victorwiebe.joplin.plugin.gtd-calendar.jpl`.
- Browser-only interaction testing remains deliberately manual because the
repository has no DOM harness; Phase 6 records that boundary explicitly.
### Phase 6 — Automated tests
#### Scope/parser tests
- [x] `children` sets infinite depth plus grouping.
- [x] Numeric depth remains non-grouped, including a numeric string.
- [x] `this-folder`, invalid scope, and omitted scope remain non-grouped.
- [x] `scope: all` remains non-grouped and retains notebook-conflict warnings.
#### Folder-tree tests
- [x] Root plus multiple children and grandchildren.
- [x] Deterministic depth-first order regardless of input folder order.
- [x] Case-insensitive sibling ordering and folder-ID tie-breaker.
- [x] Duplicate titles represented by unambiguous full paths.
- [x] A `notebook:`-selected child root with its ancestor path retained.
- [x] Empty titles, orphan parents, and cycle protection.
#### Kanban collector tests
- [x] Root-owned and descendant-owned cards appear in separate exact-owner groups.
- [x] Every card appears once with no ancestor rollup.
- [x] Empty groups are omitted while non-empty grandchildren remain.
- [x] Done-window filtering can remove an otherwise non-empty group.
- [x] Per-group sorting and warnings are correct.
- [x] SLICE9 note and to-do cards group identically.
- [x] Aggregate stats and card totals are not double-counted.
- [x] Non-grouped scope results remain unchanged.
#### Matrix collector tests
- [x] Repeat exact-owner, omission, sorting, warning, mixed-card, and total-count
coverage for Skeleton and Eisenhower modes.
- [x] Confirm all four quadrants remain local to their owning notebook group.
- [x] Confirm non-grouped scope results remain unchanged.
#### Payload/rendering contracts
- [x] Test single and grouped payload construction at the thinnest practical
boundary without adding a new browser-test dependency solely for this slice.
- [x] If SLICE8 introduced reusable DOM tests, extend them to prove independent
visible counts across notebook components; otherwise cover this manually.
- [x] Run focused parser, folder-tree, kanban, and matrix tests.
- [x] Run the complete Jest suite and record suite/test totals.
- [x] Run `npm run dist` and record the produced `.jpl` path.
- [x] Run `git diff --check` and the prohibited-reference audit.
### Phase 6 completion record
- Parser coverage now explicitly locks literal `children` grouping, numeric and
default non-grouped scopes, invalid fallbacks, and `scope: all` notebook
conflict warnings for both kanban and matrix.
- Existing folder-tree coverage proves root/descendant traversal, deterministic
ordering, duplicate and blank titles, selected-root ancestor paths, orphans,
cycles, and unchanged legacy ID-only scope behavior.
- Collector coverage now includes exact ownership, no rollups, empty-group and
done-window omission, local sorting/warnings, mixed cards, unique totals, all
four local quadrants in both matrix modes, and single-layout regressions for
`this-folder`, numeric depth, recursive children, and `scope: all`.
- Pure layout helpers cover valid empty single/grouped payload construction.
The repository still has no DOM harness, so independent click-state remains
an explicit Phase 8 manual acceptance check rather than a test-only browser
abstraction.
- Focused validation: 5 suites and 121 tests passed.
- Full validation: 16 suites and 230 tests passed.
- JavaScript syntax, TypeScript with `--skipLibCheck`, `git diff --check`, and
the prohibited-reference audit passed.
- `npm run dist` produced
`publish/com.victorwiebe.joplin.plugin.gtd-calendar.jpl` (163,328 bytes,
SHA-256 `cc5eddd2dcd74453e5ebd2c2fd4635cb34eb5996bf736efd54441931f2a54930`).
The five-file tar archive was inspected and contains the grouped notebook-view
runtime markers.
- No manual Joplin acceptance was performed; that remains Phase 8.
### Phase 7 — Documentation
- [x] Update README.md to state that `scope: children` plus `group: notebook`
produces separate notebook views for kanban and matrix blocks.
- [x] Add a parent/child/grandchild example showing root ownership, omitted empty
notebooks, full-path headings, and no rollups.
- [x] State that ungrouped `children`, numeric depth, and `scope: all` remain
aggregated.
- [x] Explain that every generated bucket has independent SLICE8 expansion state.
- [x] Explain that SLICE9 notes group by their owning notebook.
- [x] Update SPEC.md scope parsing, folder traversal, layout payloads, warning
ownership, statistics, and rendering flow.
- [x] Add the SLICE10 feature to the v1.0.0 CHANGELOG entry.
### Phase 7 README progress record
- README option tables now document `group: notebook` for kanban and matrix and
constrain it to the literal `scope: children` combination.
- Added a parent/child/grandchild example covering exact ownership, no ancestor
rollups, omitted empty notebooks, non-empty descendants, full-path headings,
deterministic order, warning/statistics placement, and independent batching.
- Documented that ordinary note cards group exactly like to-dos and that all
other scope forms remain aggregated, warning when grouping is requested.
- Updated the development test count to 230 and release-version instructions to
include the lockfile.
- `SPEC.md` and the v1.0.0 `CHANGELOG.md` now record the grouped layout contract.
### Phase 8 — Manual Joplin acceptance
Create a root notebook containing direct cards, multiple children, grandchildren,
duplicate child titles under different parents, empty notebooks, and more than
one SLICE8 batch in at least two generated views. Include SLICE9 notes and to-dos.
- [x] Confirm root-owned cards render only in the root view.
- [x] Confirm every descendant card renders only in its owning notebook view.
- [x] Confirm empty notebooks are omitted but non-empty descendants still appear.
- [x] Confirm full paths distinguish duplicate titles.
- [x] Confirm view ordering is stable and matches the documented traversal.
- [x] Confirm both matrix modes create separate complete matrices.
- [x] Confirm per-group warnings appear with the correct notebook.
- [x] Confirm overall statistics equal the unique scanned/card totals.
- [x] Confirm independent `page-size` limits and "List more" state across groups,
buckets, and multiple source blocks.
- [x] Confirm reload resets all expanded buckets.
- [x] Confirm `this-folder`, numeric depths, and `scope: all` still render one
aggregated view.
- [x] Confirm notebook targeting plus `scope: children` roots grouping at the
resolved target and uses correct full paths.
- [x] Confirm sorting, styling, navigation, completion, recurrence, and note-card
behavior do not regress.
- [x] Record user sign-off here; do not mark manual acceptance complete before
confirmation.
### Phase 8 acceptance record
- **PASSED — USER SIGN-OFF RECEIVED 2026-07-28.** The user confirmed that all
Phase 8 manual tests passed against the current production JPL.
- Accepted workflows include exact root/descendant ownership, no rollups or
duplication, empty-group handling, full-path headings, deterministic order,
both matrix modes, warning/statistics ownership, independent batching across
groups and blocks, reload reset, notebook targeting, and all non-grouped scope
regressions.
- Sorting, styling, navigation, completion, recurrence, mixed note/to-do cards,
hover/focus controls, and configured page sizes were also manually accepted.
- This manual acceptance is recorded separately from Phase 6 automated
validation. Phase 7 documentation remains pending.
## Acceptance criteria
SLICE10 is complete only when:
- Literal `scope: children` renders separate root/descendant views with every
eligible card present exactly once in its owning notebook.
- Empty groups are omitted, headings are unambiguous, and ordering is stable.
- SLICE8 state is independent per bucket and SLICE9 cards group correctly.
- Warnings and statistics are correctly owned and never double-counted.
- Every other scope form retains its prior single-view behavior.
- Focused tests, the full suite, and the production package build pass.
- Manual Joplin acceptance is explicitly confirmed.
## Files expected to change
- `src/Gtd/types.ts`
- `src/Gtd/parseKanbanConfig.ts`
- `src/Gtd/parseMatrixConfig.ts`
- `src/Gtd/folderScope.ts` and/or a new `src/Gtd/folderTree.ts`
- `src/Gtd/collectKanban.ts`
- `src/Gtd/collectMatrix.ts`
- `src/index.ts`
- `src/gtd-calendar-webview.js`
- `src/event-calendar.css`
- `src/tests/Gtd/kanban.test.ts`
- `src/tests/Gtd/matrix.test.ts`
- `src/tests/Gtd/resolveNotebook.test.ts` and/or a new folder-tree test file
- `README.md`
- `SPEC.md`
- `CHANGELOG.md`
- `SLICE10.md`
- `TASKS.md`
## Out of scope
- Grouping numeric-depth or `scope: all` results.
- Ancestor rollups or duplicated cards across parent/child views.
- Nested notebook subsections inside a single kanban or matrix.
- Persisting expanded/collapsed notebook-view state.
- Notebook-level filtering controls or user-selectable grouping modes.
- Applying notebook grouping to calendar or Gantt views.
## Dependencies and resume point
Implement after SLICE8 and SLICE9 are complete. Start by preserving
`groupByNotebook` in parser tests, then build and test ordered folder metadata.
Introduce discriminated collector layouts before changing the webview. Complete
single-view regression tests before grouped manual acceptance.