krz/orgstar

A native macOS editor for org-mode files. editor org-mode swift

docs/review-2026-10-05.md

15f6b0709d88971fb62ed432c3e5b8032643670a
orgstar/docs/review-2026-10-05.md rendered · source · history · blame · raw

136 lines · 13382 bytes

Orgstar review, 2026-10-05

Branch buffers at a831086. Emacs 31.1 / Org 9.8.7 installed, so the oracle tests ran.

1. Current state

Scope shipped. The design doc's phases 1 through 6 all have code: lossless parser with incremental reparse, TextKit 2 editor with folding and org-indent, SQLite/FTS index with a dirty-buffer overlay, three-way-merge save path with recovery copies, heading/list/table/timestamp/fill commands, agenda (day/week, TODO list, tag match, saved views, habits, reminders), native table formulas with Emacs fallback, Babel for shells/python/elisp plus output-only runners, capture with a global hotkey, HTML/Markdown export natively and PDF/ODT/LaTeX/text through emacs --batch, clocking with a menu-bar item, board/kanban, Emacs/Mac/Doom keymaps with a Vim modal engine, themes, config.toml with two-way sync, and import from an Emacs config. About 37k lines of Swift. 107 commits, all dated Oct 4 and 5.

Quality bar. Where a command exists it is checked byte-for-byte against Emacs: 31 oracle tests run by default and fail if Emacs is missing. 341 tests pass at a6139aa (the commit before Buffers). Performance gates pass on an M-series Mac (open 75 ms for 1.5 MB, typing p95 6.5 ms, memory 9.6x), not yet on the M1 Air reference machine.

Architecture holds. OrgCore/OrgIndex/OrgWorkspace/OrgPresentation import no AppKit/UIKit/SwiftUI; the only platform code is FSEvents behind #if os(macOS). Commands are pure functions over EditContext, so the iOS path the design promises is still open.

Stale docs. README says "Status: phase 1 (read-only viewer)". The design doc still names OrgSwift as the renderer and has every phase-1 checkbox unchecked. The Emacs importer tells users "Orgstar has no fonts or themes to set yet" (Sources/OrgApp/EmacsImport.swift:451).

Defects found

Where Problem
Tests/OrgAppTests/AppTests.swift:316-374 AgendaModel.refresh(open:) now takes [String: String]; the tests still pass nil and a tuple. The OrgAppTests target does not compile at a831086.
Sources/OrgCore/Keymap/Presets.swift:558,579 C-x b is bound twice; the later app.quick-open wins, so the new buffer switch is unreachable in the Emacs preset. SPC b b is also bound twice (:90, :99); there the later buffer.switch wins and :90 is dead.
Sources/OrgEditorAppKit/OrgEditor.swift:731 After any edit, hidden ranges are rebuilt without bodyFolds (line 464 passes them). CONTENTS-folded bodies reappear while the view still thinks they are folded.
Sources/OrgApp/WorkspaceModel.swift:57,158 startWatching() runs once at launch; addRoot never restarts it. A folder added during a session gets no FSEvents until relaunch.
Sources/OrgApp/DocumentSession.swift:356-410 Refile/archive into another file read the target from disk and write through Saver, ignoring that file's open buffer; the buffer catches up only by a later merge.
Sources/Orgstar/EditorView.swift:86 Every per-buffer editor's onChange calls session.bufferChanged(), which acts on the current entry. A disk change merged into a background buffer schedules no autosave for it.
Sources/OrgApp/BabelRunner.swift:48 stderr is discarded, and the Task is not kept, so a run cannot be cancelled.
Sources/OrgEditorAppKit/LineNumbers.swift:12,35 Line numbering counts newlines from offset 0 on every draw and splits the whole string on every newline edit. Added after the performance gates and not re-measured.
Sources/Orgstar/ClockViews.swift:129 Clock report reads files from disk and ignores unsaved buffers.
scripts/build-app.sh No CFBundleDocumentTypes, so .org files can't be opened from Finder or open; no icon; no URL scheme.

2. Gaps against Emacs org-mode

Grouped by how often a daily org user hits them. "Known" means the project's own plan or code already records it as not done.

Hit every day

  • Links. No follow (C-c C-o, click, RET), no insert (C-c C-l), no store, no id: creation. Links are only styled. #+LINK abbreviations, attachment:, CUSTOM_ID lookup are absent too.
  • TODO logging. No CLOSED: on DONE (org-log-done), no !/@ state notes, no LOGBOOK notes (org-log-into-drawer is hard-coded nil in Repeat.swift:3), no note prompt. Fast keys in #+TODO are parsed but unused; no fast selection. No S-left/right keyword cycling, no ORDERED/blocking.
  • Tags. Free-text prompt only. No fast selection, no #+TAGS groups, no completion from the workspace, no file tags (known).
  • Drawer and block folding. Property and LOGBOOK drawers are always open. Emacs folds them.
  • Capture. No file+datetree, id or clock targets; %^g, %^t, %^C, %:keyword fail; only :prepend is honoured (known). The user's own notes include journal and habit files, which typically depend on datetree.
  • Agenda actions. No filters (/), no clock-in, refile or archive from the agenda, no bulk actions, no log or clockreport mode, no follow mode, fixed prefix format.
  • Subtree editing. No cut/copy/paste/clone, no org-sort, no org-mark-subtree, no C-c * / C-c - toggles, no org-toggle-comment.
  • Structure templates (C-c C-,, <s TAB) and org-edit-special (C-c ').
  • Clocktable dynamic block and org-dblock-update; effort estimates (known).
  • C-c C-c covers only item, table, TBLFM and src. Missing: heading (tags), timestamp, property, footnote, clock, cookie, #+RESULTS/#+CALL, keyword refresh, dynamic block.

Hit weekly

  • Timestamps: no calendar picker, no date ranges in the prompt, no org-log-reschedule/redeadline, no C-c C-y (known).
  • Footnotes: parsed and exported, no new/goto/sort/normalize commands.
  • Tables: no in-cell field formulas or formula editor, no sort/transpose/CSV import-export, no <10> shrink, no field editor (known).
  • Navigation: no narrowing, org-goto, sparse trees, org-occur, speed keys.
  • Properties: set only; no delete, no _ALL completion, no column view.
  • Archive: no archive-tag toggle, no archive-to-sibling, no datetree/ location.
  • Export: #+OPTIONS, TOC, :noexport:, #+INCLUDE, macros, LaTeX fragments, subscripts, #+HTML_HEAD all ignored; Markdown is GFM, not ox-md. Pandoc path from the design is absent.
  • Babel: :session, :noweb, :file (dot/plantuml images), #+CALL, inline src_ execution, tangle (known).
  • Visibility: #+STARTUP only handles visibility words; indent, logdone, hidestars, align ignored. VISIBILITY property ignored.
  • Inline images: none.

Parser and settings

  • Not parsed: radio targets, $…$ LaTeX, LaTeX environments, inline tasks, entities and subscripts as objects, export snippets, citations, call_, diary sexps, table.el tables, alphabetical bullets (even with the option on), description-list terms and [@N] counters as nodes.
  • File-level :PROPERTIES: drawer is treated as a generic drawer.
  • Ignored keywords: #+TAGS, #+CONSTANTS, #+COLUMNS, #+LINK, #+OPTIONS, #+SETUPFILE, #+INCLUDE, #+MACRO, #+EXCLUDE_TAGS.

Editor and platform

  • No inline completion of any kind; no spell check; no auto-pairing; no heading-click fold; no checkbox or timestamp click; soft wrap can't be turned off; gj/gk are plain j/k.
  • Vim: no macros, marks, jumps, gv, block visual, surround, commentary, evil-org text objects (known).
  • Conflict UI has no diff view; Syncthing conflict copies open as ordinary files with no diff (known).
  • iCloud placeholders: design calls for them, only startDownloadingUbiquitousItem in the reconciler exists.
  • Distribution: ad-hoc signed, no icon, no notarization, no Sparkle, no Homebrew cask, no document types, no URL scheme (org-protocol), no Shortcuts/AppleScript, no Spotlight or Quick Look extension.
  • iOS: Package.swift declares the platform; no target exists.
  • VoiceOver checklist never run; hidden markup and stars are still in the accessibility value.

Intentionally out (design non-goals)

Elisp runtime, block storage, Mac App Store, Babel on iOS, native Calc symbolic math, sync providers beyond Syncthing and iCloud.

3. Prioritised feature list

Ordered by what stops the app from replacing Emacs for an org-only user, then by cost. Each tier is roughly one plan per bullet.

Tier 0: fix before building more

  1. Make OrgAppTests compile and pass; fix the two duplicate bindings.
  2. bodyFolds in storageEdited; restart watching on addRoot; per-buffer onChange; refile/archive through the target's open buffer.
  3. Babel: capture stderr into the message, keep the Task for cancel.
  4. Re-run the gates (ORGSTAR_GATES=1) with line numbers, pretty entities and the block band on; fix line-number counting.
  5. CFBundleDocumentTypes for .org/.org_archive, an icon. Update README status.

Tier 1: daily-use parity

  1. Links: open (C-c C-o, ⌘-click, RET on a link), insert with completion over headings/files/ids, store link, org-id-get-create, #+LINK abbreviations, CUSTOM_ID. The index already has link rows and headings(withID:).
  2. TODO logging: org-log-done, per-keyword !/@, org-log-into-drawer, note prompt through the existing Prompt loop. Habits already parse "State" notes, so this also fixes habit tracking for users with org-log-into-drawer t.
  3. Fast TODO and fast tag selection, #+TAGS groups, tag completion from the index. Honour org-todo-keywords with several sequences from config.
  4. Drawer and block folding in Presentation.hiddenRanges, default folded, TAB on a drawer line.
  5. Capture targets and escapes: file+datetree (day/week/month, :tree-type), id, clock; %^g, %^t/%^T/%^u/%^U, %^C, typed prompts; :immediate-finish, :empty-lines, :clock-in/:clock-keep, :jump-to-captured.
  6. Agenda actions: filter by tag/category/regexp/effort, clock in/out, refile, archive, bulk mark and act, log mode, org-agenda-prefix-format.
  7. Subtree commands: cut/copy/paste/clone, org-sort (entries and lists), mark subtree, C-c */C-c -, toggle comment.
  8. Structure templates and org-edit-special (a sheet with the tree-sitter grammar and the block's language).
  9. Clocktable dynamic block, org-dblock-update, Effort property and org-clock-modeline-total.
  10. C-c C-c contexts: heading tags, timestamp normalise, property line, footnote, cookie, #+RESULTS, keyword refresh, dynamic block.

Tier 2: weekly parity

  1. Date picker popover for org-read-date; date ranges; org-log-reschedule/redeadline; C-c C-y.
  2. Footnote commands (new, goto, sort, normalise).
  3. Tables: in-cell =/:= formulas, formula editor, sort, transpose, CSV import/export, <N> shrink.
  4. Narrowing, org-goto, sparse trees / org-occur (reuse the FTS and tag matcher), speed keys.
  5. Property delete, _ALL completion, column view (read-only first).
  6. Archive tag toggle, archive to sibling, datetree/ archive location.
  7. Export: #+OPTIONS (toc, num, tags, todo, ^:{}), :noexport:/:export:, #+INCLUDE, macro expansion, MathJax for LaTeX fragments, subscripts, #+HTML_HEAD; export dialog with destination and open-after.
  8. Babel: :session for shells and python, :file with inline image results (dot, plantuml, mermaid), :noweb, #+CALL, inline src_.
  9. Inline images (file: links to images, #+ATTR_ORG: :width), toggled like org-toggle-inline-images.
  10. Parser completions: radio targets, $…$, LaTeX environments, entities and subscripts as objects, inline tasks, file-level property drawer, alphabetical bullets, #+TAGS/#+CONSTANTS/#+COLUMNS/#+LINK/#+MACRO.
  11. #+STARTUP beyond visibility (indent, logdone, hidestars, align), VISIBILITY property.
  12. Conflict and Syncthing-copy diff view; recovery folder browser.
  13. Vim: macros, marks, jumps, gv, block visual, surround, commentary, evil-org text objects, :e/:bd/:bn.
  14. Editor: completion popover (tags, todo, properties, links, templates), spell check, heading click to fold, checkbox click, truncate-lines toggle, gj/gk.

Tier 3: distribution and beyond Emacs

  1. Notarised build, Sparkle, Homebrew cask, Developer ID signing.
  2. org-protocol:// URL scheme and a Shortcuts/App Intents capture action; share extension later.
  3. Spotlight importer and Quick Look extension for .org.
  4. Backlinks pane from the index's link rows; an ID graph is cheap once link resolution exists.
  5. Column view and clock reports as live sidebars rather than dynamic blocks.
  6. iCloud placeholders and NSFilePresenter path, then the iOS target: capture and agenda first, per the design.
  7. VoiceOver pass and an accessibility value that omits hidden markup.

4. Notes on process

  • The whole repo was written in two days. The oracle discipline is what makes that credible; keep it mandatory for every Tier 1 command (each maps to an org function: org-open-at-point, org-todo with logging, org-set-tags-command, org-capture datetree, org-sort).
  • Prefer one plan per Tier 1 bullet, as the existing docs/plans do, and record "not yet" lists the same way; those lists were the fastest way to produce this review.
  • The design's phase gates were never ticked and the M1 Air run is still owed. Measure once before Tier 1 adds more rendering work.