319 lines
14 KiB
Markdown
319 lines
14 KiB
Markdown
# 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
|
|
|
|
**PLANNED.** Implementation has not started. Begin only after SLICE8 and SLICE9
|
|
are complete and accepted.
|
|
|
|
## Goal
|
|
|
|
When a kanban or matrix block uses `scope: children`, 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 applies only to the literal `scope: children` keyword.
|
|
- `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 -> scopeDepth: Infinity + groupByNotebook: true
|
|
other -> existing values + 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 scope intent
|
|
|
|
- [ ] Add `groupByNotebook: boolean` to `KanbanConfig` and `MatrixConfig` in
|
|
`src/Gtd/types.ts`.
|
|
- [ ] In both parsers, set `scopeDepth = Infinity` and
|
|
`groupByNotebook = true` only for literal `scope: children`.
|
|
- [ ] Default `groupByNotebook` to false.
|
|
- [ ] Keep `this-folder`, numeric depth, invalid-value fallback, and `scope: all`
|
|
parsing unchanged.
|
|
- [ ] When `scope: all` is selected, force or leave `groupByNotebook` false;
|
|
`scopeAll` remains the controlling flag.
|
|
- [ ] Add parser tests proving `children` is distinguishable from numeric depth
|
|
and that every other scope form remains single-view.
|
|
|
|
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 2 — Ordered notebook metadata helper
|
|
|
|
- [ ] Extend `folderScope.ts` or add a focused `folderTree.ts` helper that returns
|
|
scoped folder metadata rather than only IDs when grouping is requested.
|
|
- [ ] Include `id`, `parentId`, display title, and full path for the resolved root
|
|
and every descendant.
|
|
- [ ] Build paths from the actual notebook tree, including ancestors above a
|
|
`notebook:`-selected root so headings remain unambiguous.
|
|
- [ ] Order root first, then perform depth-first traversal with siblings sorted
|
|
by case-insensitive title and folder ID tie-breaker.
|
|
- [ ] Handle missing/empty titles with a stable documented fallback such as
|
|
`Untitled notebook` while retaining the folder ID tie-breaker.
|
|
- [ ] Defend against malformed folder data (orphan parent references and cycles)
|
|
without hanging; keep every reachable scoped folder at most once.
|
|
- [ ] Leave `resolveScopedFolderIds` behavior intact for calendars, Gantt, and
|
|
non-grouped views.
|
|
|
|
### Phase 3 — Grouped collector contracts
|
|
|
|
- [ ] Add shared notebook-view metadata types plus `KanbanLayout` and
|
|
`MatrixLayout` discriminated unions in `src/Gtd/types.ts` or collector-local
|
|
result types where appropriate.
|
|
- [ ] Refactor kanban collection so each eligible card is appended to the board
|
|
associated with the current scanned `folderId` when grouping is enabled.
|
|
- [ ] Apply done-window filtering and sort each kanban group's columns using the
|
|
existing logic.
|
|
- [ ] Refactor matrix collection equivalently and sort each group's quadrants.
|
|
- [ ] Attach malformed-block/item warnings to the owning notebook group.
|
|
- [ ] Omit groups whose final `cardCount` is zero, including groups emptied by
|
|
done-window or eligibility filtering.
|
|
- [ ] Retain the current aggregate path and result behavior when grouping is off.
|
|
- [ ] Compute overall `scannedFolders`, `scannedNotes`, and `cardCount` once,
|
|
without summing repeated scans or duplicating cards.
|
|
- [ ] Ensure the host view note is excluded before group counts are finalized.
|
|
|
|
### Phase 4 — Plugin payloads and warning ownership
|
|
|
|
- [ ] Update `handleGetKanban` and `handleGetMatrix` in `src/index.ts` to return
|
|
the discriminated layout plus SLICE8 `pageSize`.
|
|
- [ ] Keep config warnings, notebook-resolution warnings, config parse errors,
|
|
and `scope: all` soft-cap warnings at the overall block level.
|
|
- [ ] Send item-level warnings inside their notebook group in grouped mode.
|
|
- [ ] Preserve the current top-level warning array in single mode.
|
|
- [ ] Return a structurally valid empty layout when the source note cannot be
|
|
resolved.
|
|
- [ ] 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 5 — Webview component reuse
|
|
|
|
- [ ] Separate the existing kanban wrapper concerns (overall title, errors,
|
|
global warnings, stats) from rendering one kanban board component.
|
|
- [ ] Do the same for one matrix component.
|
|
- [ ] In single mode, render exactly one component with current markup and visual
|
|
behavior.
|
|
- [ ] In grouped mode, iterate groups in payload order and render one full
|
|
component per group beneath a notebook-path heading.
|
|
- [ ] Render each group's item warnings adjacent to that group.
|
|
- [ ] Reuse the SLICE8 incremental-list helper inside every component; closure
|
|
state must not be shared between groups or buckets.
|
|
- [ ] When `groups` is empty, render the existing empty board/matrix once rather
|
|
than showing a blank block.
|
|
- [ ] Add only the spacing/heading CSS required to distinguish notebook views;
|
|
preserve column and matrix layout styles.
|
|
|
|
### Phase 6 — Automated tests
|
|
|
|
#### Scope/parser tests
|
|
|
|
- [ ] `children` sets infinite depth plus grouping.
|
|
- [ ] Numeric depth remains non-grouped, including a numeric string.
|
|
- [ ] `this-folder`, invalid scope, and omitted scope remain non-grouped.
|
|
- [ ] `scope: all` remains non-grouped and retains notebook-conflict warnings.
|
|
|
|
#### Folder-tree tests
|
|
|
|
- [ ] Root plus multiple children and grandchildren.
|
|
- [ ] Deterministic depth-first order regardless of input folder order.
|
|
- [ ] Case-insensitive sibling ordering and folder-ID tie-breaker.
|
|
- [ ] Duplicate titles represented by unambiguous full paths.
|
|
- [ ] A `notebook:`-selected child root with its ancestor path retained.
|
|
- [ ] Empty titles, orphan parents, and cycle protection.
|
|
|
|
#### Kanban collector tests
|
|
|
|
- [ ] Root-owned and descendant-owned cards appear in separate exact-owner groups.
|
|
- [ ] Every card appears once with no ancestor rollup.
|
|
- [ ] Empty groups are omitted while non-empty grandchildren remain.
|
|
- [ ] Done-window filtering can remove an otherwise non-empty group.
|
|
- [ ] Per-group sorting and warnings are correct.
|
|
- [ ] SLICE9 note and to-do cards group identically.
|
|
- [ ] Aggregate stats and card totals are not double-counted.
|
|
- [ ] Non-grouped scope results remain unchanged.
|
|
|
|
#### Matrix collector tests
|
|
|
|
- [ ] Repeat exact-owner, omission, sorting, warning, mixed-card, and total-count
|
|
coverage for Skeleton and Eisenhower modes.
|
|
- [ ] Confirm all four quadrants remain local to their owning notebook group.
|
|
- [ ] Confirm non-grouped scope results remain unchanged.
|
|
|
|
#### Payload/rendering contracts
|
|
|
|
- [ ] Test single and grouped payload construction at the thinnest practical
|
|
boundary without adding a new browser-test dependency solely for this slice.
|
|
- [ ] If SLICE8 introduced reusable DOM tests, extend them to prove independent
|
|
visible counts across notebook components; otherwise cover this manually.
|
|
- [ ] Run focused parser, folder-tree, kanban, and matrix tests.
|
|
- [ ] Run the complete Jest suite and record suite/test totals.
|
|
- [ ] Run `npm run dist` and record the produced `.jpl` path.
|
|
- [ ] Run `git diff --check` and the prohibited-reference audit.
|
|
|
|
### Phase 7 — Documentation
|
|
|
|
- [ ] Update README.md to state that literal `scope: children` produces separate
|
|
notebook views for kanban and matrix blocks.
|
|
- [ ] Add a parent/child/grandchild example showing root ownership, omitted empty
|
|
notebooks, full-path headings, and no rollups.
|
|
- [ ] State that numeric depth and `scope: all` remain aggregated.
|
|
- [ ] Explain that every generated bucket has independent SLICE8 expansion state.
|
|
- [ ] Explain that SLICE9 notes group by their owning notebook.
|
|
- [ ] Update SPEC.md scope parsing, folder traversal, layout payloads, warning
|
|
ownership, statistics, and rendering flow.
|
|
- [ ] Add an unreleased SLICE10 entry to CHANGELOG.md without changing the
|
|
package version until release scope is decided.
|
|
|
|
### 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.
|
|
|
|
- [ ] Confirm root-owned cards render only in the root view.
|
|
- [ ] Confirm every descendant card renders only in its owning notebook view.
|
|
- [ ] Confirm empty notebooks are omitted but non-empty descendants still appear.
|
|
- [ ] Confirm full paths distinguish duplicate titles.
|
|
- [ ] Confirm view ordering is stable and matches the documented traversal.
|
|
- [ ] Confirm both matrix modes create separate complete matrices.
|
|
- [ ] Confirm per-group warnings appear with the correct notebook.
|
|
- [ ] Confirm overall statistics equal the unique scanned/card totals.
|
|
- [ ] Confirm independent `page-size` limits and "List more" state across groups,
|
|
buckets, and multiple source blocks.
|
|
- [ ] Confirm reload resets all expanded buckets.
|
|
- [ ] Confirm `this-folder`, numeric depths, and `scope: all` still render one
|
|
aggregated view.
|
|
- [ ] Confirm notebook targeting plus `scope: children` roots grouping at the
|
|
resolved target and uses correct full paths.
|
|
- [ ] Confirm sorting, styling, navigation, completion, recurrence, and note-card
|
|
behavior do not regress.
|
|
- [ ] Record user sign-off here; do not mark manual acceptance complete before
|
|
confirmation.
|
|
|
|
## 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. |