Repository navigation
Merged
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Tasks calendar is getting month, week and day views, and each needs to know which open tasks fall on which day. Until now only the due date counted, through the grouping the side calendar and Home share. bucketCalendarEntries places a task on its due day and, when it has a different scheduled date, on that day too, flagged as scheduled; a task whose two dates match shows once. Due entries come before scheduled ones and each group keeps the scan order. Done, cancelled and forwarded tasks are left out. Tasks with no date are returned for a separate "No date" list, ordered by priority and then alphabetically. The calendar uses its own grouping so the side calendar panel and BucketTasksByDueDate stay exactly as they are; new tests pin the panel's rendering and click mapping and the existing grouping, so a later change to the Tasks calendar cannot alter them by accident. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
createNote opened the note with the default pane mode (or the mode remembered for that path) and then entered insert mode. With a preview or split default, you were typing into a buffer the screen didn't show. The new note's tab is now set to edit mode directly, which leaves the default pane mode and the remembered per-note modes unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The calendar mode drew a 35-column mini-grid with the selected day's tasks listed underneath, so a month's work could only be read one day at a time. It now fills the page: weekday names across the top, a box per day with its date in the top-left corner and its tasks inside, cut to the box's width (wide characters included) and closed with "+N more" when they do not fit. The selected day's tasks sit in a panel beside the grid and tasks without a date in a strip below it; an entry placed by its scheduled date carries a "»" marker. Week numbers and the week start follow the existing calendar preferences, and the task filter applies as before. Below 72x18 cells the body says "Terminal too small" instead of drawing a grid too cramped to read. The calendar moves into its own component that computes the layout once per frame; drawing, mouse hit-testing and the selection position all read that layout, so they cannot disagree about where a day is. The side calendar panel keeps its own renderer and is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the month grid, the half-page keys (Ctrl+D and Ctrl+U by default) now show the next and previous month, the same move as ] and [, and the selected day follows into the month shown. They go through the keymap, so a user who rebinds nav.halfPageDown or nav.halfPageUp gets the move on their own keys, with Vim mode on or off. The new tests also pin the month keys that already worked: hjkl and the arrows, Enter into the day's tasks and Esc or h back to the grid, the due-date keys, t for today and o for the daily note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The calendar showed only a month. Esc on the grid now steps through month, week and day and back, keeping the selected day, so the week shown is the one that contains it. Esc still works below a layout's minimum size, which is how to reach a smaller layout when the terminal is too small for the current one; nothing switches by itself. Week view shows seven columns in the configured week-start order and day view one full-width column, each with its tasks listed from the top, "+N more" when they overflow, and the "No date" strip pinned below. An hour list runs down the left, 00 to 23 or 12 AM to 11 PM following time_format. Tasks have no time of day yet, so the list only gives the day its shape and no task is placed in an hour row. Week numbers stay in the month view only. The layout chosen lasts for the session, across switching Tasks views and :tasks calendar, and every new session starts on the month. It is never written to the configuration, so tasks_view_mode, which the desktop app shares, still only says "calendar". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In the week and day views the half-page keys moved a whole month, as on the month grid. They now move by the layout's own unit: a month in month view, a week in week view, a day in day view, and the selected day follows. Tab, and Enter on a day with tasks, move focus from the grid into the selected day's tasks, where j and k pick an entry and the usual task keys act on it: Enter or o opens it, x toggles it, < and > move its due date and the menu key opens its task menu. Esc, Left or h return to the grid. Tab skips a day with no tasks, and Enter on such a day still opens its daily note. Tab also works without Vim mode. h and l, the arrows across week edges, t, o and n already behaved the same in every layout; new tests pin that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With Vim mode on, z switches the week and day hour list between hourly and half-hourly rows. The variant goes back to hourly each time the calendar is opened, and stays put while moving between days, weeks and the two column views. When the list does not fit the body, j/k, the arrow keys and the mouse wheel scroll it by one row, clamped at both ends. The position is kept as a time of day rather than a row index, so it survives a change of variant and moving to other days. On the grid in week and day view, j/k no longer move the selected day; h/l and the half-page keys do that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the whole hour list fits, j/k and the arrow keys on the grid scroll the selected day's column one entry at a time instead, so a day with more tasks than lines can be read without leaving the grid. A scrolled column counts the entries hidden above it with a "+N more" line at its top, as well as those still below. Picking another day returns the column to its top. With focus inside the column, the column follows the selection, so the selected task never sits off screen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tab now cycles the calendar between the grid, the selected day's tasks and the strip of undated tasks below, skipping whichever of the two is empty. In the strip, j/k and the arrow keys move the selection and the usual task keys act on it, so undated tasks can be opened, toggled or given a date without leaving the calendar. When a task leaves the strip, for example because it got a due date, the selection stays at the same position, and focus goes back to the grid once the strip is empty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
zn draws its own cursor as a reverse-video cell because Bubble Tea v1 hides the terminal cursor and parks it at the bottom after every frame. A bar or underline cursor in insert and replace mode needs the real terminal cursor, so cursorOut sits between the program and the terminal: it appends the wanted position and shape (steady bar, CSI 6 SP q, or steady underline, CSI 4 SP q) to every frame, writes it on its own when only the cursor moved, since Bubble Tea skips unchanged frames, and resets the terminal to its default shape on request. A write that is a lone control sequence gets nothing appended, and while released (for suspend or an external program) the wrapper writes no cursor bytes at all. It keeps the method set of Bubble Tea's term.File so window-size and raw mode detection still work, and uses only charmbracelet/x/ansi, which is already a dependency. Nothing uses it yet; later steps wire it in. Tests, written red first: steady shapes, the reset sequence, frame and lone-sequence writes, set-on-change, release, concurrent writes under -race, and the forwarded file descriptor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With Vim mode on, `[b` / `]b` (and `gt` / `gT`) switched tabs only while a note editor had focus, because the two-key sequences were matched only in the editor's key path. On Home, Tasks, Calendar, the database views and the side panels the first key went straight to the surface, so the sequence never completed. handleKey now matches the buffer and tab sequences before handing a key to any surface other than a note editor, honouring custom keymaps. When the second key does not complete a sequence, the held first key is replayed to the surface and the second follows, so `]` month navigation and `gg` keep working, one key later. Counts such as `3]b` stay editor-only. The held key lasts only until the next key: an overlay, chord, leader key, Ctrl+W prefix, mouse click, focus moving to a note editor or Vim mode turning off drops it without replaying it, and Esc cancels it. Tests, the cycling one red first: `]b` / `[b` from a view, a key that does not complete the sequence reaching the view, and a held key dropped by each of those paths. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
After `r`, with or without a count or register, the editor stays in normal mode until the replacement character arrives, so a host cannot tell from the mode that a character is pending. ReplacePending answers that: it is true only in normal mode while the pending keys are an optional register and count followed by `r`, and false once the character or Esc arrives, in visual mode and in insert mode. The TUI uses it to show an underline cursor while it waits. Tests: `r`, `3r` and `"ar` are pending; `rx` replaces and clears it; `r<Esc>` clears it with the text unchanged; `vr`, insert mode, a typed `r` in insert mode and another pending operator are not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While a note is edited, the reverse-video cell that stands in for the cursor hides the character under it and gives no sign of the mode. The TUI now works out, for every frame, the cursor the terminal should show: a steady bar in insert mode or with vim mode off, a steady underline in replace mode, and nothing in normal, visual and command-line mode or while the note is loading, where the drawn block stays exactly as it was. The cell is found the way the tag menu finds its anchor: the pane origin, the gutter, then the width of the text before the cursor on its wrapped row, so tabs and wide runes land on the right cell. While the terminal cursor owns the insertion point the editor stops drawing the block there and past the end of the line. Nothing reaches the terminal yet; a later commit connects the frame's cursor to the output wrapper. Tests: the bar on a plain, wrapped, CJK, tab and end-of-line position with no reverse cell drawn; the underline in replace mode; the bar with vim mode off; the block in normal and visual mode and after Esc; and the block while the note loads, with vim mode on and off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cursor follows typing, arrow keys, backspace and mouse clicks, and lands on the character under it in a single pane, either split, with the sidebar or a side panel open, with tabs hidden, in zen mode and in split note mode, and again after a resize rewraps the line. A buffer's wrapped rows are shared by every pane that draws it, and the last pane drawn wins. With one note open in two panes of different widths, a focused pane drawn first would have placed the cursor using the other pane's wrapping, on a blank cell. The buffer now keeps a copy of the rows taken when the focused pane draws it, and the cursor reads that copy. Tests: the cursor through editing and mouse moves, across the layouts above, in the second pane of each split, after a resize, and with the same wrapping note in two unequal panes with either pane focused. The normal and visual mode block is compared byte for byte with the cursor style, so a change to how it looks fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nvim shows an underline while `r` waits for the replacement character, which tells the user the next key replaces rather than moves. zn now does the same: while the editor reports a pending `r`, the terminal cursor is an underline on the character to be replaced, and once the character or Esc arrives the drawn block comes back. In visual mode `r` keeps the block. Tests: `r` and `3r` show the underline on the cursor character with no reverse cell; `rx`, `r<Esc>` and `2rx` end with one block and no terminal cursor; `v` then `r` keeps the block. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README's key table and the manual now say `gt`, `gT`, `]b` and `[b` switch tabs from any view or panel, and that the calendars keep `[` `]` for the month and the Kanban board keeps `g` for its grouping.
Bubble Tea gives a program it runs the program's own output as stdout. With the cursor wrapper in place, that output is no longer os.Stdout but a writer that is not a file, so os/exec connected the child's stdout to a pipe. `Space l e`, `:editor` and opening config.toml then broke: Vim printed "Warning: Output is not to a terminal" and Neovim drew a blank screen, while the same keys on main opened the note normally. execOnTerminal now sets the command's stdout to the terminal file the wrapper writes to (leaving a stdout the command already has alone), resets the cursor shape, and queues the hand-off. The external editor and the config action both go through it. Test: the command's stdout is the wrapper's terminal, the cursor is reset and released, and a stdout set beforehand is kept. Checked by hand in tmux with Vim and Neovim as $EDITOR, and with a probe script that reports `[ -t 1 ]` (now a tty, as on main).
runProgram passed os.Stdin to Bubble Tea as a custom input. Bubble Tea only falls back to opening the terminal when it picks the input itself, so `zn tui < /dev/null` (or a launcher that gives zn a pipe) put no terminal in raw mode: keys were echoed by the tty and never reached zn, and it could not be quit from the keyboard. main opened /dev/tty there. Run now passes no input and runProgram adds WithInput only when a caller gives one, as the tests do with their pipe. Checked by hand in tmux: `zn tui < /dev/null` opens a note from `:e` again.
Ctrl+Z and `:suspend` reset the cursor shape and released the wrapper before queueing tea.Suspend, and a ResumeMsg took it back. Bubble Tea cannot suspend on Windows: Suspend does nothing there and no ResumeMsg follows, so the wrapper stayed released for the rest of the session. The terminal cursor was left at the bottom in its default shape, and in insert mode, where the editor no longer draws its block, nothing marked the insertion point. suspend now hands the cursor over only where Bubble Tea can suspend. Both the chord and the ex command go through it. Tests: the existing suspend tests pin suspend support on, so they run on the Windows CI too; with it off, Ctrl+Z and `:suspend` keep the wrapper held, write no reset, and the bar stays.
Any toast hid the terminal cursor for its four seconds. In insert mode, and always with Vim mode off, the editor no longer draws its block there, so the insertion point vanished while the user kept typing: copying a link from the palette, "Reloaded <note>" after the external editor, or any error. A toast takes no keys, so typing goes on behind it. renderToasts now records the cells each toast is drawn over, from its left edge to the end of its rows, since the rest of those rows is cut off too. The cursor is hidden only when it falls inside one of them, and comes back on the same cell when the toast expires. Tests, with Vim mode on and off: a toast in its corner keeps the bar at the insertion point; a toast wide enough to cover the insertion point hides it, and its expiry restores it. Checked by hand in tmux with Vim mode off: after "Copy note link" the bar stays on the typing row.
The command line and text fields drew their cursor with firstCellOr and restAfterFirst. They place the terminal's cursor now, so nothing calls either helper.
The editor section says when zn shows a bar, an underline or the block, that the terminal's default cursor comes back on quit, suspend and the external editor, and what a terminal without cursor shapes shows.
…saves The view-mode section said the desktop app ignores view_mode_field and [terminal.view_mode_rules]. It does more than ignore them: the desktop rebuilds config.toml from the settings it knows, at launch and on every settings change, and the rebuilt file has no [terminal] table at all (serializeConfig in apps/desktop/src/main/app-config.ts). So rules set in zn tui, like mouse, preview_style or math, are gone once the desktop app opens. The Configuration section now says so once for every [terminal] setting, and the view-mode section points there instead of promising the settings are left alone. The real fix belongs in the desktop app, which should keep the [terminal] table as it found it.
…ch row's whole text #22 lays the settings page out under User and Vault headings and gave the view a second scroll offset, counted in display lines. Two things went wrong with it: - The offset was only capped at the page's last line, so after G in a short window, growing the window left the page scrolled down: the User and vim headings stayed off the top with blank lines below. It now stops where the last line sits at the bottom, so a taller window or a shorter filtered list shows the top again. - A row rebuilt its text from key and value, so anything refresh adds to a row's line was lost. A row now shows its filter line less the scope and prefix its headings already name; the "(invalid: ...)" note #28 adds to a bad view-mode rule shows that way. The view keeps one scroll field: v.scroll is the display offset now, and the setting-index scroll that no longer drove anything is gone from render and from the mouse wheel. Tests: the layout test keeps a row's whole line; every row a real refresh lists sits under the group it saves to; moving onto the first Vault row from above and from below keeps its headings on screen; and a tall window shows the page from the top. The cap and the heading walk-back each fail their test when reverted.
The User and Vault headings now draw the rule the Tasks list and the manual
use for their sections ("─ User · config.toml ───" and
"─ Vault · .zennotes/vault.json ───"), so the page says where an edit
lands. That file is what separates the two groups: the desktop's Settings
files rows by topic (Task folders sit under Editing), while this page lists
config keys and groups them by where they are saved.
Prefix headings (vim, editor, dailyNotes, ...) become bold body text under
the accent rule, and the blank line before Vault goes, since the rule
already separates the groups.
The settings paragraph named only periodic-note and task-exclusion controls as vault settings. Task folders and the template folder save to the vault too, and the page now groups its rows by that, so the paragraph says so.
A day with more tasks than its box or column can show ends with a "+N more" line, but in narrow boxes it was cut to the width, so it read "+4 mor" or "+2 mo". When "+N more" does not fit, the line now reads just "+N". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ault mode (by @spirit-of-light) With the review follow-ups on top of the contributor's commits.
With the review follow-ups on top of the contributor's commits.
@spirit-of-light) With the review follow-ups on top of the contributor's commits.
…pirit-of-light) With the review follow-ups on top of the contributor's commits.
…(by @spirit-of-light) With the review follow-ups on top of the contributor's commits.
The README's Views section now describes the calendar's month, week and day views, the hourly and half-hourly hour list, moving focus between the grid, the day's tasks and the No-date strip, and the due-date menu. It also notes that, unlike the desktop calendar, the terminal calendar places tasks on their scheduled date too. The key hints at the bottom of the calendar list the new keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#26 and #28 fixed the same thing from two sides. #26's showNewNoteInEditor set a new note's tab to edit right after it opened, and its follow-up called it from every creation path. #28 marks a note as new before the open (markNewNote), and since its follow-up a marked note always opens in the editor. Merged together, every creation path called both, one after the other, to the same end. The mark stays, since it acts inside the open itself, before a default, remembered or frontmatter mode is applied, and showNewNoteInEditor goes. Every creation path #26's follow-up covers already marks its note, and its tests (a new periodic, template, task-file and rollover note under a preview and a split default) pass on the mark alone, as do #28's.
The Tasks list takes no printable key with Vim off, and the calendar's grid follows the same rule, but a calendar task with focus (in the selected day's items or the No-date strip) still took every letter: `T` opened the due-date menu, `c` cancelled the task, `d`, `e`, `w`, `s`, `/` and `<`/`>` edited it, and `h` and `o` moved focus or opened the note. One of the PR's tests asserted that `T` opens the menu with Vim off. A focused task now passes letters on with Vim off, so the arrow keys, Enter, Tab and Esc do the work there, as the PR describes. In Vim mode `:` on a focused task opens the ex line, as it does on the grid; before, it did nothing there. Tests, red on the PR head: with Vim off, `T`, `c`, `d`, `w`, `/` and `e` on a focused task open nothing and leave the note unchanged, `h` leaves focus on the task, and `:` on a strip task opens the ex line.
…menu The desktop calendar binds `T` on a focused task to "move it to today" and `@` to "pick any due date", and its demo vault teaches `T` that way. The PR bound `T` to its new due-date menu (today, tomorrow, next week, the selected day, clear, other), so the same key did two different things in the two apps' calendars. `T` on a focused item or strip task now sets its due date to today, and the PR's menu moves to `@`, the desktop's key for choosing a date. Both stay Vim keys. On the grid `t` and `T` still go to today, as before. Tests: the due-menu tests press `@`; a new test checks that `T` moves a focused item and a strip task to today without opening a menu.
The calendar showed one hint line everywhere. It named `z hour step` on the month grid, where `z` does nothing, and `T due menu` while the grid had focus, where `T` goes to today. With a task focused it still named the grid's keys and none of the task's. The grid's hints now differ by view: the month grid names `h/j/k/l` for the day, and the week and day grids name `h/l` for the day, `j/k` to scroll and `z` for the hour step. With a task focused, the hints name the task keys: `j/k`, toggle, open, `T` due today, `@` due date, `<`/`>`, the menu, Tab and `Esc` back. `zn help` lists the calendar's views, focus and task keys, which the PR left at the old one-line entry. Test: the month grid hint names no hour step or due keys, the week grid hint names the hour step, and a focused task's hint names `T`, `@` and `Esc` back but no grid keys.
The month view needs a 72x18 body for its day boxes and showed "Terminal too small" below that. In an 80x24 terminal with the sidebar open the body is about 43x18, so the default view of `:tasks calendar` showed nothing there, where the calendar before the PR showed a month grid and the selected day's tasks. Below the minimum, the month view now draws a compact month: one line per week with the date and a count of the day's entries (as the side calendar panel does), the selected day's items under it, and the No-date strip at the bottom. It uses the same layout regions as the full month, so keys, Tab focus, clicks and menu anchors work unchanged. "Terminal too small" is left for bodies under 38 columns (with week numbers) or with no room for one item and one strip task under the grid. Tests: the compact month renders at one column and one row below the full minimum and at 43x18 and 38x12, with the day's count, its items and the strip; it is too small at 37x18 and 72x11; Enter, j, the menu anchor and a click on a day work in it. The too-small test that pressed Esc now uses a body too small for the compact month too.
…gate The due-menu tests carried `// AC88` to `// AC112` comments, ids from a spec that is not in the repository, so they tell a reader nothing. The PR's commit for week and day navigation also added `tab` to the Kanban board's key gate, not the calendar's. Tab is not a printable key, so the gate let it through already and the change did nothing. It goes.
Tasks carry a date and no time of day (the desktop's new `@remind:` time is relative to the date-only due date), and the PR lists a day's entries from the top of its column on purpose. But it drew an hour list beside the columns, so the first task sat next to `00`, the second next to `01` (or `00:30` after `z`), and the week read as a schedule of tasks at midnight that nobody wrote. The week and day views are now columns of the day's tasks under the header, with the strip at the bottom. The hour list, its scrolling, the half-hourly variant and `z` are gone; `j`/`k` scroll a busy day's column, which the PR already did when the hour list fit. Columns use the whole height, so the week view needs 56x8 and the day view 30x7 instead of 64x12 and 40x10. The wheel moves the selected day a week in every view, as it did in the PR when the hour list fit. Tests: the hour-list tests go; the week and day show no hour labels in either time format, `z` changes nothing in any view, column scrolling and the overflow counts are checked on a body shorter than the busy day, the strip sits under the columns, and the minimum sizes follow calMin. The editor's fold keys test stays.
…it-of-light) With six review follow-ups on top of the contributor's commits. The merge also updates ownsKey from #27's follow-up, which read v.dayFocus, a field #30 removes: it now checks that the calendar grid has focus, so [ and ] still change the month at once.
adibhanna
added a commit
to ZenNotes/zennotes
that referenced
this pull request
Nov 1, 2026
The bundled CLI is the floor a desktop build guarantees, and 0.14.0 is the newest release (ZenNotes/tui#31). It brings month, week and day views to the TUI's Tasks calendar (#30), a bar cursor while you type (#29), a note's own view mode from its frontmatter (#28), new notes that always open in the editor (#26), [b and ]b from every view (#27), and settings grouped by the file they save to (#22). The pin is the release's terminal-release.json copied byte for byte after it verified against the zn-release-1 key this app trusts (cli-update-core.ts), naming source commit 072a3866 (the v0.14.0 tag), integration protocol 1, and four archives that match both the manifest and checksums.txt. Staged through `npm run terminal:stage`; the native darwin-arm64 binary reports zn v0.14.0. The in-app manual's CLI card says 0.14.0.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
zn 0.14.0: six PRs from spirit-of-light (#22, #26, #27, #28, #29, #30), each merged with its review follow-ups on top, plus one cleanup where two of them met.
Tasks calendar: month, week and day views (Feat(tui): month, week and day views for the Tasks calendar #30).
:tasks calendaropens a full-page month that lists each day's tasks above a No date strip.Escswitches to a week or a day,Tabmoves between the grid, the day's tasks and the strip, the mouse works throughout, and the right-click position is fixed. Follow-ups:Tmoves a task to today and@opens the due-date menu, as on the desktop;zn helplists the keys;The merge updates
ownsKeyfrom Fix(tui):[band]bswitch tabs from every view and panel #27's follow-up, which read a field Feat(tui): month, week and day views for the Tasks calendar #30 removes, so[and]still change the month at once.A bar cursor while you type (Feat(tui): a bar cursor in insert mode, an underline in replace mode #29). zn shows the terminal's own cursor while text is being typed: a steady bar in insert mode and with Vim mode off, an underline in replace mode and while
rwaits. Normal and visual mode keep the block. The bar follows the command line, the palette, pickers, prompts and database cells, and the terminal's default cursor comes back on quit, Ctrl+Z, the external editor and opening the config. Follow-ups:zn tuireads the terminal when stdin is redirected;A note can choose how it opens (Feat(tui): frontmatter default mode definition #28).
[terminal] view_mode_fieldand[terminal.view_mode_rules]let frontmatter (view-mode: preview, or a rule such as"page-status=done" = "preview") pick edit, preview or split. Both are off by default.:settingsandzn configmanage them. Follow-ups:[terminal]when it savesconfig.toml.New notes open in the editor (Fix(tui): a new note opens in the editor always, despite the set default mode #26). With a preview or split default, every way of making a note now opens it in the editor, as the desktop does. The follow-up extends Fix(tui): a new note opens in the editor always, despite the set default mode #26 to periodic notes made on first open, templates,
:taskfileand:rollover.[band]bfrom every view (Fix(tui):[band]bswitch tabs from every view and panel #27). In Vim mode, the tab keys work from Home, Tasks, the calendars, databases and side panels. Follow-ups:[]and the Kanban board keepsg, without a one-key delay;Settings grouped by where they save (Feat(tui): settings page groups rows under User and Vault by prefix #22). The settings page groups its rows under "User · config.toml" and "Vault · .zennotes/vault.json". Follow-ups:
One new-note path (
020afdc). Fix(tui): a new note opens in the editor always, despite the set default mode #26 and Feat(tui): frontmatter default mode definition #28 fixed the same thing from two sides. Feat(tui): frontmatter default mode definition #28's new-note mark stays, Fix(tui): a new note opens in the editor always, despite the set default mode #26's helper goes, and both PRs' tests pass on the mark alone.Not in this release: #13 (Helix keybindings), which needs changes first.
Verified on
020afdc, and again on072a386with #30:gofmt -l,go vet ./...andgo test ./...are clean in every package.default_view_mode = "preview"::newopens in INSERT and saves;:settingsshows the User group;]bfrom Tasks moves to the next tab;:tasks calendarshows the month with tasks on their days and the No date strip,]moves to the next month at once, andEscswitches to the week view.