Skip to content

New Note Form Footer Hints Reflect The Focused Field - #933

Merged
adibhanna merged 1 commit into
ZenNotes:mainfrom
uNyanda:fix/new-note-form-footer-hints
Oct 13, 2026
Merged

adibhanna merged 1 commit into
ZenNotes:mainfrom
uNyanda:fix/new-note-form-footer-hints

Conversation

@uNyanda

@uNyanda uNyanda commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

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 ↑↓ pick reads as though ↑↓ moves between them. It does not. ↑↓ only ever moved through the choice rows under Folder or Tags. Moving between fields is Tab alone, and Tab appeared nowhere in the footer.

↵ create named 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 text Enter does nothing at all.

On arrival the form is focused in Name, which renders no list, so the footer claimed ↑↓ pick while 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:

  • ↑↓ pick appears only when a choice list is actually rendered under the focused field.
  • The ↵ label names the action rather than the field: choose folder, add tag, or create note.

Two details carry the fix:

  • A stale highlight. Folder preselects row 0 on typing so Enter picks 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 while Enter was creating the note.
  • A fourth state. In Tags, typed text that is not a valid tag leaves Enter with 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 Tab order is untouched. data-keyboard-hints stays 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 .ts rather than .tsx because vitest.config.ts includes only *.test.ts. A .tsx test 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-create 21 passed, palette-nav 9 passed. Verified manually in the running app across every state.

Known gaps

  • Shift+Tab is not advertised. It walks the fields backwards, free from the browser rather than the component, so tab next field describes 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 tag covers both "a row is highlighted" and "a valid tag is typed", because both add a tag. The earlier choose / create split was by field, which is exactly what made create ambiguous.

…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.
@adibhanna

Copy link
Copy Markdown
Contributor

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 v2.69.0 release branch as b25eadb8, shipping in 2.69.0. GitHub will mark this PR merged once that branch reaches main at release.

I added one follow-up commit on top, fb9c6fcc, kept separate so your commit stands as yours:

  • CI. The new test file seeded the store with vaultSettings: null, which the store's type doesn't allow. Vitest doesn't typecheck, so your tests passed while every CI build failed at the app-core typecheck (npm run typecheck catches it locally too). It seeds DEFAULT_VAULT_SETTINGS now.
  • The highlight check. Your comment says the highlight only counts while a row stands behind it, but the code counted any row in the list. The two only part ways when a list shrinks under the highlight without typing, and that can happen: the form follows the vault's notes, so a sync or another window can drop a tag while it's open, and the footer then said ↵ add tag while Enter created the note. The hint now reads the row exactly the way the Enter handlers do, with a test that shrinks the tag list under a highlight.
  • House style. Two comments lost their long dashes, which the project avoids everywhere, and typedTag is already trimmed, so its extra .trim() went.

On your review focus: agreed. Naming the action rather than the field is what makes ↵ honest in every state.

On the known gaps: leaving Shift+Tab out keeps the footer from wrapping, which I'd rather keep. Enter committing a typed tag in Tags stays as it is in this release; it matches Space, comma and leaving the field, and changing it is its own decision.

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.

@uNyanda

uNyanda commented Oct 13, 2026

Copy link
Copy Markdown
Contributor Author

I forgot to run npm run typecheck. Sorry about that

@adibhanna adibhanna mentioned this pull request Oct 13, 2026
@adibhanna
adibhanna merged commit b25eadb into ZenNotes:main Oct 13, 2026
5 of 10 checks passed
MichaelC001 pushed a commit to MichaelC001/zennotes that referenced this pull request Oct 14, 2026
… 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.
MichaelC001 pushed a commit to MichaelC001/zennotes that referenced this pull request Oct 14, 2026
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.
adibhanna added a commit that referenced this pull request Oct 14, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

New Note Form Footer Advertises Pick Keys That Do Nothing

2 participants