Commit Graph
9 Commits
Author SHA1 Message Date
rzen d905e73960 A card window toolbar control shows and hides the trailing sidebar — View ▸ Show Sidebar joins Edit Body, Raw Source and Add Attachment
The card window's trailing attributes sidebar has always been unconditional
since m6; this adds View ▸ Show Sidebar (a checkmark toggle, ShowComments'
shape) and a matching toolbar item on the customizable card toolbar
(NSToolbar/WindowToolbarController), a fourth default beside Edit Body,
Raw Source and Add Attachment. sidebar.right for the trailing pane; one
shared animated write path (AppPreferences.setShowCardSidebar) both faces
call, structural-voice reflow with a trailing slide-and-fade transition
(Motion.cardSidebarTransition), Reduce Motion respected throughout.

CardWindowMetrics.minimumSize gains a sidebar: Bool = true parameter so a
hidden sidebar shrinks the window's floor, composing with the existing
commentsColumn parameter. The toolbar item's read/write are injectable
closures (defaulted to the real UserDefaults-backed pair) so its plumbing
is testable without touching the developer's own preferences domain.
WindowToolbarController's observation tracking only sees @Observable
reads, so a small HostedWindowController.revalidateToolbar() plus an
onChange nudge keeps the toolbar button's on-state in step with the
View-menu row's write.

Scope held narrowly to visibility, per the card: no sidebar section
reordering, no action-moving.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
2026-08-09 09:14:51 -04:00
rzen 0a01405ced The comments column gets a handle — the beside-mount divider is now user-draggable
Kanban.xcodeproj must be regenerated (xcodegen generate) before building.

CardWindowMetrics.commentsColumnWidth was a fixed figure between the body
column and the comments pane in the side-by-side layout; it is now only the
resting default. A new CommentsColumnDivider replaces the plain Divider()
between the two panes with a real HStack element carrying a resize-left-right
cursor and a drag gesture, clamped through a new pure seam,
CardWindowMetrics.clampedCommentsColumnWidth — never narrower than the
existing commentsMinimumWidth floor, never wide enough to push the body under
its own bodyMinimumWidth. The drag tracks live in memory
(commentsColumnWidthOverride) and writes AppPreferences.commentsColumnWidthKey
exactly once, on release, mirroring LaneResizeSession's live-track/write-once
split rather than hammering UserDefaults per tick.

Persistence is app-wide via @AppStorage, matching showComments and
commentsBesideBody — the pane's other two layout bits — rather than the
per-card BoardRegistry.cardWindowFrames: this is "how the pane is arranged,"
the same kind of fact those two already are, not a per-card window geometry.
Flagged on the card thread as a call worth owner review.

Six new unit tests cover the clamp's two floors, the degenerate case where a
container is too narrow for both, and its agreement with
CardWindowMetrics.minimumSize at the window's own floor.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
2026-08-09 08:38:07 -04:00
rzen 05ae703989 replace symbol grid in card window sidebar with a compact symbol picker
The card window sidebar's Style section embedded StyleEditorView symbols-only
(showsBackground: false, showsSymbols: true), drawing the curated grid whole
inside the sidebar's own scroll view — a permanently open 5-7 row grid ahead
of every other section. Replace it with the reusable compact SymbolPicker
(already used by BoardInfoPopover for the board's glyph): a single well at
rest, the grid only inside its own popover.

- CardStyleSection no longer instantiates StyleEditorView at all. A new
  "Symbol" row mirrors the existing "Background" row's inspector shape
  (caption leading, control trailing), wiring SymbolPicker's onSelect/
  onSelectColor to icon/iconColor through StyleCommand.apply(...on: undo),
  the exact funnel the background combo already rides — so the card window's
  undo session semantics (13-native-undo.md) are unchanged.
- The picker is fed CuratedSymbols.available rather than its own smaller
  general-purpose default, so a card's curated vocabulary doesn't shrink.
- StyleEditorLayout.sidebar and showsBackground stay in StyleEditor.swift
  (still correct, still tested) rather than being cut as dead code — a
  separate, larger cleanup this card doesn't make (recorded on the card).
- DESIGN/03-board-ui.md and DESIGN/05-card-window.md: updated the sentences
  describing the sidebar hosting the style editor's symbol grid to describe
  the compact picker instead.
- Tests: CardSessionUndoTests gains a symbol/tint analogue of the existing
  background-combo session-routing test; CardSidebarTests gains a curated-set
  tripwire and an updated file-header note.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
2026-08-08 22:14:40 -04:00
rzen 9588f7b1f0 Comments, phase 3 — search, the thread find, announcements, and a11y
Board search reaches comment bodies through a search-owned transient
index: the first live-query keystroke sweeps comments/*/index.md
off-actor (.draft and comments/.trash excluded), keystrokes re-filter
in memory, the index discards on clear — the snapshot stays O(cards).
⌘F routes by focus: the comments pane gets an app-owned find bar
spanning the whole rendered thread (next/prev cross rows with
wraparound); body and composer keep NSTextFinder; Find Next/Previous
graduate from FutureCommands. Foreign comment changes speak
path-shaped beside the announcer's ladder ("New comment on 'X'",
plural folds), narrowed by EchoLedger receipts consumed through
CommentPath.classify — and that read fixed a latent footprint bug
where a comment receipt resolved against the card's attachment
listing, read .absent, and classified the user's own write as
foreign. The pane completes its a11y story: flattened comment
elements with Edit/Delete/Reveal custom actions (un-flattening
during inline edit), phrase-table vocabulary, labeled composer and
sort control, and an audit over the open pane on a comment-seeded
fixture (runnable only where automation permission exists).

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-30 21:30:22 -04:00
rzen fe3ffac48e 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
2026-07-30 20:19:52 -04:00
rzen 40322247e0 Build the style, details, and actions sidebar sections
The sidebar completes: the shared style editor gains a second anchor —
StyleEditorLayout carries the geometry (the popover keeps its settled
268/14/7/8 untouched as the default; the sidebar packs columns to its
width with no inner scroller) while every well, the batch display, the
arrow grammar, and the one applyStyle bracket stay the shared
component's. The card anchor is fixed, not tracking: the target is
this card, and the fate walk retires the window when the card goes.
Details renders every unknown frontmatter key read-only in file order —
Card.document already carried them — showing the author's own bytes
where the raw span is a value and the engine's rendering for block
scalars and empties; reserved enhanced-schema keys are ordinary
unknowns, and no keys means no section. Actions: Delete rides the same
tombstone bytes as Backspace and drop-on-trash through a one-line
seam, says nothing about selection, and lets the fate walk dismiss;
Reveal in Finder resolves through the attachment scope so the two
paths cannot disagree. History reserves its m7 slot without drawing a
header no base board can honor.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-28 12:22:04 -04:00
rzen 46397c740e Build the attachments sidebar section
The card's complete file inventory: compact QuickLook-thumbnail rows
over Card.attachments — no reference tracking, subfolders tolerated
and unsurfaced — with a quiet header add affordance and the drop hint
empty state. The whole window is the file-drop surface, Edit mode
included (the editor's drag types were already filtered; now tested),
sharing the board's folder-refusal semantics literally: FinderDrop
moved verbatim into its own file so both windows run the same
partition and loss row. Dragged text still lands at the caret and is
inert elsewhere — the window delegate accepts file payloads only.
Rows open on double-click or Return, drag out their file URL, and
Remove is a bracketed write through FileManager.trashItem — the system
Trash, never a hard delete, returning the in-Trash URL so the promise
is testable; the attachment listing is the guard, so traversal and
subfolder names refuse in one line. Keyboard-native per 05: the
section is one Tab stop, arrows walk rows by name, Space toggles the
shared QuickLook panel, Backspace removes. File > Add Attachment
(shift-cmd-A) comes alive through the same import path.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-28 11:52:37 -04:00
rzen 6dc84176fb Build Preview mode rendering
The card body's resting state: swift-markdown (pinned 0.8.0, smart
typography off — Preview renders the bytes on disk) parsed into a pure
BodyMarkup model with UTF-8 source offsets, rendered on one hosted
TextKit 1 NSTextView — chosen because find-in-text is NSTextFinder,
checkbox clicks reuse AppKit character hit-testing, links are .link
attributes, and NSTextTable's automatic layout is exactly the
columns-sized-to-contents rule. The GFM subset renders per 05; HTML
stays verbatim code-styled text; relative images resolve against the
card folder while remote URLs are never fetched, drawing a quiet chip
instead. Task checkboxes are live: a click flips exactly one byte
through a fresh-read, refuse-uneditable, stamp, atomic-replace write —
the app's only offset-addressed write, so a moved target refuses as
staleTarget and what the user saw decides the direction, netting one
toggle on a double-click. Empty bodies open in Edit per CardBodyMode's
opening rule, applied once; the Edit surface itself stays an honest
read-only stub until its card. FindCommand prefers the card body's
find over board search when a card window is focused.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-28 09:59:18 -04:00
rzen 1e65b7c986 Build the card window shell and lifecycle
The m4 scene plumbing was already honest — one WindowGroup value per
CardWindowRef enforces one-window-per-card, and CardWindowFate's
ancestor walk answered dismissal — so this card fills the window: a
two-column shell whose body column takes all resize flex and whose
sidebar width derives once from font metrics (26 characters of average
body advance plus em gutters), the five 05-ordered section headers as
placeholders, and the card body as selectable plain text until Preview
mode lands. The fate walk now returns a CardPlacement (card + lane), so
one pass answers both liveness and the live board › lane subtitle; a
board rename lands for free through displayName. Card windows remember
their frames per card in the board record (case-folded id keys,
unchanged-writes-nothing), restoring instead of cascading; only
unremembered cards take the last-used size and cascade. Store
acquisition stays gated on liveStore — a card window never opens a
board — and the close-flush hook stands with nothing to flush until the
Edit-session card.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-28 09:27:01 -04:00