Commit Graph
16 Commits
Author SHA1 Message Date
rzen c21c53be9c The board popover goes tabbed — Info, Background, Git, three empty rooms awaiting their own sessions
The symbol/name header keeps the top; below it a segmented tab bar replaces
the styling embed and the git section, both parked in-file — their seams are
settled, test-pinned design, and the tab sessions rehome surfaces, not rulings.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-08-07 13:34:23 -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 73698cd77b A reusable symbol picker — the board's glyph joins its name in the info popover
SymbolPicker: one well at rest, a 6x6 curated grid in a popover (leading
well = the level default, clearing the key), and an optional search over
the OS's full symbol inventory read from CoreGlyphs metadata. Geometry
font-derived off StyleEditorLayout's base, grid enlarged by a deliberate
1.3x. Wired inline with the rename field in the board info popover
through the StyleCommand funnel; the Title header retires. Curated set
and the search's standing vs 03's full-browser refusal await ratification.
2026-08-06 21:18:35 -04:00
rzen 0d8ecdb78b An unopenable repository fails loudly — the standing row, the paused surface, the honest heal
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
2026-08-06 18:44:12 -04:00
rzen 26239200ea Denial is not absence — detection learns the unverifiable answer 06 ruled for it
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
2026-08-06 17:53:23 -04:00
rzen 8345378972 The board settings sheet — setup leaves the popover for a home a stray click can't dismiss
The popover/sheet split lands: a board-scoped, titled, sectioned sheet on
the board window hosts everything setup-shaped, opened from the popover's
Board Settings… row and the new Board ▸ Board Settings… menu row. The three
existing setup controls relocate — add-git (whose noteFormVisible lines now
make the sheet the form-anchored failure surface), branch creation (a
standing field; create-and-switch runs the identical settle sequence), and
the commit-identity fields (the 2s visibility-scoped poll rides with them).
The popover keeps the daily face and its postures; its Pro/mode-none
section becomes header + door (.addGit renamed .noRepository). Availability
is derived from the section inventory (Pro + mode none or git), so pro-m2's
sections can't drift from the doors; the sheet's fields join the
caret-chord disable set. The audit suite pins what the free fixture can
reach; the sheet's own audit is manual until a tier override is ruled.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-01 12:07:12 -04:00
rzen 94e60cd444 The whole board-name area opens the popover — the chevron alone was the trigger
The titlebar widget grows from a 20×18 chevron into one button saying
the board's name and, on a git-mode Pro board, its branch — click
anywhere across it and the popover opens as before, anchored to the
widget. BoardInfoTitlebarSummary is the pure seam for both strings
(title falls back to the folder name per 01's naming rule; branch only
under pro + git mode, live off the observable HistoryStore.branch).
Board windows now hide the system title display through the same
hideTitle slot card windows adopted — the widget says the name, so the
chrome would only repeat it — while navigationTitle keeps feeding
window.title to the Window menu, Exposé, VoiceOver and restoration.

The widget also refreshes the branch eagerly at appearance: it used to
populate only once the popover had been opened, which would have left
the new branch line empty on a freshly opened board.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-01 08:28:53 -04:00
rzen 274ccd9ff5 Realign code with the 2026-07-31 findings-resolution rulings
The full bullet list from Implementation card bf080d9a — both ruling
batches, including the three appended mid-session by 16ef377:

- Restore subjects compose the inverse, never nest: crossing "Undo: S"
  emits "Redo: S" and vice versa; parity, not stack depth, reads a
  legacy double prefix (GitHistoryProvider.restoreSubject).
- Git-operation failures join the one-shot failure banner tier:
  BannerCenter.GitFailureBanner (undo/redo/branchSwitch/addGit), error
  tone at failure rank merged with write one-shots by recency; the
  postLoss compromise is retired at both AppModel wirings.
- order/schema optional below the board root: append-at-end reading
  (ordered siblings first, folder-name tie-break among the order-less),
  schema reads 1, both coerce-tier logged; the root keeps its
  requirements. Ranks.resolvedOrders materializes finite ranks so
  models and placement math stay untouched; first Writer rewrite
  stamps a real rank on touch, placement against an order-less sibling
  stamps that sibling inline in the same bracket. Agent guide v10
  teaches optional keys and zero-read filing. Hostile-YAML order
  shapes become coercion tests; Fixtures/Valid/optional-keys.kanban
  replaces the four retired Malformed boards.
- .gitignore is the relocation-heal noise gate: GitignoreRules pure
  matcher (standard semantics, board-root file only), loader consults
  it once per walk so matched loose files keep the stray posture;
  seeded (.DS_Store + .*.lanework-*) at board creation and template
  instantiation, healed in when missing at open — repo-nested
  included; empty file honored, existing files never edited; the
  committer's obedience via libgit2 status is pinned by test.
- Comments crash-residue sweep gates on step ownership: HistoryStep
  derives backing from its own undo expectations, backedContent unions
  both stacks, the sweep purges per-entry only what no live step owns.
- Skip-purge decoupled (16ef377): a stale-skipped coarse step strands
  whole in NativeHistoryProvider.strandedSteps — still backing, retired
  only at session end; clean exits purge as before.
- Coarse close step named "Changes to '<card>'"; the fine body-edit
  wording never leaks onto the board menu.
- Branch-switch settle clears every open card window's fine stack on
  Save All and Discard alike; the empty fold registers no coarse step.
- Close flush awaits its covering snapshot (quiesce + one generation
  bump, 1s bound), and an explicit flush now queues behind an
  in-flight one instead of skipping — the audit-caught interleaving
  could lose a close flush permanently when the debounce fired inside
  the close sequence; regression tests force both races.
- Commit comment bullets sort chronologically by created, not UUID.
- The production-unwired CardBodyEditSession.editSessionDidChange seam
  is deleted with its seam-only tests.
- Composition-root pins: beginSession composes the committer with the
  store's own EchoLedger and binds the announcer (the miswire class).
- Deliberate 06 conformance pass over every 2026-07-31-tagged
  sentence: fixed Change-custom-key subjects (the retired named
  generic was the only producer), the unbuilt Replace attachment
  vocabulary, heal commits now authored Lanework Integrity, the config
  reader scopes identity to plain [user] sections, add-git re-runs
  detection at create (a stale mode-none could initialize inside the
  user's repo), and add-git failures answer at the form or the banner.
  Structural residue filed on the Redesign board.

2554 tests / 439 suites green.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-01 07:43:45 -04:00
rzen 1f7d84bf64 Build branch switching and the popover git surface
GitBranchSwitcher holds 06's sequence as one object: settle editors
explicitly (SessionSettleGate — Save All applies raw buffers with
validation and a refused buffer cancels the whole switch; Discard
reverts buffers AND reconciles the session folders against HEAD;
never silent), flush the pending auto-commit, stamp intent in the
per-board registry, bracketed safe checkout (git_checkout_tree
GIT_CHECKOUT_SAFE + set_head — no path passes FORCE, abort
included), one reload via the async wholesale bracket (failed final
reload engages the existing read-only lock), reseed undo/redo from
the new HEAD with redo empty, clear the stamp. Create-and-switch
keeps the full sequence — the tree-cannot-change proof fails under
concurrent writers. Lock contention shows the 02 in-progress row's
waiting state ("waiting for another writer's git lock"), bounded at
30s then failing cleanly naming the lock path.

GitOperationStamp + GitOperationRecovery: the own-leftovers rule as
a pure conjunction — pause state AND matching stamp = the app's own
interrupted operation, aborted to the pre-operation state with a
banner, stamp cleared on success only; either alone defers to the
pause-and-defer stance. Checked where the committer starts.

BoardGitControls replaces the read-only branch line: branch picker,
inline create-and-switch, the abnormal-state pause note in 06's own
words with controls dimmed, and commit-identity fields that read and
write repo-local .git/config (derived default as placeholder, never
value; unfocused resync, focused keystrokes kept; 2s poll while
visible — .git is watcher-filtered by design).

Also fixes a shipped bug from the undo card: plan(reconciling:)
matched card ids as path prefixes, so the reconcile branch was inert
on every board (<lane>/<card> never matches a bare id) — a session
file the restore diff couldn't name (attachment, comment, draft)
survived Discard and landed in the next flush's commit. One shared
component-exact folder-name resolver now serves both Discard paths;
noteDiscarded takes cardFolderName; regression test verified failing
against the pre-fix code.

41 branch tests + the regression; 2374 tests / 409 suites green;
InertGitTests untouched.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-31 16:41:14 -04:00
rzen 189af238a1 Build HistoryStore — opt-in git and mode detection
The pro-m1 foundation card. SwiftGitX 0.4.0 (bundled libgit2, the
pathfinder's pin) joins the one target; new Kanban/Git/ holds
BoardGitMode (pure nearest-.git-wins detection, .git-as-file counts,
NSString ancestor walk), HistoryStore (@MainActor @Observable;
compose() is the tier gate — free tier gets no object, no detection,
no stat), GitRepository (scope-confined SwiftGitX handles: create =
init + HEAD forced to main + whole-tree "Initial board state" commit;
branch reads incl. unborn/detached; path-history ranks), GitIdentity
(derived default as a pure function + repo-local config reader — not
libgit2's merged ladder), and GitPathHistory (Mutex-guarded lazy
ranker). beginSession composes the git state beside the tier and
feeds BoardStore.makeIdentityHistoryRanker; git-mode loads pass the
git-backed IdentityHistoryRanker to BoardLoader. The popover's git
slot resolves a pure five-way matrix: free tier unchanged (absent /
BoardGitNote), Pro mode-aware — Add Git on mode none, honest prose on
repo-nested, read-only branch line on git. Provider binding
unchanged: both tiers still bind native until the undo/redo card.

42 new tests across 8 suites, all repositories built through bundled
libgit2; InertGitTests untouched and green. 2194 tests / 375 suites.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-31 13:18:07 -04:00
rzen 2c6b8fe63a Remove the App Group wholesale — one sandbox, one bookmark, one flag
Phase 2 of the one-app pivot (DESIGN 12 ▸ App-side state, re-ruled
2026-07-30; reworks 566deab). AppGroup retires; what remains is
AppStateHome — ordinary sandbox Application Support as the one home for
the registry, clipboard staging and template stores, keeping the
unit-test-host redirect (the test host is the app and would sweep real
state). Scalar defaults return to UserDefaults.standard.

BoardRecord's per-edition grant slots and openNow flags collapse to one
bookmark + one isOpenNow; the legacy-key decode and adopt-in-memory
paths go (nothing shipped with group-era records), while the founding
four-keys-required / defaults-for-everything-since decode policy stays —
a bookmarkless record decodes as the born-orphan row rather than
quarantining the list. needsReopen and the pre-anchored re-grant panel
are removed whole: the only state that flow served — a record granted by
a sibling sandbox — is unrepresentable now, and a dead bookmark of our
own was already the orphan case by explicit comment. The
indexOfRecord path fallback dies with it; path is never a key again.

The cross-process freshness stamp (mtime+size re-read) and
BoardEditionPresence with its popover "Also open in…" line retire; the
clipboard prune keeps its atomic .sweeping/ claim-then-delete, reframed
for crash residue and open -n copies rather than sibling editions. The
application-groups entitlement key is gone.

1880 tests in 317 suites green (13 cross-edition tests retired with
their subject).

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-30 17:46:32 -04:00
rzen 092300c7d2 Collapse the edition split — one target, twins merged, verify-editions retired
Phase 1 of the 2026-07-30 one-app pivot (DESIGN 0bec9a6, card c3a3ddd5):
the KanbanPro target, LaneworkPro scheme, KanbanProTests module-alias
bundle, KanbanPro/ source root and scripts/verify-editions.sh retire
wholesale. project.yml reads as a single-target file again (anchors
inlined, header rewritten in tier vocabulary).

The edition twins merge: EditionTypes -> PasteboardTypes (one
UTType(exportedAs:) home — the one app owns the family types),
EditionAbout -> AboutBox (the quiet Pro signpost survives as the About
box's one line; "…in Settings" deferred until the StoreKit phase gives
it somewhere to point). InertGitTests drops its Base prefix — the
inert-.git posture is unconditional app behavior, unsubscribed and
lapsed being one state.

Entitlements gain com.apple.security.network.client, declared now and
dormant until Pro's remotes use it; no keychain access group. The App
Group key deliberately stays — it goes with AppGroup.swift in phase 2,
since pulling it first would silently drop the app into the fallback
container. README/RELEASE.md build-and-pipeline prose updated to the
one-record world; the subscription story lands with phase 3.

1893 tests in 322 suites green.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-30 17:25:50 -04:00
rzen 566deab506 Home app-side state in the shared App Group container
Every edition declares group.dev.rzen.indie.Kanban and homes its
app-side state there from day one (12-editions.md ruling 2026-07-29):

- AppGroup namespace: container resolution with per-edition fallback
  when unprovisioned, shared UserDefaults suite, edition identity, and
  a unit-test-host redirect (the test host IS the app — its launch
  sweep and recents refresh must not touch the real shared container).
- BoardRecord: bookmark/isOpenNow replaced by per-edition grants and
  openNow keyed by bundle id; hand-written Codable keeps legacy keys
  decoding (adopted in memory as the running edition's slots, upgraded
  on first save); every other field stays common.
- RecentBoard gains needsReopen: no grant of ours but somebody's —
  first click runs an open panel pre-anchored at the recorded path,
  prompt "Grant"; recordOpen mints this edition's slot onto the
  matched shared record (path fallback only after identity fails and
  only against records holding no grant of ours, so re-granting never
  forks the record).
- Cross-edition freshness: stat-cheap mtime+size stamp re-reads the
  registry when the sibling edition wrote it, so one edition's save
  never erases the other's records wholesale.
- restorables() filters on this edition's open-now flags; the board
  popover gains BoardEditionPresence ("Also open in Lanework Pro"),
  pid-liveness-checked so crash residue never lies.
- Clipboard staging store moves to the group container; the sweep
  claims doomed trees by atomic rename into .sweeping/ then deletes,
  so the sibling's concurrent sweep is a non-event.
- Template store re-homed to the group container per the 09-templates
  re-ruling; scalars (quick-style recents, window size) move to the
  shared suite.
- verify-editions.sh: 30 checks (each edition carries exactly the
  family group). No pathfinder 1.x migrator: 1.x predates the
  registry; state starts fresh in the group container.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-29 20:18:15 -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 d61ce422a3 Give the popover git slot the contextual Pro posture
12's settled ruling supersedes the m4 placeholder: on an ordinary board
the git section is simply absent — the popover is complete in itself —
and on a board carrying an inert .git it shows only the calm one-line
note, 'This board has a git history. Lanework Pro works with it.' The
detection is a pure one-line seam checking the board root at popover
open, deliberately non-live: .git is filtered from the watch by design,
so there is no reload to hang a live fact off, and a quiet signpost
self-corrects on next open.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-28 13:22:40 -04:00
rzen aa6aaf2a11 Build the board popover — title widget, rename, styling, git slot
The window-title widget arrives as a leading titlebar accessory — a
quiet chevron on board windows only, installed and removed by the
window controller's own attach lifecycle — anchoring the one
board-level surface as a transient popover (the board window
deliberately grows no toolbar item for it). Inside: board rename
editing frontmatter title only (the folder is never renamed; an empty
commit removes the key and the window title falls back to the folder
name), the embedded shared style editor permanently targeting the
board, and the labeled Git section that this milestone only reserves
— a mode-none explanation and a disabled stub where m7's add-git,
branch, remote, and authentication controls land. Cmd-I (File >
Board Info) toggles it per window through a focused scene value,
kept apart from board-scoped transient state since a titlebar
popover belongs to one window, not to the board. Escape reverts a
dirty rename field and falls through to dismiss otherwise; a foreign
rename resyncs the field only while unfocused. 12 new tests.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-27 15:08:06 -04:00