Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
id: T-0201
title: Every filter but free text dies on reload in the record views
status: backlog
status: done
type: bug
priority: medium
area: ui
Expand All @@ -10,7 +10,12 @@ effort: S
scope: [packages/workfile/ui/src]
origin: [T-0195]
created: 2026-08-05
updated: 2026-08-05
updated: 2026-08-07
verified:
at: "2026-08-07T19:27:25.363Z"
method: local
commit: 94c9db17dcc2adea13b1e2e0f4d18e373136ee19
digest: "sha256:f03dcd31b0af609f8e57ea210b9d8e85af6dbd9e11dbb197b4757df42bf70c1e"
---

T-0195 moved the three free-text boxes off local `useState` and into the URL, the
Expand All @@ -33,8 +38,17 @@ the URL one view at a time is five chances to name the parameter differently.

## Acceptance criteria

- [ ] Docs, history and memory restore every filter they offer after a reload.
- [ ] They survive a view switch and a return, the way the work views' filters do.
- [ ] The parameter names are decided in one place, next to `q` and `find`.
- [ ] Clearing a filter removes its parameter rather than leaving it empty in the URL.
- [ ] A test covers the round trip for each view.
- [x] Docs, history and memory restore every filter they offer after a reload.
- [x] They survive a view switch and a return, the way the work views' filters do.
- [x] The parameter names are decided in one place, next to `q` and `find`.
- [x] Clearing a filter removes its parameter rather than leaving it empty in the URL.
- [x] A test covers the round trip for each view.

## Activity

- 2026-08-07 19:09Z illodev@local#42eb42f5 · claimed
- 2026-08-07 19:27Z illodev@local#42eb42f5 · released

## Notes

- 2026-08-07 19:27Z illodev@local#42eb42f5 — local verification: Playwright against a live server on the repo workspace, 24 checks: Memory narrowed 55 to 32 records and kept it across a reload, a switch to Explorer and back; History 171 rows to 2 with state and visibility both restored; the docs indexed group gone with the toggle still pressed after a reload; Back walked the narrowings. The guard was mutation-proven five ways, including the parameter-name clash with the card status filter.
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
---
id: T-0207
title: Only cards can be read in sequence; every other kind loses its place
status: backlog
status: done
type: feature
priority: medium
area: ui
tags: [navigation]
effort: S
scope: [packages/workfile/ui/src/components/RecordPanel.tsx]
scope: [packages/workfile/ui/src/components/RecordPanel.tsx, packages/workfile/ui/src/main.tsx]
origin: [ADR-0017, T-0197]
created: 2026-08-05
updated: 2026-08-05
updated: 2026-08-07
verified:
at: "2026-08-07T19:48:43.969Z"
method: local
commit: 94c9db17dcc2adea13b1e2e0f4d18e373136ee19
digest: "sha256:6cb976fc73786d6336d6bc3ed17ae612bab13464996c1f937b92d6b3301e73e8"
---

`Inspector` carries a previous/next cursor fed by `orderedIds`, so a reader
Expand All @@ -36,8 +41,24 @@ something different depending on how you arrived is worse than no next.

## Acceptance criteria

- [ ] Opening a record from a list lets the reader move to the next and previous in that list without closing the drawer.
- [ ] The order matches what the view is showing, filters and sort included.
- [ ] Opening a record from somewhere with no list shows no cursor rather than an arbitrary one.
- [ ] The control is the one `Inspector` already uses, not a second one that looks like it.
- [ ] The keyboard reaches it, and it does not capture keys the drawer already binds.
- [x] Opening a record from a list lets the reader move to the next and previous in that list without closing the drawer.
- [x] The order matches what the view is showing, filters and sort included.
- [x] Opening a record from somewhere with no list shows no cursor rather than an arbitrary one.
- [x] The control is the one `Inspector` already uses, not a second one that looks like it.
- [x] The keyboard reaches it, and it does not capture keys the drawer already binds.

## Activity

- 2026-08-07 19:27Z illodev@local#42eb42f5 · claimed
- 2026-08-07 19:48Z illodev@local#42eb42f5 · released

## Notes

- 2026-08-07 19:45Z illodev@local#42eb42f5 — Two premises corrected while doing this, both from ADR-0018 landing after the card was written.

First, which panel serves what. The card says RecordPanel covers 'decisions, learnings, incidents, conventions and documents opened from outside the docs view'. It does not: memory records get MemoryPanel and documents get DocPanel, and RecordPanel is the fallback for changelog fragments and releases. The gap was real in all three, so the fix is a control every panel renders rather than one added to RecordPanel — which is also what the criterion asking for 'the one Inspector already uses' turns out to require.

Second, and larger: ADR-0018 gave docs and history their own readers, so the shared drawer no longer opens for either. The cursor had to go into those two panes as well or serial reading in history — the very complaint T-0197 filed — would still be broken with the card closed. Five call sites, one shared control, and the list published by whichever view was showing it.

And one thing the card claims that is not being delivered: Workflow gets no cursor. Its list is a force-directed layout, not an order, so next would mean whichever node the simulation happened to place — which is exactly what the card's own rule forbids. The graph opens records with no list behind it, deliberately.
- 2026-08-07 19:48Z illodev@local#42eb42f5 — local verification: Playwright against a live server on the repo workspace, 25 checks. Memory walks its lane order in the drawer and stops at the top; History walks its rail across the section break from unpublished into releases, and a state filter narrows the cursor with the list; Docs shows no cursor until a row is clicked, then steps; the workflow graph and a record opened by URL show none at all; the card cursor still walks the visible table. Keyboard: the control takes focus and steps on Enter, Escape still dismisses the drawer and Ctrl-K still opens the palette, so it binds nothing of its own. The rule is unit-tested through recordNeighbours and mutation-proven four ways, including a lookalike control added back to the inspector.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
id: CHG-0151
title: The record views' filters survive a reload
type: fixed
area: ui
visibility: public
cards: [T-0201]
created: 2026-08-07
updated: 2026-08-07
---

Docs' managed toggle and the four chips in History and Memory now ride the address bar like the free text beside them. Narrowing Memory to open incidents, opening one and coming back keeps the narrowing instead of silently handing back the whole workspace.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
id: CHG-0152
title: Every kind of record can be read in sequence, not only cards
type: added
area: ui
visibility: public
cards: [T-0207]
created: 2026-08-07
updated: 2026-08-07
---

The previous/next cursor the card inspector has always carried is now one shared control, rendered by every panel that reads a record: memory, changelog fragments and releases, and the readers Docs and History own themselves. It walks the list the view was showing, in the order it was showing it, so it narrows when the filters do — and it is absent, rather than arbitrary, where there is no list behind the record: a link inside a body, a related row, the command palette, or a node of the workflow graph.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
id: LRN-0033
title: A card outlives the decision it was filed under, and its mechanics go stale in silence
status: active
confidence: high
related: [ADR-0018, LRN-0027, T-0207]
tags: [ui, process]
created: 2026-08-07
updated: 2026-08-07
---

T-0207 was filed as ADR-0017's surviving finding. ADR-0018 then superseded ADR-0017 and moved the layout: docs and history became views that own their readers, so the shared drawer stopped opening for either. The card was never touched, and it did not have to be wrong to be misleading — every sentence in it had been true when written.

What it still said was that `RecordPanel` serves "decisions, learnings, incidents, conventions and documents opened from outside the docs view". It serves none of those: memory records get `MemoryPanel`, documents get `DocPanel`, and `RecordPanel` is the fallback for changelog fragments and releases. So the card's scope named one file, `RecordPanel.tsx`, and a fix that touched only that file would have satisfied the card, passed review, and left serial reading in history — the complaint the whole chain started from — exactly as broken as it was.

The superseding record even flagged this. ADR-0018 has a "What survives from ADR-0017" section naming T-0207 and saying it "stands on its own". It does stand, but it stands on the old geometry: what survived was the *finding*, not the mechanics the card wrote down around it.

**Why:** a card is a snapshot of an understanding, and a decision record is the thing that changes understandings. Superseding an ADR updates the decision graph and leaves the cards that cite it describing a codebase that has moved. Nothing reports that, because nothing is broken: the links resolve, `doctor` is clean, and the prose is internally consistent.

**How to apply:** when a card's `origin` or `related` names a decision that is now `superseded`, re-read the card against the superseding one before starting — the finding usually survives and the mechanics usually do not. Treat its `scope` as the weakest part: it was written from the old arrangement and it is what silently narrows the fix. Then do what [[LRN-0027]] says and open the view, which is the only thing that settles which panel is actually on screen. Correct the premise on the card rather than quietly working around it, so the next reader inherits the correction instead of the snapshot.
27 changes: 27 additions & 0 deletions packages/workfile/docs/ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,33 @@ file is checked.
rides the address bar like every other filter (`?q=` for cards, `?find=`
for docs, history and memory). `test/filter-search.test.ts` fails if a view
grows a box of its own or a wording of its own.
- **A filter that is not in the URL is a filter that dies on reload.** Every
one of them is state the shell owns and `ui/src/query.ts` serialises — the
card axes flat (`?status=`, `?area=`, …), the record collections' axes
namespaced by view (`?docs-managed=1`, `?history-state=`,
`?memory-collection=`, `?memory-status=`). The prefix is a rule and not a
case-by-case choice: the obvious name for Memory's is `status`, which the
card filter already owns, and the loser of a clash like that filters by
nothing without saying so. A record view therefore takes its filters as a
prop and reports changes as a patch, so its coupled pairs — picking a Memory
collection clears the status that belonged to it — reach the address bar in
one write. Same suite: it fails on a view that takes one back into a
`useState`, and on a parameter that collides with a card axis.
- **A record opened from a list can be read as a sequence.** Every panel that
reads a record — the card inspector, the memory panel, the generic record
panel, and the readers Docs and History own themselves — renders
`ui/src/record-cursor.tsx`, and the rule for where previous and next go is
`recordNeighbours` in `navigation.ts`, beside the other navigation rules. The
list is whatever the view was showing, in the order it was showing it, so it
narrows when the filters do; each view publishes its own as the second
argument to `onSelect`. **Absent, not guessed, where there is no list:** a
`[[LRN-0004]]` in a body, a `related` row, the command palette, and a node of
the Workflow graph all open a record with nothing behind it, and a force
layout is not an order. At the ends of a real list the control renders with
one half disabled, which is how a reader tells "no next" from "there was
never a sequence here". It is a context rather than a prop for the reason
`read-only.tsx` gives: the panels sit in three different places, and all
three have to reach it.
- **The filter bar is one container, and it decides what may scroll away.**
`ui/src/components/FilterBar.tsx` owns the whole bar in every view that has
one — the shell, Docs, History, Memory, Workflow and the Gantt toolbar — and
Expand Down
Loading