Repository navigation
New Note Form Footer Hints Reflect The Focused Field - #933
Conversation
…t have
The New note form's footer was unconditional while the list of choices it
describes is gated, so on arrival — focused in Name, where no list is
rendered — it already claimed `↑↓ pick`. `↑↓` never moved between Name,
Folder and Tags; it only ever moved through the choice rows under Folder
or Tags, and Tab is the only field-level navigation. Neither was stated.
`↵ create` was wrong in three of the states it appeared in. With a row
highlighted, Enter picks it. In Tags, with a valid tag typed and no
highlight, Enter commits the typed tag as a chip. In Tags, with text that
is not a valid tag, Enter does nothing at all.
Derive both hints from state the component already holds:
const hasList =
focused === 'folder' ? folderRows.length > 0
: focused === 'tags' ? tagRows.length > 0
: false
const rowPicked = hasList && activeRow >= 0
const inTags = focused === 'tags'
const enterLabel =
rowPicked ? (inTags ? 'add tag' : 'choose folder')
: inTags && typedTag.trim() ? (normalizeTag(typedTag) ? 'add tag' : null)
: 'create note'
`hasList` is load-bearing in `rowPicked`. Typing in Folder preselects row
0 so Enter picks without an arrow key first (ZenNotes#467), and that index outlives
the row once the text filters the list away — folderRows[0] is then
undefined and Enter falls through to create(). A highlight pointing at
nothing is not a highlight.
The null branch is a fourth state only Tags can reach. Offering it any
label would promise an action Enter cannot take, so the hint is omitted
and the status line carries the reason.
No key handling changes, no new imports, and Tab order is untouched — the
chips and rows were already tabIndex={-1}. data-keyboard-hints stays on
the container so a touch shell still drops the hints while keeping Back
and Create.
Adds SearchCreateForm.test.ts, the first test for this component. The
extension is .ts rather than .tsx because vitest.config.ts includes only
src/**/*.test.ts, so a .tsx file would silently never run. 11 cases assert
the full hint list per state rather than one substring, so a hint that
should be absent is proven absent.
|
Thanks @uNyanda, this is a careful fix, and #932 made the review easy: every state you listed is exactly what the form does. Merged into the I added one follow-up commit on top,
On your review focus: agreed. Naming the action rather than the field is what makes On the known gaps: leaving Checked: app-core typecheck; the form, search-create and palette-nav suites (42 tests); and the built app over CDP, reading the footer in Name, Folder (with and without a highlight, and filtered to nothing) and Tags (with a highlight, and with invalid text). Every state matched your description. |
|
I forgot to run |
… row Enter picks Review follow-up to @uNyanda's footer hints (ZenNotes#933), kept apart so their commit stands as theirs. - The Enter hint counted a highlight whenever the focused field's list had any row, while the Enter handlers pick the row at the highlight. The two only part ways when a list shrinks under the highlight with no typing, which the form allows: it follows the vault's notes, so a sync or another window can drop a tag while it is open. The footer then said "add tag" while Enter created the note. The hint now reads the row exactly the way the handlers do, as the PR's own comment describes, and a new test shrinks the tag list under a highlight. typedTag is already trimmed, so its extra trim goes too. - The new test file seeded the store with vaultSettings: null, which the store's type does not allow. Vitest does not typecheck, so the tests passed while every CI build failed at the app-core typecheck; it seeds DEFAULT_VAULT_SETTINGS now. - Two comments lose their long dashes, per the house style. Checked: app-core typecheck; the form, search-create and palette-nav suites (42 tests, the new one failing on the merged code); and the built app over CDP, reading the footer in Name, Folder (with and without a highlight, and filtered to nothing) and Tags (with a highlight and with invalid text): every state matches the PR.
What's New opens on the first launch after the update with five highlights: Settings → MCP → Server token for a ZenNotes Server that needs a token (ZenNotes#830), the Claude integrations naming the tab they reach, setup skipping the layout step for a server vault, the New note form's footer naming the keys of the focused field (ZenNotes#933), and a Cloud vault another device encrypted reading as encrypted, not deleted.
Review follow-up to #939, kept apart so @uNyanda's commit stands as theirs. The comment that explains why Search Notes is no longer a toggle cited #933, the New note form footer PR, for the Ctrl+P report. The report is #938. Behavior is unchanged. Checked in the built app on macOS: Cmd+P opens Search Notes, Ctrl+N and Ctrl+P move within it, Cmd+P inside it no longer closes it, and Escape does.
Summary
The New note form's footer told the user which keys to press, and in most of the states it offered, that was wrong.
The form has three fields, Name, Folder, Tags, so a footer reading
↑↓ pickreads as though↑↓moves between them. It does not.↑↓only ever moved through the choice rows under Folder or Tags. Moving between fields isTabalone, andTabappeared nowhere in the footer.↵ createnamed the wrong action in three of the four states where it appeared: it picks a highlighted row, in Tags it commits the typed tag as a chip, and in Tags with invalid textEnterdoes nothing at all.On arrival the form is focused in Name, which renders no list, so the footer claimed
↑↓ pickwhile nothing it referred to was on screen.Fixes #932.
The change
The two hints are now derived from state the component already holds rather than hard-coded:
↑↓ pickappears only when a choice list is actually rendered under the focused field.↵label names the action rather than the field: choose folder, add tag, or create note.Two details carry the fix:
Enterpicks without an arrow key first (Improve keyboard navigation in the "Pick a folder" dialog #467), but that index outlives the row once the text filters the list away. The hint has to check that a row is still standing behind the highlight, not just that the index is non-negative. Without this, a filtered-to-nothing folder said choose whileEnterwas creating the note.Enterwith nothing to do. No↵hint is offered there, since any label would promise an action it cannot take; the status line already explains why.No key handling changed, no new imports, and
Taborder is untouched.data-keyboard-hintsstays on the container so a touch shell still drops the hints while keeping Back and Create.Tests
SearchCreateForm.test.ts, the first test for this component. 11/11 passed.It is
.tsrather than.tsxbecausevitest.config.tsincludes only*.test.ts. A.tsxtest would silently never run.Each case asserts the full hint list rather than one substring, so a hint that should be absent is proven absent: Name on mount, Folder with and without a highlight, Folder filtered to nothing, Tags with and without a highlight, Tags in a tagless vault, invalid tag text, the hint hook in every state, and the buttons remaining reachable throughout.
Existing suites unchanged:
search-create21 passed,palette-nav9 passed. Verified manually in the running app across every state.Known gaps
Shift+Tabis not advertised. It walks the fields backwards, free from the browser rather than the component, sotab next fielddescribes half the traversal. Left out deliberately because the footer already reaches five hints at its widest and wraps on a narrow window. One-line change with a matching test if wanted.↵in Tags with text typed still commits the tag. The label is now honest, but the behaviour is arguably redundant since a tag can be added three other ways. Removing it is a behaviour change and was left as its own decision.Review focus
The one judgement call worth a look: the
↵label describes the action, not the field.add tagcovers both "a row is highlighted" and "a valid tag is typed", because both add a tag. The earlierchoose/createsplit was by field, which is exactly what madecreateambiguous.