Commit 3959d9c38d
Verified · cmc
Layout: unified · split
Sources/Orgstar/ContentView.swift +3 −2
| @@ -739,7 +739,7 @@ struct BufferTabs: View { | |||
| 739 | } | 739 | } |
| 740 | } | 740 | } |
| 741 | .frame(height: 28) | 741 | .frame(height: 28) |
| 742 | .background { if let bg = theme.color("modeline-background") { bg } else { Rectangle().fill(.bar) } } | 742 | .background { Rectangle().fill(theme.color("modeline-background").map(AnyShapeStyle.init) ?? AnyShapeStyle(.bar)) } |
| 743 | .overlay(alignment: .bottom) { Divider() } | 743 | .overlay(alignment: .bottom) { Divider() } |
| 744 | } | 744 | } |
| 745 | 745 | ||
| @@ -766,7 +766,8 @@ struct BufferTabs: View { | |||
| 766 | .font(.callout) | 766 | .font(.callout) |
| 767 | .padding(.horizontal, 12) | 767 | .padding(.horizontal, 12) |
| 768 | .frame(maxHeight: .infinity) | 768 | .frame(maxHeight: .infinity) |
| 769 | .background(current ? theme.color("background") ?? Color(nsColor: .textBackgroundColor) : .clear) | 769 | // A shape, not a color: a color background runs up under the toolbar. |
| 770 | .background { Rectangle().fill(current ? theme.color("background") ?? Color(nsColor: .textBackgroundColor) : .clear) } | ||
| 770 | .overlay(alignment: .trailing) { Divider() } | 771 | .overlay(alignment: .trailing) { Divider() } |
| 771 | .contentShape(Rectangle()) | 772 | .contentShape(Rectangle()) |
| 772 | .onTapGesture { session.open(path) } | 773 | .onTapGesture { session.open(path) } |
docs/review-2026-10-05.md added +136
| @@ -0,0 +1,136 @@ | |||
| 1 | # Orgstar review, 2026-10-05 | ||
| 2 | |||
| 3 | Branch `buffers` at a831086. Emacs 31.1 / Org 9.8.7 installed, so the oracle tests ran. | ||
| 4 | |||
| 5 | ## 1. Current state | ||
| 6 | |||
| 7 | **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. | ||
| 8 | |||
| 9 | **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. | ||
| 10 | |||
| 11 | **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. | ||
| 12 | |||
| 13 | **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`). | ||
| 14 | |||
| 15 | ### Defects found | ||
| 16 | |||
| 17 | | Where | Problem | | ||
| 18 | | --- | --- | | ||
| 19 | | `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. | | ||
| 20 | | `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. | | ||
| 21 | | `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. | | ||
| 22 | | `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. | | ||
| 23 | | `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. | | ||
| 24 | | `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. | | ||
| 25 | | `Sources/OrgApp/BabelRunner.swift:48` | stderr is discarded, and the Task is not kept, so a run cannot be cancelled. | | ||
| 26 | | `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. | | ||
| 27 | | `Sources/Orgstar/ClockViews.swift:129` | Clock report reads files from disk and ignores unsaved buffers. | | ||
| 28 | | `scripts/build-app.sh` | No `CFBundleDocumentTypes`, so `.org` files can't be opened from Finder or `open`; no icon; no URL scheme. | | ||
| 29 | |||
| 30 | ## 2. Gaps against Emacs org-mode | ||
| 31 | |||
| 32 | 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. | ||
| 33 | |||
| 34 | ### Hit every day | ||
| 35 | |||
| 36 | - **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. | ||
| 37 | - **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. | ||
| 38 | - **Tags.** Free-text prompt only. No fast selection, no `#+TAGS` groups, no completion from the workspace, no file tags (known). | ||
| 39 | - **Drawer and block folding.** Property and LOGBOOK drawers are always open. Emacs folds them. | ||
| 40 | - **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. | ||
| 41 | - **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. | ||
| 42 | - **Subtree editing.** No cut/copy/paste/clone, no `org-sort`, no `org-mark-subtree`, no `C-c *` / `C-c -` toggles, no `org-toggle-comment`. | ||
| 43 | - **Structure templates** (`C-c C-,`, `<s TAB`) and **`org-edit-special`** (`C-c '`). | ||
| 44 | - **Clocktable** dynamic block and `org-dblock-update`; effort estimates (known). | ||
| 45 | - **`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. | ||
| 46 | |||
| 47 | ### Hit weekly | ||
| 48 | |||
| 49 | - Timestamps: no calendar picker, no date ranges in the prompt, no `org-log-reschedule`/`redeadline`, no `C-c C-y` (known). | ||
| 50 | - Footnotes: parsed and exported, no new/goto/sort/normalize commands. | ||
| 51 | - Tables: no in-cell field formulas or formula editor, no sort/transpose/CSV import-export, no `<10>` shrink, no field editor (known). | ||
| 52 | - Navigation: no narrowing, `org-goto`, sparse trees, `org-occur`, speed keys. | ||
| 53 | - Properties: set only; no delete, no `_ALL` completion, no column view. | ||
| 54 | - Archive: no archive-tag toggle, no archive-to-sibling, no `datetree/` location. | ||
| 55 | - 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. | ||
| 56 | - Babel: `:session`, `:noweb`, `:file` (dot/plantuml images), `#+CALL`, inline `src_` execution, tangle (known). | ||
| 57 | - Visibility: `#+STARTUP` only handles visibility words; `indent`, `logdone`, `hidestars`, `align` ignored. `VISIBILITY` property ignored. | ||
| 58 | - Inline images: none. | ||
| 59 | |||
| 60 | ### Parser and settings | ||
| 61 | |||
| 62 | - 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. | ||
| 63 | - File-level `:PROPERTIES:` drawer is treated as a generic drawer. | ||
| 64 | - Ignored keywords: `#+TAGS`, `#+CONSTANTS`, `#+COLUMNS`, `#+LINK`, `#+OPTIONS`, `#+SETUPFILE`, `#+INCLUDE`, `#+MACRO`, `#+EXCLUDE_TAGS`. | ||
| 65 | |||
| 66 | ### Editor and platform | ||
| 67 | |||
| 68 | - 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`. | ||
| 69 | - Vim: no macros, marks, jumps, `gv`, block visual, surround, commentary, evil-org text objects (known). | ||
| 70 | - Conflict UI has no diff view; Syncthing conflict copies open as ordinary files with no diff (known). | ||
| 71 | - iCloud placeholders: design calls for them, only `startDownloadingUbiquitousItem` in the reconciler exists. | ||
| 72 | - 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. | ||
| 73 | - iOS: `Package.swift` declares the platform; no target exists. | ||
| 74 | - VoiceOver checklist never run; hidden markup and stars are still in the accessibility value. | ||
| 75 | |||
| 76 | ### Intentionally out (design non-goals) | ||
| 77 | |||
| 78 | Elisp runtime, block storage, Mac App Store, Babel on iOS, native Calc symbolic math, sync providers beyond Syncthing and iCloud. | ||
| 79 | |||
| 80 | ## 3. Prioritised feature list | ||
| 81 | |||
| 82 | 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. | ||
| 83 | |||
| 84 | ### Tier 0: fix before building more | ||
| 85 | |||
| 86 | 1. Make OrgAppTests compile and pass; fix the two duplicate bindings. | ||
| 87 | 2. `bodyFolds` in `storageEdited`; restart watching on `addRoot`; per-buffer `onChange`; refile/archive through the target's open buffer. | ||
| 88 | 3. Babel: capture stderr into the message, keep the Task for cancel. | ||
| 89 | 4. Re-run the gates (`ORGSTAR_GATES=1`) with line numbers, pretty entities and the block band on; fix line-number counting. | ||
| 90 | 5. `CFBundleDocumentTypes` for `.org`/`.org_archive`, an icon. Update README status. | ||
| 91 | |||
| 92 | ### Tier 1: daily-use parity | ||
| 93 | |||
| 94 | 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:)`. | ||
| 95 | 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`. | ||
| 96 | 3. **Fast TODO and fast tag selection**, `#+TAGS` groups, tag completion from the index. Honour `org-todo-keywords` with several sequences from config. | ||
| 97 | 4. **Drawer and block folding** in `Presentation.hiddenRanges`, default folded, TAB on a drawer line. | ||
| 98 | 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`. | ||
| 99 | 6. **Agenda actions**: filter by tag/category/regexp/effort, clock in/out, refile, archive, bulk mark and act, log mode, `org-agenda-prefix-format`. | ||
| 100 | 7. **Subtree commands**: cut/copy/paste/clone, `org-sort` (entries and lists), mark subtree, `C-c *`/`C-c -`, toggle comment. | ||
| 101 | 8. **Structure templates** and **`org-edit-special`** (a sheet with the tree-sitter grammar and the block's language). | ||
| 102 | 9. **Clocktable** dynamic block, `org-dblock-update`, `Effort` property and `org-clock-modeline-total`. | ||
| 103 | 10. **`C-c C-c` contexts**: heading tags, timestamp normalise, property line, footnote, cookie, `#+RESULTS`, keyword refresh, dynamic block. | ||
| 104 | |||
| 105 | ### Tier 2: weekly parity | ||
| 106 | |||
| 107 | 1. Date picker popover for `org-read-date`; date ranges; `org-log-reschedule`/`redeadline`; `C-c C-y`. | ||
| 108 | 2. Footnote commands (new, goto, sort, normalise). | ||
| 109 | 3. Tables: in-cell `=`/`:=` formulas, formula editor, sort, transpose, CSV import/export, `<N>` shrink. | ||
| 110 | 4. Narrowing, `org-goto`, sparse trees / `org-occur` (reuse the FTS and tag matcher), speed keys. | ||
| 111 | 5. Property delete, `_ALL` completion, column view (read-only first). | ||
| 112 | 6. Archive tag toggle, archive to sibling, `datetree/` archive location. | ||
| 113 | 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. | ||
| 114 | 8. Babel: `:session` for shells and python, `:file` with inline image results (dot, plantuml, mermaid), `:noweb`, `#+CALL`, inline `src_`. | ||
| 115 | 9. Inline images (`file:` links to images, `#+ATTR_ORG: :width`), toggled like `org-toggle-inline-images`. | ||
| 116 | 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`. | ||
| 117 | 11. `#+STARTUP` beyond visibility (`indent`, `logdone`, `hidestars`, `align`), `VISIBILITY` property. | ||
| 118 | 12. Conflict and Syncthing-copy diff view; recovery folder browser. | ||
| 119 | 13. Vim: macros, marks, jumps, `gv`, block visual, surround, commentary, evil-org text objects, `:e`/`:bd`/`:bn`. | ||
| 120 | 14. Editor: completion popover (tags, todo, properties, links, templates), spell check, heading click to fold, checkbox click, truncate-lines toggle, `gj`/`gk`. | ||
| 121 | |||
| 122 | ### Tier 3: distribution and beyond Emacs | ||
| 123 | |||
| 124 | 1. Notarised build, Sparkle, Homebrew cask, Developer ID signing. | ||
| 125 | 2. `org-protocol://` URL scheme and a Shortcuts/App Intents capture action; share extension later. | ||
| 126 | 3. Spotlight importer and Quick Look extension for `.org`. | ||
| 127 | 4. Backlinks pane from the index's link rows; an ID graph is cheap once link resolution exists. | ||
| 128 | 5. Column view and clock reports as live sidebars rather than dynamic blocks. | ||
| 129 | 6. iCloud placeholders and `NSFilePresenter` path, then the iOS target: capture and agenda first, per the design. | ||
| 130 | 7. VoiceOver pass and an accessibility value that omits hidden markup. | ||
| 131 | |||
| 132 | ## 4. Notes on process | ||
| 133 | |||
| 134 | - 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`). | ||
| 135 | - 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. | ||
| 136 | - 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. | ||