Comments, phase 2 — the pane, the composer, and the inline session

The card window recomposes into three componentized panes (body,
comments, attributes) with two mounts — beside or body-over-comments
at ~3:2 — behind View ▸ Comments Beside Body. View ▸ Show Comments is
one persisted app-wide bit, no content-derived auto-show; File ▸ Add
Comment flips it on and focuses the composer. The thread renders
author lines, edited markers, card-subset Markdown bodies, and
read-only Quick Look chips under a count header with the sort-
direction control. The composer edits comments/.draft/ on the slow
cadence (blur, close, quit, ~30s interval), Escape only moves focus,
⌘↩ posts. Inline edit is a body-edit session in miniature: 700ms
debounce, Save/⌘↩ commits, Cancel and Escape revert to session-start
bytes, close flushes. File drops within either authoring surface
carve out of the window-wide card default into that surface's
attachments/; paperclips cover the no-drag path. Close flush runs
inline flush, then draft save, then the comments/.trash purge;
open sweeps crash residue.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-07-30 20:19:52 -04:00
parent f68ac3668e
commit fe3ffac48e
27 changed files with 4496 additions and 24 deletions
+7 -1
View File
@@ -33,12 +33,18 @@ Lanework is in early development. This list tracks what has actually shipped and
- **New Board and Duplicate** — File ▸ New Board… (⌥⌘N) opens a Pages-style template chooser with a mini per-lane preview per template, then a save panel seeded with the template's own name; the new board's frontmatter title is the document name you chose, so the window title and the Finder name start out matching. **A template is itself a board** — an ordinary schema-valid folder read by the ordinary loader, so its display name, icon, blurb and lanes all come off its own `index.md`, and authoring one is adding a folder rather than writing code. Creating from one copies that folder: fresh GUIDs for every lane and card, `created`/`modified` stamped today (born, not forked from the template), the blurb becoming the new board's description, attachments and card bodies byte for byte, strays and symlinks carried verbatim — and `.git` and `.trash/` deliberately left behind, so a new board is never silently in git mode and never born with trash. Nothing half-made is ever left where you pointed: a create that fails or is cancelled removes its own partial, and a name already taken is refused rather than replaced. The chooser lists **your own templates** after the bundled ten — keyed ones in the order they carry, then keyless boards by name — and a button beside its heading reveals the templates folder in the Finder, creating it if you've never used one: they're plain board folders in there, so dropping a board in makes it a template, no key required, and the app never edits what it didn't write. A folder it can't read is **still listed**, marked and carrying the loader's own sentence, because one bad template must never take the chooser down with it. **File ▸ Save as Template** copies the frontmost board into that folder — pending work flushed first so it misses no keystroke, `.git` and `.trash/` left behind (a template is content, not history, and not a fork), everything else including `CLAUDE.user.md` and every GUID and timestamp carried verbatim, and a chooser position appended after your existing templates. A name already in the folder auto-renames Finder-style ("Roadmap 2") rather than overwriting or refusing, the copy is cancellable from its progress row (cancelling removes the partial), and a quiet line tells you which template you just made. File ▸ Duplicate (⇧⌘S) forks the frontmost board to a Finder-style "Board copy" sibling (then "copy 2", "copy 3"), preceded by the pending-work flush so the copy misses nothing and with the original left open beside it. The copy is literal: every GUID kept — a whole-board copy is a new identity namespace — `.trash/` carried along so the copy matches its own copied history, strays and timestamps untouched.
- **The card window** — ⌘↩ or a double-click opens a card in its own window: two full-height, independently scrolling columns — a wide body column and a narrow attributes sidebar whose fixed width is derived from font metrics, so the window's resize flex all goes to the body. The title bar carries the card's title live and subtitles it "⟨board⟩ ⟨lane⟩", following the card as it moves between lanes and re-reading the board's name as it's renamed. The body column shows the title, a quiet created/modified/by line built from whichever frontmatter keys exist, and the body itself; the sidebar leads with the Attachments section (below) and its remaining sections are stacked headers awaiting their content. Reopening a card focuses the window it already has, and the window closes itself the moment its card stops being on the board — moved to the trash (entering the trash counts as deleted), gone with its deleted lane, purged, or moved to another board; a dirty Edit buffer flushes into the card's new location first, so the keystrokes survive a later restore.
- **The card window** — ⌘↩ or a double-click opens a card in its own window: three componentized, independently scrolling panes — a wide body pane, the comments pane (below) when it's shown, and a narrow attributes sidebar whose fixed width is derived from font metrics, so the window's resize flex all goes to the body. The title bar carries the card's title live and subtitles it "⟨board⟩ ⟨lane⟩", following the card as it moves between lanes and re-reading the board's name as it's renamed. The body column shows the title, a quiet created/modified/by line built from whichever frontmatter keys exist, and the body itself; the sidebar leads with the Attachments section (below) and its remaining sections are stacked headers awaiting their content. Reopening a card focuses the window it already has, and the window closes itself the moment its card stops being on the board — moved to the trash (entering the trash counts as deleted), gone with its deleted lane, purged, or moved to another board; a dirty Edit buffer flushes into the card's new location first, so the keystrokes survive a later restore.
- **Attachments** — the sidebar's first section is the card's complete file inventory: every top-level file of its `attachments/` in Finder order, body-embedded ones included, as compact rows carrying a small QuickLook thumbnail (the file's Finder icon until one is generated, and for anything QuickLook won't preview) beside a middle-truncated filename. The whole window is the drop surface — drag files anywhere in it, Edit mode and raw source included, and they import as attachments with Finder-style renames on collision, because the text editor deliberately declines file drags while dragged *text* still lands at the caret exactly as it always did. Folders refuse at the cursor and a mixed drag imports its files and says how many folders it skipped. File ▸ Add Attachment… (⇧⌘A) and a quiet plus in the section header are the same act from the menu bar and the pointer, both opening a multi-select panel into the same import path the board's own file drops use. The section is keyboard-native: it takes focus, arrows walk the rows, Space QuickLooks the selected one in the system's own panel, Return opens it in its default app, and ⌫ moves it to the **system** Trash — never a hard delete, and deliberately distinct from the board's own trash, which is why a failure there says "Couldn't move 'shot.png' to the Trash". Rows drag out their file URL, so a file goes to Finder or another app with no export path of its own; a right-click offers Open, Reveal in Finder and Remove; and File ▸ Reveal in Finder points at the selected attachment while the section holds focus, the card's folder otherwise. Every write is an ordinary bracketed one — one reload, one commit, one banner on failure — and the read-only lock disables adding and removing in place.
- **The card body — Preview and Edit** — the body is read as a fully rendered Markdown preview and written as raw Markdown, never a WYSIWYG halfway house. Preview lays out headings, emphasis, code, quotes, lists, GFM tables, thematic breaks and images resolved against the card's own folder; HTML shows verbatim as code, remote images never load (Preview does no networking), links open in the browser or the file's default app, and task-list checkboxes are live — clicking one flips exactly that character in the file and touches no other byte. ⌘E toggles View ▸ Edit Body, Return in Preview enters it, Escape leaves it, and a card whose body is empty opens straight into the editor with the cursor ready. Edit is a monospaced editor with lightweight syntax highlighting — headings emphasized, bold and italic styled, code tinted, link targets and structural markers dimmed — that is presentation only: the text stays the raw Markdown character for character, smart quotes and dashes off. It saves ~700 ms after you stop typing, and flushes the moment you leave Edit or close the window, so neither the preview nor the disk ever lags what you typed. ⌘Z is the editor's own undo, scoped to the session; ⌘F is find-in-text over whichever surface is showing. Three write rules keep the file honest: a body nobody touched is never re-serialized (byte-identical on disk, modification date included), an edit typed and then undone is not written, and the app's own save echoing back through the watcher is not written again. If the file changes underneath you while the buffer has unsaved keystrokes, the buffer wins — the board, the preview and every other window take the new version while your text stays exactly where it is, and your save then lands over theirs. A close that cannot save stops and asks: try again, save a copy elsewhere, or discard.
- **Comments** — every card carries a thread of comments, each one a folder of its own beside the card's body, so a comment is a Markdown file an agent can write and a human can read in Finder. **View ▸ Show Comments** puts the pane in every card window and keeps it there — one app-wide setting that persists across launches, with no auto-hiding cleverness: a card with nothing said about it yet shows the empty thread and the composer, because the invitation is the point, and deleting the last comment never closes the pane. **View ▸ Comments Beside Body** chooses where it sits — beside the body by default, or stacked under it at a fixed three-to-two split for narrow displays — and the panes are identical either way; the window's minimum width grows only while the column is beside the body. The thread stays visible through Edit mode, and Raw Source still swaps the whole content area, comments included. Each comment reads as a quiet author line (the self-reported `author`, the timestamp, and "· edited" when it has been), the rendered Markdown body in the card body's own subset — tables, code, quotes, images resolved against the card's folder — and attachment chips with Quick Look. There are no avatars: there is no identity system behind the name, and a comment with no author renders without one rather than with a placeholder standing in. The header carries the count and a sort control that flips the thread between oldest-first and newest-first, app-wide and remembered.
- **The composer, and the draft behind it** — the always-visible text area at the thread's newest end is backed by a real file, `comments/.draft/`, so restore-on-reopen is just the app reading it again: close the window mid-sentence, come back tomorrow, and your half-written comment is where you left it — and it rides git and syncs across machines like anything else in the board. Its saves are deliberately **slow**: on blur, on window close, on quit, and on a lazy half-minute tick — not the body editor's 700 ms — so typing never becomes a stream of commits. Escape moves focus out and touches nothing; the draft is a durable file, so emptying it is the discard gesture, and a draft emptied of text with no files deletes its own folder rather than leaving litter. ⌘↩ posts (a Comment button twins it), which renames the draft to a fresh identity and restamps it in one write — chronology is when you posted, not when you started drafting. File ▸ Add Comment turns the pane on if it's off and puts the cursor in the composer, in one gesture.
- **Editing, deleting, and attaching to comments** — a comment's context menu carries Edit, Delete and Reveal in Finder. **Edit is the body editor in miniature**: an inline session with syntax highlighting and debounced saves straight into the comment's own file (so a crash costs nothing), Save or ⌘↩ as its commit point, and Cancel — or Escape, its keyboard twin — reverting to the bytes the session opened on; closing the window flushes it exactly as the body's does, because a dismissal is not an abandon. **Delete is immediate and undoable, with no confirmation**: the comment moves into a `comments/.trash/` beside the draft, ⌘Z is the move back, and the folder is emptied when the window closes — with any residue from a session that died swept at the next open. **Comment attachments author in place**: a file dropped inside the composer lands in the draft's `attachments/`, one dropped inside an open inline edit lands in that comment's, and everywhere else in the window the ordinary card-wide import still applies. A quiet paperclip on both authoring surfaces covers the no-drag path, chips on a surface you're authoring carry Remove (to the **system** Trash, never a hard delete), and a posted comment's chips are read-only — Edit the comment to change its files.
- **Raw source** — View ▸ Raw Source (⌥⌘E) swaps the card window's whole content area — title, body and sidebar — for the literal `index.md` in a monospaced editor with Cancel and Apply. It's the escape hatch that keeps everything reachable in-app: unknown keys an agent added, hand-written comments, exotic YAML the app has no control for. Entering flushes whatever you were typing and then reads the file fresh off disk, never an in-memory copy. Apply validates through the very same fail-fast parse the loader uses — a broken proposal stops with a detailed alert naming the line, source mode stays open with your text, and the file on disk is untouched — and a valid one is written byte for byte, the only write in the app that neither stamps `modified` nor clears a `modified-by` you typed or kept, because you wrote those bytes and nothing may quietly edit them. The reload then refreshes every window. Escape is Cancel, ⌘↩ is Apply, ⌥⌘E toggled off applies too, Return just types; Cancel and closing the window discard without ceremony, and a card deleted out from under an open buffer discards it rather than letting a stale Apply undelete the card. ⌘E stands down while source mode is up, ⌘F still finds, and ⌘Z is the editor's own undo. A file that isn't valid UTF-8 declines to open as source rather than showing you a lossy guess of it.
- **The board popover** — a quiet chevron beside the window title (File ▸ Board Info, ⌘I, which toggles it) opens the board's one configuration surface. Renaming edits the board's frontmatter `title` and nothing else — the folder is never renamed, so the app's display name and the Finder document name are free to diverge — and clearing the field removes the key entirely, dropping the window title back to the folder name rather than to "Untitled"; the edit commits on Return and on click-away, Escape abandons it, and an unchanged title writes nothing at all. Below it sits the same style editor every other anchor uses, aimed permanently at the board; on an ordinary board the popover ends there, and only a board carrying a `.git` gets a closing note — "This board has a git history. Lanework Pro works with it." The read-only lock disables the surface without closing it.