Skip to content

Core 2.78.1: notes open to read on a phone, title rendered and keyboard down - #1005

Open
adibhanna wants to merge 6 commits into
mainfrom
fix/phone-read-open-core
Open

adibhanna wants to merge 6 commits into
mainfrom
fix/phone-read-open-core

Conversation

@adibhanna

Copy link
Copy Markdown
Contributor

Core 2.78.1 for the phones: two problems found walking the 2.78.0 phone builds. Only packages/app-core, bridge-contract and shared-domain move to 2.78.1 (a3a5c081); the desktop app stays at 2.78.0 until its next release.

  • A note opened to read shows its first line rendered (004619cc). Live preview revealed the Markdown markers of the selection's line with the editor unfocused, so a note opened to read (caret parked on line 1) showed its title as # Title. New lib/cm-source-reveal.ts: until an editor has taken focus since its note opened, every line renders; the first focus (picked up in a microtask of the focus event, so desktop never paints a frame without the markers) turns revealing on, and a blur leaves it on. The same gate covers all eight reveal sites (live preview markers and links, callouts, rules, fences and bullets, wikilinks, highlights, template tokens, math, diagrams and embeds), in every editor surface.
  • A Search pick on a phone opens the note to read, keyboard down (0a46efb4). New editor-host option openNotesToRead?: () => boolean (installEditorHost), asked at each pick because a tablet's layout can change at runtime. When it answers true, picks in note search (notes and tagged blocks), Search in all notes, the outline and the buffer switcher open the note at the pick without focusing the editor. The phone shells add one line (openNotesToRead: isPhoneViewport).

Desktop: identical in normal use, checked frame by frame against 2.78.0 over CDP (opening from Home, sidebar, Search and Search Text; Vim motions with an edit reaching disk; a palette over a focused editor; Read to Edit; Split; find). The only visible difference is where an editor has no focus: the note restored at launch, a hovered link popover and the pinned reference pane show their first line rendered until focused.

Verification: typecheck 8/8; app-core 3,613 (new: reveal gate 8, pick and close hand-offs 5, palette assertions), shared-domain 2,164. A rehearsal core from 0a46efb4 in both shells: shell typecheck and tests pass; iPhone web build at phone size and a native Android API 37 emulator show the title rendered on open, Search picks with the keyboard down, and a tap into the text raising the keyboard and the line's markers.

… as raw Markdown

Found on the phones once their read-open guard shipped (iPhone 1.34.0,
Android 1.1.49): a note opened from Home, a list or a zennotes:// link
now opens with the keyboard down, as it should, and with its title drawn
as a raw `# Garden plan`. Live preview hides the Markdown markers on
every line except the ones the selection covers, and a note opened to be
read still has a selection: the caret parked on line 1. Nobody was
editing that line, but it read as source. Desktop showed the same
wherever an editor sits unfocused: a hovered link's preview popover, the
pinned reference pane, and the note a window restores at launch.

Source is now revealed only on lines the user is editing. A state field
(lib/cm-source-reveal.ts) says whether the editor has taken the focus
since its note opened. Until it has, every line renders, the caret's
too. The first focus turns revealing on, and a blur leaves it on, so a
palette, a menu or a dialog over a note being edited redraws nothing
(and a phone's keyboard going down is not the end of editing either). A
note put into an editor in place of another starts over, unless the
editor holds the focus as it arrives: a tab switch or a followed link
from the keyboard keeps revealing. Every reveal asks the same question:
the line markers, links, ==highlights==, template tokens, callout heads,
rules, fences and bullets, and the math, Mermaid/D2 and embed blocks.
The editors that share these extensions (the main panes, the reference
pane, the link popover, floating and external-file windows) all carry
the field; one without it (Quick Capture, the template editor) reveals
as before.

The focus is taken up in a microtask of the focus event, not on
CodeMirror's own focus notice, which comes 10 ms later: by then a frame
with the caret's line rendered would already be painted, and every note
desktop opens would flicker its first line. A focus a phone shell turns
straight back (the read-open guard blurs it in focusin) never reveals.

What it deliberately does not change: when anything takes the focus,
and the parser (the note grammar's full reparse is untouched). Desktop
opens a note and focuses it exactly as before, so the caret's line shows
its markers from the first painted frame.

Desktop, visible only where an editor is unfocused: the link preview
popover and the reference pane draw their first line rendered, a note a
split's other pane switched to while unfocused renders its caret line,
and a note restored at launch renders its caret line until the editor is
clicked or focused.

Tests: cm-source-reveal (an unfocused note renders its caret line; an
editor without the field reveals; the first focus reveals before any
timer runs; a blur keeps revealing; a refused focus never reveals; a
note swapped in unfocused is read, swapped in focused reveals; a note
left to read renders again; highlights and inline math follow it). In
the built app over CDP with isolated stores, sampled once per painted
frame on the 2.78.0 build and on this one: opening from Home, the
sidebar, Search and Search Text in Vault, Vim gg/j/k and an A edit that
reaches disk, a palette over a focused editor, Read to Edit, Split and
the find panel paint the same caret-line states on both; the link
popover and the launch restore show the change. On the iPhone shell
(web build at phone size, rehearsal core from this tree): a note opened
from Home, and the one restored at launch, show their heading rendered
with no `#`, and a tap into the text focuses it and reveals that line.
…d down

The phones' read-open guard keeps the keyboard down for a note opened
from Home, the lists and a zennotes:// link, but a pick in Search still
raised it: every one of core's searches ends with focusEditorNormalMode,
the hand-off desktop wants (the next keystroke edits or moves in the
note), and on a phone that focus is the soft keyboard over half the note
the user came to read. Traced on the iPhone shell, that one call was the
only editor focus after a pick from the note search, from Search in all
notes and from the outline, and after cancelling a search too (the
shell's Cancel sends the Escape that App's window handler answers the
same way). The outline jump also focused the editor itself.

The host now says how it wants a pick to land. EditorHostOptions (the
editor host the shells already install for native typing and keyboard
insets) gains `openNotesToRead`, asked at each pick. Where it returns
true, a pick in the note search (notes and tagged blocks), Search in
all notes, the outline palette and the buffer switcher opens the note
at the pick without taking the focus, and the note is left to read
(every line rendered, see the previous commit). If the closing palette
gave the focus back to an editor that opened it (an iPhone keeps the
focus in the text under a tap on a button), that hand-back is undone a
frame later. Closing one of those searches without a pick leaves the
focus where the palette returns it, to what had it before. Where it is
absent, false or throws, nothing changes: desktop and the web app focus
exactly as before.

Why the editor host and not a bridge-contract capability: the answer is
the phone layout, which a tablet changes at runtime (Split View), so it
has to be a question asked at the pick, not a static capability; the
shells already register this host; and the bridge is the vault and IO
contract, with nothing for desktop or web to say here. The touch media
query was not enough either: it is true on tablets in the desktop
layout, which keep core's focus as the guard does.

What it deliberately does not do: the list openers the shells' guard
already covers keep focusing as before (the guard refuses it), and the
command, template and drawing palettes keep their hand-off. Creating a
note from Search still focuses it, as a new note should be written in.

Shells: one line in src/ui-mobile/editor-host.ts, in both phones:
`openNotesToRead: isPhoneViewport` in the installEditorHost options
(isPhoneViewport from '../viewport'). Without it, a phone that adopts
this core behaves as today for Search.

Tests: editor-focus (desktop focuses; a host that answers no or throws
keeps it; a reading host leaves the editor unfocused and read, undoes a
hand-back to the editor, and leaves the focus alone on close);
SearchPalette (a pick goes through the pick hand-off, Escape through the
close one) and SearchPaletteBlocks (a block pick). On the iPhone shell
(web build at phone size, rehearsal core from this tree, the one line
above): picks from Search (from a note and from Home), Search in all
notes and the outline open the note at the pick with the editor never
focused and the caret line rendered; Cancel keeps it unfocused; a tap
into the text focuses it and shows that line's `#`. Without the line,
the same picks focus as before. On desktop over CDP every search still
focuses the editor, painted the same as on 2.78.0.
…and keyboard down

The phones shipped 2.78.0's core in Android 1.1.48 and iPhone 1.34.0, and
walking those builds found two core-side problems: a note opened to read
showed its first-line heading as raw Markdown (live preview revealed the
caret line's markers with the editor unfocused, 004619c), and a Search
pick raised the keyboard on a phone (0a46efb). The shared packages move
to 2.78.1 so Android 1.1.50 and the replacement iPhone 1.34.0 build adopt
it. The desktop app stays at 2.78.0 until its next release, which carries
both changes.
… command, keyboard down

After 0a46efb a Search pick on a phone opened its note to read, but two
of core's hand-offs still raised the keyboard there. Closing the command
palette, the template palette or the drawing palette without picking
anything called focusEditorNormalMode, from the palette's own Escape and
from App's window handler that answers the shells' Cancel button. And a
command run from the command palette that only goes to a note already
there (Go Back, today's daily note) was followed by the same editor
focus, twice for the periodic notes: the palette refocuses after every
command that lands on the editor, and openDailyNoteForDate and its
weekly and monthly twins ask for the focus themselves (#353).

Both now go through the editor host's `openNotesToRead`, the question
0a46efb added. The two hand-offs it introduced are renamed for what they
now cover: focusEditorAfterReadOpen (a note opened to be read, from a
search pick or a command) and focusEditorAfterPaletteClose (any of these
palettes closed without a pick). On a host that opens notes to read, a
palette closed without a pick leaves the focus where the palette returns
it, to whatever had it before the palette opened, so backing out over a
note being read keeps the keyboard down and backing out over a note
being written keeps it up. Desktop and the web app focus the editor
exactly as before, with the same retries.

Commands say whether they open to read through a new
`Command.opensToRead`, asked just before the command runs (afterwards a
daily note exists either way). The rule: a command whose whole effect is
to put an existing note on screen opens it to read; a command that
creates a note, or works on the note in place, keeps the focus.

Open to read (the palette hands over like a search pick):
- nav.back, nav.forward, nav.toggle-recent (Go Back, Go Forward, Switch
  to Previous Note): history only holds notes that exist.
- tab.reopen, tab.next, tab.previous (Reopen Closed Tab, Next Tab,
  Previous Tab): another note that is already open.
- tab.close (Close Tab): what remains on screen is the neighboring tab.
- note.daily.today, note.weekly.thisWeek, note.monthly.thisMonth: only
  when that note is there already. When the command has to create it,
  the note is new and is focused to be written in, as today. The
  command passes `readIfExisting`, so the opener's own focus request
  follows the same rule (a `read` detail on zen:focus-editor that App
  lands like a pick); other callers of those openers (the leader keys,
  the Calendar, Home, the phones' Daily note row) are unchanged.
- note.daily.rollover (Roll Over Unfinished Tasks to Today): the same
  rule on today's note.

Keep the focus, unchanged: every command that creates a note to write
in (note.new.quick, note.new.inbox, note.new.here, task.new,
task.new.folder, database.new, drawing.new, embed.drawing.new, and the
template palette's create and insert); commands that act on the open
note in place (format, rename, favorite, cover, archive and restore,
copy, export, folds, task state, the editor toggles); nav.focus.editor
and ref.focus, whose point is the focus; and split.right/split.down. The
command palette has no command that follows a link: Open Link Popover
peeks at one over the current note and keeps its own focus. Commands
that open another palette or dialog still hand nothing over, and a
theme picked in the palette still returns to the editor.

What it deliberately does not do: change the commands themselves,
Home or the Calendar, or anything on a host without `openNotesToRead`.

Tests: commands (exactly those eleven commands answer opensToRead,
history and tab commands always, the periodic ones only for an existing
note, the writing commands never, and the periodic commands ask their
opener for readIfExisting); store (the daily opener requests a read
landing only for an existing note opened that way); editor-focus (a
read open keeps its caller's retries on desktop); a palette test (the
command, template and drawing palettes close through the close
hand-off, a read command lands through the read hand-off, a writing
one still focuses). app-core 3,624 and shared-domain 2,164 pass;
typecheck clean (8/8). In the built app over CDP with isolated stores,
on 2.78.0 and on this build: Escape out of the command palette (from
its input and from a window Escape like the phones' Cancel), the
template palette and the drawing palette, then Open Today's Daily Note
creating the note, Go Back, Open Today's Daily Note again, Switch to
Previous Note, Close Tab, Reopen Closed Tab and Next Tab each leave the
same note on screen with the editor focused on both builds.
Comment on lines +59 to +65
import {
findDailyNoteForDate,
findMonthlyNoteForDate,
findWeeklyNoteForDate,
isCalendarToggleAvailable,
noteFolderSubpath
} from './vault-layout'
…xcept while Cloud needs you

Piotr asked on Discord (2026-10-28), with an Android screenshot: "On
android, can we hide the cloud connected / setup bar??" On the phones
the bar along the bottom carries only its Cloud half, so a device that
is signed in but not linked showed "ZenNotes Cloud · Cloud connected ·
Set up" under every note for as long as it stayed that way, and on
Android the circle was lifted above that row too. There was no way to
turn it off, on the phones or on desktop.

Settings, Appearance, Chrome gains Status bar, on by default, next to
the title bar switch, with the words people search for (cloud, cloud
connected, footer, word count). Turned off, the bar is not drawn at all
on any surface: desktop gets its 32 px back, and on Android the Cloud
row goes and the circle drops back to the bottom, because the shell
sizes that row and lifts the circle from the bar's data-cloud-sync-*
hooks, which are no longer in the page.

It stays off except while Cloud needs the person. cloudSyncNeedsUser
(cloud-auto-sync.ts) is the same set the phones already key their Cloud
row on: the attention and error phases (a stopped or failed run,
including Can't reach Cloud), a Cloud vault that went away and a device
Cloud signed out (both drawn as attention, now one cloudSyncShownPhase
that the status row uses too), and files or the vault settings question
waiting for a decision. Then the bar comes back until that is dealt
with, so turning it off never hides the one place that says sync has
stopped. Zen mode still clears the bar whatever it says, as before.
statusBarVisible (lib/status-bar-visibility.ts) holds the rule.

It is a portable preference: showStatusBar in PORTABLE_PREF_KEYS,
show_status_bar under [appearance] in config.toml beside
show_window_title_bar. The phones keep it with the rest of app-core's
preferences (zen:prefs:v2, which both shells mirror into native
storage), so it needs no shell code. Keyboard: the command palette's
Toggle Status Bar (and so :view_status_bar), and `:set statusbar`,
`:set nostatusbar`, `:set statusbar?` (alias `stb`) in Vim mode, the way
`:set undofile` works. No single-key binding, so Vim-off gating is
untouched. The option is defined without a default value: codemirror-vim
applies a truthy default through the callback when the option is
defined, and the first desktop relaunch over a saved
show_status_bar = false came back with the bar on.

Docs: the Appearance entry, the Cloud card and the ex command list in
help.ts; the website follows in its own branch.

What it deliberately does not do: change the iPhone, whose shell
already shows the Cloud row only while Cloud needs the user, so there
the switch changes nothing until the shell decides otherwise; or add a
global shortcut or leader key, which none of the other chrome switches
have.

Tests: statusBarVisible (on by default, off until Cloud needs the user,
Zen mode wins); cloudSyncNeedsUser (quiet for the everyday phases; needs
the user for attention, error, a removed vault, a signed-out device,
waiting files even mid-sync, and the settings question); the Settings
search finds Status bar by "cloud connected" and its switch turns it
off; Toggle Status Bar flips and saves it; `:set statusbar` leaves a
saved choice alone when it is defined (fails with a truthy default) and
sets, clears and reports it; config.toml round-trips
[appearance].show_status_bar; PORTABLE_DEFAULTS still matches
DEFAULT_PREFS. app-core 3,636, shared-domain 2,164 and desktop 1,198
pass; typecheck clean (8/8). In the built app over CDP with isolated
stores: Toggle Status Bar hides the bar and the editor gains 32 px,
config.toml says show_status_bar = false, `:set statusbar` and
`:set nostatusbar` bring it back and hide it; with the main process's
Cloud handlers swapped for a linked account whose sync fails, the hidden
bar comes back as "Sync failed", and goes again once a sync succeeds;
a relaunch with a fresh userData and the same config.toml opens with
the bar hidden.
…oard down

Found while verifying 42865ca: on a phone, Toggle Status Bar from the
command palette raised the keyboard. Every command that lands on the
editor is followed by focusEditorNormalMode, so any setting or view
toggle run from the palette (Light Sidebar, Line Numbers, Word Wrap,
Live Preview, a theme) put the soft keyboard over half the note, with
nothing to type. Two commands also focused the editor themselves: the
four fold commands (runFoldCommand) and Copy Link Under Cursor.

The palette now asks where the keyboard should go
(lib/command-landing.ts), with the editor host's `openNotesToRead`, the
same question the Search picks and palette closes ask. The rule
defaults to leaving the focus where it was before the palette opened,
and names the commands that should give the editor the keyboard, so a
toggle added later cannot raise a phone's keyboard by being forgotten.
On desktop every answer focuses the editor, exactly as before.

On a phone, after a command run from the palette:

- read, the note is left to read, keyboard down (e443b86, unchanged):
  nav.back, nav.forward, nav.toggle-recent, tab.reopen, tab.next,
  tab.previous, tab.close, and note.daily.today, note.weekly.thisWeek,
  note.monthly.thisMonth and note.daily.rollover when that note already
  exists.
- edit, the editor takes the keyboard, because typing or the focus is
  the point: note.new.quick, note.new.inbox, note.new.here, task.new,
  task.new.folder, database.new, drawing.new, embed.drawing.new; the
  four periodic-note commands when they create the note; template.create
  and template.insert; note.format (Format Markdown),
  editor.reflow-paragraph and editor.add-comment; nav.focus.editor and
  ref.focus; split.right, split.down and the four pane.focus commands.
- stay, the focus goes back where it was: everything else. That covers
  every View, UI and App command (sidebar, status bar, line numbers,
  sort and group, panels, Tasks, Tags, Atlas, zoom, themes, Settings);
  the Editor toggles (word wrap, live preview, completed-task style,
  which-key hints, tabs, keep panels, undo history, auto reveal, quick
  note dates, PDF embeds); task state, folds, copies and exports;
  archive, trash, rename, favorite and cover; vault, Cloud, CLI and
  workflow commands. Enable and Disable Vim Mode stay too: the mode
  applies the next time the editor has the focus, and on a phone that
  is a tap into the text, with a hardware keyboard or without.

The fold commands and Copy Link Under Cursor no longer focus the
editor themselves on such a host.

What it deliberately does not do: change desktop, the web app, or the
commands a phone's own menu runs through runAppCommand (those never
handed the focus over).

Tests: commandLanding (an existing note is read even for a writing id,
the edit list takes the keyboard, toggles and an unknown future command
stay); every id on the edit list is a real command; the palette hands a
toggle back through the close hand-off and still focuses for a writing
command. app-core 3,641, shared-domain 2,164 and desktop 1,198 pass;
typecheck clean (8/8). In the built app over CDP with isolated stores,
on 2.78.0 and on this build, Light Sidebar, Line Numbers: Relative,
Disable Word Wrap, Completed Tasks: Strikethrough, Fold All Headings
and Lists, Disable Live Preview and Format Markdown each leave the
editor focused on both (Toggle Status Bar too, on this build).

This branch has not been deployed

No deployments
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.

2 participants