312 lines · 73.6 KB · Markdown
RawDownload

Changelog

2026-06-22

  • Code-health pass across the desktop app and marketing site (now scoring 100/100 on React Doctor). Mostly internal, with a few user-relevant effects: more screen-reader labels on editor and settings controls, semantic landmarks in the editor chrome, a lighter backdrop blur on the anchor-warning banner, and DOMPurify sanitization of rendered Mermaid diagram SVG. Also broke several module import cycles (notably by splitting page-kind views from their behavior so the stores no longer pull in the editor UI), removed unused dependencies (motion, react-resizable-panels) and dead code, and parallelized a few independent file reads.

2026-06-16

  • Drag files and folders in the sidebar's Everything tree to move them. Drop onto a folder to move inside it, onto a file to move into that file's folder, or onto empty tree space to move to the workspace root; a multi-selection moves together. Open tabs, pinned files, and expanded folders follow the move, and name collisions are reported instead of overwriting. Dragging uses pointer events so it coexists with the existing Finder-drop-to-open behavior.
  • Open each new window at a random on-screen position instead of stacking them in the same spot, so it is obvious when more than one window is open. The position is chosen within the active display's work area (the screen minus menu bar/dock) and clamped so the whole window stays visible; it prefers the display under the cursor. This applies to secondary windows — compact single-file windows, new workspace windows, drag-drop opens, and second launches — while the first window keeps its default placement.

2026-06-09

  • Make compact mode fully global instead of workspace-scoped. Opening a markdown file directly (CLI, Finder, drag-drop) now opens a standalone compact window with no workspace at all — no file/folder indexing, no recursive watcher, no gitignore loading — so single-file windows open fast. The open file is still watched for external changes via a lightweight non-recursive watch on its parent directory that survives atomic-rename saves.
  • Back the compact file picker with a global "recently opened files" list persisted in the app data dir (capped at 30, deduped, pruned of deleted files on read). Every file that becomes active in any window — workspace or compact — is recorded.
  • Replace the workspace-scoped compact chrome: windows hosting a workspace always render workspace chrome. The appearance.compact-mode setting and its palette toggle are removed in favor of a new "Open File in Compact Window" command that opens the active file standalone. In compact windows, the command palette searches the global recents list, creates new files next to the active file, and routes workspace opens to a new window. Dropping a folder on a compact window opens a new workspace window; dropping a file on a workspace window opens a new compact window; opening the same file twice focuses the existing compact window.
  • Support navigating between linked files inside a compact window: clicking an internal markdown link moves to the linked file in place with back/forward history, and the single-file watcher re-points to whichever file is active (covering link clicks, back/forward, and the picker through one subscription) so external-change reloads always track the visible file.
  • Add back/forward arrows to the top-left of the compact topbar (mirroring the existing Alt+←/→ shortcuts), disabled at the ends of history. Drop the dimmed parent-directory path from the compact picker rows so they show the title only.
  • Rework the compact picker into a sidebar-styled, non-hovering, labeled, title-only global recents list that omits the active file and reveals a small X on row hover to remove entries from recents. The global recents store records a per-file opened_at timestamp (migrating the previous string-array format in place) and exposes a remove_recent_file command.

2026-06-05

  • Refactor the marketing website from a plain Vite React app to TanStack Start. The homepage now lives in Start file routes with a root document for metadata, favicon, stylesheet, and analytics script handling; the build prerenders / into apps/website/dist/client for the existing Cloudflare static assets Worker.
  • Keep marketing-site demo videos visible in prerendered output instead of relying on a hydration-time loadeddata event to reveal them.

2026-06-03

  • Redesign the sidebar into collapsible Pinned, Recents, and Everything sections. The existing file tree now lives under Everything; files can be pinned/unpinned into Pinned; Recents is metadata-sorted from the workspace index with cached pagination so Show More does not rescan the workspace.
  • Keep the sidebar compact in small workspaces by hiding Recents below 10 indexed files, and show recent files in 4-item pages when it is visible.
  • Add compact single-file windows. Opening a markdown file directly now uses editor-only chrome with no sidebar, sidebar toggle, or tabs; a top dropdown reuses the real Pinned, Recents, and Everything navigation to switch files while staying compact.
  • Hide the document stats footer in compact mode so single-file windows keep the bottom edge clean.
  • Animate the compact file picker with CSS instead of shared-layout projection. The pill shrink-wraps the title up to 240px, truncates long names, uses the same caret as the sidebar sections pointing down when closed, and uses a transform-based div shell that scales/translates between the trigger and popover positions while counter-scaling navigator content from its horizontal center and compensating corner radii/border widths so the inside items and rounded surface stay visually stable. The navigator list sizes to its content with a 420px max height instead of filling a fixed tall panel. The picker morph and content fades now run at 260ms. The light-mode popover uses a subtle floating-card-style shadow that fades in and out with the popover, the light-mode trigger surface now reads as a subtle gray and waits 300ms after close before fading out with the same smooth opacity curve, and the closed trigger surface only responds to the actual pill hover/focus area. The dark-mode trigger surface uses a white 9% fill that fades during picker transitions with a short close delay, popover content uses an explicit opacity/translate animation from a -30px Y offset inside the mask, picker hover no longer triggers sidebar row hover styles, and the animated shadow filter was removed to reduce paint cost.
  • Add Compact Mode as a persisted Appearance setting and command-palette toggle, and remove the debug-only compact launch environment variable.
  • Stop auto-expanding and scrolling the Everything tree when a file is opened from Pinned, Recents, search, links, or other open paths. The explicit tab context menu Reveal in sidebar action still expands ancestors and scrolls the tree on demand.
  • Add a large subtle shadow to the shared floating card surface used by the command palette and related popovers.

2026-06-02

  • Fix Mermaid error messages rendering as a vertical column of characters. The diagram stage now expands to fill the viewport when displaying an error so the message lays out horizontally, and resets on valid re-render.

2026-06-01

  • Stabilize scrolling through documents with folded Markdown tables. Table preview widgets now provide CodeMirror with a deterministic height estimate before they enter the measured viewport, so the editor no longer suddenly changes document height, scrollbar thumb size, or scroll position as tables virtualize in.
  • Fix opening a folder or file from the CLI, Finder, or by dropping onto the dock so it lands in a single window instead of spawning a duplicate empty one on cold start. On macOS the open target arrives via the system open event (not argv), which previously raced window creation; it now seeds the main window's startup open target while that window is still unhydrated, and opens a new window only when the app is already running with a different workspace.
  • Make the startup open target a single source of truth (startup_open) that Rust sets before the webview loads and the client reads once during startup hydration — same lifecycle as settings — so an explicit open no longer restores the previous session's tabs alongside the requested file. Runtime drag-and-drop onto a live window still uses the separate pending-open queue.

2026-05-28

  • Animate the sidebar folder caret so it rotates smoothly between collapsed (pointing right) and expanded (pointing down) instead of swapping icons instantly. The caret rotation is now the only animated transition on sidebar rows — the hover background highlight and the icon/label opacity changes apply instantly.
  • Add a "Sidebar File Label" appearance setting that chooses whether sidebar file entries show the document title (frontmatter title: or leading # heading, falling back to the filename) or always the filename. Defaults to title, preserving current behavior.
  • Add a "Rename..." action to the sidebar file context menu, so files can be renamed from the sidebar like folders already could. It opens the existing inline rename field, which always edits the filename (not the displayed title), regardless of the Sidebar File Label setting.
  • Update the app icon across all platforms (macOS .icns, Windows .ico, Linux/Store PNGs, and Android/iOS launcher assets) and sync the marketing website favicon to match.
  • Show an arrow caret on folder hover in the sidebar, replacing the folder icon, so expand/collapse intent is clearer. Icons and labels now fade in/out with opacity rather than color transitions for a cleaner visual hierarchy.

2026-05-27

  • Make the root dev script delegate to the desktop package's dev script, which now starts the Tauri development workflow, and route desktop build/preview scripts through Vite+ built-ins instead of raw Vite binaries.
  • Refresh compatible Rust and JavaScript dependency lockfiles. The JavaScript update moves the local Vite+ toolchain to the latest compatible lockfile version, aligns the root TypeScript config with the app-level bundler resolution used by Vite/Vitest checks, and clears the current package audit with direct dependency bumps plus targeted transitive overrides.

2026-05-26

  • Change the default editor line height to 1.8 so new and reset paragraph spacing matches the app's typographic fallback.
  • Constrain unordered-list and TODO-list prefix interactions to three stable pre-body caret positions: line start, marker start, and body start. Collapsed carets no longer land inside hidden indentation or marker text, ArrowLeft/ArrowRight step across those boundaries, Backspace at body start removes the marker plus one indent level for nested items, Backspace at marker start removes one indent level, and Tab/Shift-Tab now applies to selected list lines without changing the visual list renderer.
  • Stabilize unordered-list and TODO-list drag selection by replacing bullet/task point widgets plus zero-width hidden prefixes with measurable source-backed prefix spans. The real markdown prefix now owns the hit-test geometry while CSS draws the bullet or checkbox on that span, preserving nested alignment, empty-item carets, checkbox toggles, and list keyboard behavior without widget boundary snaps.
  • Fix rendered table-cell links so clicking markdown links and Obsidian wiki links navigates instead of unfolding the table source. Table-cell wiki links now render aliases correctly, including table-escaped separators like [[Format your notes\|Formatting]].
  • Render inline markdown inside folded table preview cells. Bold, italic, inline code, links, strikethrough, escaped pipes, and common HTML entities now display as rendered inline content in table headers and body cells, while inline HTML stays text-only and the unfolded source-editing path is unchanged.
  • Render unfolded markdown tables as codeblock-styled source lines in the main editor. Tables still fold to the rendered preview when the selection is outside them, but selecting into a table now keeps the markdown editable while giving it the same code surface treatment as fenced code.
  • Fix Markdown links/images and wiki-style links whose labels, aliases, folder names, filenames, or generated pasted-image asset paths contain spaces. Bare-space destinations such as [Todo](Writer TODOs.md) and ![image.png](Writer TODOs-assets/file.png) now parse as full links/images, angle-bracket destinations like <Writer TODOs.md> normalize to the intended local path, percent-encoded paths still decode, and pasted images now wrap generated destinations with spaces in angle brackets.
  • Add 1rem top padding to rendered editor Markdown headings through a shared heading line class, covering both ATX and Setext heading syntax while preserving the existing hanging ATX hash behavior.
  • Make sidebar icons and labels promote to the full theme foreground color on hover, selection, and active file states so they read white in dark mode and dark in light mode instead of staying gray.
  • Make fenced Markdown code blocks and inline code follow the editor font-size setting while keeping their monospace code font.

2026-05-25

  • Keep the blinking caret visible on empty bullet and TODO list items by anchoring the existing marker widget at the hidden prefix end, giving CodeMirror a body-column coordinate target without an extra empty-line widget.
  • Center ordered-list markers in a minimum 3ch inline-block column, tune the ordered-line hanging indent, and wrap ordered item text in the same cm-list-body span used by bullet lists, so list numbers align consistently while long list numbers can grow naturally.
  • Fix list drag-selection and TODO checkbox regressions from the list-prefix renderer. Bullet and task prefixes now render as point widgets with hidden source-prefix text instead of Decoration.replace ranges, so pointer hit-testing no longer snaps selections or clicks to the prefix boundary. TODO checkboxes now use a single non-native .cm-checkbox span with CSS-drawn checked state, avoiding native input focus during drag-selection and restoring nested checkbox alignment. The hidden source prefix now uses zero-inline-width clipped text with 1px metrics instead of font-size: 0, because WebKit's posAtCoords can fail on font-size: 0 task prefixes and leave TODO text selections with no drawn highlight.
  • Center TODO checkbox squares within the editor line box so they visually align with the task text instead of riding the inline baseline.
  • Stop the .cm-selectionBackground rectangle from blinking out for a frame while drag-selecting across **bold** or ~~strikethrough~~ ranges (most visible on bullet-list lines, where atomic bullet/spacer skips keep the head dwelling near hidden-mark boundaries). Root cause is the same browser-engine quirk the 2026-05-18 wrapped-bullet softIndentExtension fix names: coordsAtPos measured against a cm-emphasis wrapper around a font-size: 0 cm-hidden-token collapses to a zero-rect at the origin, and drawSelection uses the same measurement to size its selection rect. Switch the StrongEmphasis/Emphasis and Strikethrough hide specs to a Decoration.replace-based hide (new opt-in removeFromDOM: true flag on HidableNodeSpec; new hideInlineReplaceDecoration) so the ** / ~~ chars are absent from the visible DOM entirely instead of being kept as zero-font-size spans — coordsAtPos then anchors on the surrounding real text nodes and returns a stable rect. ATXHeading hide stays on the mark-based hideInlineDecoration because heading-decorations.ts co-styles the same range with cm-heading-hash and needs a real DOM span to position the hung-margin hash.

2026-05-19

  • Make Mermaid canvas trackpad pinch zoom more responsive while leaving Cmd-wheel, keyboard, and button zoom increments unchanged.
  • Add a reset button to Mermaid canvas controls, shared by inline widgets and the fullscreen overlay. It sits above the zoom-in/out buttons, mirrors the existing 0 keyboard shortcut, and restores the fit-to-viewport pan/zoom state after exploratory navigation.
  • Style the Mermaid inline source editor with a monospace font, a darker recessed background in dark mode, and a narrower pane (33% vs 45%) to give the diagram more room.
  • Fix list-item nested-indent alignment on proportional fonts by deriving the hanging indent from the leading-whitespace character count rather than glyph width, so nesting depth is consistent regardless of font rendering.

2026-05-18

  • Replace the Mermaid canvas "Edit code" / "Preview" text pill with a 28px square code icon toggle that matches the zoom and fullscreen controls. The toggle keeps the same glyph in both states and uses a subtle pressed visual state while source editing is open.
  • Make the inline Mermaid source editor selection fully CodeMirror-drawn. The nested editor already mounts drawSelection(), and the canvas stylesheet now forces the browser-native ::selection layer transparent inside .cm-mermaid-canvas-editor, so selected diagram source no longer gets a doubled native highlight.
  • Add a fullscreen view for mermaid diagrams. An "⛶" icon button next to the existing "Edit code" toggle opens the rendered diagram in a viewport-sized native <dialog> (showModal()), reusing the same pan/zoom canvas so users can explore detail on dense diagrams without leaving the document. Esc, the ✕ close button, or a backdrop click dismisses. Implementation extracts all .cm-mermaid-canvas-* styles from the editor's baseTheme into a stand-alone mermaid-canvas.css — EditorView.baseTheme scopes selectors under the editor's root class, so its rules don't reach a <dialog> mounted at document.body, and the dialog would otherwise paint an unstyled canvas. mountMermaidCanvas now accepts optional onToggleEdit / onExpand / onClose callbacks driving a shared top-right control cluster; the in-editor widget wires toggle+expand, the fullscreen view wires only close. See SPECs/mermaid-fullscreen-diagram-spec.md.
  • Fix bullet-list items whose first line starts with **bold** rendering their wrapped second line flush-left, outside the bullet. softIndentExtension measures the hanging indent as coordsAtPos(line.from + nonContent.length) - coordsAtPos(line.from). The default side: 1 walks into the span starting at the bullet boundary, and when that span is the cm-emphasis wrapper around a ** font-size: 0 hidden-token, some browser engines collapse the resulting bounding rect to a zero-rect at the origin. indentWidth then falls to 0, if (!indentWidth) continue short-circuits, and the line picks up no padding-inline-start decoration — so the wrapped portion renders at the cm-content content edge instead of aligning to the post-bullet column. *italic* happened to dodge it because the 1-char hidden span reported a usable rect; the 2-char ** is the actual trigger. Pass side: -1 so the measurement anchors on the trailing edge of the - text node (always rendered, always has a real rect) regardless of what wrapper comes next. Drive-by simplification: collapse the now-duplicated StrongEmphasis / Emphasis specs in hide/index.ts back into a single combined nodeName: ["StrongEmphasis", "Emphasis"] entry sharing one cm-emphasis wrapper class, so bold and italic produce identical DOM and can't drift apart again.
  • Stop the caret-hijack when clicking on a line that contains **bold**, *italic*, or ~~strikethrough~~. The default emphasis / strong-emphasis / strikethrough hide specs keyed only on the inner mark node (EmphasisMark, StrikethroughMark) with no parent wrapper, while InlineCode (which the user reported behaves correctly) keys on the parent and adds a nodeDecoration wrapper class — that wrapper gives CodeMirror a stable inline click target for posAtCoords to resolve into, instead of resolving directly into the zero-width hidden-mark spans on otherwise-bare emphasized text. Mirror the InlineCode shape for the three emphasis-family nodes: a parent-keyed spec with a cm-strong-emphasis / cm-emphasis / cm-strikethrough nodeDecoration wrapper plus subNodeNameToHide for the marks. Also adds two optional fields to HidableNodeSpec — predicate for parent-context filtering (e.g. disambiguating CodeMark under InlineCode vs FencedCode) and hideZone for overriding the selection-touch zone — but the emphasis fix itself doesn't rely on them; they're hooks for future spec authors who hit similar shape mismatches.
  • Eliminate the remaining unfurl shifts during pointer drag-selections. The 2026-05-14 freeze gate handled hides prosemark dropped mid-drag (re-apply via a diff overlay) but couldn't suppress hides prosemark added mid-drag — clicking inside an already-unfurled node and dragging past it still produced one layout shift on the way out. Now that @prosemark/core is vendored (#49), gate this at the source: a new unfurlFreezeFacet short-circuits the hideExtension and foldExtension StateField.update paths when any provider contributes true (doc changes still re-map decoration positions, only the selection-touch rebuild is suppressed). drag-selection-gate.ts drives the facet from dragFrozenSelectionField and drops ~200 lines of diff-overlay machinery (frozenHideDecorationsField, frozenFoldDecorationsField, the identity diff and proximity diff helpers and their Prec.high overlays) — no more concerns about doubled cm-inline-code wrappers or stacked image widgets, because there's no overlay anymore. Mouseup still re-asserts selection: state.selection so the unfreeze transaction itself triggers a single fresh rebuild against the final live selection.
  • Fix wikilink autocomplete dropdown's selected row rendering as near-invisible white text on a light overlay in light mode. CodeMirror's bundled autocomplete theme sets color: white on &light .cm-tooltip-autocomplete ul li[aria-selected], and our override in wiki-link-extension.ts only specified color: inherit (lower specificity, lost to the bundled rule). Pin the override to var(--text-primary) !important so the row's foreground stays readable on the translucent --surface-selected background in both light and dark themes.
  • Fix the wikilink autocomplete popover bleeding editor text through in light mode. Root cause: backdrop-filter: blur(...) on the popover was a no-op because EditorScrollContainer applies mask-image: linear-gradient(...) to its scrolling div — a CSS mask establishes a compositing context that neutralizes backdrop-filter on every descendant, regardless of the descendant's position: fixed. CodeMirror's autocomplete tooltip is appended to view.dom.parentNode by default, which lives inside the masked region. Opt the wiki-link extension into tooltips({ parent: document.body }) so the popover is appended at the body level, escaping the mask entirely and letting the blur actually paint. Pair with a dedicated .cm-tooltip.cm-tooltip-autocomplete block in App.css that mirrors the surface-card chrome (16px radius, --line-subtler border, 16px backdrop blur, isolation: isolate, drop shadow) and paints bg-base 70% via ::before for the card fill. Can't share --surface-card directly because that variable is set to transparent in light mode (for settings-panel static cards). !important is required on background/border/radius because @codemirror/view's baseTheme injects &light .cm-tooltip { backgroundColor: #f5f5f5; border: 1px solid #bbb } after our stylesheet with higher specificity. wiki-link-extension.ts keeps only the inner-list spacing and aria-selected styling — the card chrome lives in App.css.

2026-05-14

  • Freeze markdown unfurl across pointer drag-selections. Drag-selecting across inline markdown used to make syntax markers flip between rendered and raw on every mousemove — bold asterisks, heading hashes, link brackets, list bullets, etc. — reflowing text under the cursor and causing unintended selections. Generalize the drag-freeze pattern that mermaid widgets already used (pointerdown snapshots the selection into dragFrozenSelectionField, pointerup/pointercancel/blur clears it with a no-op selection: state.selection nudge so prosemark's tr.selection-gated fields recompute) into a shared editor-area/drag-selection-gate.ts, mount it once at the editor's top-level extension list, and have wiki-link / table / html-block / mermaid all read the frozen ranges instead of state.selection. For prosemark's hideExtension and foldExtension — whose internals we can't reach to swap in a frozen-selection check — snapshot each field's DecorationSet at pointerdown and contribute the difference between snapshot and live at Prec.high: only the decorations prosemark dropped because the live selection now touches their node, never the wrapper marks (cm-inline-code, etc.) prosemark re-emits regardless of selection, which would otherwise duplicate as nested spans with doubled padding around inline code. The hide overlay diffs by identity (prosemark allocates each hide-decoration instance once at module load); the fold overlay diffs by range proximity with ±1 slack (fold specs allocate fresh Decoration instances per call, and the slack defers to live for imageExtension's boundary-widget case so we don't render two stacked image widgets). Known limitations: if the user clicks inside an already-unfurled node and drags past it, prosemark hides that node once the selection leaves (one shift instead of dozens); and images themselves still flip mid-drag because ImageWidget isn't exported from @prosemark/core.

2026-05-12

  • Heading anchor links. Clicking [See setup](#setup) smooth-scrolls the current document to the matching heading without touching navigation history. Clicking [Roadmap](planning/roadmap.md#q3) opens the target file in the current tab (push to per-tab history) and scrolls to the heading once the doc loads. Slugs follow GFM: lowercase, spaces → -, github-slugger's punctuation strip, duplicate-collision suffixes -2, -3, … in document order. Slug generation is a pure module shared by the section rail's Copy heading link and the link resolver — single source of truth, unit-tested. Unresolved anchors still navigate to the target file, scroll to the top, and surface a small auto-dismissing inline warning instead of failing silently. See SPECs/heading-anchor-links-spec.md.
  • Add a section-indicator rail to the editor body. A thin column on the left edge of the editor pane renders one short horizontal tick per heading (H1–H3 by default), indented by depth. The tick of the heading currently nearest the top of the viewport brightens. Hovering the rail reveals a text-only outline popover listing every heading; the current heading at each level is bold full-color while the rest are muted. Clicking a tick or row smooth-scrolls to that heading; right-clicking offers Copy heading link. Headings are parsed from the file content (skipping fenced code blocks); active tracking is driven by a scroll-event listener using CodeMirror's layout model (view.lineBlockAt/view.documentTop). See SPECs/section-indicators-spec.md.
  • Fix the editor tab's "Reveal in sidebar" context-menu action and auto-reveal every newly-opened file in the sidebar tree. Previously onRevealInSidebar only called setActiveTab — the row's highlight depends on FileTreeNode actually rendering, and flatten-tree.ts skips children of collapsed folders, so a deep file's row was never in the DOM. New single coordinator at apps/desktop/src/lib/reveal-in-sidebar.ts expands every ancestor folder from the workspace root down to the file's parent (awaiting each toggleDirectory so directoryCache fills in), then scrollIntoView({ block: "nearest" }) on the row via a new data-tree-path attribute on FileTreeNode. The tab action additionally flips appearance.sidebar-visible back on if the sidebar was collapsed. Auto-reveal hangs off a single useEditorStore.subscribe to activeFilePath in apps/desktop/src/hooks/use-auto-reveal-active-file.ts, mounted at the App root — every open path (sidebar click, wikilink Cmd-click, command palette, drag-drop, welcome, recents, back/forward, session restore) converges through that field, so the call sites stay untouched. A module-level token cancels stale reveals when back/forward is held. Auto-reveal does not force the sidebar open — only the explicit menu action does. See SPECs/reveal-in-sidebar-and-external-watcher-spec.md.

2026-05-09

  • Hang ATX heading hash markers (#/##/…/######) in the left margin so heading text aligns flush with body content, the typographic treatment used by Typora, iA Writer, Bear, and Notion. A new headingDecorations ViewPlugin walks the syntax tree for visible ATXHeading[1-6] nodes and emits a line decoration (.cm-heading-line + level class for the line's containing-block) plus a mark decoration (.cm-heading-hash) on [headerMark.from, min(headerMark.to + 1, node.to)] — the same range as @prosemark/core's default hide spec, so the spans align exactly and CodeMirror merges the classes onto a single element. CSS uses position: absolute; right: 100%; padding-right: 0.4em to take the hash out of the inline flow into the page-padding gutter, and font-size: inherit !important to override the hide spec's .cm-hidden-token { font-size: 0px } (specificity ties at 0,2,0; the runtime-injected theme is loaded after our stylesheet, so !important pins the override). Hashes stay visible whether the caret is on the heading or off; the chars remain real source positions, so typing/deleting them works as normal and the parser drops the heading when the trailing space is removed. The .cm-scroller clip-path (from the selection-rect-bleed fix) was relaxing the LEFT side by --writer-editor-side-padding so the hash can render in the gutter — selection-rect-bleed regression is prevented by a new .cm-selectionLayer counter-clip, which CM6 normally sizes 0×0 (contain: size style); we override width: 100%; height: 100%; contain: layout paint so a layer-level clip-path: inset(0 var(--writer-text-col-inset)) actually clips selection rects to the inner text column. Right side stays at the strict text-col-inset on the scroller. See SPECs/heading-hash-margin-spec.md.

2026-05-08

  • Fix the editor selection rendering as a single giant rectangle that bled past the text column and overlapped block widgets (mermaid canvas, table, html block) for any multi-line selection. CodeMirror's drawSelection paints the multi-line filler rectangle using lineStyle.paddingLeft/Right from the first .cm-line it queries, so with the page inset on .cm-content the rect spans the entire .cm-content box including the page padding gutters; and @prosemark/core's softIndentExtension applies inline padding-inline-start per list line, so the rect's left edge can shift with whichever line is first in the document and list-item text bleeds outside it. Fix adds clip-path: inset(0 X) on .cm-scroller, where X resolves to the live text-column inset (max(side-padding, (100% − outer-width) / 2 + side-padding)). The wrong-bounded rect from drawSelection is still painted, but the parts that extend into the page-padding gutters are simply not displayed. Cursor, text, widgets, and search match highlights already live inside the clipped column, so nothing else changes. CSS-only, single declaration. See SPECs/selection-rect-bleeds-past-text-spec.md.
  • Stop the mermaid canvas widget from flipping into source/edit-mode mid-drag when a text selection passes through the fence range. A pointerdown on the editor now snapshots the live selection into a dragFrozenSelectionField, and mermaidFoldExtension's editMode predicate evaluates against that frozen snapshot instead of the live selection until pointerup/pointercancel/blur clears the gate (with a paired no-op selection: state.selection to nudge foldExtension to recompute). Setting keepDecorationOnUnfold: true on the spec stops @prosemark/core's foldExtension from short-circuiting when the live selection touches the fence — without it, prosemark dropped the decoration entirely on first crossing and the gate had nothing to gate. Side benefit: the previously-unreachable Edit-mode Decoration.widget branch is now actually reached. See SPECs/mermaid-drag-selection-edit-mode-flip-spec.md.

2026-05-07

  • Finish the canonical-path plumbing for the watcher fix. prepare_workspace_state already canonicalized workspace_root, but build_restore_bundle and the frontend's openWorkspace were still calling read_directory with the user's raw input, so child entry paths landed in directoryCache under an un-canonical key while the watcher fired canonical paths. The cache miss made the spec's /var/... repro only "fixed" for root-level events; subdirectory changes silently desynced. Both call sites now use the canonical root from WorkspaceInfo.root. Also: the watcher now walks every .md descendant when a folder enters the tree via Create(Folder) or Modify(Name) (rename-in), since FSEvents on macOS doesn't re-emit per-child Create events for a renamed inode — without the walk, search and the sidebar's directory-has-markdown indicator would silently drift from disk on rename. is_workspace_ignored now consumes the event-kind-derived is_dir instead of the unreliable path.is_dir() (which returns false on a deleted path), so dir-only gitignore rules like dist/ correctly match removed directories.
  • Reflect Finder "Move to Trash" and mv renames in the sidebar. macOS surfaces these as Modify(Name(_)) rather than Remove(_) — the previous fix only handled Create/Remove, so trashing a file (the most common way users delete) didn't update the UI. The watcher now treats Modify(Name) as a directory-membership change and uses path.exists() as ground truth: vanished .md paths get pruned from the index, vanished non-.md paths trigger a subtree prune in case it was a renamed-away folder, and the parent's fs:directory-changed fires either way.
  • Reflect external deletes and non-markdown changes in the sidebar. Follow-up to today's external-file-watcher fix: the watcher only emitted fs:directory-changed for the parent when an .md file was created or removed, so deleting a .txt, image, or any folder produced no UI refresh. Also path.is_dir() is unreliable on a deleted path (the file is already gone by the time we read the event), so folder deletes that came in as Remove(File) rather than Remove(Folder) were misclassified. The event-handling block is consolidated: is_dir now trusts the FSEvent kind first and falls back to a live stat, every Create/Remove fires a parent fs:directory-changed regardless of file type, and a removed folder also drops its descendants from the file index in case FSEvents coalesced their per-child events into a single batch. The Create branch now checks path.exists() before adding to the index so coalesced Create+Remove pairs from FSEvents don't leak phantom entries.
  • Pick up external file and folder changes reliably. Three fixes in apps/desktop/src-tauri/src/watcher.rs and commands/workspace.rs: (1) should_ignore now strips the workspace root before scanning for dot-prefixed components, so a workspace at e.g. ~/.notes/ keeps firing events instead of having every path filtered. (2) prepare_workspace_state canonicalizes the workspace root on open, so FSEvents-emitted paths (which are always canonical — macOS aliases /var → /private/var, plus user symlinks) line up with the frontend's stored root and the path === root check in the file watcher hook actually matches. (3) is_self_write now uses a TTL window instead of consuming the entry on first match — a single Writer save fans out into Create + Modify(Metadata) + Modify(Data) on FSEvents, and the previous logic only suppressed the first one, letting the others bounce back as fs:file-changed events that could clobber in-progress edits. See SPECs/external-file-watcher-spec.md.
  • Revamp the mermaid canvas edit-toggle. Instead of placing a caret at a single point inside the fence, clicking "Edit code" now selects the entire fence range (EditorSelection.single(fenceTo, fenceFrom)) — the same pattern the table widget uses. selectionTouchesRange from @prosemark/core is overlap-based with inclusive bounds, so the range selection reliably flips the syntax facet into edit mode regardless of where the head lands. Fence positions are looked up live at click time via view.posAtDOM + a syntax-tree walk (no positions captured on the widget instance), eliminating an entire class of stale-state bugs where a previous click would leave the caret stuck at doc start, or a click on widget B would jump to widget A's old fenceTo+1. The widget identity simplifies to source + editMode, and the cursor stash, debounce guard, and eq() side-effect are deleted. The renderer (beautiful-mermaid) is now called synchronously from toDOM — the IntersectionObserver-deferred render and the "Loading diagram…" placeholder are gone, so the widget paints with its final SVG in the same frame the wrapper enters the DOM. The SVG cache is bounded LRU (50 entries) and the output is sanitised (script blocks and on*= event handlers stripped) before reaching innerHTML.
  • Fix the editor scroll jumping on every keystroke after the Cmd+F polish landed. The custom EditorView.scrollHandler was firing for any scroll-into-view (including CodeMirror's default cursor tracking on typing) and unconditionally re-positioning the cursor at the safe-zone top. Now gated by a searchScrollIntent StateField that only flips on for transactions tagged select.search (find next/previous, jump-to-match) or input.replace (replace next/all), and the handler only nudges the scroll when the match's line is actually outside the safe zone — typing and ordinary cursor moves fall through to CodeMirror's default scroll, and stepping between already-visible matches no longer jumps.

2026-05-06

  • Polish in-document find (Cmd+F): the active match now scrolls into a clear zone away from the editor's top/bottom fade mask and progressive blur — search() is configured with a custom scrollToMatch that uses EditorView.scrollIntoView with a yMargin derived from EDITOR_SAFE_SCROLL_MARGIN. Add Cmd+G / Cmd+Shift+G for next/previous match (handled at Prec.highest in the editor keymap and in the find/replace inputs' onKeyDown). Add an IDE-style match overview rail along the right edge of the editor pane: thin marks (one per match, accent-colored for the active match) render outside the masked scroll area, click to jump. The query is mirrored into editor-search-store so the overview can subscribe without prop-drilling. See SPECs/cmd-f-spec.md.
  • Add a Preferences… entry (Cmd+,) to the Writer application menu that opens the Settings tab in the focused window. Rename the macOS-only CLI install item to Install 'writer' Command Line Tool… (and the uninstall counterpart) and group it next to Preferences. See SPECs/install-cli-menu-placement-spec.md.
  • Render mermaid diagrams inside a fixed-height canvas frame with drag-pan, wheel/button zoom (clamped to 0.25×–4×), fit-to-viewport reset, and an edit-code toggle. The canvas is keyboard-accessible (arrow-key pan, +/-/0 zoom, Enter toggles edit). The toggle moves the caret into or out of the fence, reusing the existing syntax-facet path that already swaps between rendered preview and source. With a fixed widget height the heightmap settles immediately, removing the per-source measurement cache and the loading→rendered height shift.
  • Swap the mermaid renderer to beautiful-mermaid. The canvas widget (fixed-height frame, drag-pan, wheel/button zoom, edit-code toggle) stays. Drops the custom monochrome theme map, mermaid.js-specific edge-label CSS overrides, theme-sync mutation observer, and the mermaid package itself. The renderer is now sync with a single visual style — the theme argument to renderMermaid is preserved as a no-op for caller compatibility but no longer affects output or cache keying.

2026-05-04

  • Update the app icon.

2026-05-03

  • Style the editor's selected-text highlight with a dimmed accent color instead of the browser default. Adds a derived --editor-selection-bg: color-mix(in srgb, var(--accent) 30%, transparent) token in App.css and applies it to .cm-selectionBackground (both focused and unfocused) plus .cm-content ::selection as a fallback for the native browser highlight.
  • Fix the caret rendering offset after using back/forward navigation between files by avoiding transition-scheduled history swaps and using CodeMirror's measured cursor drawing.
  • Fix the editor caret rendering as a black bar in dark mode by overriding @codemirror/view's default border-left: solid black on .cm-cursor with the theme's --text-primary token at higher specificity.
  • Remove the scale "pop" animation that played when toggling a task checkbox.
  • Fix the task checkbox checkmark not appearing in production builds by allowing data: URIs in the Tauri img-src CSP directive (the inline SVG check icon was being blocked).

2026-05-02

  • Fix Obsidian-style wikilinks with aliases, escaped table pipes, and heading or block fragments so Sandbox vault links open the intended notes.
  • Unify the default theme accent to #FF6A00 across light and dark modes.
  • Style task list checkboxes with the accent color and use a Hugeicons checkmark for the checked state.
  • Fix tables getting horizontally squeezed when a single cell holds a long word; columns now lay out at their natural widths and the table scrolls instead.
  • Fix the caret jumping to the wrong position when switching tabs.
  • Fix the editor not auto-focusing after creating a new file from the command palette. The dialog now closes before the file is opened so its focus trap and focus restoration finish before the editor mounts and calls view.focus().
  • Land the caret after the # heading prompt when a new file opens so the user can type the title immediately.

2026-05-01

  • Update the app icon.
  • Default new document content to a heading prompt (# ) so a fresh file opens ready for a title.

2026-04-30

  • Fix the tab chrome snapping when toggling the sidebar. The tabs now render from the app layout as a root-level overlay whose left edge transitions between the expanded sidebar edge and the collapsed safe inset, instead of switching sidebar-dependent padding inside the moving editor area. See SPECs/sidebar-toggle-tab-chrome-shift-spec.md.
  • Rename the bundled Codex theme preset to Writer. The preset values are unchanged; only the default preset label and bundled theme folder changed.
  • Show recent workspaces in the macOS Dock icon context menu. The menu is built from Writer's existing recent workspace list, omits missing folders, and reuses the multi-window open path so selecting an already-open workspace focuses that window instead of duplicating it. See SPECs/recent-workspaces-dock-menu-spec.md.
  • Prepare the repository for a fresh private GitHub repo: remove internal triage artifacts and screenshots, replace starter READMEs with Writer-specific docs, and point release/updater metadata at joelbqz/writer-computer.

2026-04-28

  • Swap the hand-rolled sidebar SVGs for Hugeicons. The sidebar toggle now uses SidebarLeftIcon, the file tree uses Folder01Icon for collapsed folders, Folder02Icon for expanded folders, and File02Icon for files, and the file-browser search button uses Search01Icon, all from the public @hugeicons/core-free-icons package via the <HugeiconsIcon> wrapper from @hugeicons/react. Visual sizing is 16px in the tree and the search input, 18px in the toggle. Icon spans set the icon to --fg-base (white in dark mode, black in light) and dim uniformly via opacity: 0.54 on the wrapping span, so the icon reads at the same intensity as the row's --text-muted label.
  • Indent blockquote text past the prosemark vertical bar. .cm-blockquote-line got a padding-inline-start: 1em override in prosemark-theme.css so the bar (absolutely positioned at ~6–11px) no longer abuts the first character of the quoted line.
  • Drop redundant tracking-[-0.01em] / tracking-[-0.02em] letter-spacing overrides across the editor footer/pane, sidebar nodes, settings rows, and palette chrome. The 13px UI text already inherits a base letter-spacing: -0.01em from the chrome stack, so the per-element overrides were either redundant or fighting the global value.
  • Replace CodeMirror's default Cmd+F search panel with a custom React overlay that matches the app chrome and renders above the scroll-fade mask. The overlay floats at the bottom-right of the editor pane, uses --surface-card + backdrop blur for the surface and --surface-input for the find/replace inputs, and exposes Find, Previous (↑), Next (↓), Replace toggle, Replace, and Replace All for literal case-insensitive search. Cmd+F is intercepted at Prec.highest and routes to the overlay; Esc closes; Enter (Shift+Enter) jumps to next (previous). Search lifecycle now closes both the React overlay and CodeMirror's hidden search panel, clears stale editor-view references on close/unmount, and updates CodeMirror from search input events instead of a render effect. Adds @codemirror/search as a direct dep, the search({ literal: true }) extension for the search state field, and a small Zustand bridge (editor-search-store.ts).
  • Replace the SF Pro / -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif font stack with -apple-system-body, ui-sans-serif, -apple-system, system-ui, "Segoe UI", Helvetica, "Apple Color Emoji", Arial, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol" everywhere it was hard-coded: the --ui-font / --editor-font defaults in App.css, the .cm-content fallback in prosemark-theme.css, the theme.dark.ui-font / theme.dark.editor-font schema defaults, and the bundled default, high-contrast, and writer/dark themes' UI + editor fonts plus warm-paper's UI font. The Geist/Inter prefix in writer/light and warm-paper's Iowan/Charter editor serif are preserved.
  • Polish the wiki-link autocomplete popover to match the command palette. Switch the tooltip font from the editor's monospace to var(--ui-font) at 13px, give it a 12px outer radius with var(--surface-palette) background, var(--line-subtle) border, and a 20px backdrop blur. Add 4px container padding, round each item to 8px, and use var(--surface-selected) for the selected row so it has enough contrast against the popover surface.

2026-04-26

  • CSS-var-driven theming system. Replace the hand-tuned static palette with seven user-controlled primary tokens per mode (light/dark): Accent, Background, Foreground, UI font, Editor font, Translucency, Contrast. Every other token (borders, hover bgs, secondary text, cards, scrollbars) is now derived in CSS via color-mix() and calc() so JS only writes primaries. Translucency is tied to background opacity and applies app-wide — every surface inherits the same translucent base so OS vibrancy bleeds through globally. Contrast scales the alpha multiplier on overlay layers, going from flat at 0 to maximum legibility at 100. Adds a Themes section to the Preferences panel with two stacked Light/Dark cards mirroring the mock: preset dropdown (Writer / Default / Warm Paper / High Contrast / Custom), Import (paste a JSON blob), Copy theme (writes JSON to clipboard), live preview as you edit. Editing any token after picking a preset auto-flips the dropdown to Custom without erasing edits. New color and range setting types render a hex+swatch picker and a slider+number readout respectively. Translucency clamps at 5% min opacity at the apply layer so the window can't go fully invisible. See SPECs/theming-system-spec.md.
  • Standardize UI text at 13px to match the macOS system menu size. Replace every Tailwind text-xs (12px) and text-sm (14px), plus the explicit text-[12px] and text-[14px] sites, with text-[13px] across the tab strip, sidebar search button, file-tree empty state, document footer, welcome screen, command-palette result subtitles, settings panel, and error boundary. Polish the Settings cards: add a --line-subtler border, round corners up to rounded-2xl, and switch the light-mode --surface-card token from a translucent gray to solid white so the cards read as cards on a tinted background.

2026-04-25

  • Drop "Rename..." from the sidebar file context menu. Folder rename and the document-title rename in the editor are unchanged, and the inline-rename UI still runs when naming a freshly created file. Updates sidebar-file-context-menu-spec.md and the file-context-menu test fixture.
  • Fix slow launch and welcome-screen flash when opening a folder or file via the writer CLI (or drag-drop / dock arg). get_startup_state now prefetches the workspace restore bundle for the pending open's workspace — previously it skipped the prefetch whenever pending_open was set, falling back to the four-step open_workspace → read_directory → get_recent_workspaces → load_session IPC waterfall. The frontend also now processes the pending-open before flipping isStartupResolved, so React's first render is the populated AppLayout instead of WelcomeScreen repainting to the workspace once the awaits resolve.

2026-04-24

  • Collapse the Writer submenu's "Install 'writer' Command in PATH" / "Uninstall…" pair into a single item that toggles its label based on the current install state. On menu setup and after a successful install/uninstall, refresh the label via MenuItem::set_text so only the relevant action is ever shown. Backed by a CliMenuItem managed-state wrapper and a cli.toggle event that dispatches to the existing install/uninstall handlers based on cli_status().
  • Show the Writer app icon in updater dialogs ("You're up to date", "Update Available", and check/install errors) instead of the generic macOS caution bubble. rfd on macOS falls back to CFUserNotificationDisplayAlert (which always renders a generic system icon) when no parent window is set; pass the frontmost webview window as the parent so it uses the NSAlert path, which picks up [NSApp applicationIconImage].
  • Fix raw-markdown flash when navigating between files in a tab. The previous fix (#103) reset undo history per file via view.setState(EditorState.create(...)), which rebuilt every state field and ViewPlugin from scratch — CodeMirror painted the bare .cm-line DOM for one frame before Prosemark's syntax-tree-driven decorations caught up. Switch back to an in-place view.dispatch({ changes }) swap (annotated addToHistory.of(false)) and clear the undo history by wrapping prosemarkBasicSetup() in a per-view Compartment and reconfiguring it [] → prosemarkBasicSetup() on path change. Removing the compartment discards the historyField state; re-adding initializes it fresh. The language/syntax-tree state and outer decoration plugins stay alive across the swap, so decorations remain continuous.
  • Replace the command palette's nucleo fuzzy search for files with a plain case-insensitive substring match against the relative path, matching the behavior already used for commands. Rank hits by whether the substring lands inside the filename vs. a parent directory, then by match position, then by path length. Spaces in the query are normalized to dashes so "my note" matches my-note.md. Drop the nucleo crate. Highlight indices still mark the matched span so the underline in the result subtitle keeps working. Commands render above files in the combined results list and take the default selection.
  • Redesign the Settings page in a Linear-style layout. Drop the in-page search input and sticky category headers; settings now render in a centered max-w-2xl column with a large "Preferences" title, muted category sub-headings, and each group wrapped in a rounded --surface-card container with --line-subtler dividers between rows. Reset the cards with -mx-4 so row text aligns under the category label. Each row is top-aligned so labels stay at the top when lists grow. The Reset affordance moved left of its control with a reserved slot, so toggling modified state no longer shifts layout. Added per-theme --surface-input, --surface-card, --focus-border, and --select-chevron tokens; <select> now uses appearance-none with a themed chevron so its rounded-lg matches the other inputs. Animated the boolean toggle via translateX. Removed the broken border-radius: 4px override from the app-wide :focus-visible rule (it was stomping on every focused element's own radius) and added [data-settings-panel] to the focus-outline opt-out so settings inputs use their subtle border-based focus indicator. Wrapped the scroll area in EditorScrollContainer so the Settings page gets the same top/bottom fades as document pages. List remove button matches the tab-bar close button style.

2026-04-23

  • Rework the Cmd+P command palette chrome and unify files + commands in one search. Remove the dark overlay backdrop and switch the dialog surface to a translucent panel with a 20px backdrop-filter blur, themed via a new --surface-palette token (dark mode uses a lighter gray rgba(42,42,42,0.8), light mode keeps --surface-primary at 80%). Drop the legacy > prefix and the separate commands mode: Cmd+P/Cmd+Shift+P both open a single palette where files and commands are filtered together under a single "Suggested"/"Results" group. Add a required description field to the PageKind interface and surface it (plus per-command descriptions) as a subtitle under each command palette entry. Rename the new-tab "Go to file" action to "Search" to match.
  • Support multiple Writer windows, one workspace per window. Opening a workspace from the sidebar switcher, welcome screen, command palette, or a dock/drag-drop now builds a new in-process WebviewWindow instead of swapping the workspace in the current window. Refactored the Rust side to a per-window state map: the former global AppState became a WorkspaceState struct, and a new AppState container keys one WorkspaceState per Tauri window label. Every IPC command that touches workspace data (fs, search, settings, startup, workspace, session) now looks up its state via webview.label(); the file watcher, gitignore matcher, pending-open queue, and settings layer all live per-window. Re-added tauri-plugin-single-instance with a handler that opens a new window on secondary launches, and hooked the window close event to tear down the watcher. If another window already hosts the requested workspace it's focused instead of duplicated. See SPECs/multi-window-spec.md.
  • Generalize the tab system into a kind-agnostic shell. Settings now lives in a proper tab (singleton on Cmd+,) alongside file tabs instead of a full-screen overlay; keep-alive visibility toggles preserve form state on switch. Tabs are a flat { id, location, back, forward } over a Location union, and every kind-specific behavior dispatches through a page-kind strategy registry (title, paths, rewritePath, removePath, serialize, Component, renderFooter, supportsFileContextMenu). Back/forward history is per-tab and kind-agnostic. Session files switch to a tagged-enum wire shape with no legacy fallback — workspaces with pre-spec session files open fresh on first launch. See SPECs/tabbed-pages-spec.md.
  • Fix undo/redo history leaking across files in a tab. Swapping files reused the CodeMirror view and dispatched a content-replacing transaction, which kept the prior file's entries in the undo stack — Cmd+Z could revert the swap or step into the previous file's edits. Replace the view's state via view.setState(EditorState.create(...)) on both file navigation and disk reload so each file starts with a fresh history.
  • Wire up the writer shell command install flow on macOS. Collapse writer-cli into the main Writer binary as a multi-call executable: main.rs dispatches to CLI mode when argv[0]'s basename is writer, so "install" is a single symlink from /usr/local/bin/writer to Writer.app/Contents/MacOS/Writer. Add "Shell Command: Install 'writer' Command in PATH" and "Shell Command: Uninstall…" menu items under the Writer submenu, backed by new cli_status / install_cli / uninstall_cli Tauri commands; install escalates to an osascript admin-auth prompt when /usr/local/bin isn't user-writable and refuses to clobber a pre-existing non-symlink at the target. No second bin target, no bundle-resources staging, no build hooks — everything ships through the normal signed/notarized release flow. Completes SPECs/writer-open-cli-spec.md.

2026-04-21

  • Polish the editor scrollbar so it sits visibly on top of the doc-container fade. Combine the vertical fade mask with a horizontal mask that keeps the scrollbar gutters fully opaque, inset the progressive blur overlays past the gutters, and add 12px transparent top/bottom borders on the scroll container so the scrollbar doesn't reach the corners. Bump the scrollbar to 18px wide and inset the thumb with a 6px transparent border via background-clip: padding-box.
  • Bump editor/app background opacity for better legibility (--editor-bg 0.3 → 0.4 in dark; --bg and --editor-bg 0.3 → 0.8 in light).
  • Make the app and editor backgrounds translucent (--bg 0.4 dark / 0.3 light, --editor-bg 0.4 dark / 0.3 light) and switch the tab active/hover background to rgba(255,255,255,0.08) / rgba(0,0,0,0.08) so window vibrancy reads through consistently.
  • Replace the solid-bg scroll fades on the tab strip and sidebar file list with a shared ScrollFade wrapper that uses mask-image to fade content to transparent at the scroll edges. Move the tab-bar + button out of the scroll container so the mask no longer fades it.
  • Give the editor pane an always-on top/bottom transparent mask fade via a dedicated EditorScrollContainer component, paired with a single backdrop-filter: blur(3px) overlay per edge that ramps off via a gradient mask.
  • Replace the close-button reveal gradient on each tab with a mask-image fade on the title text so the title transparently fades out under the × instead of being covered by a solid-bg gradient.

2026-04-20

  • Add a standalone writer CLI (writer-cli binary) that opens a folder or markdown file in the desktop app, reusing the existing drag-drop path resolver and single-instance argv handoff. Extract shared path resolution into open_target.rs so drag-drop, RunEvent::Opened, and the CLI stay in lockstep. See SPECs/writer-open-cli-spec.md.
  • Remove the divider below the frontmatter panel so it sits flush with the editor body.

2026-04-18

  • Render mermaid diagrams in a monochrome palette that adapts to the app theme. Switch to Mermaid's base theme with explicit themeVariables so nodes draw as borders-and-text only (transparent fills), edge labels sit on the editor background so they cover connector lines, and gitGraph uses a grayscale branch ramp instead of the default vivid colors. Cache the measured widget height and reserve it on remount so diagrams don't collapse-and-re-expand as they scroll in and out of the CodeMirror viewport.

2026-04-17

  • Rework the app chrome so the editor runs edge-to-edge: remove the outer paddings and rounded card, drop the empty top bar, and float the sidebar toggle at the top-left so toggling the sidebar no longer shifts layout. Align the sidebar header and macOS traffic lights with the tabs chrome, add full-height left/right sidebar dividers that adapt to light and dark themes, and reserve left space in the tabs bar when the sidebar is collapsed so tabs no longer sit under the traffic lights. Make the tabs bar a Tauri drag region.
  • Move the workspace switcher into the sidebar footer, swap in a Geist chevrons-up-down icon, and replace the custom dropdown with a native Tauri menu that opens upward from the footer.
  • Infer the document title for display only. Remove the title input; the first H1 (or title: in frontmatter) stays inline in the body and is rendered by the editor. Tabs, sidebar rows, and the window title run inferTitle(body, frontmatter) whenever content or frontmatter changes. Delete the title-hiding machinery (getVisibleFrontmatter, mergeVisibleFrontmatter, setFrontmatterTitle, the updateTitle store action, and the reserved-key filter in the frontmatter entries hook); the frontmatter panel now shows every key verbatim. Move the panel above the editor with a bottom divider.
  • Polish the frontmatter edit flow. Typing --- at the top of a document mounts the frontmatter panel with an empty row already seeded and its key input focused. Rows whose key is still empty on blur are dropped automatically, so clicking Add property and then clicking away leaves no stray placeholders. When the last row is removed (via ×, Backspace on an empty row, or blur), the entire frontmatter block is removed from disk and focus returns to the editor. Modeled as a small state machine over rows — no transient store flag, no watcher effect, no ref plumbing. See SPECs/frontmatter-edit-flow-spec.md.

2026-04-16

  • Resolve extensionless markdown links and trailing-slash paths to workspace files so imported doc corpora (Obsidian vaults, doc repos) open without rewriting. Stop rewriting absolute markdown paths as workspace-relative on link insert.
  • Address mermaid review feedback: dark-theme tuning, Alt+Arrow handling fix, and extensionless-link lookup adjustments.

2026-04-15

  • Fix editor shortcut clashes and introduce the Obsidian-aligned markdown formatting keymap (Cmd+B bold, Cmd+I italic, Cmd+K link, Cmd+E inline code, plus heading/list/quote/task toggles). Sidebar toggle stays on Cmd+\. Resolves the Alt+Shift+Arrow selection clash. See SPECs/editor-shortcuts-clash-spec.md.
  • Add Format, Paragraph, and Insert submenus to the editor body context menu, reusing the new formatting commands. See SPECs/editor-context-menu-submenus-spec.md.
  • Render ```mermaid fenced code blocks as inline SVG diagrams in the editor. See SPECs/mermaid-diagrams-spec.md.
  • Adjust editor title and list spacing (#88).

2026-04-14

  • Remove the nested-list CodeMirror view plugin and keep list indentation purely CSS-based with uniform padding for all rendered list items.
  • Improve editor tab-switch responsiveness. Editor panes are now keyed by tab instead of disappearing whenever the launcher is active, inactive panes no longer ref-wire scroll-fade bookkeeping on activation, bottom editor padding/fade is always present to protect the footer region, focus handoff no longer scroll-jumps the document, and watcher-driven reloads now skip files with an in-flight save so self-saves do not trigger redundant reload work. See SPECs/editor-tab-switch-performance-spec.md.
  • Fix "app not responding" when switching workspaces, including the case where read_directory held the workspace_ignore read lock across per-file I/O and blocked the next switch's write lock. The previous workspace's walker now exits within a directory boundary instead of running to completion, watcher events queued against a prior workspace no longer mutate the new workspace's state, and open_workspace / restore_workspace return before starting the watcher or loading the full ignore matcher. See SPECs/workspace-switch-hang-spec.md.

2026-04-13

  • Replace tab/content divider with a gradient + backdrop-blur fade overlay.
  • Float tab bar above the editor content with a solid-color active/hover state.
  • Add extra top spacing to the document title so it clears the fade region.
  • Fix loading screen flash when opening files.
  • Replace loading text with ASCII spinner.
  • Use ⌘ symbol in new tab page shortcut labels.
  • Reduce search icon-to-label gap.

2026-04-12

  • Redesign welcome screen with Add Folder and Open File actions.
  • Show document titles instead of filenames in tabs and sidebar.
  • Add sidebar toggle button and polish chrome UI.
  • Reveal tab close button on hover with gradient fade.
  • Polish tab bar, editor links, and focus indicators.
  • Speed up sidebar toggle animation from 220ms to 140ms.
  • Add ⌘P shortcut hint to sidebar search button.
  • Fix clipboard permissions so copy path actions work.
  • Make document footer overlay content with absolute positioning.

2026-04-10

  • Polish chrome UI: compact controls, edge fades, auto-hide scrollbars.
    • Introduced --chrome-control-height and --chrome-control-padding CSS variables for consistent sizing across tab bar, sidebar search, and controls.
    • Moved back/forward navigation buttons from a floating overlay into the tab bar.
    • Added edge fade effects (color gradient + progressive blur) at the bottom of the editor pane when content overflows, and a subtle top border on scroll.

Lines 1–273 of 312.