Conversation
The "Forward task to" picker listed every other note in the vault and filtered as you typed, but the selection stayed on the raw text in the input. Pressing Enter therefore submitted what you had typed as a note name, failed the picker's own check that the destination exists, and answered "Pick an existing note". An arrow key or Tab first was the only way through. This is the friction #467 removed from the folder picker, and it landed harder here. A folder picker accepts a brand-new path, so an unmatched Enter just creates it; this picker only accepts a note that already exists, so the missing preselection turned into an error message instead of a no-op. The fix is the same opt-in flag that change introduced: autoHighlightFirst, so the first match is selected once a non-empty query is typed. The prompt construction moves into a pure buildForwardTaskPrompt() alongside the build*Prompt helpers in move-note.ts, which is what makes the behaviour testable; forwardTaskWithPicker keeps the store lookup and the write. Ctrl+J / Ctrl+K, asked for in the issue, already worked: #467 put the key handling in PromptModal itself, so every prompt with suggestions inherited it. Nothing on screen said so, so the hint line now reads "↑↓ or ⌃J/⌃K pick a note · Enter to forward" and the manual entries say the same. Verified by driving the built app over CDP against a throwaway vault: Ctrl+J, Ctrl+J, Ctrl+K walk the selection 0, 1, 0; typing "res" leaves the first match highlighted; Enter rewrites the source line to - [>] … [[Research]] and appends the backlinked copy to the target note.
ZenNotes advertised block references and half-supported them. The link opened the right note and stopped there, leaving you to find the block by hand, while [[Note#Heading]] scrolled to its section correctly. The cause was one deliberate null. wikilinkHeadingAnchor recognized `^` well enough to strip it so the note resolved, then reported "no heading here", and all six click surfaces read that as "just open the note". Each of them had its own copy of the same `heading ? openWikilinkHeading : selectNote` branch, which is why a missing anchor kind stayed missing: adding one meant remembering six call sites. They now share openWikilinkTarget, which dispatches on the raw target, so the next anchor kind lands everywhere at once. Block references are a whole feature now rather than a syntax the app tolerated: - Following [[Note^id]] scrolls to the marked block from a click, a Cmd/Ctrl-click, the gd motion, and the reading view. [[^id]] targets a block in the current note. - Typing `^` inside a wikilink lists the target's ids with each block's own text beside it, mirroring the `#` heading source. Whichever marker opens the anchor owns it, so a target cannot be both. - ![[Note^id]] embeds just that block, a bullet's children included, and shows a notice when the id is gone instead of silently inlining the whole note. - The Connections panel names the block a note reached for, recovered from the anchors that backlink resolution strips. - The `^id` marker is addressing, not prose, so it is hidden in the reading view and in the editor, and returns when the cursor is on the line. Ids are letters, digits and hyphens ending a line, so 2^3 and x ^ y stay arithmetic and a marker inside a fence stays an example. A marker alone on its line tags the paragraph above it, which is how a block gets named without touching its text. The block scanner does not carry its own copy of the frontmatter and fence rules. parseOutline's loop became scanMarkdownLines and both walk it, because a fifth copy of that logic is exactly the foot-gun this repo keeps paying for. Verified by driving the built app over CDP against a throwaway vault: a block link moves the editor from the top of the note to the marked bullet, its sibling bullets render with their markers hidden while the cursor line keeps its own, and the heading path is unchanged.
* editor: Typst word completion in math regions (slice 1)
The sibling of the LaTeX source for notes whose typesetter is Typst:
bare-word commands (sum, alpha, frac) complete from two letters on
inside the same math regions, gated on mathRendererOf so each source
stays out of the other typesetter's notes. Argument-taking functions
insert as snippets with Typst syntax (frac(a, b), sum_(i=1)^(n),
mat(1, 2; 3, 4)); the icon slot shows the exact Unicode glyph
(α, ∑, ∫, ℝ) — no typesetting needed for previews yet.
Starter table (~90 words): greek, core constructs, accents, set/logic
symbols, named functions.
* editor: compiled Typst previews in the completion row (slice 2)
Templated options now typeset their preview through the shared Typst
render queue: the Unicode glyph paints immediately, the compiled SVG
swaps in when ready, and the svg cache makes every later popup
instant. The preview source is the snippet template with its fields
unwrapped (frac(${a}, ${b}) previews as frac(a, b)), so preview and
insertion cannot drift apart. Glyph-only entries (greek, symbols)
keep their exact Unicode form — no compile needed.
* editor: arrows, comparisons, sets and text styles in Typst completion (slice 3)
Extends the table with the remaining everyday families: arrows with their
ASCII shorthands noted in the detail (arrow.r / ->), comparisons (lt.eq,
gt.eq, eq.not), sets (inter, nothing, subset.eq, in.not), circled
operators under their canonical post-0.13 names (plus.o, times.o — the
.circle spellings are deprecated in the bundled compiler), the dif
differential, and text styles (bold, upright, cal, bb) as snippets with
compiled previews. Every name compile-checked against typst 0.15.
---------
Co-authored-by: Adib Hanna <adibhanna@gmail.com>
Expose libglvnd through the shell wrapper so ANGLE can dlopen libEGL.so.1, and use makeShellWrapper so the conditional Wayland Ozone flag expands at runtime.
Forwarding moved a single line: the parent flipped to [>] and a copy landed in the destination, while every indented subtask stayed behind as orphaned open work. The task lost its breakdown exactly when it moved notes. forwardTaskSubtreeAtIndex now carries the whole indented block, using the same walk the daily rollover uses (deeper indent until a blank line, dedent, or fence), so the two carry mechanisms agree on what belongs to a task. The destination gets a faithful pre-flip copy: done, cancelled, and in-progress children keep their state, tokens travel verbatim, and nesting is re-based under the unindented parent copy. In the source, open subtasks ([ ] and [/]) flip to [>] beside the parent so they stop reading as live work; done and cancelled children deliberately keep their state, because that history is what the record preserves. Only the parent carries the [[Target]] link. Verified in the built app over CDP against a scratch vault, plus new shared-domain and store tests. In-app help describes the new behavior; the website docs mirror is updated alongside.
…al (#610) Carrying a task across daily notes left a [>] record in each note, and two things turned those records back into apparent live work. The daily due inference stamped every record with its note's date, so the Forwarded group showed the same task once per hop, each with a different due date. And the calendar kept every origin on its day (a side decision from #476), so one task drew dots on every date it had ever been carried through. Both stop at the shared layer. inferDailyTaskDueDates now exempts forwarded records: the work left that note, so the note's date says nothing about when it is due. bucketTasksByDueDate drops undated forwarded records: a record of a move is not unscheduled work. A forwarded record with an explicit due: written on its line keeps both its date chip and its calendar slot, which is exactly the case #476 chose to protect (the copy is written without the due: token). The Forwarded group itself stays: it is the bullet-journal record, collapsed by default, and now shows its entries without resurrected dates. Verified in the built app over CDP: a 15th -> 16th -> 17th carry chain now puts one task on one day instead of three.
…605 review) The reading view stripped ^ids per mdast text node, which deleted real prose (a mid-line ^word before emphasis lost itself AND its joining space) while leaving genuine anchors on non-final paragraph lines visible. The editor hide was a bare per-line regex, so literal ^tails inside code fences and frontmatter were blanked. The standalone-marker walk climbed the raw line array, so a ^id after the frontmatter anchored, and embedded, the YAML itself; the embed walk stopped at the first blank line, dropping loose children and once slicing an indented fence open so the rest of the embedding note rendered as code. One grammar now decides everything. scanMarkdownLines and the block-anchor logic move to shared-domain; parseBlockAnchors owns fence and frontmatter boundaries for navigation, extraction, and a new stripBlockAnchorMarkers that removes markers from the SOURCE, line- preserving, before remark ever runs. The live preview hides only what the parser accepts (anchors memoized per document), the DOCX export strips through the same function, and a standalone marker after a fenced block now tags the fence, whole. Obsidian's spellings work as-is: [[Note#^id]], [[#^id]], and markdown-style (Note.md#^id) all parse as block references, internal links carry a raw anchor that openWikilinkTarget types in one place, and [[Note#^ completes block ids. Completion also stops serving a session-long snapshot: the body cache validates against the note's updatedAt, so an id created seconds ago appears immediately. Connections rows keep their excerpt and append the block pointer instead of replacing one with the other, and only backlink rows pay for the block scan.
…tems (#605 review) Four web-workflow edges from the release review, one of them data loss: - Saving a workflow whose slug differed from the previous filename only by case deleted the file that was just written: on a case-insensitive filesystem both spellings name one physical file, and the cleanup compared path strings. Both the Go server and the desktop applier now compare file identity (os.SameFile / dev+ino) before removing. - The apply guard counted a change's BEFORE bytes against MaxNoteBytes, so an oversized note could never be shrunk, moved, or trashed from the web client. Only what a run writes counts now. - A note carrying invalid UTF-8 made every apply 409 forever: JSON coerces the bad bytes to U+FFFD on the way to the browser, and the client can only echo that back. The conflict check now also accepts the wire view of the disk bytes, matching what /notes/read served. - An over-cap run failed as "invalid workflow run counts" right after the dry run promised success; the errors now name the limit crossed. Alongside: the command palette gates workflows on canManageWorkflows instead of runtime === 'desktop', so capable web workspaces get the palette rows and vim ex-commands their view already had; the web bridge derives supportsWorkflows from the cached /capabilities response instead of mutating a const, and fires its two independent pre-apply requests concurrently; undo reads each journaled file once instead of twice while holding the exclusive vault lock; and the three synced copies of the workflow op schema now name each other.
…review) The MCP/CLI task parser and the Go server both matched [>] in their task regex but mapped no forwarded state, so a forwarded record read as an OPEN task. With subtree forwarding (#611) flipping whole blocks to [>], every carried task doubled in MCP listings, zn task list, and the web client: the records beside their live copies. All four copies of the parser now agree on the state; MCP's open filter, zn's default listing (with a [>] glyph under --all), and the server's JSON carry it. The subtree walk itself moves into one shared taskBlockEnd used by BOTH carry mechanisms, daily rollover and forwarding, so they can never disagree about which lines belong to a task. Loose lists now travel whole (a blank line no longer strands children live in the source under a forwarded parent), and mixed tab/space children re-base by whitespace count instead of being copied at their absolute depth. Also in this pass: the new Typst completion strings and the branch's new comments lose their em dashes (the house rule), the in-app manual gains the Obsidian block-link spellings and the math command completion it was missing, and the Nix wrapper comment reads without the dash too.
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.
The 2.29.0 release branch so far. Two issues from @uNyanda, both keyboard-flow papercuts that turned out to have more behind them than the reports suggested.
Block references actually reach the block (#601)
[[Note^block-id]]opened the right note and stopped there, while[[Note#Heading]]scrolled to its section correctly.The cause was one deliberate
null.wikilinkHeadingAnchorrecognized^well enough to strip it so the note resolved, then reported "no heading here", and all six click surfaces read that as "just open the note". Each carried its own copy of the sameheading ? openWikilinkHeading : selectNotebranch, which is why a missing anchor kind stayed missing: adding one meant remembering six call sites. They now shareopenWikilinkTarget, which dispatches on the raw target.Since block references were unimplemented rather than broken, this covers the whole feature:
[[Note^id]]scrolls to the block from a click, a Cmd/Ctrl-click,gd, and the reading view.[[^id]]targets a block in the current note.^inside a wikilink lists the target's ids with each block's own text beside it, mirroring the#heading source.![[Note^id]]embeds just that block, a bullet's children included, and shows a notice when the id is gone instead of inlining the whole note.^idmarker is hidden in the reading view and the editor, returning when the cursor is on the line.Ids are letters, digits and hyphens ending a line, so
2^3andx ^ ystay arithmetic and a marker inside a fence stays an example.Worth a look in review:
parseOutline's loop becamescanMarkdownLines, shared with the block scanner, so the frontmatter and fence rules do not get a fifth copy.The forward-task picker takes Enter on the first match (#600)
Half of that issue was already shipped: Ctrl+J / Ctrl+K worked, because #467 put the key handling in
PromptModalrather than in the folder picker. Nothing on screen said so.The auto-select was genuinely missing, and it bit harder here than in the folder picker. This prompt only accepts an existing note, so Enter after typing a filter submitted the raw text, failed validation, and answered "Pick an existing note". The picker now preselects the first match, and the hint reads
↑↓ or ⌃J/⌃K pick a note · Enter to forward.Verification
npm run typecheckclean at 7/7 andnpm run test:runclean at 6/6, with 53 new unit tests.Both features were also driven over CDP against a throwaway vault with isolated user-data and config directories, per the verification doctrine:
resleaves the first match highlighted; Enter rewrites the source line to- [>] … [[Research]]and appends the backlinked copy to the target.Demo clips for both are in
docs/releases/v2.29.0/media/(gitignored), ready for the release page.Docs
Both mirrored surfaces move together:
packages/app-core/src/lib/help.tshere, and the website half in ZenNotes/website#9.Note that the house flow is a fast-forward of
mainat release time rather than a merge, so this PR is for review rather than for merging with the button.Landed on the branch since this PR opened
frac(a, b), from two letters on, inside$…$and math fences.[>]records no longer inherit daily-note dates or dot the calendar on every day they passed through.Review hardening pass
A full
/code-review highran against this PR before release; every verified finding was fixed on the branch in three commits (08dde4e,90d24e2,185d038). Highlights:[[Note#^id]],[[#^id]], and(Note.md#^id)spellings all reach the block.[>]forwarded state, so carried tasks stop doubling as phantom open work in MCP tools,zn, and the web client, and the rollover and forwarding walks share one subtree function so loose lists travel whole.Verification after the pass: typecheck 7/7, shared-domain 1485 / app-core 1667 / desktop 586 tests,
go vetplus the full Go suite with three new regression tests, and nine CDP checks against the rebuilt app covering the review's executable repros.