Add configurable kanban and matrix card limits
This commit is contained in:
parent
187fd34531
commit
cb656b2df8
@ -2,6 +2,14 @@
|
||||
|
||||
All notable changes to GTD Calendar are documented here. Versions follow the plugin's `manifest.json` / `package.json` version, which also matches the published npm releases.
|
||||
|
||||
## Unreleased — Incremental kanban and matrix card limits
|
||||
|
||||
- Added `page-size` to `gtd-kanban` and `gtd-matrix` (positive integer, default 10); invalid values warn and fall back to 10.
|
||||
- Each kanban column and matrix quadrant initially shows one batch. **List more** reveals the next batch independently without changing saved configuration; reload resets expansion.
|
||||
- Hovering over or focusing **List more** shows the exact number of hidden entries. The control uses a linked accessible tooltip, retains keyboard focus between intermediate clicks, and disappears after the final batch.
|
||||
- Collectors still return complete sorted results and statistics; batching is transient webview state, so headings and totals remain complete.
|
||||
- Validation: 164 automated tests passed, the production JPL was built and inspected, and manual Joplin acceptance passed on 2026-07-27.
|
||||
|
||||
## 0.7.0 — Gantt chart
|
||||
|
||||
- New `gtd-gantt` block: a project-grouped timeline of bars and milestones.
|
||||
|
||||
10
README.md
10
README.md
@ -94,6 +94,7 @@ todos: all
|
||||
sort-type: due-date
|
||||
in-progress-tag: in-progress
|
||||
card-detail: hover
|
||||
page-size: 10
|
||||
done-window: 7
|
||||
```
|
||||
````
|
||||
@ -116,8 +117,11 @@ A completed to-do always lands in **Done**, even if it still carries the in-prog
|
||||
| `sort` | `asc`, `desc` | `asc` | Sort direction. |
|
||||
| `in-progress-tag` | text | `in-progress` | The tag that places a to-do in the In Progress column. |
|
||||
| `card-detail` | `hover`, `always`, `none` | `hover` | Whether each card's due date / hover text shows on hover, always, or never. |
|
||||
| `page-size` | positive integer | `10` | Cards initially shown in each column and revealed by each **List more** click. Invalid values warn and fall back to 10. |
|
||||
| `done-window` | integer or `all` | `7` | How many days back the Done column reaches. |
|
||||
|
||||
Each column applies `page-size` independently after filtering and sorting. When a column has more cards, **List more** reveals the next batch; hovering over it or focusing it with the keyboard shows exactly how many entries remain. Expansion lasts only in the current rendered view and resets when the note is reloaded. Column headings and board statistics always show full totals.
|
||||
|
||||
Cards are compact (title, recurrence ↻ if applicable) and click through to the to-do. The board is **read-only** in this version — it reflects your to-dos' state but doesn't change it. (Drag-and-drop to move cards between columns is planned.)
|
||||
|
||||
## The Matrix (Skeleton & Eisenhower)
|
||||
@ -129,6 +133,7 @@ A third view: a 2×2 prioritisation matrix. Add a `gtd-matrix` block anywhere:
|
||||
title: Priorities
|
||||
todos: all
|
||||
mode: skeleton
|
||||
page-size: 8
|
||||
```
|
||||
````
|
||||
|
||||
@ -165,6 +170,9 @@ Quadrants from the urgent/important tags: Do First (both), Schedule (important),
|
||||
| `in-progress-tag` | any tag name | `in-progress` | Skeleton mode's active row — same tag as the kanban's In Progress column. |
|
||||
| `urgent-window` | integer (days) | `3` | Skeleton mode: how close a due date must be to count as "due soon". |
|
||||
| `card-detail` | `hover`, `always`, `none` | `hover` | Card detail line behaviour, as on the kanban. |
|
||||
| `page-size` | positive integer | `10` | Cards initially shown in each quadrant and revealed by each **List more** click. Invalid values warn and fall back to 10. |
|
||||
|
||||
Each quadrant expands independently in `page-size` batches. The **List more** hover/focus popup reports the number still hidden, and reloading the note resets all expanded quadrants. Quadrant headings and matrix statistics continue to show full totals.
|
||||
|
||||
Cards behave like kanban cards: compact, ↻ for recurring, click to open. Read-only.
|
||||
|
||||
@ -247,7 +255,7 @@ Issues and ideas: [the repository](https://gitea.skeletonworks.online/vwiebe/jop
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm test # 88 unit tests
|
||||
npm test # 164 unit tests
|
||||
npm run dist # builds publish/*.jpl
|
||||
```
|
||||
|
||||
|
||||
76
SLICE1.md
Normal file
76
SLICE1.md
Normal file
@ -0,0 +1,76 @@
|
||||
# SLICE 1 — Groundwork: process rules + body-fetch optimization
|
||||
|
||||
> **State-saving rule:** update this file immediately after EVERY completed task (tick the checkbox, update Status and Resume notes) and whenever pausing for input — automatically, without being asked. State on disk must always match reality.
|
||||
|
||||
## Status
|
||||
**COMPLETE** (2026-07-15). All six tasks done; 97/97 tests pass. Ready to
|
||||
proceed to SLICE2.
|
||||
|
||||
Decision recorded 2026-07-15: body-fetch optimization (Task 4) is scoped
|
||||
**plumbing-only, behaviour-preserving** — add the fields-hint adapter param and
|
||||
drop `body` only where `extractGtdBlock` is provably never called (both-`none`
|
||||
paths). No change to card styling/title for `todos: all`. This lands the adapter
|
||||
plumbing SLICE3's `scope: all` needs without altering render output.
|
||||
|
||||
Pending user input (non-blocking, defaults recorded in SLICE4):
|
||||
1. gtd-gantt fence disambiguation: `project:` key present = item block,
|
||||
absent = chart block (chart filters via `filter-project:`).
|
||||
2. Dateless gantt items: excluded with a warning (no Unscheduled strip).
|
||||
|
||||
## Goal
|
||||
Set up the working rules for the whole effort and land the performance
|
||||
optimization that `scope: all` (SLICE3) depends on.
|
||||
|
||||
## Tasks
|
||||
- [x] Verify `.git/info/exclude` covers local guidance, planning files, and`n other workspace-only metadata. Confirmed 2026-07-15; no change needed.
|
||||
- [x] Establish the .jpl archive rule: `scripts/archive-jpl.sh` moves existing
|
||||
`publish/*.jpl` to `build-archive/<name>-<version>-<yyyymmdd-HHMMSS>.jpl`.
|
||||
Wired as the `predist` npm script so it runs automatically before every
|
||||
`npm run dist`. Validated 2026-07-15 (archives + no-op-on-empty both work).
|
||||
CORRECTION (2026-07-15, during first real `npm run dist`): the archive was
|
||||
originally written to `publish/archive/`, but webpack's `buildMain` step
|
||||
runs `fs.removeSync(publishDir)` (wipes it) and `package.json`
|
||||
`"files": ["publish"]` would ship it to npm. Moved to repo-root
|
||||
`build-archive/` (git-ignored, outside publish/). local guidance + .gitignore
|
||||
updated. Re-validated with a full `npm run dist`: archive survives, fresh
|
||||
jpl written to publish/.
|
||||
- [x] Fix `jest.config.js` ESM/CJS mismatch (`export default` → `module.exports`).
|
||||
Done 2026-07-15: `npx jest` runs, all 88 tests pass (8 suites).
|
||||
- [x] Body-fetch optimization (plumbing-only, behaviour-preserving). Done 2026-07-15:
|
||||
- `DataAdapter.getNotesInFolder` gains an `includeBody: boolean` fetch hint;
|
||||
`RawNote.body` is now optional. Real adapter (`index.ts`) requests
|
||||
`NOTE_FIELDS_WITH_BODY` vs `NOTE_FIELDS` accordingly.
|
||||
- Each collector computes `needsBody` before the fetch:
|
||||
events = `notes !== "none" || todos !== "none"`;
|
||||
kanban/matrix = `todos !== "none"`.
|
||||
These are exactly the paths where `extractGtdBlock` is reached, so render
|
||||
output is identical — body is only dropped for degenerate empty views.
|
||||
- `extractGtdBlock` param widened to `string | undefined` (already guarded
|
||||
by `body || ""`).
|
||||
Investigation confirmed the slice's stated "todos: all" win is NOT
|
||||
behaviour-preserving (block is read for card styling even in `all` mode),
|
||||
so it was deliberately excluded per the recorded decision. The value here is
|
||||
the adapter plumbing that SLICE3's `scope: all` needs.
|
||||
- [x] Unit tests for the fields-hint logic. Done 2026-07-15:
|
||||
`src/tests/Gtd/fieldsHint.test.ts` — a recording adapter asserts each
|
||||
collector passes the correct `includeBody` flag per mode (9 cases).
|
||||
- [x] Run full test suite; record results here. 2026-07-15: **97 passed, 97 total**
|
||||
(9 suites) — 88 pre-existing + 9 new fields-hint cases.
|
||||
|
||||
## Decisions / notes
|
||||
- Node on this machine: 18.19.1 (WSL Ubuntu). Jest 30.
|
||||
- Run all commands via `wsl -d Ubuntu -- bash -c "cd ~/projects/joplin-plugin-gtd-calendar && ..."`.
|
||||
|
||||
## Resume notes
|
||||
SLICE1 is complete. Files touched:
|
||||
- `jest.config.js` (`export default` → `module.exports`)
|
||||
- `scripts/archive-jpl.sh` (new) + `predist` npm script in `package.json`
|
||||
- `src/Gtd/types.ts` (`RawNote.body` optional; `getNotesInFolder` gains `includeBody`)
|
||||
- `src/index.ts` (`NOTE_FIELDS` / `NOTE_FIELDS_WITH_BODY`, conditional fetch)
|
||||
- `src/Gtd/collectEvents.ts`, `collectKanban.ts`, `collectMatrix.ts` (`needsBody`)
|
||||
- `src/Gtd/gtdBlock.ts` (param `string | undefined`)
|
||||
- `src/tests/Gtd/fieldsHint.test.ts` (new)
|
||||
|
||||
Nothing left open. `npx tsc --noEmit` shows only pre-existing `api/` typing
|
||||
errors (generated Joplin typings referencing absent modules); no new errors in
|
||||
`src/`. Next: SLICE2.
|
||||
319
SLICE10.md
Normal file
319
SLICE10.md
Normal file
@ -0,0 +1,319 @@
|
||||
# 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.
|
||||
64
SLICE2.md
Normal file
64
SLICE2.md
Normal file
@ -0,0 +1,64 @@
|
||||
# SLICE 2 — `notebook:` option for all gtd block types
|
||||
|
||||
> **State-saving rule:** update this file immediately after EVERY completed task (tick the checkbox, update Status and Resume notes) and whenever pausing for input — automatically, without being asked. State on disk must always match reality.
|
||||
|
||||
## Status
|
||||
**COMPLETE** (2026-07-15). SLICE1 done; all tasks landed; 112/112 tests pass.
|
||||
|
||||
## Goal
|
||||
Every view block (`gtd-calendar`, `gtd-kanban`, `gtd-matrix`, later `gtd-gantt`)
|
||||
accepts `notebook: <name-or-path-or-id>` to root the folder scan at a specific
|
||||
notebook, possibly outside the host note's tree. `scope:` then applies relative
|
||||
to that notebook.
|
||||
|
||||
## Design (agreed with user 2026-07-13)
|
||||
- Accept, in order of resolution:
|
||||
1. A raw 32-char hex folder id.
|
||||
2. A `Parent/Child/...` title path (case-insensitive match on titles).
|
||||
3. A bare title — if unique, use it; if ambiguous, warn and fall back to the
|
||||
host folder (consistent with the plugin's warn-don't-fail convention).
|
||||
- Unknown notebook → warning + fall back to host folder.
|
||||
- Folder titles are needed: extend `DataAdapter.getFolders()` fields to include
|
||||
`title` (currently id/parent_id only).
|
||||
|
||||
## Tasks
|
||||
- [x] Extend `RawFolder` + `joplinAdapter.getFolders()` with `title`. Done:
|
||||
`RawFolder.title` is optional (`title?: string`) — folder scoping uses
|
||||
id/parent_id only, so scope-only test fixtures don't need it; the real
|
||||
adapter always requests `["id","parent_id","title"]`.
|
||||
- [x] New `src/Gtd/resolveNotebook.ts` (pure): folders + spec → `{ folderId,
|
||||
warning }`. Resolution order id → path → unique bare title; any miss warns
|
||||
and falls back to the host folder. Also exports `parseNotebookOption`
|
||||
(shared trim/null normaliser used by all three config parsers).
|
||||
- [x] Add `notebook` key to parseCalendarConfig / parseKanbanConfig /
|
||||
parseMatrixConfig (`string | null`, default null; "notebook" added to each
|
||||
knownKeys list).
|
||||
- [x] Wire in `src/index.ts`: `resolveScanFolder()` helper resolves the notebook
|
||||
(or returns host folder) and pushes any warning onto `config.warnings`;
|
||||
all three handlers pass the resolved id to their collector.
|
||||
- [x] Tests: `src/tests/Gtd/resolveNotebook.test.ts` — resolver (id, unknown id,
|
||||
unique title, ambiguous title, missing title, path, path-disambiguation,
|
||||
unresolvable path, empty), `parseNotebookOption`, config parsing for all
|
||||
three views, and a collector-level integration (resolved notebook reroots
|
||||
collectEvents' scan). 15 new cases.
|
||||
- [x] Docs: README option tables (all three views) + SPEC.md §2.1/§2.3/§2.4
|
||||
(row + example lines). Workspace planning files remain git-excluded.
|
||||
- [x] Full test run: **112 passed, 112 total** (10 suites) on 2026-07-15.
|
||||
`npx tsc --noEmit` clean for `src/` (only pre-existing api/ + node_modules
|
||||
lib noise).
|
||||
|
||||
## Resume notes
|
||||
SLICE2 complete. Files touched:
|
||||
- `src/Gtd/types.ts` (`RawFolder.title?`, `notebook` on all three configs)
|
||||
- `src/Gtd/resolveNotebook.ts` (new; resolver + `parseNotebookOption`)
|
||||
- `src/Gtd/parseCalendarConfig.ts`, `parseKanbanConfig.ts`, `parseMatrixConfig.ts`
|
||||
(notebook key + knownKeys)
|
||||
- `src/index.ts` (getFolders title field, `resolveScanFolder`, 3 handler wirings)
|
||||
- `src/tests/Gtd/resolveNotebook.test.ts` (new)
|
||||
- `README.md` (3 tables), `SPEC.md` (§2.1 row + 3 example lines)
|
||||
|
||||
Design note: notebook resolution lives in `index.ts` (needs the folder tree),
|
||||
so collectors stay unchanged — they still take a plain `folderId`. When
|
||||
`notebook:` is set, `getFolders()` is fetched once in the handler and again
|
||||
inside the collector; acceptable (only on notebook use), could be deduped later
|
||||
if it matters. Next: SLICE3 (`scope: all`).
|
||||
75
SLICE3.md
Normal file
75
SLICE3.md
Normal file
@ -0,0 +1,75 @@
|
||||
# SLICE 3 — `scope: all` (every notebook)
|
||||
|
||||
> **State-saving rule:** update this file immediately after EVERY completed task (tick the checkbox, update Status and Resume notes) and whenever pausing for input — automatically, without being asked. State on disk must always match reality.
|
||||
|
||||
## Status
|
||||
**COMPLETE** (2026-07-15). SLICE1 & SLICE2 done; all tasks landed; 120/120
|
||||
tests pass; full `npm run dist` builds clean.
|
||||
|
||||
## Goal
|
||||
`scope: all` on any view block scans every notebook in the Joplin profile.
|
||||
User explicitly wants this despite the cost ("very dangerous, yes").
|
||||
|
||||
## Design
|
||||
- `resolveScopedFolderIds` gains an "all" path: return every folder id
|
||||
(skip BFS entirely). Config parsers map `scope: all` → a sentinel
|
||||
(e.g. scopeDepth = -1 or a separate `scopeAll: boolean`) — decide during
|
||||
implementation, keep the type honest.
|
||||
- Guardrails (agreed):
|
||||
- Prominent "scanned N notebooks / M notes" footnote when scope is `all`
|
||||
(stats plumbing already exists in every payload).
|
||||
- Body-fetch optimization from SLICE1 applies.
|
||||
- Soft warning in the rendered view when scanned notes exceed ~2000
|
||||
(advisory only, never blocks).
|
||||
- `notebook:` + `scope: all` together: `all` wins; warn about the ignored
|
||||
`notebook:` key.
|
||||
|
||||
## Design decisions (as implemented)
|
||||
- Representation: a separate **`scopeAll: boolean`** on each config (not a
|
||||
`scopeDepth` sentinel) — `all` is not a tree depth, so an honest flag reads
|
||||
cleaner and keeps `scopeDepth` meaningful.
|
||||
- Scope resolution lives in `folderScope.ts`: `resolveScopedFolderIds` gains a
|
||||
4th `scopeAll = false` param; when true it returns `folders.map(id)` and skips
|
||||
BFS entirely (root/depth ignored).
|
||||
- `notebook:` + `scope: all`: parser emits a warning and `all` wins;
|
||||
`resolveScanFolder` short-circuits to the host folder (root is irrelevant when
|
||||
every notebook is scanned).
|
||||
- Soft cap: `index.ts` pushes a ⚠ warning when `scopeAll` and scanned notes
|
||||
> 2000 (`SCOPE_ALL_SOFT_CAP`), advisory only. Footnote: payloads carry
|
||||
`scopeAll`; the webview appends `· scope: all (every notebook)` to the existing
|
||||
stats meta line in all three render paths.
|
||||
|
||||
## Tasks
|
||||
- [x] Parser changes (all three view types) + warnings for conflicts. Done:
|
||||
`scope: all` → `scopeAll=true` in calendar/kanban/matrix parsers;
|
||||
notebook+all conflict warning in each.
|
||||
- [x] `folderScope.ts` all-folders path + tests. Done (4th param; collectors
|
||||
pass `config.scopeAll`).
|
||||
- [x] Footnote + soft-cap warning in webview render paths. Done: `scopeAll` on
|
||||
payloads + meta-line marker (3 paths); soft-cap warning via `warnings[]`
|
||||
(rendered as ⚠, no extra webview work — existing warning path).
|
||||
- [x] Tests: parser, scope resolution, collector with multi-root folders.
|
||||
`src/tests/Gtd/scopeAll.test.ts` — parser (all three + conflict + no-false-
|
||||
positive), folderScope scopeAll path + unchanged non-all behaviour, and a
|
||||
multi-root collector integration proving `all` spans disconnected
|
||||
notebooks. 8 new cases.
|
||||
- [x] Docs: README (3 scope rows + calendar caution) + SPEC.md §2.1 row and all
|
||||
three example blocks, including the performance/guardrail caution.
|
||||
- [x] Full test run: **120 passed, 120 total** (11 suites) on 2026-07-15.
|
||||
`npm run dist` builds clean; archive rule confirmed working.
|
||||
|
||||
## Resume notes
|
||||
SLICE3 complete. Files touched:
|
||||
- `src/Gtd/types.ts` (`scopeAll` on all three configs)
|
||||
- `src/Gtd/folderScope.ts` (scopeAll param + all-folders path)
|
||||
- `src/Gtd/parseCalendarConfig.ts`, `parseKanbanConfig.ts`, `parseMatrixConfig.ts`
|
||||
(scope: all + notebook-conflict warning; kanban/matrix now parse notebook
|
||||
before the return so the conflict check has it)
|
||||
- `src/Gtd/collectEvents.ts`, `collectKanban.ts`, `collectMatrix.ts` (pass scopeAll)
|
||||
- `src/index.ts` (`resolveScanFolder` short-circuit, `SCOPE_ALL_SOFT_CAP` +
|
||||
`scopeAllSoftCapWarning`, `scopeAll` on 3 payloads)
|
||||
- `src/gtd-calendar-webview.js` (footnote marker in 3 stats blocks)
|
||||
- `src/tests/Gtd/scopeAll.test.ts` (new)
|
||||
- `README.md`, `SPEC.md`
|
||||
|
||||
Next: SLICE4.
|
||||
86
SLICE4.md
Normal file
86
SLICE4.md
Normal file
@ -0,0 +1,86 @@
|
||||
# SLICE 4 — gtd-gantt data layer
|
||||
|
||||
> **State-saving rule:** update this file immediately after EVERY completed task (tick the checkbox, update Status and Resume notes) and whenever pausing for input — automatically, without being asked. State on disk must always match reality.
|
||||
|
||||
## Status
|
||||
**COMPLETE** (2026-07-15). Pure data layer landed; 140/140 tests pass;
|
||||
`tsc --noEmit` clean. No rendering/index wiring yet — that is SLICE5.
|
||||
|
||||
Resolved open question (2026-07-15): dateless/invalid-date gantt items are
|
||||
**excluded with a warning** (the recorded default) — no Unscheduled strip. If a
|
||||
strip is wanted later it's an additive change to collectGantt + the payload.
|
||||
|
||||
## Goal
|
||||
Pure data layer for the gantt: item opt-in parsing, project grouping,
|
||||
collection, config parsing. No rendering yet.
|
||||
|
||||
## Design (agreed with user 2026-07-13)
|
||||
- Items opt in with their own ```gtd-gantt fenced block (separate from ```gtd):
|
||||
- **Notes** (bars): `project: bluesky`, `begin-date: yyyy-mm-dd`,
|
||||
`end-date: yyyy-mm-dd` (accept same date formats as resolveDate.ts).
|
||||
- **To-dos** (milestones): `project: bluesky`; the milestone date is the
|
||||
to-do's native **due date**. begin/end on a to-do → warning, treated as
|
||||
milestone anyway.
|
||||
- Rows/swimlanes are grouped by `project:`.
|
||||
- **Chart block vs item block disambiguation** (same fence name!): a
|
||||
`gtd-gantt` block containing a `project:` key is an ITEM declaration;
|
||||
without `project:` it is the CHART config block. The markdown-it renderer
|
||||
must apply the same rule (SLICE5). Item blocks render as a small inline
|
||||
badge ("gantt: bluesky"), not a chart.
|
||||
- Chart config keys: `title`, `notebook`, `scope`, `filter-project`
|
||||
(optional; limit to one project), `sort` / `sort-type` (row order within
|
||||
project), `card-detail` semantics for hover text. Defaults follow the
|
||||
existing parsers' conventions; invalid values warn + fall back.
|
||||
- Semantics: completed to-do milestones render checked/struck (consistent
|
||||
with calendar). Items with unparseable/missing dates → warning +
|
||||
excluded (a gantt has no Unscheduled section — confirm with user if one
|
||||
is wanted; see open questions).
|
||||
- Reuse styling keys? Item gtd-gantt block also accepts `bg-colour`,
|
||||
`fg-colour`, `title`, `text` mirroring the gtd block.
|
||||
|
||||
## Open questions for user (non-blocking, defaults chosen)
|
||||
- Dateless gantt items: currently planned as warn+exclude. Alternative:
|
||||
an "Unscheduled" strip like the calendar. DEFAULT: exclude with warning.
|
||||
|
||||
## Tasks
|
||||
- [x] `src/Gtd/ganttBlock.ts` — `extractGanttBlock` mirrors gtdBlock.ts
|
||||
(fence regex for `gtd-gantt`, same YAML/empty/malformed handling). Parses
|
||||
project / begin-date / end-date + styling keys (bg-colour/fg-colour/title/
|
||||
text). Classification is the caller's job via `block.project` (non-null ⇒
|
||||
item, null ⇒ chart).
|
||||
- [x] `src/Gtd/parseGanttConfig.ts` — chart config: title, scope (+ scope: all
|
||||
+ notebook conflict warning), notebook, filter-project, sort, sort-type
|
||||
(`begin-date`|`title`|`modified-date`), card-detail. Invalid → warn + default.
|
||||
- [x] `src/Gtd/collectGantt.ts` — scope (scopeAll-aware) → fetch (always with
|
||||
body) → item filter (project present, ≠ chart) → filter-project → bar vs
|
||||
milestone → group by project → sort → compute range. Returns typed
|
||||
`GanttChart { projects, rangeStart, rangeEnd, warnings, scannedFolders,
|
||||
scannedNotes, itemCount }`.
|
||||
Rules: notes → bars (begin+end required & valid, end≥begin else warn+exclude);
|
||||
to-dos → milestones on their due date (begin/end on a to-do → warning;
|
||||
no due date → warn+exclude); completed/recurring flags on milestones;
|
||||
projects merged case-insensitively, output alphabetical.
|
||||
- [x] Types in `src/Gtd/types.ts` — GanttSortType, GanttItemBlock,
|
||||
GanttBlockResult, GanttConfig, GanttBar, GanttMilestone, GanttProject,
|
||||
GanttChart.
|
||||
- [x] Thorough unit tests — `src/tests/Gtd/gantt.test.ts` (20 cases): block
|
||||
extraction (chart vs item, styling, Date coercion, malformed), config
|
||||
(defaults/full/conflict/invalid), collector (grouping+range, ignore
|
||||
chart/plain/host, bar bad/reversed dates, milestone due-date + begin/end
|
||||
warning, milestone no-due-date, completed+recurring, filter-project,
|
||||
begin-date sort + direction, case-insensitive project merge).
|
||||
- [x] Full test run: **140 passed, 140 total** (12 suites) on 2026-07-15.
|
||||
`tsc --noEmit` clean for src/.
|
||||
|
||||
## Resume notes
|
||||
SLICE4 complete — pure data layer only. New files:
|
||||
- `src/Gtd/ganttBlock.ts`, `parseGanttConfig.ts`, `collectGantt.ts`
|
||||
- gantt types appended to `src/Gtd/types.ts`
|
||||
- `src/tests/Gtd/gantt.test.ts`
|
||||
|
||||
NOT done here (belongs to SLICE5): the markdown-it renderer's chart-vs-item
|
||||
disambiguation, the `getGantt` message handler in `src/index.ts`, and any
|
||||
webview rendering. `collectGantt`'s signature mirrors the other collectors
|
||||
(`adapter, hostNoteId, hostFolderId, config`) so index wiring will look like
|
||||
handleGetKanban. Item blocks must render as a small inline badge (not a chart) —
|
||||
that's a SLICE5 renderer concern. Next: SLICE5.
|
||||
72
SLICE5.md
Normal file
72
SLICE5.md
Normal file
@ -0,0 +1,72 @@
|
||||
# SLICE 5 — gtd-gantt rendering
|
||||
|
||||
> **State-saving rule:** update this file immediately after EVERY completed task (tick the checkbox, update Status and Resume notes) and whenever pausing for input — automatically, without being asked. State on disk must always match reality.
|
||||
|
||||
## Status
|
||||
**COMPLETE** (2026-07-15). All rendering code landed; 140/140 unit tests pass;
|
||||
`tsc` clean; `npm run dist` builds the `.jpl`. User manually verified in Joplin
|
||||
desktop. README + SPEC gantt docs added (beyond the original task list, at user
|
||||
request). No automated front-end tests (per the earlier webview-testing
|
||||
decision; disambiguation mirrors the unit-tested collectGantt).
|
||||
|
||||
## Goal
|
||||
Render the gantt in the webview: fence interception, message plumbing, DOM, CSS.
|
||||
|
||||
## Tasks
|
||||
- [x] `src/gtd-calendar-renderer.ts`: intercepts `gtd-gantt` fences. A block with
|
||||
a non-empty `project:` value (cheap regex `GANTT_PROJECT_LINE`, no YAML
|
||||
lib) → inline `<span class="gtd-gantt-badge">gantt: <project></span>`;
|
||||
otherwise emits the chart placeholder (`data-block-type="gantt"`). Project
|
||||
value is HTML-escaped and unquoted. Empty-project alignment matches
|
||||
collectGantt (empty ⇒ chart).
|
||||
- [x] `src/index.ts`: `handleGetGantt` + `getGantt` switch case, mirroring
|
||||
handleGetKanban (parse → selectedNote → resolveScanFolder → collectGantt →
|
||||
payload with title/cardDetail/scopeAll/configError/warnings+softcap/chart/
|
||||
stats). No-selectedNote path returns an empty chart.
|
||||
- [x] `src/gtd-calendar-webview.js`: `renderGantt` + `buildGanttGrid` and helpers
|
||||
(offsetDays, clampOffset, monthTicks, todayLocalISO, tooltips). Single CSS
|
||||
grid: label column (sticky) + `repeat(totalDays, 26px)`; month tick header
|
||||
row; project header rows; bars via `grid-column` from day offsets (bg/fg
|
||||
colour or `colourFromId` hash); milestones as ◆ markers (done → dimmed +
|
||||
struck label, ↻ for recurring); today line spanning body rows; click →
|
||||
openNote; hover text via native `title` (suppressed when card-detail:none);
|
||||
title/configError/warnings/stats blocks as in other views (stats footnote
|
||||
carries the scope: all marker).
|
||||
- [x] `src/event-calendar.css`: `.gtd-gantt-*` styles (badge, scroll container,
|
||||
grid, sticky corner/labels, ticks, bars, milestones, today line), using
|
||||
the same rgba/theme-var conventions as existing styles.
|
||||
- [x] Manual verification in Joplin desktop — CONFIRMED 2026-07-15. Verified
|
||||
live: item-block badge (`gantt: bluesky`) renders inline (not a chart);
|
||||
chart renders with title, month tick header, alphabetical project header
|
||||
(`bluesky (3)`), a note **bar** spanning begin→end (Jul 1–14), ◆
|
||||
**milestones** on to-do due dates, and the red **today** line at Jul 15.
|
||||
Footnote reports item/note/folder counts. Host-note exclusion confirmed
|
||||
(an item block in the chart's own note is correctly omitted — put items in
|
||||
separate notes). Unknown-option warning fires correctly for stray keys like
|
||||
`todos` (gantt has no notes/todos inclusion option).
|
||||
- [x] Docs (added at user request; not in the original task list): README gantt
|
||||
section (item vs chart, bar/milestone rules, chart options table) + SPEC.md
|
||||
§2.5 (dual-role disambiguation rule, item/chart schemas). Screenshots not
|
||||
committed — verified interactively instead.
|
||||
|
||||
## Resume notes
|
||||
Code complete. Files touched:
|
||||
- `src/gtd-calendar-renderer.ts` (gantt fence + badge/chart disambiguation)
|
||||
- `src/index.ts` (handleGetGantt + switch case + imports)
|
||||
- `src/gtd-calendar-webview.js` (dispatch + renderGantt/buildGanttGrid + helpers)
|
||||
- `src/event-calendar.css` (.gtd-gantt-* styles)
|
||||
|
||||
Design choices worth noting for a resumed session:
|
||||
- Grid layout is a single `display:grid` with explicit gridRow/gridColumn per
|
||||
element (label col = 1, day columns = 2..N+1). Bars: `grid-column: off+2 /
|
||||
endOff+3` (inclusive end). Milestones: `off+2 / span 1`. Today line:
|
||||
`grid-row: 1 / <rowIndex>` at the today column.
|
||||
- Hover uses native `title` tooltips (not the calendar's hover-card DOM) — simpler
|
||||
and reliable; revisit if richer hover cards are wanted. card-detail:none
|
||||
suppresses tooltips; "always" currently behaves like "hover" (no inline text on
|
||||
bars) — a possible future refinement, noted but out of scope.
|
||||
- No automated renderer/webview tests by design (front-end testing deferred, per
|
||||
the SLICE-boundary discussion). Disambiguation regex mirrors the unit-tested
|
||||
collectGantt classification.
|
||||
|
||||
Next after manual sign-off: SLICE6.
|
||||
55
SLICE6.md
Normal file
55
SLICE6.md
Normal file
@ -0,0 +1,55 @@
|
||||
# SLICE 6 — Polish, docs, full verification
|
||||
|
||||
> **State-saving rule:** update this file immediately after EVERY completed task (tick the checkbox, update Status and Resume notes) and whenever pausing for input — automatically, without being asked. State on disk must always match reality.
|
||||
|
||||
## Status
|
||||
**COMPLETE** (2026-07-18). Docs, CHANGELOG, roadmap, consistency sweep, and
|
||||
tests all done. Cross-view end-to-end pass in Joplin signed off by the user:
|
||||
dashboard note stacking gtd-calendar + gtd-kanban + gtd-matrix (skeleton &
|
||||
eisenhower) + gtd-gantt all render independently; scope/depth, todos: all,
|
||||
colour/icon/title/text overrides, recurring ↻, kanban states, matrix quadrants,
|
||||
gantt bars/milestones (incl. to-do begin/end-ignored warning), dual-role note,
|
||||
drilldown/hover, and invalid-config fallback warnings all verified. All spot
|
||||
checks pass. → proceed to SLICE7 (final review / release prep).
|
||||
|
||||
Versioning decision (2026-07-15): **split** into 0.6.0 (notebook + scope: all)
|
||||
and 0.7.0 (gantt). **0.6.0 is functionally COMPLETE** (notebook targeting +
|
||||
scope: all + body-fetch, verified in the 2026-07-18 E2E pass); 0.7.0 (gantt) is
|
||||
likewise complete. Note: the actual manifest/package version bump + publish is
|
||||
the separate release step NOT part of this slice —
|
||||
current version on disk is still 0.5.0.
|
||||
|
||||
## Tasks
|
||||
- [x] README.md: "Gantt chart" section (item-block + chart options tables) added
|
||||
(during SLICE5, at user request). `notebook:`/`scope: all` rows in all view
|
||||
tables + `all` performance caution were landed in SLICE2/SLICE3. Screenshots
|
||||
not committed (verified interactively).
|
||||
- [x] SPEC.md: §2.5 gtd-gantt (chart + item schema, dual-role rule) added in
|
||||
SLICE5; notebook/scope-all semantics in §2.1 (SLICE2/3); §9 roadmap updated
|
||||
here with SHIPPED v0.6.0 (notebook + scope: all + body-fetch) and v0.7.0
|
||||
(gantt) sections.
|
||||
- [x] CHANGELOG.md: 0.7.0 (Gantt) and 0.6.0 (notebook + scope: all) entries added
|
||||
per the split decision.
|
||||
- [x] Consistency sweep: verified notebook-conflict warning wording identical
|
||||
across all 4 parsers; `scope: all` handled in all 4; card-detail/unknown-
|
||||
option wording consistent; doc default tables match parser defaults.
|
||||
Pre-existing note (NOT changed — out of scope, avoids behaviour change): the
|
||||
calendar's `sort-type` uses `modified_date` (underscore) while kanban/matrix/
|
||||
gantt use `modified-date` (hyphen); this cross-block spelling difference is
|
||||
already documented in SPEC §9's reconciliation note.
|
||||
- [x] Full jest run green: **140 passed, 140 total** (12 suites), up from 88 at
|
||||
the start of this effort.
|
||||
- [x] End-to-end pass in Joplin desktop across all four view types, including a
|
||||
dashboard note stacking all four blocks. **Signed off by user 2026-07-18;
|
||||
all spot checks pass.** publish/*.jpl (built SLICE5) is current — docs-only
|
||||
changes since don't affect the bundle.
|
||||
|
||||
## Resume notes
|
||||
SLICE6 fully COMPLETE as of 2026-07-18 — E2E signed off by the user (dashboard
|
||||
note stacking all four view types rendered independently; all spot checks pass).
|
||||
Files touched here: `CHANGELOG.md` (0.6.0 + 0.7.0 entries), `SPEC.md` (§9 roadmap
|
||||
SHIPPED sections). README/SPEC gantt schema + notebook/scope docs landed earlier.
|
||||
|
||||
Next: SLICE7 (final review / release prep). Release version bump (0.6.0/0.7.0)
|
||||
and publish are deferred to the separate release step, NOT this
|
||||
slice — version on disk is still 0.5.0.
|
||||
58
SLICE7.md
Normal file
58
SLICE7.md
Normal file
@ -0,0 +1,58 @@
|
||||
# SLICE 7 — Release
|
||||
|
||||
> **State-saving rule:** update this file immediately after EVERY completed task (tick the checkbox, update Status and Resume notes) and whenever pausing for input — automatically, without being asked. State on disk must always match reality.
|
||||
|
||||
## Status
|
||||
**COMPLETE** (2026-07-18) — v0.7.0 published to npm (registry confirms
|
||||
`latest: 0.7.0`) and manual sanity-open in Joplin desktop signed off by user.
|
||||
All seven slices DONE. Nothing outstanding.
|
||||
|
||||
**Release-cut decision made with user (2026-07-18): ONE release, tagged 0.7.0.**
|
||||
Rationale: gantt is woven through all four shared files (73 refs — 33 webview,
|
||||
19 types.ts, 12 renderer, 9 index.ts), so a gantt-free 0.6.0 would be error-prone
|
||||
surgery on an intermediate nobody tested; the 2026-07-18 E2E sign-off was on the
|
||||
combined state. So npm goes 0.5.0 → 0.7.0 (0.6.0 not separately published); the
|
||||
CHANGELOG keeps BOTH the 0.7.0 (gantt) and 0.6.0 (notebook/scope) entries as a
|
||||
documentation split. Version bumped to 0.7.0 in src/manifest.json + package.json.
|
||||
|
||||
## Tasks
|
||||
- [x] Decide with user: **ONE release, tagged 0.7.0** (see Status). CHANGELOG keeps
|
||||
both 0.6.0 + 0.7.0 entries as documentation.
|
||||
- [x] Bump `version` in BOTH `src/manifest.json` and `package.json` → 0.7.0 (match).
|
||||
- [x] CHANGELOG.md entry finalized (0.7.0 at top, both feature entries present —
|
||||
written in SLICE6).
|
||||
- [x] **Archived existing publish/*.jpl first** — predist (scripts/archive-jpl.sh)
|
||||
moved it to build-archive/com.victorwiebe...-0.7.0-20260718-115536.jpl
|
||||
(no overwrite; three 0.5.0 archives preserved). NB the archive filename is
|
||||
stamped with the *current* package.json version (0.7.0), not the version the
|
||||
archived jpl was built at — cosmetic, git-ignored dir, left as-is.
|
||||
- [x] `npm run dist` — GREEN. jest 140/140 first. New publish/*.jpl (156 KB)
|
||||
embeds version 0.7.0 (verified in publish/*.json).
|
||||
- [x] Sanity-open the .jpl in Joplin desktop — **DONE** (2026-07-18): user
|
||||
completed manual visual check, signed off.
|
||||
Path: publish/com.victorwiebe.joplin.plugin.gtd-calendar.jpl
|
||||
- [x] `npm publish` — **DONE** (2026-07-18): user re-logged in and published.
|
||||
Registry (https://registry.npmjs.org/) confirms `latest: 0.7.0`.
|
||||
- [x] **Committed + pushed to Gitea** (2026-07-18): commit 187fd34
|
||||
"v0.7.0: Gantt chart, notebook targeting & profile-wide scope" (30 files,
|
||||
+2300/-40), pushed 9780d95..187fd34 master->master. Per user: `.npmrc`
|
||||
(browser=true) was included. workspace planning files and build-archive/ correctly
|
||||
excluded from the release commit. NB: no git tag created — tagging was abandoned after 0.3.0 (0.4.0
|
||||
& 0.5.0 untagged); left consistent with recent practice.
|
||||
- [x] Mark all SLICE files DONE; summarize outcome here. **DONE** —
|
||||
SLICE1–6 already COMPLETE; SLICE7 now COMPLETE (this file).
|
||||
|
||||
## Outcome
|
||||
2026-07-18: **v0.7.0 shipped.** The full slice effort (notebook targeting,
|
||||
profile-wide `scope: all`, and the gtd-gantt view) is released as a single
|
||||
0.7.0 (0.6.0 documented in CHANGELOG but never separately published, per the
|
||||
one-release decision). Final state:
|
||||
- Version 0.7.0 in src/manifest.json + package.json (match).
|
||||
- Committed + pushed to Gitea (commit 187fd34, master).
|
||||
- `npm run dist` green, 140/140 tests, clean 0.7.0 jpl (embedded version verified).
|
||||
- **Published to npm — registry confirms `latest: 0.7.0`.**
|
||||
- Manual sanity-open in Joplin desktop signed off by user.
|
||||
All seven slices DONE. No follow-ups outstanding.
|
||||
|
||||
## Resume notes
|
||||
Nothing to resume — SLICE1–7 all COMPLETE, v0.7.0 live on npm.
|
||||
275
SLICE8.md
Normal file
275
SLICE8.md
Normal file
@ -0,0 +1,275 @@
|
||||
# SLICE 8 — Incremental card limits for 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 (2026-07-27). All seven phases passed, including automated validation, production packaging, documentation, and user-confirmed manual Joplin acceptance.**
|
||||
|
||||
## Goal
|
||||
|
||||
Keep large kanban columns and matrix quadrants readable by rendering cards in
|
||||
configurable batches. Every bucket initially shows 10 cards by default. A local
|
||||
"List more" control reveals the next batch without changing the source note or
|
||||
persisting UI state. Reloading the rendered note resets all buckets.
|
||||
|
||||
## Confirmed behavior
|
||||
|
||||
- Add `page-size:` to both `gtd-kanban` and `gtd-matrix` blocks.
|
||||
- Default: `10`.
|
||||
- Valid values: positive integers only.
|
||||
- Invalid values produce a warning and fall back to 10.
|
||||
- Apply the limit independently after existing filtering and sorting to:
|
||||
- Backlog, In Progress, and Done on kanban boards.
|
||||
- All four quadrants in both matrix modes.
|
||||
- "List more" reveals the next `page-size` entries in only that bucket.
|
||||
- Show the control only when hidden entries remain.
|
||||
- Hovering or focusing the control shows the exact number of additional entries
|
||||
remaining, for example `13 more entries`.
|
||||
- Clicking updates the remaining count and removes the control after the final
|
||||
batch.
|
||||
- Expansion exists only in the current webview DOM. It is not written to the
|
||||
note, settings, plugin process, or project data.
|
||||
- Column/quadrant headings continue to show total card counts, not visible counts.
|
||||
- Collector results and overall statistics continue to represent all cards.
|
||||
|
||||
## Architecture and data flow
|
||||
|
||||
```text
|
||||
gtd-kanban / gtd-matrix YAML
|
||||
|
|
||||
v
|
||||
parseKanbanConfig / parseMatrixConfig
|
||||
validates page-size -> normalized pageSize
|
||||
|
|
||||
v
|
||||
handleGetKanban / handleGetMatrix
|
||||
returns pageSize alongside the complete sorted board
|
||||
|
|
||||
v
|
||||
renderKanban / renderMatrix
|
||||
gives each column or quadrant its own visibleCount
|
||||
|
|
||||
v
|
||||
renderIncrementalCardList
|
||||
renders slice(0, visibleCount), remaining summary, and List more
|
||||
```
|
||||
|
||||
The collectors (`collectKanban.ts` and `collectMatrix.ts`) must not paginate or
|
||||
truncate. Keeping pagination in the webview avoids corrupting totals, changing
|
||||
sort semantics, or requiring another plugin-process request on every click.
|
||||
|
||||
## Implementation plan
|
||||
|
||||
### Phase 1 — Configuration contract
|
||||
|
||||
- [x] Add `pageSize: number` to `KanbanConfig` and `MatrixConfig` in
|
||||
`src/Gtd/types.ts`.
|
||||
- [x] Add `page-size` to each parser's known-key list.
|
||||
- [x] Normalize omitted `page-size` to 10.
|
||||
- [x] Accept numeric or YAML numeric-string values only when they resolve to a
|
||||
finite positive integer.
|
||||
- [x] Warn with consistent wording for zero, negative, fractional, non-numeric,
|
||||
`NaN`, or infinite values and use 10.
|
||||
- [x] Add parser tests in `src/tests/Gtd/kanban.test.ts` and
|
||||
`src/tests/Gtd/matrix.test.ts` for:
|
||||
- omitted/default value;
|
||||
- `1`, `10`, and a larger valid override;
|
||||
- numeric-string compatibility;
|
||||
- zero, negative, fractional, and non-numeric fallback;
|
||||
- recognition as a known key (no unknown-option warning).
|
||||
|
||||
## Progress log
|
||||
|
||||
- 2026-07-27 — Phase 1 complete. Added shared `page-size` normalization and
|
||||
required `pageSize` fields for kanban/matrix config. Focused validation:
|
||||
2 suites passed, 59/59 tests. Full validation: 12 suites passed, 164/164
|
||||
tests. No manual testing applies yet because the value is not sent to or used
|
||||
by the webview until later phases.
|
||||
|
||||
### Phase 2 — Plugin-to-webview payload
|
||||
|
||||
- [x] Add `pageSize: config.pageSize` to successful kanban and matrix responses
|
||||
in `src/index.ts`.
|
||||
- [x] Add the same normalized value to missing-source-note/error responses so
|
||||
rendering behavior is structurally consistent.
|
||||
- [x] Do not change collector arguments, result shapes, sorting, card totals, or
|
||||
scope behavior.
|
||||
|
||||
- 2026-07-27 — Phase 2 complete. Added normalized `pageSize` to successful
|
||||
and missing-source-note responses for both kanban and matrix handlers. No
|
||||
collector or rendering changes. Full Jest validation: 12 suites passed,
|
||||
164/164 tests. `npx tsc --noEmit --skipLibCheck` passed; the unmodified
|
||||
command remains blocked by unresolved generated Joplin declarations and the
|
||||
repository's Jest/TypeScript library mismatch. No manual testing applies yet.
|
||||
### Phase 3 — Shared incremental renderer
|
||||
|
||||
- [x] Add one shared helper in `src/gtd-calendar-webview.js`, tentatively
|
||||
`renderIncrementalCardList(cards, pageSize, detail, contentScriptId)`.
|
||||
- [x] Keep `visibleCount` in the helper's closure, initialized to
|
||||
`Math.min(pageSize, cards.length)`.
|
||||
- [x] Render cards through the existing `renderCard` function so styling and
|
||||
click-to-open behavior remain unchanged.
|
||||
- [x] On each click, increase `visibleCount` by `pageSize`, capped at the full
|
||||
card count, then render only the newly revealed cards or refresh the local
|
||||
list without affecting other buckets.
|
||||
- [x] Reuse the helper from `renderColumn` and `matrixQuadrant`; pass
|
||||
`payload.pageSize || 10` from both top-level renderers as a defensive
|
||||
fallback for stale payloads.
|
||||
- [x] Preserve the existing total-count text in every column/quadrant heading.
|
||||
|
||||
- 2026-07-27 — Phase 3 complete. Added one incremental card-list helper and
|
||||
wired it to all three kanban columns and all four matrix quadrants. Each
|
||||
bucket owns independent closure state, initially renders one batch, appends
|
||||
one batch per click, removes the basic control when complete, and preserves
|
||||
total heading/stat counts. Validation: JavaScript syntax and `git diff --check`
|
||||
passed, `npx tsc --noEmit --skipLibCheck` passed, and 12 Jest suites passed
|
||||
with 164/164 tests. Full-file formatting was intentionally not applied because
|
||||
the legacy webview does not match the default formatter and doing so rewrites
|
||||
roughly 2,000 unrelated lines. Tooltip, accessibility association, and final
|
||||
CSS remain Phase 4; manual Joplin acceptance is deferred until then.
|
||||
|
||||
### Phase 4 — Control, summary popup, and accessibility
|
||||
|
||||
- [x] Render "List more" as a real `button` with `type="button"`.
|
||||
- [x] Place the button outside card elements so its click cannot bubble into a
|
||||
card's note-open handler.
|
||||
- [x] Add a dedicated tooltip element whose text is generated from the remaining
|
||||
count with correct singular/plural wording (`1 more entry`, `N more entries`).
|
||||
- [x] Associate the button and tooltip with `aria-describedby` using a unique ID.
|
||||
- [x] Show the tooltip on both `:hover` and `:focus-visible`; do not rely only on
|
||||
the native `title` attribute.
|
||||
- [x] Add focused styles in `src/event-calendar.css` for the control, tooltip,
|
||||
positioning, keyboard focus, and light/dark theme compatibility.
|
||||
- [x] Update or remove the button and tooltip atomically after every expansion.
|
||||
|
||||
- 2026-07-27 — Phase 4 complete. Added a real button, unique tooltip IDs,
|
||||
`aria-describedby`, exact singular/plural remaining counts, an updating
|
||||
accessible label, and pointer-hover/keyboard-focus popup behavior. New cards
|
||||
insert before the control so focus survives intermediate expansion; the
|
||||
control is removed only after the final batch. Added scoped, theme-aware CSS.
|
||||
Validation: JavaScript syntax, `npx tsc --noEmit --skipLibCheck`, and
|
||||
`git diff --check` passed; 12 Jest suites passed with 164/164 tests. Visual
|
||||
and keyboard behavior still requires manual Joplin acceptance after a current
|
||||
package is built.
|
||||
|
||||
### Phase 5 — Automated verification
|
||||
|
||||
The current Jest configuration uses `jest-environment-node` and the webview is a
|
||||
browser IIFE with no DOM-test harness. This slice will not add a new DOM runtime
|
||||
solely for one interaction. Automated coverage will therefore focus on the
|
||||
stable configuration contract, while DOM behavior receives explicit manual
|
||||
acceptance. If implementation exposes a genuinely reusable pure batching helper
|
||||
without duplicating browser logic, add focused unit tests for it; otherwise do
|
||||
not create a test-only abstraction.
|
||||
|
||||
- [x] Run focused parser tests for kanban and matrix configuration.
|
||||
- [x] Run the complete Jest suite and record suite/test totals.
|
||||
- [x] Run `npm run dist`, which includes the repository's full test/build/package
|
||||
workflow, and record the produced `.jpl` path.
|
||||
- [x] Run `git diff --check`.
|
||||
- [x] Re-run the repository-wide prohibited-reference audit.
|
||||
|
||||
- 2026-07-27 — Phase 5 complete. Focused kanban/matrix validation passed
|
||||
2/2 suites and 59/59 tests; the full suite passed 12/12 suites and 164/164
|
||||
tests. JavaScript syntax, `npx tsc --noEmit --skipLibCheck`, and
|
||||
`git diff --check` passed. `npm run dist` archived the previous package to
|
||||
`build-archive/com.victorwiebe.joplin.plugin.gtd-calendar-0.7.0-20260727-150417.jpl`
|
||||
and created `publish/com.victorwiebe.joplin.plugin.gtd-calendar.jpl`
|
||||
(158,720 bytes, manifest version 0.7.0). The JPL tar contains all five expected
|
||||
runtime files, and its webview contains the `pageSize` and tooltip markers.
|
||||
Both prohibited-reference audits returned no matches. No browser-only test
|
||||
abstraction was added because the batching helper remains a DOM-owning closure;
|
||||
its visual and keyboard behavior stays within explicit manual acceptance.
|
||||
|
||||
### Phase 6 — Documentation
|
||||
|
||||
- [x] Add `page-size` to the kanban and matrix option tables in README.md.
|
||||
- [x] Add examples showing the default and a custom batch size.
|
||||
- [x] Explain that expansion is per bucket, advances one batch per click, and
|
||||
resets on reload.
|
||||
- [x] Update SPEC.md with config validation, rendering ownership, and transient
|
||||
state semantics.
|
||||
- [x] Add an unreleased SLICE8 entry to CHANGELOG.md without changing the package
|
||||
version until release scope is decided.
|
||||
|
||||
- 2026-07-27 — Phase 6 README work complete. Added `page-size` to both
|
||||
option tables, an explicit default kanban example, a custom matrix example,
|
||||
and the per-bucket expansion, remaining-count popup, full-total, and reload
|
||||
reset behavior. Updated the README test count to the current 164. README diff
|
||||
validation passed. SPEC.md and CHANGELOG.md were completed after Phase 7
|
||||
acceptance; Phase 6 and SLICE8 are now complete.
|
||||
|
||||
### Phase 7 — Manual Joplin acceptance
|
||||
|
||||
**PASSED — USER SIGN-OFF RECEIVED 2026-07-27.** All manual checks below
|
||||
passed against the Phase 5 package.
|
||||
|
||||
Use a test dashboard that includes a kanban, Skeleton matrix, and Eisenhower
|
||||
matrix. Prepare buckets containing 0, 1, 10, 11, 20, and 21+ cards.
|
||||
|
||||
- [x] Confirm 0–10 cards show no "List more" control.
|
||||
- [x] Confirm 11 cards show 10 initially and reveal the final card with one click.
|
||||
- [x] Confirm 21+ cards expand by exactly 10 per click with the default.
|
||||
- [x] Confirm a custom `page-size` changes both initial and subsequent batches.
|
||||
- [x] Confirm the hover and keyboard-focus popup always reports the exact number
|
||||
still hidden.
|
||||
- [x] Confirm expansion in one column/quadrant does not alter another.
|
||||
- [x] Confirm multiple blocks in one note maintain independent state.
|
||||
- [x] Confirm card order, styling, hover detail, recurrence glyphs, completion
|
||||
styling, and click-to-open behavior remain unchanged.
|
||||
- [x] Confirm headings and overall statistics retain full totals.
|
||||
- [x] Confirm reloading the rendered note resets every expanded bucket.
|
||||
- [x] Confirm invalid `page-size` values warn and render with the default of 10.
|
||||
- [x] Record user sign-off here; do not mark manual acceptance complete before
|
||||
confirmation. **Confirmed by user 2026-07-27: all passed.**
|
||||
|
||||
- 2026-07-27 — User reported "all passed" for the complete Phase 7 checklist.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
SLICE8 is complete only when:
|
||||
|
||||
- Every kanban column and matrix quadrant initially renders no more than its
|
||||
normalized `page-size`.
|
||||
- Every click reveals at most one further batch in only the selected bucket.
|
||||
- The remaining-count popup is exact and usable with pointer and keyboard.
|
||||
- Fully expanded buckets have no "List more" control.
|
||||
- Reloading resets expansion.
|
||||
- Full card collections, ordering, headings, statistics, navigation, styling,
|
||||
and warnings remain correct.
|
||||
- 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/index.ts`
|
||||
- `src/gtd-calendar-webview.js`
|
||||
- `src/event-calendar.css`
|
||||
- `src/tests/Gtd/kanban.test.ts`
|
||||
- `src/tests/Gtd/matrix.test.ts`
|
||||
- `README.md`
|
||||
- `SPEC.md`
|
||||
- `CHANGELOG.md`
|
||||
- `SLICE8.md`
|
||||
- `TASKS.md`
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Server-side pagination or lazy fetching from Joplin.
|
||||
- Persisting expanded state across reloads.
|
||||
- A global "expand all" control.
|
||||
- Limits for calendar, unscheduled, or Gantt views.
|
||||
- Notebook grouping from SLICE10.
|
||||
- Inclusion of normal note cards from SLICE9.
|
||||
|
||||
## Outcome and resume point
|
||||
|
||||
SLICE8 is complete. `page-size` is implemented for kanban and matrix views, the
|
||||
current production JPL is in `publish/`, all 164 automated tests pass, and manual
|
||||
Joplin acceptance is signed off. Nothing remains in this slice; proceed to SLICE9.
|
||||
252
SLICE9.md
Normal file
252
SLICE9.md
Normal file
@ -0,0 +1,252 @@
|
||||
# SLICE 9 — Include opted-in note cards in 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 is complete.
|
||||
|
||||
## Goal
|
||||
|
||||
Allow ordinary Joplin notes (`is_todo: 0`) containing a `gtd` fenced block to
|
||||
appear as cards in kanban and matrix views. Preserve existing to-do behavior and
|
||||
continue excluding normal notes that have not explicitly opted in.
|
||||
|
||||
## Confirmed behavior
|
||||
|
||||
- A normal note is eligible only when `extractGtdBlock` reports `found: true`.
|
||||
- An empty `gtd` block is a valid opt-in, matching existing calendar semantics.
|
||||
- A malformed `gtd` block still opts the note in and surfaces the existing
|
||||
warning; fallback card properties come from the source note.
|
||||
- The existing `todos:` option continues to govern only to-dos. It does not
|
||||
disable or broaden normal-note inclusion.
|
||||
- No new `notes:` option is introduced in this slice.
|
||||
- Kanban placement for normal notes:
|
||||
- configured in-progress tag -> In Progress;
|
||||
- otherwise -> Backlog;
|
||||
- never -> Done.
|
||||
- Matrix placement for normal notes:
|
||||
- Eisenhower mode uses the existing important and urgent tags;
|
||||
- Skeleton mode uses the existing in-progress tag plus urgent tag/date rules.
|
||||
- A note date comes only from the `gtd` block. To-do date resolution remains
|
||||
block date first, then `todo_due`.
|
||||
- Note cards reuse block title, colours, icon, and text; missing values fall back
|
||||
exactly as existing cards do.
|
||||
- Normal notes are never completed and never show the recurring-to-do marker,
|
||||
even if they happen to carry the configured recurrence tag.
|
||||
- The host kanban/matrix note remains excluded from its own results.
|
||||
- SLICE8 batching counts note and to-do cards together after final sorting.
|
||||
|
||||
## Inclusion decision table
|
||||
|
||||
| Item | `gtd` block | `todos:` | Kanban | Matrix |
|
||||
|---|---:|---|---|---|
|
||||
| Normal note | absent | any | excluded | excluded |
|
||||
| Normal note | present/empty/malformed | any | included | included |
|
||||
| To-do | absent | `gtd-only` | excluded | excluded |
|
||||
| To-do | absent | `all` | included | included if incomplete |
|
||||
| To-do | present | `gtd-only` or `all` | included | included if incomplete |
|
||||
| To-do | any | `none` | excluded | excluded |
|
||||
| Completed to-do | any included mode | any | Done-window rules | excluded |
|
||||
|
||||
## Architecture and data flow
|
||||
|
||||
```text
|
||||
scoped RawNote
|
||||
|
|
||||
+-- host note? ------------------------------> exclude
|
||||
|
|
||||
+-- ordinary note
|
||||
| extract gtd block
|
||||
| absent -> exclude
|
||||
| found -> build note card -> tags -> bucket
|
||||
|
|
||||
+-- to-do
|
||||
existing todos/completion rules
|
||||
-> extract block -> tags -> build card -> bucket
|
||||
|
||||
combined bucket -> existing sort -> SLICE8 incremental rendering
|
||||
```
|
||||
|
||||
Collection remains responsible for eligibility, card normalization, tags,
|
||||
bucketing, sorting, warnings, and totals. The webview should not need separate
|
||||
rendering logic for note cards.
|
||||
|
||||
## Implementation plan
|
||||
|
||||
### Phase 1 — Card model and shared builder
|
||||
|
||||
- [ ] Add an explicit card discriminator to `KanbanCard` in `src/Gtd/types.ts`,
|
||||
preferably `isTodo: boolean`, so completion/recurrence rendering does not
|
||||
infer item kind indirectly.
|
||||
- [ ] Extract duplicated kanban/matrix card construction into a small shared
|
||||
helper only if doing so reduces real duplication without changing public
|
||||
collector contracts.
|
||||
- [ ] For normal notes set:
|
||||
- `isTodo: false`;
|
||||
- `completed: false` and `completedTime: 0`;
|
||||
- `isRecurring: false`;
|
||||
- date via `resolveEventDate(note, block)`, which correctly ignores `todo_due`
|
||||
for normal notes.
|
||||
- [ ] For to-dos preserve current completion and recurrence behavior and set
|
||||
`isTodo: true`.
|
||||
- [ ] Update `renderCard` only as needed to choose a neutral note glyph when a
|
||||
normal note has no custom icon; retain current checkbox glyphs for to-dos.
|
||||
- [ ] Decide and document the neutral fallback glyph during implementation using
|
||||
an existing project-compatible symbol; do not alter custom icons.
|
||||
|
||||
### Phase 2 — Kanban eligibility and bucketing
|
||||
|
||||
- [ ] Refactor the `collectKanban.ts` loop so host exclusion occurs first, then
|
||||
item-kind-specific eligibility.
|
||||
- [ ] For to-dos, preserve `todos: none`, `gtd-only`, and `all` behavior exactly.
|
||||
- [ ] For normal notes, call `extractGtdBlock` and exclude only when no block is
|
||||
found.
|
||||
- [ ] Surface malformed-block warnings for included notes with the existing
|
||||
message format.
|
||||
- [ ] Fetch tags only after an item is eligible.
|
||||
- [ ] Bucket completed to-dos into Done using the existing done-window cutoff.
|
||||
- [ ] Bucket incomplete to-dos and normal notes by the configured in-progress
|
||||
tag; untagged eligible notes go to Backlog.
|
||||
- [ ] Sort the combined note/to-do arrays with the existing configured sorter.
|
||||
|
||||
### Phase 3 — Matrix eligibility and bucketing
|
||||
|
||||
- [ ] Apply the same host, item-kind, and shortcode eligibility split in
|
||||
`collectMatrix.ts`.
|
||||
- [ ] Preserve completed-to-do exclusion.
|
||||
- [ ] Allow eligible normal notes through because they have no completion state.
|
||||
- [ ] Fetch tags only after eligibility is established.
|
||||
- [ ] Reuse the current Eisenhower tag axes without note-specific exceptions.
|
||||
- [ ] Reuse the current Skeleton active/due-soon rules; dateless normal notes
|
||||
naturally fall into the not-due-soon column unless marked urgent.
|
||||
- [ ] Sort combined note/to-do quadrant arrays with the existing sorter.
|
||||
|
||||
### Phase 4 — Body-fetch contract
|
||||
|
||||
Normal-note eligibility can be known only by reading the body. Consequently,
|
||||
kanban and matrix collectors must request bodies for every scanned folder even
|
||||
when `todos: none`. The previous `needsBody = config.todos !== "none"`
|
||||
optimization is no longer valid for these two views.
|
||||
|
||||
- [ ] Set `includeBody: true` for kanban and matrix folder-note fetches.
|
||||
- [ ] Update collector comments so they no longer claim `todos: none` guarantees
|
||||
an empty board.
|
||||
- [ ] Update `src/tests/Gtd/fieldsHint.test.ts` to expect body fetches in all
|
||||
kanban/matrix modes.
|
||||
- [ ] Leave calendar and Gantt body-fetch behavior unchanged.
|
||||
- [ ] Document this intentional performance tradeoff in SPEC.md: explicit note
|
||||
opt-in requires body inspection, but tag requests remain limited to
|
||||
eligible items.
|
||||
|
||||
### Phase 5 — Automated tests
|
||||
|
||||
#### Kanban
|
||||
|
||||
- [ ] Include a normal note with a valid block.
|
||||
- [ ] Include a normal note with an empty block.
|
||||
- [ ] Include a malformed-block note and retain its warning.
|
||||
- [ ] Exclude a normal note without a block.
|
||||
- [ ] Exclude the host note even when it has a block.
|
||||
- [ ] Place tagged notes in In Progress and untagged notes in Backlog.
|
||||
- [ ] Prove normal notes never enter Done and never become recurring.
|
||||
- [ ] Preserve all `todos:` modes and done-window behavior.
|
||||
- [ ] Verify mixed sorting for due date, title, and modified date.
|
||||
|
||||
#### Matrix
|
||||
|
||||
- [ ] Cover all four Eisenhower quadrants with normal notes.
|
||||
- [ ] Cover Skeleton active/inactive and due-soon/not-due-soon combinations.
|
||||
- [ ] Cover dateless and explicitly urgent normal notes.
|
||||
- [ ] Include empty and malformed blocks; exclude absent blocks and the host.
|
||||
- [ ] Preserve completed-to-do exclusion and all `todos:` modes.
|
||||
- [ ] Verify mixed sorting for due date, title, and modified date.
|
||||
|
||||
#### Shared/integration contracts
|
||||
|
||||
- [ ] Update existing `KanbanCard` fixtures for the item discriminator.
|
||||
- [ ] Verify tags are not requested for ineligible plain notes or excluded to-dos.
|
||||
- [ ] Verify `cardCount` includes all eligible note and to-do cards once.
|
||||
- [ ] Verify SLICE8 works on the combined ordered arrays without renderer changes.
|
||||
- [ ] Run focused kanban, matrix, and fields-hint suites.
|
||||
- [ ] 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 6 — Documentation
|
||||
|
||||
- [ ] Update README.md kanban and matrix sections with a normal-note example.
|
||||
- [ ] State clearly that normal notes require a `gtd` block and that `todos:`
|
||||
controls only to-dos.
|
||||
- [ ] Document kanban placement, both matrix placement modes, note date rules,
|
||||
malformed/empty block behavior, and fallback glyph behavior.
|
||||
- [ ] Update SPEC.md inclusion tables, collection flow, card model, and body-fetch
|
||||
tradeoff.
|
||||
- [ ] Add an unreleased SLICE9 entry to CHANGELOG.md without changing the package
|
||||
version until release scope is decided.
|
||||
|
||||
### Phase 7 — Manual Joplin acceptance
|
||||
|
||||
Use mixed views containing ordinary notes, incomplete to-dos, completed to-dos,
|
||||
empty blocks, malformed blocks, and plain notes.
|
||||
|
||||
- [ ] Confirm only opted-in normal notes appear.
|
||||
- [ ] Confirm empty blocks opt in and malformed blocks warn without disappearing.
|
||||
- [ ] Confirm normal-note Backlog/In Progress placement on kanban.
|
||||
- [ ] Confirm normal notes never appear in Done.
|
||||
- [ ] Confirm all four quadrants in both matrix modes behave as specified.
|
||||
- [ ] Confirm block title/date/colour/icon/text overrides render on normal notes.
|
||||
- [ ] Confirm neutral note glyph and custom-icon precedence.
|
||||
- [ ] Confirm normal notes do not show completion or recurrence styling.
|
||||
- [ ] Confirm clicking note cards opens the correct source note.
|
||||
- [ ] Confirm `todos: none`, `gtd-only`, and `all` still affect only to-dos.
|
||||
- [ ] Confirm SLICE8 limits and remaining counts include the mixed card total.
|
||||
- [ ] Confirm sorting, warnings, headings, statistics, and reload behavior.
|
||||
- [ ] Record user sign-off here; do not mark manual acceptance complete before
|
||||
confirmation.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
SLICE9 is complete only when:
|
||||
|
||||
- Every normal note with a found `gtd` block appears exactly once in its expected
|
||||
bucket, while every plain normal note remains excluded.
|
||||
- Existing to-do inclusion, completion, recurrence, done-window, and matrix rules
|
||||
remain unchanged.
|
||||
- Note styling, dates, navigation, warnings, totals, and sorting are correct.
|
||||
- Mixed arrays obey SLICE8 batching without special renderer branches.
|
||||
- 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/collectKanban.ts`
|
||||
- `src/Gtd/collectMatrix.ts`
|
||||
- Optional shared card-builder module if justified by implementation
|
||||
- `src/gtd-calendar-webview.js`
|
||||
- `src/tests/Gtd/kanban.test.ts`
|
||||
- `src/tests/Gtd/matrix.test.ts`
|
||||
- `src/tests/Gtd/fieldsHint.test.ts`
|
||||
- `README.md`
|
||||
- `SPEC.md`
|
||||
- `CHANGELOG.md`
|
||||
- `SLICE9.md`
|
||||
- `TASKS.md`
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Including normal notes that lack a `gtd` block.
|
||||
- Adding a general `notes: all` or `notes: none` option.
|
||||
- Giving normal notes a completion workflow.
|
||||
- Editing notes or tags from a kanban/matrix card.
|
||||
- Changing calendar or Gantt inclusion behavior.
|
||||
- Child-notebook grouping from SLICE10.
|
||||
|
||||
## Dependency and resume point
|
||||
|
||||
Implement after SLICE8. Start with collector tests and the explicit card-kind
|
||||
model, then change eligibility/body fetching. Finish automated regression checks
|
||||
before documentation and manual acceptance.
|
||||
23
SPEC.md
23
SPEC.md
@ -86,10 +86,11 @@ sort-type: due-date # due-date | title | modified-date (default: du
|
||||
sort: asc # asc | desc (default: asc)
|
||||
in-progress-tag: in-progress # default: "in-progress"
|
||||
card-detail: hover # hover | always | none (default: hover)
|
||||
page-size: 10 # positive integer (default: 10)
|
||||
done-window: 7 # days, or "all" (default: 7)
|
||||
```
|
||||
|
||||
Three columns, always shown: **Backlog**, **In Progress**, **Done**. To-dos only — plain notes never appear. Bucketing: `todo_completed > 0` → Done (wins regardless of tags); else the `in-progress-tag` → In Progress; else → Backlog. Backlog shows everything uncompleted and untagged, dated or not. Done is filtered to to-dos completed within `done-window` days (`all` for full history), preventing unbounded growth. Sorting is per-column; under `due-date`, cards without a due date sort after all dated ones regardless of direction. Read-only in the current version — reflects to-do state, does not change it.
|
||||
Three columns, always shown: **Backlog**, **In Progress**, **Done**. To-dos only — plain notes never appear. Bucketing: `todo_completed > 0` → Done (wins regardless of tags); else the `in-progress-tag` → In Progress; else → Backlog. Backlog shows everything uncompleted and untagged, dated or not. Done is filtered to to-dos completed within `done-window` days (`all` for full history), preventing unbounded growth. Sorting is per-column; under `due-date`, cards without a due date sort after all dated ones regardless of direction. `page-size` must be a positive integer; invalid values warn and fall back to 10. Each column initially renders at most that many cards and reveals one additional batch per **List more** click. Expansion is independent per column and resets when the rendered note reloads; headings and statistics retain complete totals. Read-only in the current version — reflects to-do state, does not change it.
|
||||
|
||||
### 2.4 The `gtd-matrix` block (lives in the matrix note)
|
||||
|
||||
@ -107,6 +108,7 @@ important-tag: important # Eisenhower mode only; default: "important"
|
||||
in-progress-tag: in-progress # Skeleton mode only; default: "in-progress"
|
||||
urgent-window: 3 # Skeleton mode only, days; default: 3
|
||||
card-detail: hover
|
||||
page-size: 10 # positive integer; per quadrant (default: 10)
|
||||
```
|
||||
|
||||
A 2×2 grid with labelled axes. To-dos only; **completed to-dos are excluded entirely** in both modes (a prioritisation view, not a tracking view — the kanban's Done column is where completions live). The board is internally positional (top-left / top-right / bottom-left / bottom-right); each mode supplies its own axis and quadrant labels.
|
||||
@ -115,7 +117,7 @@ A 2×2 grid with labelled axes. To-dos only; **completed to-dos are excluded ent
|
||||
|
||||
**`mode: eisenhower`** — the classic: rows = `important-tag`; columns = `urgent-tag`. Quadrants: Do First (both), Schedule (important only), Delegate (urgent only), Eliminate (neither).
|
||||
|
||||
A warning is raised if `urgent-tag` and `important-tag` resolve to the same tag (Eisenhower mode only — the axes would collapse). Read-only.
|
||||
A warning is raised if `urgent-tag` and `important-tag` resolve to the same tag (Eisenhower mode only — the axes would collapse). `page-size` has the same validation and transient behavior as the kanban, applied independently to each quadrant after sorting. Read-only.
|
||||
|
||||
### 2.5 The `gtd-gantt` block (dual-role: chart **and** item declaration)
|
||||
|
||||
@ -198,8 +200,9 @@ The calendar note itself is always excluded from results, as is any note whose o
|
||||
- **Fixed upstream bug:** `Calendar/index.ts` imported `WeekGrouping` from `Month/MonthGrouping`, so the week view silently grouped by month. Corrected in the fork.
|
||||
- **Type distinction:** todos render with a checkbox glyph (☐ / ☑ when completed); notes render with a document glyph. Glyphs are suppressed if the item supplies its own `icon`. Recurring to-dos (per the `recurring` tag) get a ↻ suffix to the right of any icon, across every view.
|
||||
- **Unscheduled section:** below the calendar grid, split into two labelled sub-sections — to-dos first, then notes — each hidden when empty or switched off (`unscheduled-todos` / `unscheduled-notes`).
|
||||
- **Kanban** renders three fixed columns (Backlog / In Progress / Done) of compact cards; a "hover" mode shows due date and hover text only on mouseover (`card-detail`).
|
||||
- **Matrix** renders a 2×2 grid with axis and quadrant labels supplied per-mode by the plugin process; cards are visually and behaviourally identical to kanban cards.
|
||||
- **Kanban** renders three fixed columns (Backlog / In Progress / Done) of compact cards; a "hover" mode shows due date and hover text only on mouseover (`card-detail`). Each column independently renders cards in `page-size` batches.
|
||||
- **Matrix** renders a 2×2 grid with axis and quadrant labels supplied per-mode by the plugin process; cards are visually and behaviourally identical to kanban cards. Each quadrant owns an independent batch counter.
|
||||
- **Incremental card lists:** collectors always return complete sorted arrays and full statistics. The main process sends normalized `pageSize`; the webview owns transient `visibleCount` state per column/quadrant and appends one batch per **List more** click. A linked tooltip (`aria-describedby`) reports the exact remaining count on pointer hover and keyboard focus. No expansion state is persisted; rerender/reload resets it.
|
||||
- **Drilldown:** every tile, card, hover-card row, and chip across all three block types is clickable and opens the source note.
|
||||
|
||||
---
|
||||
@ -377,6 +380,18 @@ Two scope-axis additions shared across all four view blocks (see §2.1 / §2.5 f
|
||||
|
||||
A fifth view: a project-grouped timeline, on the **views axis** like the matrix (another read-only rendering of existing notes/to-dos), not the authoring axis. Unlike the other blocks, gantt items opt in through their **own** `gtd-gantt` block carrying a `project:`, and the same fence name is disambiguated by that key (item vs chart — see §2.5). Notes become bars (`begin-date`→`end-date`), to-dos become ◆ milestones on their due date; grouped into per-project swimlanes, no Unscheduled section (dateless/invalid items are excluded with a warning). Data layer is pure and unit-tested (`ganttBlock.ts`, `parseGanttConfig.ts`, `collectGantt.ts`); rendering is a CSS-grid timeline (month ticks, today line, horizontal scroll, click-to-open). Item blocks render in place as a small `gantt: <project>` badge. Read-only. Open follow-ups (deferred): richer hover cards (currently native tooltips), `card-detail: always` inline text on bars, an optional Unscheduled strip.
|
||||
|
||||
### Unreleased — Incremental kanban/matrix card limits — COMPLETE
|
||||
|
||||
Kanban columns and matrix quadrants now render in configurable `page-size`
|
||||
batches (default 10). Parsing is shared and rejects zero, negative, fractional,
|
||||
non-numeric, or non-finite values with a warning and fallback to 10. Collectors
|
||||
remain complete and unchanged; normalized `pageSize` crosses the plugin payload
|
||||
boundary, while the webview owns independent transient expansion state for every
|
||||
bucket. **List more** reveals one batch, reports the exact remaining count through
|
||||
an accessible hover/focus tooltip, preserves focus between intermediate clicks,
|
||||
and disappears at completion. Reload resets all expansion. Automated validation
|
||||
passed at 164 tests and manual Joplin acceptance passed 2026-07-27.
|
||||
|
||||
### v0.5.0+ — The authoring axis (drag-and-drop + create-from-view) — one deliberate epic
|
||||
|
||||
Clustered because they share write-back machinery, and adopting any of them commits the plugin to mutating the vault:
|
||||
|
||||
189
TASKS.md
Normal file
189
TASKS.md
Normal file
@ -0,0 +1,189 @@
|
||||
# TASKS — SLICE8 through SLICE10
|
||||
|
||||
Use this file as the cross-slice implementation checklist. Update it whenever a
|
||||
task is completed or its status changes. Keep automated validation results and
|
||||
manual Joplin acceptance recorded separately.
|
||||
|
||||
## Overall status
|
||||
|
||||
- [x] SLICE8 complete — incremental card limits
|
||||
- [ ] SLICE9 complete — opted-in note cards
|
||||
- [ ] SLICE10 complete — child-notebook boards and matrices
|
||||
|
||||
Implementation order: SLICE8, then SLICE9, then SLICE10.
|
||||
|
||||
## SLICE8 — Incremental card limits for kanban and matrix views
|
||||
|
||||
### Configuration and types
|
||||
|
||||
- [x] Add normalized `page-size` fields to `KanbanConfig` and `MatrixConfig` in
|
||||
`src/Gtd/types.ts`.
|
||||
- [x] Recognize `page-size` in `parseKanbanConfig.ts` and
|
||||
`parseMatrixConfig.ts`.
|
||||
- [x] Default `page-size` to 10.
|
||||
- [x] Accept positive integer overrides.
|
||||
- [x] Warn and fall back to 10 for zero, negative, fractional, or non-numeric
|
||||
values.
|
||||
- [x] Add parser tests for defaults, valid overrides, and invalid fallbacks.
|
||||
- [x] Send normalized `pageSize` in successful and missing-source-note kanban
|
||||
and matrix responses.
|
||||
- [x] Preserve collector arguments, result shapes, sorting, totals, and scope
|
||||
behavior while adding the payload field.
|
||||
|
||||
### Rendering and interaction
|
||||
|
||||
- [x] Add reusable incremental-list behavior to
|
||||
`src/gtd-calendar-webview.js`.
|
||||
- [x] Initially render at most `page-size` cards in each kanban column.
|
||||
- [x] Initially render at most `page-size` cards in each matrix quadrant.
|
||||
- [x] Add a separate "List more" control to every bucket with hidden cards.
|
||||
- [x] Reveal the next `page-size` cards in only the clicked bucket.
|
||||
- [x] Update the remaining-entry count after every click.
|
||||
- [x] Remove the control when no hidden cards remain.
|
||||
- [x] Reset all expansion state when the rendered note reloads.
|
||||
- [x] Keep complete collection results and total-card statistics; apply limits
|
||||
only while rendering.
|
||||
|
||||
### Accessibility and styling
|
||||
|
||||
- [x] Style the button and remaining-count popup in `src/event-calendar.css`.
|
||||
- [x] Show the exact remaining-entry count on pointer hover.
|
||||
- [x] Make the same summary available on keyboard focus.
|
||||
- [x] Ensure activating "List more" never triggers a card's note-open action.
|
||||
|
||||
### Tests, documentation, and acceptance
|
||||
|
||||
- [x] Test empty buckets and buckets smaller than one batch.
|
||||
- [x] Test buckets containing exactly one batch.
|
||||
- [x] Test one card beyond a batch boundary.
|
||||
- [x] Test repeated expansion and final-control removal.
|
||||
- [x] Test independent expansion across columns, quadrants, and stacked blocks.
|
||||
- [x] Document `page-size` and transient expansion in README.md.
|
||||
- [x] Document the behavior and data-flow boundary in SPEC.md.
|
||||
- [x] Add the feature to CHANGELOG.md.
|
||||
- [x] Run the full automated test suite and record command/results.
|
||||
- [x] Run the production package build and record command/artifact.
|
||||
- [x] Manually verify kanban with more than 20 cards in one column in Joplin.
|
||||
- [x] Manually verify Skeleton and Eisenhower matrices with more than 20 cards
|
||||
in one quadrant in Joplin.
|
||||
- [x] Confirm sorting, card navigation, styling, warnings, and statistics remain
|
||||
unchanged.
|
||||
- [x] Mark SLICE8 complete in `SLICE8.md` and this file.
|
||||
|
||||
## SLICE9 — Include opted-in note cards in kanban and matrix views
|
||||
|
||||
### Collection and types
|
||||
|
||||
- [ ] Update kanban collection to inspect ordinary notes (`is_todo: 0`).
|
||||
- [ ] Include an ordinary note only when its body contains a `gtd` block.
|
||||
- [ ] Keep ordinary notes without a `gtd` block excluded.
|
||||
- [ ] Keep the host view note excluded even when it contains a `gtd` block.
|
||||
- [ ] Preserve current `todos:` behavior for to-do notes.
|
||||
- [ ] Place eligible normal notes with the configured in-progress tag in the
|
||||
kanban In Progress column.
|
||||
- [ ] Place other eligible normal notes in the kanban Backlog column.
|
||||
- [ ] Never place normal notes in the kanban Done column.
|
||||
- [ ] Apply existing urgent/important tag bucketing to normal notes in an
|
||||
Eisenhower matrix.
|
||||
- [ ] Apply existing date bucketing to normal notes in a Skeleton matrix.
|
||||
- [ ] Reuse `gtd` title, date, colour, icon, and text overrides on note cards.
|
||||
- [ ] Ensure normal notes are never marked completed or recurring.
|
||||
- [ ] Update card types to represent notes and to-dos accurately.
|
||||
- [ ] Fetch note bodies wherever opted-in notes may be collected while retaining
|
||||
only safe body-fetch optimizations.
|
||||
|
||||
### Tests
|
||||
|
||||
- [ ] Test kanban inclusion for a note containing a valid `gtd` block.
|
||||
- [ ] Test kanban exclusion for a plain note.
|
||||
- [ ] Test kanban Backlog and In Progress placement for note cards.
|
||||
- [ ] Test host-note exclusion and malformed `gtd` block warnings.
|
||||
- [ ] Test that current to-do and Done behavior is unchanged.
|
||||
- [ ] Test note-card bucketing in Skeleton matrix mode.
|
||||
- [ ] Test note-card bucketing in Eisenhower matrix mode.
|
||||
- [ ] Test completed-to-do exclusion remains unchanged in matrices.
|
||||
- [ ] Test mixed note/to-do sorting and click-to-open payloads.
|
||||
- [ ] Test that SLICE8 limits use the combined note and to-do card count.
|
||||
|
||||
### Documentation and acceptance
|
||||
|
||||
- [ ] Document note-card opt-in rules and examples in README.md.
|
||||
- [ ] Update SPEC.md with mixed note/to-do behavior.
|
||||
- [ ] Add the feature to CHANGELOG.md.
|
||||
- [ ] Run the full automated test suite and record command/results.
|
||||
- [ ] Run the production package build and record command/artifact.
|
||||
- [ ] Manually verify mixed note/to-do kanban views in Joplin.
|
||||
- [ ] Manually verify mixed note/to-do views in both matrix modes in Joplin.
|
||||
- [ ] Confirm existing to-do filtering, completion, sorting, styling, warnings,
|
||||
and navigation do not regress.
|
||||
- [ ] Mark SLICE9 complete in `SLICE9.md` and this file.
|
||||
|
||||
## SLICE10 — Separate child-notebook kanban and matrix views
|
||||
|
||||
### Scope and folder grouping
|
||||
|
||||
- [ ] Preserve whether normalized kanban and matrix configuration came from the
|
||||
explicit `scope: children` keyword.
|
||||
- [ ] Keep numeric scope, `this-folder`, and `scope: all` semantics unchanged.
|
||||
- [ ] Add a shared folder-tree helper that returns notebook IDs, titles, paths,
|
||||
and parent relationships in deterministic tree/path order.
|
||||
- [ ] Handle nested notebooks and duplicate notebook names correctly.
|
||||
- [ ] Create one result group for cards owned directly by the root notebook.
|
||||
- [ ] Create one result group for cards owned directly by each descendant
|
||||
notebook.
|
||||
- [ ] Ensure every in-scope card appears in exactly one notebook group.
|
||||
- [ ] Omit empty notebook groups.
|
||||
- [ ] Preserve one overall empty-state presentation when all groups are empty.
|
||||
|
||||
### Payloads and rendering
|
||||
|
||||
- [ ] Add named per-notebook kanban and matrix groups to collection result types.
|
||||
- [ ] Retain compatible single-view results for every scope except `children`.
|
||||
- [ ] Provide aggregate and per-view statistics without double-counting notes or
|
||||
cards.
|
||||
- [ ] Preserve warnings at the appropriate overall or notebook-view level.
|
||||
- [ ] Update plugin-to-webview messages and handlers for grouped results.
|
||||
- [ ] Render a complete kanban or matrix component for every non-empty group.
|
||||
- [ ] Use the full notebook path as each view heading.
|
||||
- [ ] Give every generated view independent SLICE8 expansion state.
|
||||
- [ ] Apply the configured `page-size` independently within every view bucket.
|
||||
- [ ] Group SLICE9 note cards by owning notebook in the same way as to-do cards.
|
||||
|
||||
### Tests
|
||||
|
||||
- [ ] Test preservation of the explicit `children` scope token.
|
||||
- [ ] Test deterministic folder ordering, nested paths, duplicate names, and
|
||||
empty groups.
|
||||
- [ ] Test that kanban cards appear exactly once in their owning notebook group.
|
||||
- [ ] Test the same invariant in Skeleton and Eisenhower matrices.
|
||||
- [ ] Include both note and to-do cards in grouping tests.
|
||||
- [ ] Test aggregate statistics and warnings for double-counting regressions.
|
||||
- [ ] Test independent batching and "List more" state across generated views.
|
||||
- [ ] Test that numeric scope, `this-folder`, and `scope: all` still produce one
|
||||
view with unchanged behavior.
|
||||
|
||||
### Documentation and acceptance
|
||||
|
||||
- [ ] Document `scope: children` multi-view behavior in README.md.
|
||||
- [ ] Add a parent/child/grandchild notebook example to README.md or SPEC.md.
|
||||
- [ ] Update architecture and payload behavior in SPEC.md.
|
||||
- [ ] Add the feature to CHANGELOG.md.
|
||||
- [ ] Run the full automated test suite and record command/results.
|
||||
- [ ] Run the production package build and record command/artifact.
|
||||
- [ ] Manually verify a parent notebook containing multiple children and
|
||||
grandchildren in Joplin.
|
||||
- [ ] Confirm headings, ordering, empty-group handling, no duplication, sorting,
|
||||
styling, navigation, warnings, and independent expansion.
|
||||
- [ ] Mark SLICE10 complete in `SLICE10.md` and this file.
|
||||
|
||||
## Final cross-slice verification
|
||||
|
||||
- [ ] Run the complete automated suite after all three slices are integrated.
|
||||
- [ ] Build the final production `.jpl` and record its path and version.
|
||||
- [ ] Verify a dashboard containing kanban and both matrix modes together.
|
||||
- [ ] Verify more than 20 mixed note/to-do cards across multiple child notebooks.
|
||||
- [ ] Confirm reload resets every expanded list to its configured initial size.
|
||||
- [ ] Record manual Joplin acceptance without conflating it with automated checks.
|
||||
- [ ] Re-run the repository-wide prohibited-reference audit.
|
||||
- [ ] Ensure README.md, SPEC.md, CHANGELOG.md, TASKS.md, and SLICE8–10 reflect the
|
||||
final implementation state.
|
||||
12
package-lock.json
generated
12
package-lock.json
generated
@ -2385,10 +2385,6 @@
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
@ -2469,10 +2465,6 @@
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "CC-BY-4.0"
|
||||
@ -6186,10 +6178,6 @@
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#
|
||||
# Archive existing publish/*.jpl builds before a new `npm run dist`.
|
||||
#
|
||||
# Per CLAUDE.md: never overwrite a previous .jpl. This moves each current
|
||||
# Never overwrite a previous .jpl. This moves each current
|
||||
# publish/*.jpl into build-archive/ with a version + timestamp suffix so a
|
||||
# fresh build can't clobber the last one. No-op when no .jpl is present.
|
||||
#
|
||||
|
||||
29
src/Gtd/pageSize.ts
Normal file
29
src/Gtd/pageSize.ts
Normal file
@ -0,0 +1,29 @@
|
||||
export const DEFAULT_PAGE_SIZE = 10;
|
||||
|
||||
/**
|
||||
* Parse the shared kanban/matrix `page-size` option.
|
||||
* Only positive finite integers (or their non-empty string form) are valid.
|
||||
*/
|
||||
export default function parsePageSize(
|
||||
value: any,
|
||||
warnings: string[]
|
||||
): number {
|
||||
if (value === undefined) return DEFAULT_PAGE_SIZE;
|
||||
|
||||
const supportedType = typeof value === "number" || typeof value === "string";
|
||||
const candidate = supportedType ? Number(value) : NaN;
|
||||
if (
|
||||
supportedType &&
|
||||
String(value).trim() !== "" &&
|
||||
Number.isFinite(candidate) &&
|
||||
Number.isInteger(candidate) &&
|
||||
candidate > 0
|
||||
) {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
warnings.push(
|
||||
`Invalid page-size "${value}" (using ${DEFAULT_PAGE_SIZE})`
|
||||
);
|
||||
return DEFAULT_PAGE_SIZE;
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
import { KanbanConfig, InclusionMode, KanbanSortType } from "./types";
|
||||
import { parseNotebookOption } from "./resolveNotebook";
|
||||
import parsePageSize from "./pageSize";
|
||||
|
||||
const INCLUSION_MODES: InclusionMode[] = ["gtd-only", "all", "none"];
|
||||
const SORT_TYPES: KanbanSortType[] = ["due-date", "title", "modified-date"];
|
||||
@ -22,6 +23,7 @@ export default function parseKanbanConfig(raw: any): KanbanConfig {
|
||||
"sort",
|
||||
"in-progress-tag",
|
||||
"card-detail",
|
||||
"page-size",
|
||||
"done-window",
|
||||
];
|
||||
for (const key of Object.keys(input)) {
|
||||
@ -130,6 +132,8 @@ export default function parseKanbanConfig(raw: any): KanbanConfig {
|
||||
}
|
||||
}
|
||||
|
||||
const pageSize = parsePageSize(input["page-size"], warnings);
|
||||
|
||||
return {
|
||||
title,
|
||||
scopeDepth,
|
||||
@ -140,6 +144,7 @@ export default function parseKanbanConfig(raw: any): KanbanConfig {
|
||||
sort,
|
||||
inProgressTag,
|
||||
cardDetail,
|
||||
pageSize,
|
||||
doneWindow,
|
||||
warnings,
|
||||
};
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
IN_PROGRESS_TAG,
|
||||
} from "./types";
|
||||
import { parseNotebookOption } from "./resolveNotebook";
|
||||
import parsePageSize from "./pageSize";
|
||||
|
||||
const INCLUSION_MODES: InclusionMode[] = ["gtd-only", "all", "none"];
|
||||
const SORT_TYPES: KanbanSortType[] = ["due-date", "title", "modified-date"];
|
||||
@ -34,6 +35,7 @@ export default function parseMatrixConfig(raw: any): MatrixConfig {
|
||||
"in-progress-tag",
|
||||
"urgent-window",
|
||||
"card-detail",
|
||||
"page-size",
|
||||
];
|
||||
for (const key of Object.keys(input)) {
|
||||
if (!knownKeys.includes(key)) warnings.push(`Unknown option "${key}"`);
|
||||
@ -151,6 +153,8 @@ export default function parseMatrixConfig(raw: any): MatrixConfig {
|
||||
}
|
||||
}
|
||||
|
||||
const pageSize = parsePageSize(input["page-size"], warnings);
|
||||
|
||||
return {
|
||||
mode,
|
||||
title,
|
||||
@ -165,6 +169,7 @@ export default function parseMatrixConfig(raw: any): MatrixConfig {
|
||||
inProgressTag,
|
||||
urgentWindow,
|
||||
cardDetail,
|
||||
pageSize,
|
||||
warnings,
|
||||
};
|
||||
}
|
||||
|
||||
@ -136,6 +136,8 @@ export interface KanbanConfig {
|
||||
sort: "asc" | "desc";
|
||||
inProgressTag: string;
|
||||
cardDetail: "hover" | "always" | "none";
|
||||
/** Cards initially shown, and revealed per "List more" action. */
|
||||
pageSize: number;
|
||||
/** Days; Infinity means "all". */
|
||||
doneWindow: number;
|
||||
warnings: string[];
|
||||
@ -190,6 +192,8 @@ export interface MatrixConfig {
|
||||
/** Skeleton mode: days ahead within which a due date counts as "due soon". */
|
||||
urgentWindow: number;
|
||||
cardDetail: "hover" | "always" | "none";
|
||||
/** Cards initially shown, and revealed per "List more" action. */
|
||||
pageSize: number;
|
||||
warnings: string[];
|
||||
}
|
||||
|
||||
|
||||
@ -321,6 +321,61 @@
|
||||
gap: 0.4em;
|
||||
}
|
||||
|
||||
.gtd-list-more-control {
|
||||
position: relative;
|
||||
align-self: center;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
margin: 0.15em 0;
|
||||
}
|
||||
|
||||
.gtd-list-more {
|
||||
font: inherit;
|
||||
font-size: 0.82em;
|
||||
line-height: 1.3;
|
||||
padding: 0.3em 0.7em;
|
||||
border: 1px solid rgba(128, 128, 128, 0.55);
|
||||
border-radius: 4px;
|
||||
background-color: rgba(128, 128, 128, 0.1);
|
||||
color: var(--joplin-color, inherit);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.gtd-list-more:hover {
|
||||
background-color: rgba(128, 128, 128, 0.2);
|
||||
}
|
||||
|
||||
.gtd-list-more:focus-visible {
|
||||
outline: 2px solid currentColor;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.gtd-list-more-tooltip {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: calc(100% + 0.35em);
|
||||
z-index: 10;
|
||||
width: max-content;
|
||||
max-width: 16em;
|
||||
transform: translateX(-50%);
|
||||
padding: 0.3em 0.5em;
|
||||
border: 1px solid rgba(128, 128, 128, 0.55);
|
||||
border-radius: 4px;
|
||||
background-color: var(--joplin-background-color, white);
|
||||
color: var(--joplin-color, black);
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
|
||||
font-size: 0.78em;
|
||||
line-height: 1.3;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.12s ease-in-out;
|
||||
}
|
||||
|
||||
.gtd-list-more-control:hover .gtd-list-more-tooltip,
|
||||
.gtd-list-more-control:focus-within .gtd-list-more-tooltip {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.gtd-kanban-card {
|
||||
border: 1px solid rgba(128, 128, 128, 0.4);
|
||||
border-radius: 4px;
|
||||
|
||||
@ -498,23 +498,25 @@
|
||||
done: [],
|
||||
};
|
||||
const detail = payload.cardDetail || "hover";
|
||||
const pageSize = payload.pageSize || 10;
|
||||
|
||||
const columns = document.createElement("div");
|
||||
columns.className = "gtd-kanban-columns";
|
||||
|
||||
columns.appendChild(
|
||||
renderColumn("Backlog", board.backlog, detail, contentScriptId)
|
||||
renderColumn("Backlog", board.backlog, pageSize, detail, contentScriptId)
|
||||
);
|
||||
columns.appendChild(
|
||||
renderColumn(
|
||||
"In Progress",
|
||||
board.inProgress,
|
||||
pageSize,
|
||||
detail,
|
||||
contentScriptId
|
||||
)
|
||||
);
|
||||
columns.appendChild(
|
||||
renderColumn("Done", board.done, detail, contentScriptId)
|
||||
renderColumn("Done", board.done, pageSize, detail, contentScriptId)
|
||||
);
|
||||
|
||||
wrapper.appendChild(columns);
|
||||
@ -536,7 +538,7 @@
|
||||
el.appendChild(wrapper);
|
||||
}
|
||||
|
||||
function renderColumn(title, cards, detail, contentScriptId) {
|
||||
function renderColumn(title, cards, pageSize, detail, contentScriptId) {
|
||||
const column = document.createElement("div");
|
||||
column.className = "gtd-kanban-column";
|
||||
|
||||
@ -545,15 +547,84 @@
|
||||
header.textContent = title + " (" + cards.length + ")";
|
||||
column.appendChild(header);
|
||||
|
||||
column.appendChild(
|
||||
renderIncrementalCardList(cards, pageSize, detail, contentScriptId)
|
||||
);
|
||||
return column;
|
||||
}
|
||||
|
||||
let nextListMoreTooltipId = 1;
|
||||
|
||||
function renderIncrementalCardList(cards, pageSize, detail, contentScriptId) {
|
||||
const list = document.createElement("div");
|
||||
list.className = "gtd-kanban-cards";
|
||||
|
||||
cards.forEach(function (card) {
|
||||
list.appendChild(renderCard(card, detail, contentScriptId));
|
||||
});
|
||||
let visibleCount = 0;
|
||||
let moreControl = null;
|
||||
let moreButton = null;
|
||||
let moreTooltip = null;
|
||||
|
||||
column.appendChild(list);
|
||||
return column;
|
||||
function remainingText(count) {
|
||||
return count + " more " + (count === 1 ? "entry" : "entries");
|
||||
}
|
||||
|
||||
function createMoreControl() {
|
||||
const tooltipId =
|
||||
"gtd-list-more-tooltip-" + nextListMoreTooltipId++;
|
||||
|
||||
moreControl = document.createElement("div");
|
||||
moreControl.className = "gtd-list-more-control";
|
||||
|
||||
moreButton = document.createElement("button");
|
||||
moreButton.type = "button";
|
||||
moreButton.className = "gtd-list-more";
|
||||
moreButton.textContent = "List more";
|
||||
moreButton.setAttribute("aria-describedby", tooltipId);
|
||||
moreButton.addEventListener("click", function (event) {
|
||||
event.stopPropagation();
|
||||
renderNextBatch();
|
||||
});
|
||||
|
||||
moreTooltip = document.createElement("span");
|
||||
moreTooltip.id = tooltipId;
|
||||
moreTooltip.className = "gtd-list-more-tooltip";
|
||||
moreTooltip.setAttribute("role", "tooltip");
|
||||
|
||||
moreControl.appendChild(moreButton);
|
||||
moreControl.appendChild(moreTooltip);
|
||||
}
|
||||
|
||||
function renderNextBatch() {
|
||||
const nextVisibleCount = Math.min(
|
||||
visibleCount + pageSize,
|
||||
cards.length
|
||||
);
|
||||
for (let index = visibleCount; index < nextVisibleCount; index += 1) {
|
||||
const card = renderCard(cards[index], detail, contentScriptId);
|
||||
if (moreControl) list.insertBefore(card, moreControl);
|
||||
else list.appendChild(card);
|
||||
}
|
||||
visibleCount = nextVisibleCount;
|
||||
|
||||
const remaining = cards.length - visibleCount;
|
||||
if (remaining > 0) {
|
||||
if (!moreControl) {
|
||||
createMoreControl();
|
||||
list.appendChild(moreControl);
|
||||
}
|
||||
const summary = remainingText(remaining);
|
||||
moreTooltip.textContent = summary;
|
||||
moreButton.setAttribute("aria-label", "List more; " + summary);
|
||||
} else {
|
||||
if (moreControl) moreControl.remove();
|
||||
moreControl = null;
|
||||
moreButton = null;
|
||||
moreTooltip = null;
|
||||
}
|
||||
}
|
||||
|
||||
renderNextBatch();
|
||||
return list;
|
||||
}
|
||||
|
||||
function renderCard(card, detail, contentScriptId) {
|
||||
@ -650,6 +721,7 @@
|
||||
},
|
||||
};
|
||||
const detail = payload.cardDetail || "hover";
|
||||
const pageSize = payload.pageSize || 10;
|
||||
|
||||
// Axis header row: blank corner + column labels
|
||||
const grid = document.createElement("div");
|
||||
@ -665,6 +737,7 @@
|
||||
labels.quadrants.topLeft,
|
||||
board.topLeft,
|
||||
"do-first",
|
||||
pageSize,
|
||||
detail,
|
||||
contentScriptId
|
||||
)
|
||||
@ -674,6 +747,7 @@
|
||||
labels.quadrants.topRight,
|
||||
board.topRight,
|
||||
"schedule",
|
||||
pageSize,
|
||||
detail,
|
||||
contentScriptId
|
||||
)
|
||||
@ -685,6 +759,7 @@
|
||||
labels.quadrants.bottomLeft,
|
||||
board.bottomLeft,
|
||||
"delegate",
|
||||
pageSize,
|
||||
detail,
|
||||
contentScriptId
|
||||
)
|
||||
@ -694,6 +769,7 @@
|
||||
labels.quadrants.bottomRight,
|
||||
board.bottomRight,
|
||||
"eliminate",
|
||||
pageSize,
|
||||
detail,
|
||||
contentScriptId
|
||||
)
|
||||
@ -725,7 +801,14 @@
|
||||
return cell;
|
||||
}
|
||||
|
||||
function matrixQuadrant(title, cards, kind, detail, contentScriptId) {
|
||||
function matrixQuadrant(
|
||||
title,
|
||||
cards,
|
||||
kind,
|
||||
pageSize,
|
||||
detail,
|
||||
contentScriptId
|
||||
) {
|
||||
const quadrant = document.createElement("div");
|
||||
quadrant.className = "gtd-matrix-quadrant gtd-matrix-" + kind;
|
||||
|
||||
@ -734,14 +817,9 @@
|
||||
header.textContent = title + " (" + cards.length + ")";
|
||||
quadrant.appendChild(header);
|
||||
|
||||
const list = document.createElement("div");
|
||||
list.className = "gtd-kanban-cards";
|
||||
|
||||
cards.forEach(function (card) {
|
||||
list.appendChild(renderCard(card, detail, contentScriptId));
|
||||
});
|
||||
|
||||
quadrant.appendChild(list);
|
||||
quadrant.appendChild(
|
||||
renderIncrementalCardList(cards, pageSize, detail, contentScriptId)
|
||||
);
|
||||
return quadrant;
|
||||
}
|
||||
|
||||
|
||||
@ -249,6 +249,7 @@ async function handleGetKanban(message: { rawConfig?: string }) {
|
||||
return {
|
||||
title: config.title,
|
||||
cardDetail: config.cardDetail,
|
||||
pageSize: config.pageSize,
|
||||
configError:
|
||||
configError ||
|
||||
"Could not determine which note contains this kanban.",
|
||||
@ -271,6 +272,7 @@ async function handleGetKanban(message: { rawConfig?: string }) {
|
||||
return {
|
||||
title: config.title,
|
||||
cardDetail: config.cardDetail,
|
||||
pageSize: config.pageSize,
|
||||
scopeAll: config.scopeAll,
|
||||
configError,
|
||||
warnings: [
|
||||
@ -316,6 +318,7 @@ async function handleGetMatrix(message: { rawConfig?: string }) {
|
||||
return {
|
||||
title: config.title,
|
||||
cardDetail: config.cardDetail,
|
||||
pageSize: config.pageSize,
|
||||
configError:
|
||||
configError ||
|
||||
"Could not determine which note contains this matrix.",
|
||||
@ -344,6 +347,7 @@ async function handleGetMatrix(message: { rawConfig?: string }) {
|
||||
return {
|
||||
title: config.title,
|
||||
cardDetail: config.cardDetail,
|
||||
pageSize: config.pageSize,
|
||||
scopeAll: config.scopeAll,
|
||||
configError,
|
||||
warnings: [
|
||||
|
||||
@ -17,6 +17,7 @@ describe("parseKanbanConfig", () => {
|
||||
sort: "asc",
|
||||
inProgressTag: "in-progress",
|
||||
cardDetail: "hover",
|
||||
pageSize: 10,
|
||||
doneWindow: 7,
|
||||
});
|
||||
expect(c.warnings).toHaveLength(0);
|
||||
@ -31,6 +32,7 @@ describe("parseKanbanConfig", () => {
|
||||
sort: "desc",
|
||||
"in-progress-tag": "WIP",
|
||||
"card-detail": "always",
|
||||
"page-size": 25,
|
||||
"done-window": 14,
|
||||
});
|
||||
expect(c.title).toBe("Board");
|
||||
@ -40,9 +42,32 @@ describe("parseKanbanConfig", () => {
|
||||
expect(c.sort).toBe("desc");
|
||||
expect(c.inProgressTag).toBe("wip"); // lowercased for comparison
|
||||
expect(c.cardDetail).toBe("always");
|
||||
expect(c.pageSize).toBe(25);
|
||||
expect(c.doneWindow).toBe(14);
|
||||
});
|
||||
|
||||
test.each([
|
||||
[1, 1],
|
||||
[10, 10],
|
||||
[25, 25],
|
||||
["12", 12],
|
||||
])("page-size accepts positive integers (%p)", (input, expected) => {
|
||||
const c = parseKanbanConfig({ "page-size": input });
|
||||
expect(c.pageSize).toBe(expected);
|
||||
expect(c.warnings).toHaveLength(0);
|
||||
});
|
||||
|
||||
test.each([0, -1, 1.5, "many", "", NaN, Infinity, true])(
|
||||
"invalid page-size %p warns and falls back to 10",
|
||||
(input) => {
|
||||
const c = parseKanbanConfig({ "page-size": input });
|
||||
expect(c.pageSize).toBe(10);
|
||||
expect(c.warnings).toEqual([
|
||||
`Invalid page-size "${input}" (using 10)`,
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
test("done-window accepts 'all' as Infinity", () => {
|
||||
expect(parseKanbanConfig({ "done-window": "all" }).doneWindow).toBe(
|
||||
Infinity
|
||||
|
||||
@ -21,10 +21,33 @@ describe("parseMatrixConfig", () => {
|
||||
inProgressTag: "in-progress",
|
||||
urgentWindow: 3,
|
||||
cardDetail: "hover",
|
||||
pageSize: 10,
|
||||
});
|
||||
expect(c.warnings).toHaveLength(0);
|
||||
});
|
||||
|
||||
test.each([
|
||||
[1, 1],
|
||||
[10, 10],
|
||||
[25, 25],
|
||||
["12", 12],
|
||||
])("page-size accepts positive integers (%p)", (input, expected) => {
|
||||
const c = parseMatrixConfig({ "page-size": input });
|
||||
expect(c.pageSize).toBe(expected);
|
||||
expect(c.warnings).toHaveLength(0);
|
||||
});
|
||||
|
||||
test.each([0, -1, 1.5, "many", "", NaN, Infinity, true])(
|
||||
"invalid page-size %p warns and falls back to 10",
|
||||
(input) => {
|
||||
const c = parseMatrixConfig({ "page-size": input });
|
||||
expect(c.pageSize).toBe(10);
|
||||
expect(c.warnings).toEqual([
|
||||
`Invalid page-size "${input}" (using 10)`,
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
test("mode parses and warns on invalid", () => {
|
||||
expect(parseMatrixConfig({ mode: "eisenhower" }).mode).toBe(
|
||||
"eisenhower"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user