Commit Graph
9 Commits
Author SHA1 Message Date
rzen ece33bbf78 The two pickers rhyme — one two-zone chrome, a face onto a standalone browser, a trigger onto the popover
The colour combo was a wide two-zone field with a second door onto the Colors
panel; the symbol picker was a small square button with one. Both now subclass
one `ComboFieldControl`, so they are the same width, height, radius and trigger
by construction: click the face for the standalone picker, click the chevron for
the quick list. The symbol face opens a new floating browser over the OS's own
category, ordering and keyword plists out of CoreGlyphs.bundle — searchable,
categorised, trademark-restricted glyphs withheld.

The palette grows twelve to sixteen per table, filling the hue ring's four
widest gaps with lime, jade, indigo and magenta at each table's own saturation
and brightness. That gives the Style… popover's background grid a third row and
the tint grid its third row of four, and both grids gain an Other… row onto the
system colour picker — which the card sidebar's combo has had all along and the
primary styling surface never did. An arbitrary hex already round-tripped; it is
asserted now, including that an unquoted one is a YAML comment and no value.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
2026-08-09 09:44:30 -04:00
rzen db863ba011 Three curated symbol vocabularies — boards, lanes, and cards each pick from their own shelf
`CuratedSymbols` was one flat ~65-glyph list serving every style-editor target alike. It is
now three level-specific sets — `boards` (project/container/identity), `lanes`
(workflow/stage/status), `cards` (work-item/content) — each ~30-40 entries, seeded from the
original list and `SymbolPickerCatalog.defaultSet`, reorganized by which level a glyph actually
reads as being about. Overlap is kept where a glyph genuinely fits everywhere (`flag`, `star`).

Wiring:
- `BoardInfoPopover`'s board-glyph `SymbolPicker` now passes `CuratedSymbols.availableBoards`
  instead of the picker's domain-agnostic default.
- The card sidebar's `SymbolPicker` (`CardSidebarSections`) now passes
  `CuratedSymbols.availableCards` instead of the old flat `available`.
- The style editor's own curated grid (`StyleEditorView`, the Style… popover's only remaining
  anchor) reads `CuratedSymbols.availableForStyleEditor(level:spansLevels:)`: a homogeneous
  target reads its own level's set, and a target that somehow spans more than one level (today
  unreachable — 04-interactions.md's cards-XOR-lanes rule keeps a live selection homogeneous)
  reads the three combined, via a new `BoardStore.styleTargetSpansLevels` seam that asks the
  question `styleLevel(of:)` deliberately collapses.
- `CuratedSymbols.combined` (the three sets' stable-order union) also replaces the old `.all`
  in `SymbolPickerCatalog`'s full-catalog fallback.
- `SymbolPickerCatalog.defaultSet` is kept as the fallback for a caller naming no level (a
  future saved-search picker, say) rather than retired.

DESIGN/03-board-ui.md and DESIGN/05-card-window.md's Styling/sidebar prose amended minimally
where they named "the curated set" as a single list.

Tests: three new/rewritten suites in KanbanTests/StyleModelTests.swift (set shape, availability,
overlap, `combined`, the style-editor level/span decision, the board-anchor width tripwire), one
new test in KanbanTests/StyleWriteTests.swift (`styleTargetSpansLevels`), and the old
single-list-pinning tests in KanbanTests/SymbolPickerTests.swift and KanbanTests/CardSidebarTests.swift
updated to the new set names. 2834 tests, 487 suites green (KanbanTests, arm64); one unrelated
flaky failure (RootRecoveryTests.vanishAndReturn under full-suite load) passed clean in isolation.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
2026-08-09 01:26:36 -04:00
rzen 7ac34651a2 The board's symbol takes a tint — a 4×2 colour row under the picker's glyphs, and the glyph itself moves into the titlebar
iconColor stops being hand-written-only (user-ruled, superseding 03's
"schema yes, control no"): it rides StyleCommand.apply → applyStyle as
the third styled dimension — per-dimension no-op skip, one bracket, one
history step, ExpectedField.iconColor for staleness. The SymbolPicker
grows an opt-in colour row (leading None plus seven Palette.foregrounds
hues, None removes the key); the board popover is its one caller. The
window-title widget now draws the board's resolved glyph in that tint
beside the name. Doc realignment filed on the Redesign board (Minor).

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-08-07 18:36:49 -04:00
rzen 9c857ae0cc Selected-ness rides down as a compared parameter — a marquee crossing repaints its faces, not the board
A selection change re-ran every CardFaceView on the board (180 bodies ≈ 85 ms on
the 6×30 fixture, 515 ≈ 233 ms on a real 515-card board, debug): the face's body
read store.selection in three places — isSelected, the drag replica's count, and
the context menu's styleTarget — and Observation invalidates every reader of the
property, past the equatable gate entirely. The band overlay stayed cheap, which
is why the marquee tracked the cursor while the highlight lagged ~0.4 s behind.

Now LaneView and TrashLaneView hoist one selection read per body and hand each
face isSelected/selectedCount as compared parameters; StyleMenuItems takes its
target as a deferred closure; TrashLaneRowView gains the same treatment plus the
Equatable gate it never needed before. Select-one-card: 180 bodies → 1. A
growing band costs the selection's own running size; the real board's crossing
fell 233 → 112 ms — the remainder is lane bodies re-measuring their masonry, a
separate lane-level finding recorded in RENDER-INSTRUMENTATION.md.

Also: select() gains defaultsSoleMember — the marquee's explicit nils never
avoided the sole-member default, so a one-card band acquired a selectionHead and
could scroll the lane out from under its own drag.

MarqueeRenderCostTests pins the shape: redundant samples cost zero bodies, a
growing band pays per crossing, and selectionStillRepaints holds a ≤8 budget.
2026-08-07 15:10:35 -04:00
rzen 9766e1f61c The Colors panel joins the palette — the combo ratified, and each anchor composes the halves it needs
Four rulings close Redesign Contradiction 3452893f (2026-08-06): the in-app
escape hatch is ratified in full, reversing 2026-07-29's palette-only rule —
the combo's Other… opens the system Colors panel, a pick landing on a palette
color stores the name, anything else the hex. Free-picked colors change no
contrast story: they land on the same runtime ink computation hand-written hex
always got (10 amended to say so; no warning surface is owed). Anchor
ownership: the card sidebar's background story is the combo alone — the well
grid's background half stays with the other anchors (StyleEditorView gains
showsBackground beside showsSymbols; the popover's symbol half already went to
its inline SymbolPicker). Quick-style recents stay palette-vocabulary — a
panel pick never enters them.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
2026-08-06 22:05:48 -04:00
rzen 71664dab02 Give card windows their own undo stacks and coarsen the close
Phase B of the two-level undo card: every card-window gesture — comment
post/delete/edit, body Edit sessions, style and details changes —
registers fine-grained on the window's own stack (window.undoManager
answers with it; board ⌘Z never sees mid-session card steps; an empty
window stack beeps, never falls through). Window close folds the stack
into one coarse values-based board step ("Edit card 'X'") — per-target
per-field later-wins merge, so foreign mid-session writes stay out by
construction, a no-net-change session registers nothing, and any stale
component skips the whole step. The comments/.trash purge defers with
the coarse step via a step-retirement seam on the providers: it runs
when the step leaves the board stack or the board session ends; the git
provider retires dropped steps on register, which keeps Pro's
purge-at-close-flush structural with no tier check. Interim on git
boards: gestures still auto-commit per debounce until phase C's
close-flush commit.

2432 tests in 418 suites green.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-07-31 19:39:50 -04:00
rzen 8564814754 Implement visual accommodations and Full Keyboard Access
Full relative text scaling per DESIGN/10: BoardMetrics is the board
strip's geometry as a pure function of the body point size
(CardWindowMetrics' twin) — lane plate/header/band, card
corner/stripe/padding, masonry spacing, the drop model's nominal card
height, resize-handle geometry, trash hatch pitch, and both window
floors all derive from an em; CardFaceMetrics folded in. The two fixed
font sizes (welcome brand/glyph) went relative; the toolbar search
field is 17 ems like the transient bar's. The no-horizontal-scroll
invariant is pinned by test at six text sizes by twelve lane counts.

Accommodations is Motion's sibling for the visual settings: Increase
Contrast adds a flat point to strokes (monotone, hierarchy-preserving),
gives borderless card/lane plates a resting separator hairline, and
takes faded accents to full alpha; Reduce Transparency turns the
transient search bar's glass solid and does the same for the alpha
washes that composite over a user-chosen board background (trash plate,
hatched header, drag shadow). Reduce Motion audited — every animated
surface already routes through Motion with a reduced variant; no gaps.

Full Keyboard Access: the template chooser's tiles were pointer-only —
now focusable, arrow-navigable (clamped, StyleWellGrid's rule), Space
picks, Return stays the sheet's default action, focus names the
selection one-way. The board's single tab stop shows its focus ring
under FKA (focusEffectDisabled inverts). Style editor verified already
conformant. Edge accents verified text-free; trash hatch pitch now
font-derived so it still reads as hatching at large text.

1549 unit tests green, both schemes build.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-29 08:48:25 -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 c6298c2e41 Build the styling system and shared style editor
One style-editor component, anchor-agnostic: a background grid (None
well plus the 12 palette colors) and a curated symbol grid (the
pathfinder's five-dozen set, leading well removing the icon key for
the level default), selection-aware across cards, lanes, and the
board itself. Batch edits compute per-dimension state — uniform,
mixed (no well selected), or an off-palette value labeled verbatim
outside the grids — and choosing a well applies to the whole target
set as one write bracket, skipping no-ops per field. The popover
tracks its target set live per the freshly ratified rule: targets
re-resolve by UUID on every reload, a vanished target leaves the set,
an emptied set dismisses the editor, and nothing ever silently
retargets to the board. Anchors landing now: Board > Style
(Opt-Cmd-S) and the card/lane context menus, which also carry the
quick-style recents row (app-wide, persisted, capped at six, None
never recorded) and the lane's width control twinning the menu
chords. The styling system's other two renders arrive with it: a
lane's background paints the C7 top-edge band, the board's paints
the window content background — malformed values paint nothing and
stay byte-identical on disk. 31 new tests.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-27 14:47:26 -04:00