Skip to content

Feat(tui): frontmatter default mode definition - #28

Open
spirit-of-light wants to merge 14 commits into
ZenNotes:mainfrom
spirit-of-light:frontmatter-view-mode
Open

spirit-of-light wants to merge 14 commits into
ZenNotes:mainfrom
spirit-of-light:frontmatter-view-mode

Conversation

@spirit-of-light

Copy link
Copy Markdown
Contributor

A note can now choose the pane mode it opens in from its own frontmatter. The main use is a writing workflow: a page carries page-status: in-progress while it is being written, and once it is set to done it opens in preview, since it no longer needs editing.

Two [terminal] settings drive it, and both are off by default, so nothing changes for anyone who does not set them:

  • view_mode_field names a frontmatter field (view-mode is the suggested name). A note with view-mode: preview, edit or split opens in that mode. A list, an empty value or any other word is ignored.
  • [terminal.view_mode_rules] maps a "field=value" pair to a mode, for example "page-status=done" = "preview". Keys and values compare case-insensitively after unquoting, and a list field matches when any element does. When several rules match, the first in sorted order wins, and a valid field value beats every rule.

Both live under [terminal], which the desktop app ignores. zn never writes them into the vault, and the desktop is unaffected.

What wins when they disagree:

  • The frontmatter beats the default view mode, the remembered per-note mode and keep_view_mode_across_notes. Applying it never changes either of the last two; only a mode you switch to yourself does.
  • A note's body is often read after its tab appears. The tab opens at once in the mode it would have had anyway and switches once the frontmatter is read. A mode you pick before that wins.
  • A newly created note opens in the editor whatever its frontmatter or template says. That holds for every way of creating and opening a note: new note, from a template, a periodic note created on first open, a new task file and rollover.
  • A tab restored from the saved session keeps its saved mode. :ref and a sidebar peek stay in preview. A split copy follows the source tab, keeping a mode switched by hand. Reopening a closed tab follows the frontmatter, and the closed tab's mode comes back only when the frontmatter asks for none.
  • A tab that is already open keeps its mode when its frontmatter is saved with a new value. The new value applies the next time the note opens.

:settings shows the field and the rules. An "(add rule)" row asks for the field, the value and the mode in turn. Enter on a rule changes its mode or removes it, and d removes it too. Invalid rules (no =, an empty field or value, or a bad mode) are never applied, never stop the config loading, and are listed with their reason so they can be fixed or removed. zn config list, get and set work on terminal.view_mode_rules.<field>=<value>, and setting a rule that differs only in case changes the existing one.

This is independent of #26, which opens new notes in the editor whatever the default mode. Neither depends on the other, and both orders merge cleanly. With #26 and the open settings-page grouping branch merged on top, the build and the full test suite pass.

Tests cover the resolver, opening with quick and slow reads, the precedence above, every note-creation path, split copies, session restore, :ref, the sidebar peek, reopening, the settings rows and editing, zn config, invalid and hand-edited rules, a check that nothing is written to the vault, and the README and help text. Each commit builds and passes the touched packages' tests on its own. go test -race ./..., go vet ./..., go build ./cmd/zn, git diff --check and bash scripts/sandbox.sh --smoke pass.

Checked by hand in the TUI on a sandbox vault. With the default config, notes carrying page-status: done or view-mode: split open in the editor as before. After setting the field and adding the page-status=done rule through :settings, the done page opens in preview, the view-mode: split page in split, and an in-progress page in the editor.

🤖 Generated with Claude Code

spirit-of-light and others added 14 commits October 31, 2026 22:51
zn tui is gaining a way to open a note in edit, split or preview from its
own frontmatter. This adds the configuration behind it, without changing
what any note does yet.

`[terminal] view_mode_field` names the frontmatter field that sets a
note's view mode; it is empty by default, which keeps the feature off.
`[terminal.view_mode_rules]` maps `"field=value"` to a mode, so a field
such as `page-status = done` can choose preview. Both live under
`[terminal]`, which the desktop app ignores, so a save from the desktop
cannot drop them.

Rule keys and values are trimmed, unquoted and lower-cased, and rules
are ordered by their lower-cased `field=value`, which is the order the
first match will be taken in. A rule with a bad
mode, no `=` or an empty field name is kept in the file and reported with
its reason but never applied, and an invalid rule never stops the config
from loading. SetViewModeRule and RemoveViewModeRule rewrite only that
table; removing works on the key exactly as written, so a malformed rule
can still be deleted, and removing the last rule drops the table.

Tests, written red first, cover the defaults, loading the sub-table,
invalid rules and field values, normalising and ordering, and writing and
removing rules while keeping the rest of the file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
frontmatterPaneMode reads a note's frontmatter and says which pane mode
it asks for, for the coming change that opens notes in that mode. Nothing
calls it yet.

A valid value in the configured field (`view-mode: preview`) wins. The
field name and its value compare without regard to case; a list, an empty
value or any word other than edit, split or preview gives no mode, and the
rules are tried instead. A rule matches when its field holds its value, or
any element of a list field does, compared case-insensitively after
unquoting; rules are tried in the order they arrive in, so the first match
wins. A note without frontmatter, or a vault with no field and no rules,
gets no mode.

Tests cover the field, every way it can fall through, rule matching with
case, quotes and lists, rule order, and a field that beats a matching rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With `[terminal] view_mode_field` set or a valid rule in
`[terminal.view_mode_rules]`, a note opened in a new tab now takes the
pane mode its frontmatter asks for, both from openNote and from quiet
opens such as the jump list and pickers.

A note's body is often read in the background after its tab appears, so
the tab opens in the mode it would have had anyway and is marked as
waiting; once the text is loaded the frontmatter sets the mode, and the
tab visibly switches on a slow read rather than holding up the open. A
manual mode switch before the load clears the mark, so the user's choice
is never overridden. The frontmatter mode beats the remembered per-note
mode and the keep-across mode, but only writes the tab: neither of those
is changed by it, so one finished note does not make every later note
open in preview. A tab that is already open keeps its mode when the note
is saved with new frontmatter; the new value applies the next time the
note is opened.

With the field empty and no valid rules, which is the default, no tab is
marked and opening a note behaves exactly as before.

Tests cover the default config, the field and a rule on open, both
beating the remembered and keep-across modes, a deferred read, a manual
switch before and after the load, saving and reopening, quiet opens, and
that applying a mode leaves the remembered modes alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A tab opened in the mode its frontmatter asks for has three neighbours
that must not lose or override that choice.

Splitting a tab before its note has loaded now gives the copy the same
waiting state, so both tabs take the frontmatter mode once the text is
in. A copy of a tab whose mode was switched by hand keeps that mode, as
before. A tab restored from the last session keeps the mode it was saved
with; the frontmatter does not change it. A note pinned as a reference
with `:ref` always stays in preview, whatever its frontmatter asks for.

Tests cover a restored tab, splits before and after the load, a split
of a manually switched tab, and a reference whose frontmatter asks for
edit or split.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ntmatter

With a view-mode field or rules configured, a note created from a
template that says `view-mode: preview`, or whose first fields match a
preview rule, would otherwise open straight into preview. A note that has
just been made is about to be written, so it should never start there.

Every action that creates a note and opens it now marks the note first:
a new note, a note from a template, a periodic note created on first
open, a new task file, and today's daily note when a rollover creates
it. The open that follows uses the mark up, starts in the editor and does
not wait for the frontmatter. Notes created without being opened, such
as a daily note made by adding a task to it or a saved capture, carry no
mark; the first time they are opened they follow their frontmatter like
any other note. With the feature off, a new note opens in the mode it
does today.

Tests cover each of the five creation paths, an existing periodic note,
notes created without opening, the feature off, and reopening a new note
after closing it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The view-mode rules live in `[terminal.view_mode_rules]`, a table inside
`[terminal]` that `zn config` could not reach: it splits a key at the
first dot and only knew the four top-level map tables.

`zn config list` now shows every rule as
`terminal.view_mode_rules.<field>=<value> = <mode>`, invalid ones too, so
a hand-edited mistake is visible; `list --json` carries the same entries.
`zn config get` reads a rule by that name, and `zn config set` adds a new
rule or changes an existing one. Set checks the rule before writing: a
bad mode, a key without `=`, an empty field or an empty value exits with
an error and leaves config.toml as it was. The mode is stored lower-cased.
`terminal.view_mode_field` reads and sets like any other terminal key.
Removing a rule stays with the settings view and `zn config edit`, since
`zn config` has no unset.

Tests cover listing, reading, adding and changing a rule, every kind of
bad input, and setting the field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`:settings` now shows and edits the frontmatter view-mode configuration,
so it never needs config.toml opened by hand.

`terminal.view_mode_field` appears with the other terminal settings and
is edited like them; while it is empty its prompt suggests `view-mode`
as an example rather than filling it in. Right after the terminal rows
come the rules, as `terminal.view_mode_rules.<field>=<value> = <mode>`:
the valid ones in the order they are matched, then the invalid ones,
each ending in its reason, such as `(invalid: no "=")`. A rule shows its
key as written in the file. Last comes an "(add rule)" row, which stays
visible whatever the filter.

Enter on the add row asks for the field, then the value, then offers
edit, split and preview; nothing is written until a mode is picked, Esc
at any step writes nothing, and an empty field, a field holding "=", an
empty value or a rule that already exists in another case is refused.
Enter on a rule offers the three modes and remove; `d` on a rule row
also removes it, and the footer names `d` while one is selected. A rule
with a bad mode can be fixed by picking a mode; one whose key is
malformed offers only remove. Rules are changed and removed by their key
as written, so hand-typed keys are found. After every change the
preferences are reloaded and the rows refreshed.

settingRow keeps its four fields: a rule row's key is the rule's key,
and whether a row is a setting, a rule, an invalid rule or the add row
is worked out from the row itself.

Tests cover editing the field, rule order, invalid rules marked and
ignored when a note opens, filtering, adding with every kind of bad
input and cancel, changing, removing with the picker and with `d`, the
footer hint, fixing a bad mode, and malformed rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A hand-written rule such as `"status=" = "edit"` was read as valid and
listed in `:settings` as an ordinary rule, yet choosing a new mode for it
there failed, because writing a rule already refused an empty value. The
rule looked usable but could only be removed.

An empty value, including an empty quoted one, now makes a rule invalid
when it is read, the same as a missing `=` or an empty field. Such a rule
never matches a note; `:settings` and `zn config list` show it with the
reason "empty value", and its picker offers only remove. The check that
runs before a rule is written is now the same parse, so reading and
writing agree on what a valid rule is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README gains a section on opening notes in a pane mode from their
frontmatter: `[terminal] view_mode_field`, off by default with
`view-mode` suggested, the `[terminal.view_mode_rules]` format with a
`page-status=done` example, what makes a rule invalid and how to remove
one, the order in which the field, the rules, the remembered and
keep-across modes, restored tabs, new notes and `:ref` win, and how to
edit both in `:settings` or with `zn config`. It says only zn tui reads
them. The settings table lists both under `[terminal]`.

zn tui's help adds a row for the field and the rules next to the pane
mode keys, and says a manual mode switch always wins over frontmatter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two end-to-end tests guard the feature as a whole. One opens notes in
frontmatter modes, lets a slow read land, switches, splits and closes
tabs, and adds and removes a rule from `:settings`, then checks that
every file in the vault, `.zennotes` included, is byte-for-byte as it
was: the configuration lives only in zn's own config.toml. The other
adds a rule in `:settings`, opens a matching note in that mode, changes
the rule's mode, and reopens the note in the new one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three cases were covered only below the open path or not at all. A note
whose frontmatter matches nothing (a bad field value, a list in the
field, or no matching rule) now has a test that it opens exactly as it
would with the feature off, against a preview default, a remembered
mode and the keep-across mode, before and after a slow read. Invalid
rules are shown to be ignored when a note opens while a valid rule
beside them still applies, and an empty field is shown to leave the
rules working after a slow read. A last test keeps the README's
description of the settings from going missing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…frontmatter

Two ways of opening a note set the tab's mode themselves right after
opening it, and with frontmatter view modes on, a slow read of the note
could then land and replace that mode.

Peeking a note from the sidebar asks for preview, as `:ref` does; the
frontmatter could then switch the peek to edit or split. Both now set
their mode through one helper that also stops the frontmatter from
taking it back.

Reopening a closed tab put back the mode the tab was closed in, and the
frontmatter replaced it only when the read happened to be slow. A
reopened note is now treated like any note opened in a new tab: its
frontmatter mode wins whether the read is quick or slow, and the closed
tab's mode stands only when the frontmatter asks for none.

Tests cover a peek of a note whose frontmatter asks for edit or split,
and reopening a closed tab with a quick and a slow read, with and
without a frontmatter mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two hand-edited or command-line cases slipped past the rule handling.

`zn config set terminal.view_mode_rules.Page-Status=done split` added a
second entry next to an existing `page-status=done`, although both are
the same rule: the new one then won the tie, and `:settings` listed two
rows for one rule. Writing a rule now looks for an entry with the same
field and value, compared the way rules are matched, in any case or
quoting and whether or not its mode is valid, and changes that entry
under the key it already has.

A rule such as `"a=b" = 1` or `= true` was dropped while loading, so it
was ignored but also missing from `:settings` and `zn config list`,
where invalid rules are shown with a reason and can be removed. Such a
mode is now kept as text, so the rule shows up as invalid with "bad
mode" and can be removed like any other.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… tab alone

When the note behind a closed tab had been deleted, reopening it opened
nothing, but the closed tab's mode was still written to whichever tab
was active, and with frontmatter view modes on that tab was then given
its own frontmatter mode, replacing one the user had picked. The
reopened mode is now applied only to the reopened note's tab.

The README's list of what wins now says that a reopened tab follows its
frontmatter, and that a sidebar peek stays in preview like `:ref`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@spirit-of-light spirit-of-light changed the title Feat(tui): a note opens in the pane mode its frontmatter asks for Feat(tui): frontmatter default mode definition Nov 1, 2026
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.

1 participant