- Undo back to the saved state clears the dirty marker
the document keeps a fingerprint of the text as last loaded or saved; hashing only runs when the length matches, so it costs nothing on ordinary edits
- Folder tree refresh
button in the panel header, a *Refresh* item on every folder's context menu, and View ▸ Refresh Folder Tree
- Auto-completion
the language's keywords plus identifiers near the caret, via Edit ▸ Complete Word (⌃Space, and macOS's own ⌥Esc). Nothing is preselected, so typing always wins
- Overwrite (INS) mode
per document, toggled from Edit ▸ Overwrite Mode or the INS/OVR status-bar indicator; a newline is never overwritten
- Optional spell checking
for prose file types (Normal Text, Markdown, LaTeX, NFO), off by default and never enabled in code
- Recent files prune
entries whose file has gone are dropped on launch and on use
- Bookmarks
click the gutter margin to toggle, ⌘F2 / F2 / ⇧F2 to toggle and step through them (wrapping), plus *Remove Bookmarked Lines* and *Clear All Bookmarks*. Marks are held as character offsets and shift with edits above them
- Line bookmarks
click the gutter margin to toggle, ⌘F2 / F2 / ⇧F2 to toggle and step through them (wrapping), plus *Remove Bookmarked Lines* and *Clear All Line Bookmarks*. Named "line" throughout since files can be bookmarked too; the keys are untouched, and Search ▸ Line Bookmarks is where they still live. Marks are held as character offsets and shift with edits above them
- Replace in Files
Search ▸ Replace in Files… or the find bar's In Files scope. Confirms with the exact match and file counts before writing, preserves each file's encoding and line endings, skips files open with unsaved changes, reloads clean tabs afterwards, and reports any file it could not write
- Embedded lexers in markup
<script> and <style> bodies are handed to the JavaScript and CSS lexers, with block-comment state carried across lines and the closing tag matched case-insensitively
- CSS brace tracking
(fell out of the above) — the CSS lexer now follows { } across lines instead of guessing per line, so single-line rules colour correctly
- Large-file highlighting
the highlighter now caches the lexer state each line starts in, so painting any region starts from the state it genuinely begins in instead of assuming "normal" 200 lines back. An edit only invalidates the cache from its own line down, and repaints that are not edits (scrolling, retheming) keep it
- Document map
View ▸ Show Document Map. Draws one bar per screen row rather than per line (sampling the longest line in each slice), echoes the real token colours where they have been computed, marks the viewport, and click/drag scrolls the editor
- Column / rectangular selection and editing
⌥-drag selects the same columns on every line; typing and backspace apply to all of them as one undoable change (AppKit collapses multi-range edits to the first range, so this is done by hand). Plus the Column Editor (⌥⌘C): insert text or a decimal/hex/octal number sequence, with optional zero padding, down the column
- Integrated terminal
(Terminal ▸ Show Terminal, ⌃`) — a real xterm, bound either to a tab (started in that file's folder, so switching tabs switches to a shell already rooted where the file lives) or to one of the workspace's folders, which the panel asks about when there is a real choice. A file terminal can be pinned (Terminal ▸ Pin Terminal to Keep It Open) to keep it running past its tab, and reopening the file takes the pinned shell back over; a project terminal is pinned by definition. The header's ⇄ ends the shell and puts that question back up, for a terminal that should now be about something else. Hiding the panel only hides it, and the panel goes to full height and back without losing the height it had. A login shell, so the PATH is the user's own even when the app is launched from Finder; full TUI programs run in it. While any shell is alive the open files are polled and clean tabs reload themselves, so edits a command makes show up in the editor
- Split terminals
(Terminal ▸ Split Terminal, ⌃⇧5, or the header's ⧉) — the panel divides into up to three columns, as the editor divides into panes. A split is a second shell on the *same* binding, so both halves export the same file, pin together and go when the tab does; it starts in the directory the shell it came from is currently in. The keyboard follows the click, Focus Next Terminal steps round the columns, and a tab switch only ever changes the pane being worked in
- More than one terminal at a time
(Terminal ▸ New Terminal, ⌃⇧N, or the header's ✚) — a new shell *in place of* the one on screen rather than beside it, which is what makes it different from a split. It asks the same question the first terminal is picked from, over the top of what is running, and the ✕ goes back. The question is put differently, though: the panel resolving what to show is asked *for* you and prefers doing something over asking, while this is asked *by* you and asks whenever there is more than one answer — so choosing "this file" once can no longer put a project terminal out of reach. Every open shell is then listed down the right-hand side, click to switch, drag its edge to resize, and it draws itself only once there are two to list. Ending the shell the panel was showing asks the same question rather than sliding another one into its place — which is what closing a terminal used to do, and once "this file" had been answered it did it by starting the terminal again
- Word-wrap indicators
(View ▸ Show Wrap Indicators, 1.9.0) — a ↵ where the editor broke a line because it ran out of width, so a long line reads as one line rather than as several short ones. Asked of the last character of each laid-out fragment rather than of the layout manager, since a fragment broken mid-word ends on ordinary text while one that ended on its own ends on the newline that ended it — and the end of the buffer is neither, or every file would gain an indicator after its last line. Drawn just after the text rather than at the right margin, but clamped to the container, because a long word broken mid-word ends hard against the edge and one character further along is outside the container where nothing is painted at all
- Virtual space
(View ▸ Virtual Space, 1.9.0) — the caret may stand past the end of a line: → walks out into it, a click lands where it was clicked, and typing pads with spaces to get there. Only the *drawing* moves; the selection stays a range over characters that exist, so undo, the tools, the find bar and the agent never see a position that does not. The reason it earns its place is vertical — ↓ through a ragged block keeps the column it started in instead of collapsing onto the end of every short line it crosses. Off under word wrap, where a logical line is several rows and a column is not a place on screen; and ⌫ walks back through the columns rather than deleting the last real character of the line, which is text the caret was never next to
- The folder tree watches the filesystem
, and so do the plugin panels — one FSEvents stream per window over whatever roots it has open, so a file made in a terminal, a branch switched, a git pull or an agent writing to disk all appear without anybody pressing anything. FSEvents rather than a dispatch source per directory, which needs a file descriptor each and a real project exhausts the process's table of them.
- Reveal in Folder Tree
on a tab's context menu and on a Find in Files result, which opens every folder between the workspace root and the file, marks its row and scrolls to it. Disabled, rather than silent, for a file under no open root.
- Diagrams in a preview1.10.0
(1.10.0) — PreviewNode.figure carries a drawing a plugin has laid out itself: a coordinate space and a flat list of boxes, ellipses, polylines, closed polygons and labels, drawn by Linelark in the editor's own theme. The app knows nothing about UML, flowcharts or any other kind, so a diagram language is a plugin rather than a release; linelark.measureText is what lets a plugin size a box to the words in it, measured with the font the app will actually paint. API generation 9. Document Preview 1.3.0 draws Mermaid — flowchart, class, state and sequence — and PlantUML — class, sequence and state — from a fence in a Markdown file or from a .mmd or .puml file of its own. PlantUML needed no editor work at all, which was the point of where the line was drawn
- Diff view
File ▸ Compare with File on Disk, which opens the buffer against what is on disk as a two-column diff tab. Shipped in 1.7.0; §7 phase 3 then reused it to show an agent's proposed edit before it lands, which is the same review surface. UnifiedDiff.patch was the missing half: the renderer read git diff output, and the newer side of this comparison has never been written anywhere for git to read. It is checked *against* git diff line for line over randomised inputs, which is the only independent opinion available about whether a diff is right
- Phase 1 — read-only MCP
A local server over a Unix domain socket (0600, its path handed to each shell as $LINELARK_MCP_SOCKET beside $LINELARK_FILE), JSON-RPC, every call hopping to the main thread because NSTextStorage is not readable off it. Tools: open_tabs, active_buffer, selection, read_lines, tail_lines and find_in_buffer — that last one through FindEngine, so it searches the buffer *as typed* rather than the file on disk, which is the whole reason to ask the editor instead of running grep. Scoped to the window whose shell made the call and not to WorkspaceRegistry.current: the front window follows the user's clicks, so "the active tab" would otherwise drift away from the session that asked. Reads are capped and ranged, or a 40 MB buffer arrives in a context window whole. The installer writes the bridge, registers the server in ~/.claude.json and pre-approves these tools, the way it already writes the SessionStart hook. No UI at all.
- Phase 2 — the dock
A ClaudeConversation per window, a right-dock panel, an AgentSession wrapping the headless process, start/stop from the titlebar toolbar, and a sign-in step that opens the terminal panel with claude in it rather than failing behind the curtain. The conversation resumes across a relaunch, keyed by WindowScope the way tabs and frames already are — a panel that forgot everything on quit would be the only thing in the app that does. Read and talk; no write access to anything yet.
- Phase 3 — agency
switch_tab, open_file, replace_lines and insert_lines, the last two going through EditorSession.replace(range:with:actionName:), so an agent's edit is one undoable edit that reaches every pane — the same rule a plugin's writes already follow. It landed with the §5 diff view, because that is the same review surface: an edit that can be undone still has to be *seen* first, so the card in the dock carries the patch the buffer would become rather than the name of the tool asking. The load-bearing half is the permission prompt: a headless agent asking to write hits Claude Code's own permission system, which has no TUI to ask in, so --permission-prompt-tool points at an MCP tool of ours and the approve/deny is drawn in the dock.
- Phase 4 — triggers
An explicit button first, then file events (save, open), then an interval. Each one off by default and switched on separately. Debounced, because a burst of saves is one event and not six turns; and gated on the busy state the stream already reports, so a trigger arriving mid-turn is queued or dropped rather than interleaved into a running one.
- Phase 5 — autostart
Last on purpose. It is only safe once stop, interrupt, the busy state, the per-trigger switches and a visible indication of what a session is spending all exist — an agent that starts itself before any of those is a background process the user cannot see, stop or account for. Two of those five did not exist yet, so the phase is three things rather than one.
- Whose permission system is in charge
Not a phase — a correction to phase 3, which had built a second permission system and stood it in front of one the user had already configured. The symptom was plain enough: the dock could not run ./build.sh or swift test, so it could not check its own work, and every buffer edit stopped for a card — the right trade while edits were the only thing it did, and not once the list was meant to include running the build.
- Published model vocabulary1.9.0
(1.9.0). Not a phase — a correction to all of them. The model aliases and effort levels were static let arrays, so a model gaining a name cost a rebuild, a notarization and, for the Store edition, a review: days of latency on a one-word change. AgentProfile publishes them beside the plugin catalog and fetches them the same way, cached on disk, falling back to what shipped in the binary when there has never been a fetch.
- The vendors' own tools in the right dock
Two more panels beside the Claude dock, each a terminal running claude or codex — the real thing, unmodified, updating the moment they ship it. Added because the two bargains are genuinely different and neither contains the other: this app driving claude is what gives permission cards showing a diff, edits under one ⌘Z and a conversation the window keeps; the vendor's own interface is what gives everything they build that nobody here has heard of yet.