406 lines
20 KiB
Markdown
406 lines
20 KiB
Markdown
# SLICE 14 — Opt-in kanban drag and drop (v2.0 foundation)
|
||
|
||
> **State-saving rule:** update this file after every completed task and whenever
|
||
> work pauses. Keep implementation, automated validation, production packaging,
|
||
> and manual Joplin acceptance as separate status boundaries.
|
||
|
||
## Status
|
||
|
||
**COMPLETE.** Phases 1–7 are complete. Focused and full automation passed, the
|
||
production JPL was built and inspected, and the user confirmed every Phase 7
|
||
manual Joplin acceptance test passed on 2026-07-31.
|
||
|
||
## Goal
|
||
|
||
Allow users to move kanban cards between Backlog, In Progress, and Done from the
|
||
rendered-note view. Moves persist to Joplin, so refreshes and other dashboards
|
||
derive the same workflow state.
|
||
|
||
````
|
||
```gtd-kanban
|
||
title: Editable projects and tasks
|
||
editable: yes
|
||
notes: all
|
||
todos: all
|
||
```
|
||
````
|
||
|
||
Existing blocks remain read-only when `editable` is omitted.
|
||
|
||
## Confirmed product contract
|
||
|
||
- Desktop rendered-note webview only; cards are DOM elements, not canvas pixels.
|
||
- `editable: yes` enables mutation; default `no` preserves every v1 block.
|
||
- Parse `yes | no` case-insensitively. Invalid, empty, or non-scalar values warn
|
||
and fall back to `no`.
|
||
- Editable boards provide pointer drag and a keyboard-operable **Move to…**
|
||
control. Dragging is never the only interaction.
|
||
- Successful moves persist through Joplin's data API, then refresh the affected
|
||
board from canonical data.
|
||
- Failed/stale moves show an inline error and converge to canonical state.
|
||
- Moves change workflow state, not free-form ordering; configured sorting remains
|
||
authoritative.
|
||
- Cards stay in their current notebook; cross-group moves are out of scope.
|
||
|
||
## State transitions
|
||
|
||
An intentional move canonicalizes configured workflow markers. Normal read-only
|
||
collection still treats Done as higher priority than In Progress.
|
||
|
||
### Ordinary opted-in notes
|
||
|
||
| Destination | Persisted change |
|
||
|---|---|
|
||
| Backlog | Remove `done-tag` and `in-progress-tag`. |
|
||
| In Progress | Add `in-progress-tag`; remove `done-tag`. |
|
||
| Done | Add `done-tag`; remove `in-progress-tag`. |
|
||
|
||
Notes remain ordinary notes. Their `gtd` block, content, dates, notebook, and
|
||
unrelated tags remain unchanged.
|
||
|
||
### Native Joplin to-dos
|
||
|
||
| Destination | Persisted change |
|
||
|---|---|
|
||
| Backlog | Set `todo_completed` to `0`; remove `in-progress-tag`. |
|
||
| In Progress | Set `todo_completed` to `0`; add `in-progress-tag`. |
|
||
| Done | Set `todo_completed` to current epoch milliseconds; remove `in-progress-tag`. |
|
||
|
||
`done-tag` remains note-only. Moving an already completed to-do to Done is a
|
||
no-op rather than rewriting its completion history.
|
||
|
||
### Required cases
|
||
|
||
| Item | Source | Destination | Result |
|
||
|---|---|---|---|
|
||
| Note | Backlog | In Progress | Add progress, remove done. |
|
||
| Note | Backlog/In Progress | Done | Add done, remove progress. |
|
||
| Note | Done | In Progress | Remove done, add progress. |
|
||
| Note | Done/In Progress | Backlog | Remove both. |
|
||
| To-do | Backlog | In Progress | Reopen if needed, add progress. |
|
||
| To-do | Backlog/In Progress | Done | Complete natively, remove progress. |
|
||
| To-do | Done | In Progress | Reopen, add progress. |
|
||
| To-do | Done/In Progress | Backlog | Reopen, remove progress. |
|
||
| Either | Any | Same column | No mutation or refresh. |
|
||
|
||
## Repeating to-dos
|
||
|
||
Native completion may trigger Repeating To-Dos to advance or replace an item.
|
||
Use Joplin's real `todo_completed` field; do not simulate recurrence. Always
|
||
refetch after completion and accept canonical state if the item changes during
|
||
the round trip. Explicitly test completing and reopening recurring to-dos.
|
||
|
||
## Architecture and trust boundary
|
||
|
||
```text
|
||
pointer drop or keyboard Move to…
|
||
-> constrained webview intent
|
||
-> main process reparses config and requires editable:yes
|
||
-> mutation service fetches current note and tag identities
|
||
-> validate eligibility, scope, group, type, and destination
|
||
-> apply minimal idempotent completion/tag operations
|
||
-> recollect and rerender the initiating board from canonical data
|
||
```
|
||
|
||
The webview sends intent only:
|
||
|
||
```ts
|
||
type KanbanDestination = "backlog" | "inProgress" | "done";
|
||
|
||
interface MoveKanbanCardIntent {
|
||
view: "kanban";
|
||
hostNoteId: string;
|
||
cardId: string;
|
||
destination: KanbanDestination;
|
||
rawConfig: string;
|
||
viewInstanceId: string;
|
||
}
|
||
```
|
||
|
||
Never accept arbitrary REST paths, patches, tag IDs, or tag-operation lists from
|
||
the webview. Reparse configuration and independently derive every write.
|
||
|
||
Add narrowly scoped, testable operations for:
|
||
|
||
- fetching one current note, including type, completion, parent, and body when
|
||
eligibility must be checked;
|
||
- listing tags with both ID and title;
|
||
- finding/creating a configured tag by normalized title;
|
||
- idempotently attaching/detaching a tag;
|
||
- patching only `todo_completed` on a native to-do.
|
||
|
||
Tag matching stays case-insensitive. Reuse an existing case variant. If duplicate
|
||
same-title tags exist, do not create another; select deterministically and test it.
|
||
|
||
### Fresh-state validation
|
||
|
||
Before writing, verify that the host note exists, the card is not the host, the
|
||
destination is fixed, the block still has `editable: yes`, the item remains
|
||
admitted by `notes`/`todos`, ordinary notes retain a found `gtd` block, and the
|
||
item remains in resolved scope. Grouped boards must retain exact notebook
|
||
ownership. Stale conditions make no write and trigger a canonical refresh.
|
||
|
||
### Multi-step safety
|
||
|
||
Joplin note/tag operations are separate and may not be transactional. Compute
|
||
changes first, skip already-correct writes, order changes sensibly, and refetch
|
||
after success or failure. Do not compensate with stale values that might
|
||
overwrite a concurrent user edit.
|
||
|
||
## Webview interaction
|
||
|
||
Prefer pointer events for consistent desktop behavior and click/drag separation.
|
||
|
||
- Show a drag affordance only on editable cards.
|
||
- Start after a movement threshold; ordinary clicks still open notes.
|
||
- Use pointer capture and visible dragged/valid-target/active-target states.
|
||
- Support long, scrolling, grouped, and paginated boards.
|
||
- Escape, cancellation, lost capture, outside drop, and same-column drop do not
|
||
write.
|
||
- Scope targets to the nearest board and `viewInstanceId`, never another block.
|
||
- Disable duplicate submissions and show pending/error feedback.
|
||
- Refresh canonically; sorting may place a moved card beyond the visible page.
|
||
|
||
Keyboard/accessibility requirements:
|
||
|
||
- provide a focusable **Move to…** control with fixed destinations;
|
||
- disable the current destination;
|
||
- preserve keyboard open-note behavior and restore focus after rerender;
|
||
- announce pending, success, failure, and destination through `aria-live`;
|
||
- give controls and targets useful accessible names.
|
||
|
||
## Rendering and refresh
|
||
|
||
- Carry stable `hostNoteId` and per-render `viewInstanceId` context.
|
||
- Multiple blocks retain independent request/pagination state.
|
||
- Refresh only the initiating instance and reuse collectors for sorting, totals,
|
||
warnings, grouping, and cards.
|
||
- Optimistic preview is allowed; canonical payload remains authoritative.
|
||
- Preserve expansion where practical or document/test consistent reset behavior.
|
||
- Reject stale async responses with a per-instance request sequence.
|
||
|
||
## Implementation plan
|
||
|
||
### Phase 1 — Configuration and protocol
|
||
|
||
- [x] Add `editable: boolean` to `KanbanConfig`, default `false`.
|
||
- [x] Parse/validate `editable: yes | no` and add complete parser tests.
|
||
- [x] Define destinations, constrained intents, success, stale, and error types.
|
||
- [x] Add stable host/view identity to payload/context.
|
||
- [x] Prove existing blocks retain identical read-only behavior.
|
||
|
||
### Phase 1 completion record
|
||
|
||
- Added strict scalar `editable: yes | no` parsing. Omitted and `no` remain
|
||
read-only; invalid, empty, non-string, array, and object values warn and use
|
||
`no`.
|
||
- Added fixed Kanban destinations, a constrained semantic move intent, typed
|
||
success/stale/error results, and a runtime destination guard. The protocol
|
||
contains no arbitrary paths, patches, tag IDs, or tag-operation lists.
|
||
- Every rendered placeholder now owns a stable `viewInstanceId` for its render.
|
||
Kanban payloads echo that identity and the canonically resolved host note ID,
|
||
including a null host identity on missing-source responses.
|
||
- Focused automation passed: 2 suites, 62 tests. TypeScript with
|
||
`--skipLibCheck`, webview JavaScript syntax, and `git diff --check` passed.
|
||
- Full regression automation passed: 17 suites, 261 tests. No production JPL
|
||
was built and no manual Joplin acceptance was performed in this phase.
|
||
|
||
### Phase 2 — Testable mutation service
|
||
|
||
- [x] Add fresh note/tag reads, find-or-create, membership changes, and native
|
||
completion patches behind a narrow adapter.
|
||
- [x] Implement every note and to-do transition above.
|
||
- [x] Make changes idempotent and preserve unrelated data.
|
||
- [x] Test transitions, conflicting tags, custom names/case, duplicate tags,
|
||
no-ops, partial failures, and stale/concurrent reads.
|
||
|
||
### Phase 2 completion record
|
||
|
||
- Added a writable adapter separate from the existing read-only collector
|
||
abstraction. Its surface is limited to fetching one note and tag identities,
|
||
listing/creating tags, attaching/detaching memberships, and patching only
|
||
`todo_completed`.
|
||
- Added canonical note and native-to-do transitions for all three destinations.
|
||
Operations are minimal and idempotent, tag matching is case-insensitive,
|
||
existing case variants are reused deterministically, duplicate attached tags
|
||
are removed deterministically, and unrelated fields/tags are untouched.
|
||
- Partial write failures propagate without stale compensating writes so the
|
||
later handler can refetch canonical state. Missing-card reads fail before any
|
||
write.
|
||
- Focused mutation automation passed: 1 suite, 13 tests. Full regression
|
||
automation passed: 18 suites, 274 tests. TypeScript with `--skipLibCheck` and
|
||
`git diff --check` passed. No package or manual test was performed.
|
||
|
||
### Phase 3 — Message validation
|
||
|
||
- [x] Register the kanban move message in the existing handler.
|
||
- [x] Reparse config and require `editable: yes` in the main process.
|
||
- [x] Reject malformed intent, arbitrary destinations, missing/host/filtered/
|
||
out-of-scope items, lost opt-in, and cross-group movement without writes.
|
||
- [x] Return typed user-safe results and keep diagnostic detail in logs.
|
||
- [x] Prove webview input cannot select arbitrary paths, fields, tags, or writes.
|
||
|
||
### Phase 3 completion record
|
||
|
||
- Registered `moveKanbanCard` and added a testable validation service between
|
||
the untrusted webview message and the Phase 2 mutation adapter.
|
||
- The handler requires an exact constrained message shape, fixed destination,
|
||
non-empty host/card/view identities, matching currently selected host, valid
|
||
reparsed YAML, and explicit `editable: yes`. Unknown fields are rejected, so
|
||
paths, patches, tag operations, and arbitrary write instructions cannot be
|
||
smuggled alongside a valid destination.
|
||
- Fresh folders and the freshly fetched card are used to revalidate notebook
|
||
targeting, scope, exact current parent ownership, host exclusion, item type
|
||
filters, and ordinary-note/to-do `gtd` eligibility immediately before writes.
|
||
- Expected stale conditions and failures return typed user-safe results.
|
||
Unexpected API detail is retained only in plugin-process diagnostic logging.
|
||
- Focused validation passed: 3 suites, 43 tests. Full regression validation
|
||
passed: 19 suites, 295 tests. TypeScript with `--skipLibCheck`, webview syntax,
|
||
and `git diff --check` passed. Packaging and manual acceptance remain pending.
|
||
|
||
### Phase 4 — Pointer drag and drop
|
||
|
||
- [x] Add editable-only affordances and complete pointer lifecycle handling.
|
||
- [x] Implement thresholds, capture, target detection, scrolling, cancellation,
|
||
Escape, lost capture, pending state, and failure UI.
|
||
- [x] Preserve clicks, details, styling, pagination, and navigation.
|
||
- [x] Prevent cross-block/group drops and duplicate submissions.
|
||
- [x] Refresh canonically and ignore stale responses.
|
||
|
||
### Phase 4 completion record
|
||
|
||
- Added an explicit editable-only drag handle to each Kanban card. Pointer drag
|
||
begins only after six pixels of movement; ordinary card clicks retain the
|
||
existing open-note path, while a completed/cancelled drag suppresses the
|
||
synthetic click that could otherwise open a note accidentally.
|
||
- Pointer capture, active-card styling, valid/active target styling, viewport
|
||
edge scrolling, Escape, pointer cancellation, lost capture, outside drops,
|
||
and same-column no-ops are implemented. Drop targets are restricted to the
|
||
originating board element, preventing movement across notebook groups or
|
||
stacked blocks.
|
||
- Per-view pending state prevents duplicate submissions and temporarily disables
|
||
handles. Inline pending/error feedback is shown without changing the card
|
||
arrays optimistically.
|
||
- Every accepted/rejected move triggers a canonical `getKanban` refresh for the
|
||
initiating `viewInstanceId`. Per-instance request sequencing and echoed
|
||
identity prevent older or foreign responses from replacing the current view.
|
||
- Full regression automation passed: 19 suites, 295 tests. TypeScript with
|
||
`--skipLibCheck`, webview syntax, and `git diff --check` passed. The repository
|
||
has no browser DOM harness, so pointer visuals and behavior remain explicit
|
||
Phase 7 manual acceptance items. No production JPL was built in this phase.
|
||
|
||
### Phase 5 — Keyboard and accessibility
|
||
|
||
- [x] Add keyboard-operable **Move to…** controls.
|
||
- [x] Preserve open-note keys and predictable focus.
|
||
- [x] Add accessible names, instructions, states, and live announcements.
|
||
- [x] Test keyboard-only movement among all columns.
|
||
|
||
### Phase 5 completion record
|
||
|
||
- Every editable card now exposes a native keyboard-operable **Move to…**
|
||
selector with fixed Backlog, In Progress, and Done options. The current
|
||
destination and placeholder are disabled; choosing another destination uses
|
||
the same constrained message and canonical refresh as pointer movement.
|
||
- Editable cards are keyboard-focusable links with accessible open-note names
|
||
and Enter/Space activation. The pointer-only handle is removed from the tab
|
||
and accessibility order so it does not create an inert keyboard stop.
|
||
- Move controls carry card-specific accessible names and visible focus styles.
|
||
Pending, success, stale, and error status messages use atomic live regions;
|
||
controls are disabled while a request is active.
|
||
- Canonical rerender restores focus to the moved card's selector when it remains
|
||
visible. If sorting or pagination removes the card from the rendered batch,
|
||
focus moves to the status region instead of being lost.
|
||
- Read-only boards retain their previous markup/focus behavior and expose no
|
||
drag handle, move selector, or mutation status UI.
|
||
- Full regression automation passed: 19 suites, 295 tests. TypeScript with
|
||
`--skipLibCheck`, webview syntax, and `git diff --check` passed. With no DOM
|
||
harness in this repository, end-to-end keyboard and screen-reader behavior
|
||
remains a Phase 7 manual acceptance boundary. No JPL was built in this phase.
|
||
|
||
### Phase 6 — Documentation, regression, and packaging
|
||
|
||
- [x] Document opt-in editing, transitions, sorting, recurrence caveats, keyboard
|
||
controls, and desktop scope in README.md and SPEC.md.
|
||
- [x] Revise text that says all cards are read-only or drag is merely planned.
|
||
- [x] Add an unreleased v2.0.0 CHANGELOG entry without releasing.
|
||
- [x] Verify calendars, matrices, Gantt, read-only boards, filters, groups,
|
||
warnings, totals, navigation, and styling do not regress.
|
||
- [x] Run focused tests, full Jest, TypeScript, webview syntax, whitespace,
|
||
prohibited-reference checks, and production JPL build/inspection.
|
||
|
||
### Phase 6 completion record
|
||
|
||
- README documents the exact `editable: yes` opt-in, pointer and keyboard
|
||
controls, note/to-do transitions, sorting/pagination authority, recurrence,
|
||
cancellation/failure behavior, preservation guarantees, and desktop scope.
|
||
- SPEC documents parsing, transition tables, constrained protocol and trust
|
||
boundary, mutation safety, request sequencing, canonical refresh, focus/live
|
||
announcements, and the remaining multi-dashboard refresh limitation. The old
|
||
planned-authoring roadmap is replaced with the implemented release-pending
|
||
contract. Historical v1 read-only text and current matrix/Gantt read-only text
|
||
remain intentionally scoped and accurate.
|
||
- CHANGELOG contains an unreleased 2.0.0 Kanban entry. Package and manifest
|
||
versions intentionally remain 1.0.0 until the consolidated v2 release phase;
|
||
nothing was published or pushed.
|
||
- Focused automation passed: 4 suites, 96 tests. Full automation passed: 19
|
||
suites, 295 tests. TypeScript with `--skipLibCheck`, webview JavaScript syntax,
|
||
`git diff --check`, stale-wording review, and packaged-output prohibited-path/
|
||
planning-reference audits passed.
|
||
- `npm run dist` passed. The fresh production artifact is
|
||
`publish/com.victorwiebe.joplin.plugin.gtd-calendar.jpl` (177,664 bytes,
|
||
embedded manifest version 1.0.0, SHA-256
|
||
`749db5d334457ba4818a075c9d0df4f47bda53a478f0dc9904cbab5f8d0d4e33`).
|
||
Its five expected runtime files and Kanban mutation, pointer, keyboard,
|
||
accessibility, and request-sequencing markers were inspected.
|
||
- No manual Joplin acceptance was performed; every Phase 7 item remains open.
|
||
|
||
### Phase 7 — Manual Joplin acceptance
|
||
|
||
- [x] Omitted/`no`/invalid `editable` states remain read-only as documented.
|
||
- [x] Pointer moves notes through all columns and persists exact configured tags.
|
||
- [x] Pointer moves to-dos through all columns and persists native completion and
|
||
progress state.
|
||
- [x] Conflicting note tags are canonicalized only by an intentional move.
|
||
- [x] Custom workflow tags preserve unrelated/similarly named tags.
|
||
- [x] Same-column, cancelled, outside, Escape, and interrupted drags do not write.
|
||
- [x] Click still opens; drag does not accidentally open.
|
||
- [x] Keyboard-only movement works, restores focus, and announces results.
|
||
- [x] Stale state/API failure cannot leave false UI or corrupt unrelated data.
|
||
- [x] Sorting, totals, pagination, colours, glyphs, details, and Done styling stay
|
||
correct after refresh.
|
||
- [x] Single, grouped, multiple-block, and long scrolling boards work.
|
||
- [x] Completing/reopening a recurring to-do works with Repeating To-Dos.
|
||
- [x] Record explicit user sign-off separately from automation.
|
||
|
||
### Phase 7 acceptance record
|
||
|
||
- **PASSED — USER SIGN-OFF RECEIVED 2026-07-31.** The user confirmed all Phase 7
|
||
manual tests passed against the inspected Phase 6 production JPL.
|
||
- Accepted coverage includes read-only opt-in boundaries, ordinary-note and
|
||
native-to-do transitions, conflicting/custom/unrelated tags, pointer
|
||
cancellation and click separation, keyboard movement and focus/live feedback,
|
||
stale/API-failure convergence, sorting/pagination/styling, grouped and stacked
|
||
boards, long scrolling, and Repeating To-Dos integration.
|
||
- This manual acceptance is recorded separately from Phase 6 automation and
|
||
packaging. Slice 15 has not begun.
|
||
|
||
## Out of scope
|
||
|
||
- Free-form ordering or persisted rank.
|
||
- Moving between notebooks/groups or changing `parent_id`.
|
||
- Editing card fields or arbitrary tags.
|
||
- Calendar, Gantt, or matrix drag (SLICE 15 covers Eisenhower).
|
||
- Skeleton drag, mobile support, or recurrence reimplementation.
|
||
- Publishing v2.0.0 before both slices and release acceptance are complete.
|
||
|
||
## Acceptance criteria
|
||
|
||
- Only an explicitly editable kanban can mutate data.
|
||
- Every destination produces the exact canonical state above.
|
||
- Main-process validation uses fresh Joplin data and constrained intent.
|
||
- Pointer and keyboard workflows are complete and accessible.
|
||
- Failures converge to canonical state without overwriting unrelated changes.
|
||
- All read-only and v1.0.0 behavior remains compatible.
|
||
- Automation, packaging, and manual acceptance are recorded separately.
|