Commit Graph
37 Commits
Author SHA1 Message Date
abkslm 5cf3303135 Merge nucleic/nimble-quartz-ibis-etdb into dev 2026-07-21 18:59:08 -07:00
abkslm 3068d6a46a Merge nucleic/amber-thistle-dingo-urcn into dev 2026-07-20 18:29:32 -07:00
abkslm effa3c91fa Merge nucleic/amber-thistle-dingo-urcn into dev 2026-07-20 18:08:22 -07:00
abkslm 5caafc6212 Merge nucleic/nimble-lunar-viper-ttz9 into dev 2026-07-13 15:00:39 -07:00
abkslm 24e68ccf19 Merge nucleic/lucid-quartz-otter into dev 2026-07-12 23:01:22 -07:00
abkslm 0997b8194d Merge nucleic/sleek-slate-ferret into dev 2026-07-11 22:48:04 -07:00
abkslm 5e858a1088 Merge nucleic/plucky-opal-weasel into dev 2026-07-11 02:49:29 -07:00
abkslm 2f8d05d51b Merge nucleic/plucky-opal-weasel into dev 2026-07-11 02:28:09 -07:00
abkslm 0c20e9d0ef Merge nucleic/amber-ember-tapir into dev 2026-07-09 23:41:06 -07:00
abkslm 3e6d048e0f Merge nucleic/calm-drift-tapir into dev 2026-07-09 20:15:09 -07:00
abkslmandClaude Opus 4.8 1786fba01f Mesh P5: finish transfer feature set — moved/arrived visibility, recovery, bulk hand-off
Completes Phase 5 of the multi-device mesh / session-transfer program (docs/MESH_TRANSFER.md)
except the two-Mac memory-carry spike. All additive + capability-gated; SyncProtocol stays v1.

- Moved-session visibility: additive SessionSummary.movedTo (MovedDestination), decode-defaulted.
  A moved session no longer silently vanishes — the source keeps a read-only "Moved to <Mac>"
  tombstone under Archived (name resolved live from paired Macs), surfaced on relaunch without
  rebuilding a runnable controller, and sent on the wire so phones see it too.

- Relaunch recovery driven from launch (+ on every peer reconnect, single-flight):
  AppStore.recoverInterruptedTransfers clears abandoned pre-tombstone locks, discards orphaned
  inbound staging, and re-drives a tombstoned commit via SessionTransferCoordinator.recoverTombstoned
  (bounded, idempotent; a dest that lost staging leaves the lock, never revives the source).

- Bulk "Hand off active sessions…": transferableSessions + moveSessionsToPeer (sequential, rollup
  error) behind a "Hand off…" button → HandoffSheet checklist in RemoteAccessView.

- Arrived-from provenance (mirror of moved-to): GRDB v24 arrived_from_device_id/arrived_at; the
  importer stamps them at staging; additive SessionSummary.arrivedFrom (ArrivedFrom); a subtle
  "Arrived from <Mac>" marker on the sidebar (live name) + iOS row (host-baked name).

- Stranded-arrival "Activate anyway": the importer persists the staged Session to the staging dir
  at .ready, so a destination that relaunches before commit can recoverableInboundTransfers() and
  activateRecoveredTransfer()/clearInboundStaging(). AppStore surfaces pendingArrivedTransfers with
  activate/discard, shown in a new "Interrupted arrivals" section. (A .ready lock with no manifest
  is now cleared as unrecoverable.)

Tests: +6 core, +2 protocol across WireMessageTests, SessionTransferTests, AppStoreTests,
AppStoreSyncBridgeTests. Full package builds; Swift suites green. iOS NucleicRemote edits reviewed
but not compiled here (separate Xcode target).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-04 17:02:15 -07:00
abkslmandClaude Fable 5 6bd439dd66 Mesh P5 (engine): session-transfer wire, schema, git bundle, coordinator/importer
Builds the tested foundation for session transfer Mac→Mac (docs/MESH_TRANSFER.md
Phase 5) — a 2-phase commit that moves an idle session's git branch, transcript,
row projection, and best-effort agent memory to another Mac. This commit is the
transfer *engine*; the live AppStore/PeerClient/UI wiring is the next layer.

Wire (NucleicProtocol): TransferMessages.swift — TransferOffer / TransferAccept /
TransferReject / TransferChunk / TransferChunkAck payloads, SessionTransferRecord,
ProjectDescriptor (matches by UUID / root commit / normalized remote),
TransferRejectReason (raw-string, forward-compatible). ClientMsg source→dest verbs
(transferOffer/Chunk/Commit/Cancel) + HostMsg dest→source replies; the replies ALSO
map to SyncClient.Event + messageLoop so the source, receiving them as a client,
doesn't drop them. WireCapabilities.canReceiveSessionTransfer (decodeIfPresent).

Persistence (v23): moved_to_device_id/moved_at tombstone columns (archived path,
NOT a new SessionStatus case) + session_transfer table with a UNIQUE-while-active
index; store methods beginTransfer/updateTransferState/activeTransfer/
allActiveTransfers/tombstoneSession/activateTransferredSession (atomic + idempotent).

Git (WorktreeManager): createTransferBundle (prereq-based with prereq-free fallback),
createFromTransfer (bundle verify + fetch + worktree add), discardAfterTransfer,
presentSHAs/branchExists; TransferGitProbe for repo shape (shallow/partial/LFS/
submodule) + identity (root commit + normalized remote). git-bundle round-trip
verified against real temp repos.

Transcript: SessionHeader.maxSupported + TranscriptHeaderRewriter (repoint paths /
drop backendSessionID for the fresh-context fallback) — first consumer of versioning.

Engine: SessionTransferPackage (stage items + per-item sha256, bundle built post-
accept vs haveSHAs), SessionTransferCoordinator (source 2-phase commit, tombstone-
before-commit, windowed chunk acks, typed errors), SessionTransferImporter (dest
stage-non-runnable → activate-on-commit, best-effort native memory carry with fresh-
context fallback). ConnectionHandler dispatches the verbs gated on .control + mac
peer (peerKind now persisted); SyncHostBridge importer surface with reject-defaults.

Tests: 763 core + 105 protocol green (+~28). Wire codec/backcompat; GRDB v23 schema
+ lock lifecycle + UNIQUE-while-active (disk path) + atomic idempotent activate;
git bundle round-trip (prereq/prereq-free) + shape/identity probes; transcript
rewrite/versioning; full loopback transfer (real repos + GRDB + in-memory channel):
happy path w/ tombstone ordering + worktree discard + activation, project-not-found
/version-mismatch rejects, commit idempotency, double-transfer lock, native memory carry.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-04 03:06:38 -07:00
abkslmandnucleic 5e00404575 nvrsion: promote trunk to dev
Nucleic-Promote: 1
Co-authored-by: Nucleic <[email protected]>
2026-06-27 19:33:40 -07:00
abkslm 4284ad7c81 nvrsion: promote trunk to dev
Nucleic-Promote: 1
2026-06-27 14:57:57 -07:00
NucleicandClaude Opus 4.8 174da0301e Add nvrsion: per-file shared-trunk versioning for multi-agent orchestration
An opt-in (Beta, Nucleic-Control-only) versioning mode where a project's agent
sessions share one nucleic/trunk checkout, lock individual files per-edit, land each
completed edit into the trunk immediately, and release fast — instead of holding a
session-long lock until a big merge. Conflicts are structurally impossible within the
trunk (serialized per-file writes + forced re-ground), so 'merge' collapses to 'commit'.

- Phase A: ProjectNvrsion config, migration v20-nvrsion, nvrsionActive gate, Beta toggle
- Phase B: NvrsionTrunk actor (ensureTrunk/land/regroundOnGrant), shared-trunk topology
  (no per-session worktree), per-edit host-mediated path-scoped commit + release
- Phase C: NvrsionReleaseGovernor keep-warm idle eviction, launch crash-recovery,
  flip-safety guard
- Phase D: pre-land validation hook, trunk->base squash promotion + 'Promote trunk' UI

Design and rationale: docs/NVRSION.md. Full suite green (585 tests).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-25 21:32:09 -07:00
abkslmandClaude Opus 4.8 38664a02b2 Autoship: lock every edited file + keep autoship armed on a merge conflict
Three fixes to the Nucleic Control autoship/file-lock system.

1. File locking now covers every edit in a turn, not just the first.
   handleApprovalCall arbitrated each edit only while !conflictOverridden,
   and a successful lock grant (.granted -> .proceed) latched that flag, so
   after the first edited file every later edit skipped arbitration and
   acquired no lock. Parallel sessions could then edit the same file and
   collide at autoship time. The latch was a leftover from the old
   interactive-prompt model (arbitrate is now automatic and never prompts);
   removed it so every edit re-arbitrates. Re-arbitrating a file the session
   already holds is a cheap LockManager fast-path no-op.

2. A merge *conflict* no longer turns autoship off. It sets a sticky
   autoShipConflict marker, leaves autoship armed (so the work re-ships on
   the next clean merge), and is flagged prominently: the sidebar
   "needs attention" icon, the header/Control-panel ship status, an in-chat
   note, and lastError. A hard merge *error* still disarms via autoShipFailed.

3. When a conflict's work finally lands -- the queue's next merge or the
   agent's own merge observed via the git shim -- the conflict marker clears
   and the live ship status updates to .merged, so the UI recovers. Fixes the
   stuck state where, after a conflict turned autoship off, the agent's own
   merge appeared in the activity feed but markShippedIfLanded bailed (it
   required autoShip) and the Control panel kept showing the stale conflict.

Adds a session column (auto_ship_conflict, migration v19), SessionController
mutators, UI wiring, and tests.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-18 05:12:37 -07:00
abkslm 6457968433 Merge nucleic/upbeat-ancient-vole into dev 2026-06-17 19:34:05 -07:00
abkslm 695972bd7d Merge nucleic/jolly-harbor-koala into dev 2026-06-16 01:07:33 -07:00
abkslm aa249edf69 Merge nucleic/ivory-pebble-vole into dev 2026-06-16 00:37:39 -07:00
abkslm 4bff8d77c1 Merge nucleic/quiet-feather-bison into dev
Sort sidebar sessions by last user message instead of any activity.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

# Conflicts:
#	Sources/NucleicCore/Persistence/GRDBMetadataStore.swift
2026-06-15 23:38:53 -07:00
abkslm 66fb04dfaa Nucleic: Add Recents Section 2026-06-15 23:14:41 -07:00
abkslmandClaude Opus 4.8 bbab8e98b3 Phase A: parent/root lock-domain pointers + repoint merge targets
Add Session.parentRef (release target), rootRef (lock domain = the tree's top-level branch, invariant under cascade), and parentSessionID. GRDB migration v14 + SessionRow round-trip. createSession computes them: a base that names a live session's branch makes this a nested child inheriting that session's rootRef. Repoint SessionController.integrate/unmergedFiles and AppStore.shipIfCompleted/shipTargetLabel from project.defaultBranch to the session's parentRef — so a session releases against and merges into its REAL parent (fixing the wrong-target half of the leak) and the merge queue serializes per real parent. New test parentAndRootRefsTrackTheTreeAndPersist; 262 tests pass. Implements LOCKING.md Phase A (§3).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-15 20:23:31 -07:00
abkslmandClaude Opus 4.8 1ea002e894 Show an icon on the sidebar when autoship fails/disables for a session
When autoship aborts on a merge conflict or merge error it turns itself off; the
chat's sidebar entry now shows an exclamation-triangle marker until the user
re-enables autoship. Unlike the shipped marker (lastEventWasAutoship), this is
sticky session state, not tied to the latest event — a failure needs attention
and shouldn't vanish the moment something else happens.

- Session + SessionSummary gain `autoShipFailed`; persisted via migration
  v13-autoship-failed.
- SessionController.markAutoShipFailed() turns autoship off and sets the marker
  atomically; the merge queue's conflict/failed paths now call it instead of a
  plain setAutoShip(false). setAutoShip(true) clears the marker (re-opt-in). A
  clean user toggle-off leaves it untouched.
- RootView.SessionRow renders the warning icon, taking precedence over the
  shipped icon.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-15 03:38:21 -07:00
abkslmandClaude Opus 4.8 2aa41d3f09 Show an autoship icon on the sidebar when autoship was the latest event
When the merge queue logs an autoship note and nothing has happened in the
session since, the chat's sidebar entry now shows a shipping-box glyph. The
signal is tracked on the canonical event pipeline: `Session.lastEventWasAutoship`
is set true while the newest transcript event is an autoship note and cleared
the moment any other event (a user message, an agent turn) lands.

- NoteEvent: shared `autoshipPrefix` + `isAutoship` so the pipeline recognizes
  merge-queue notes without coupling to each outcome's wording; AppStore's four
  autoship notes now interpolate the prefix.
- SessionController.ingest: set/clear the flag per event.
- Session + SessionSummary carry the flag; persisted via migration
  v12-last-event-autoship.
- RootView.SessionRow renders the icon in the trailing cluster.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-15 03:23:45 -07:00
abkslm dbf6814ebf Merge nucleic/frosty-feather-heron into dev 2026-06-15 02:22:12 -07:00
abkslm 955a104917 Merge branch 'claude/practical-wu-4e2ce4' into main
Autoship mode: Ship toggle + per-target FIFO merge queue for safe
agent-driven merges into the default branch.

# Conflicts:
#	Sources/NucleicCore/Persistence/GRDBMetadataStore.swift
2026-06-13 18:49:55 -07:00
abkslmandClaude Opus 4.8 035c5ae331 Autoship mode: agents safely merge to main via a merge queue
Add a "Ship" toggle beside "Auto". When on, a session squash-merges its
branch into the project's default branch the moment the agent's turn is
classified `.completed` — no human babysitting the merge.

Safety / race handling:
- New `MergeQueue` actor serializes merges FIFO, strictly one-at-a-time per
  (project, target) branch, with per-session dedupe so a re-fired `.completed`
  can't double-ship. Different repos/branches still run in parallel.
- Each merge runs through the existing `integrate` path, which checks out the
  live target tip and merges (never force) — a stale branch is merged into the
  post-other-merges branch, never overwriting newer work.
- On conflict the merge aborts (branch intact, target tree clean), Ship turns
  off for that session, and an error banner surfaces. No blind retries.
- Fix: squash-merge conflicts now reset --hard HEAD instead of `merge --abort`
  (which is a no-op for --squash, leaving the main checkout dirty).

Ship implies Auto (an agent can't ship if every tool call blocks on a human).

Wiring: `Session.autoShip` (+ v9 GRDB migration), `SessionController.setAutoShip`,
`AppStore` owns the queue, injects the integrator, observes status into
`shipStatuses`, and triggers from `classifyDisposition`. Ship toggles added to
SessionDetailView (with a header status pill) and HomeView (new-chat default).

Tests: MergeQueue serialization/dedupe/status + git-backed end-to-end through
the queue (parallel ships land in order; conflicting second stops without
overwriting the first). Docs: RUNTIME_ARCHITECTURE §3.1.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-13 17:53:20 -07:00
abkslmandClaude Opus 4.8 17601aa169 To-Do: store AI triage on add/edit instead of recomputing on click
Triage verdicts (level, rank, reason) are now computed in the background when
an idea is added or its text edited — and whenever the open set changes
(complete/dispatch/delete) — then persisted onto each Todo. The Triage button
just toggles between FIFO and the stored impact ranking; no pass runs on click.

- Todo gains triageLevel/triageRank/triageReason; migration v9-todo-triage adds
  the columns and TodoRow maps them.
- AppStore drops the in-memory triageItems cache; openTodos and the triage
  accessors read the stored fields. retriageOpenTodos() runs the provider pass
  off the main loop, guarded by a token so a newer pass wins; ensureTodoTriage()
  backfills on load.
- TriageButton toggles the sort and shows a spinner while a background pass
  refreshes the stored verdicts.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-13 01:12:09 -07:00
abkslmandClaude Opus 4.8 9599f966b9 Classify idle agents as awaiting-input vs done for an accurate sidebar
In conversational mode every finished turn lands on `.awaitingInput`, so the
sidebar showed "Awaiting input" both when the agent asked a question and when it
simply finished the task. Add a soft `TurnDisposition` refinement: the
Intelligence provider (Apple Foundation Models, heuristic fallback) classifies
the agent's last reply as awaiting-input vs. completed, and the sidebar renders
"Done"/green vs. "Awaiting input"/accent accordingly.

Canonical `SessionStatus` is unchanged — disposition is a presentation-only
signal, written through SessionController (still the single writer), persisted
(v8 migration), and cleared when a new turn starts. Classification runs for every
session on `runFinished`, not just the open one.

Also fixes a pre-existing race the new test surfaced: `summarizeTodoItem` wrote a
stale todo back into `self.todos` after an `await`, which could clobber a
concurrent move/edit/delete.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-12 19:07:03 -07:00
abkslmandClaude Opus 4.8 ff10d440a9 Add per-project sandboxing via Apple's container
Opt-in, per-project execution sandbox: when enabled, a project's sessions run
`claude` inside an isolated Linux VM (Apple `container`) with the worktree
bind-mounted, instead of directly on the host. Off by default — existing
behavior is unchanged.

- Domain: ProjectSandbox (enabled/image/idleTimeout) on Project; ContainerSpec
  on RunSpec/ResumeSpec.
- Persistence: migration v7 adds project.sandbox_config (JSON).
- ContainerRuntime: thin `container` CLI wrapper (preflight, default-image build,
  run/exec/stop/delete/list, host-gateway discovery).
- ContainerManager: app-level per-session lifecycle — ensureRunning, idle
  auto-stop, teardown, orphan reconcile.
- ClaudeCodeBackend: wraps the claude invocation in `container exec` when a
  ContainerSpec is present; binds the approval MCP server on 0.0.0.0 and rewrites
  its URL to the VM gateway so the containerized child can reach it.
- Repo root + worktree base mounted at identical paths (git links + cwd-hash
  resolve); host ~/.claude mounted read-only and seeded into a writable
  claude-home so credentials are never mutated but native resume still works.
- UI: ProjectSettingsSheet (toggle/image/idle) + "Sandboxed" badge; AppStore
  gains updateProject.
- Tests: 9 new (arg construction, mount formatting, name parsing/derivation,
  sandbox JSON round-trip, MCP host rewrite). 112 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-12 18:26:37 -07:00
abkslmandClaude Opus 4.8 720dfe54d6 Summarize each to-do item; show original prompt beneath; sort oldest-first
Each open idea now gets its own short summary (model-backed with heuristic
fallback), shown emphasized with a sparkles "summarized" icon, and the full
original text beneath it — smaller and dimmer, since that text is what gets
dispatched as the agent's prompt. Lists are sorted oldest-first (first-in at
the top).

- Todo.summary (persisted, v6 migration): nil = not yet summarized, "" = no
  distinct summary, else the summary; text stays canonical as the prompt
- summarizeTodo on IntelligenceProviding (default heuristic todoLine + AFM
  override that compresses to a short imperative title)
- AppStore generates item summaries on add / on load (once) / on text edit,
  off the main loop; short ideas fold to "" so they aren't retried
- openTodos + grouping sorted by createdAt ascending (stable)
- TodoRow: summary + sparkles icon over a dimmer original snippet
- Tests: long-vs-short item summary, oldest-first ordering, summary column

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-12 17:48:14 -07:00
abkslmandClaude Opus 4.8 c17428c426 Add to-do inbox: Cmd-T quick capture + dispatch agents from home
Capture ideas anywhere with ⌘T into a persisted to-do inbox, listed on the
home dashboard. Each open idea can be "dispatched" — pick a project and it
spawns an agent seeded with the idea's text, marks the todo dispatched, and
links the spawned session. Lets the user park an idea while other agents are
busy and start it when they choose.

- Todo domain model + TodoStatus (open/dispatched/done)
- v5-todos GRDB migration, CRUD, and store protocol methods
- AppStore: todos state, quickTodoPresented flag, add/update/dispatch/delete
- QuickTodoSheet (⌘T) + TodoSection on the home screen
- Tests: GRDB round-trip, dispatch flow, edit/delete/complete

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-12 17:30:34 -07:00
abkslmandClaude Opus 4.8 0a21ab7075 Move summary in-flow, mark default model/effort, cache summaries
- The summary card no longer overlays the transcript (it covered the first message
  at the top). It's now an in-flow, right-aligned row below the header that pushes
  the transcript down — collapsed it's a compact chip, expanded it grows in place.
- Model/effort dropdown items show a "house (default)" marker on the app default
  (distinct from the checkmark on the currently-selected value).
- Summaries are cached on the session (DB v4 `summary` column) and persisted: opening
  a chat shows the stored summary instantly with no model call; it only regenerates
  when a turn ends or on manual refresh.

Test: summaryIsCachedAndNotRegeneratedOnReopen. Full suite 93 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-12 14:46:06 -07:00
abkslmandClaude Opus 4.8 99b5bdaa8e Auto mode, model SKUs + defaults, favorite/archive/delete, summary/title/padding
- Auto mode (per the safety question): wired to Claude's built-in --permission-mode
  auto — its own classifier auto-approves safe actions and routes destructive ones
  to our approval UI ("all but destructive"). Per-chat toggle (bolt) + a global
  "start new chats in Auto" default. Session.auto + RunSpec/ResumeSpec.autoApprove +
  DB v3 column; transcript still shows every auto-approved call.
- Model/effort: pickers list full SKUs (claude-opus-4-8, …[1m], sonnet, haiku) and
  always show the effective concrete value (never "Default"); Settings adds default
  model / default effort / default-auto; new chats inherit them.
- Favorite / archive / delete chats via right-click context menu AND swipe actions;
  favorites sort first with a star, archived collapse into a per-project section,
  archiving closes the open chat. Session.favorite/archived (DB v3).
- Summary reworked into sectioned, glanceable Markdown (Now / Done / Next) rendered
  with MarkdownText; window title shows the project name (app name gone); padding
  below project names.

Tests: newChatsInheritDefaults, favoriteArchiveDeleteChat. Full suite 92 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-12 14:38:06 -07:00
abkslmandClaude Opus 4.8 d6f580acc3 Add project deletion (cascades chats, worktrees, branches)
- SessionMetadataStore.deleteProject + GRDB impl.
- AppStore.deleteProject: stops each live session, removes its worktree + branch
  via WorktreeManager.discard, deletes the session and project records, clears
  selection. The user's actual repository is never touched — only the
  .nucleic-worktrees checkouts.
- Sidebar project context menu gains a destructive "Delete Project…" with a
  confirmation dialog spelling out what's removed.

Test: deleteProjectCascadesSessionsAndWorktrees (project + summaries gone,
worktree dir removed, branch deleted). Full suite 84 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-12 02:39:08 -07:00
abkslmandClaude Opus 4.8 4d0d1a34a6 Dedup projects, rename, per-session model + effort pickers
- Duplicate projects rejected: addProject compares canonical repo paths (symlinks
  resolved) and refuses a repo that's already registered.
- Rename: SessionController.rename + AppStore.renameOpenSession (pencil in the chat
  header → alert); AppStore.renameProject (sidebar project context menu → Rename).
  Renames change the display title only; branch/worktree slug is untouched.
- Model + effort, per session, applied to the NEXT turn (each turn is a fresh
  process, so they can change mid-conversation):
  - --model (Default/Opus/Sonnet/Haiku) and --effort (low/medium/high/xhigh/max,
    both real claude flags) threaded through RunSpec/ResumeSpec → ClaudeCodeBackend.
  - Session.effort + GRDB v2 migration column; SessionController.setModel/setEffort.
  - Toolbar menus in the session detail bound to the open session.
- AppStore exposes openSession (full state) so the detail view reads title/model/
  effort/status directly.

Tests: project dedup, session+project rename, model/effort set, effort persistence
(v2 column). Full suite 83 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-12 02:35:42 -07:00
abkslmandClaude Opus 4.8 766e9d4490 M1: GRDB metadata store (RUNTIME §4)
Adds the project's first external dependency, GRDB 7.11 (the plan's locked choice;
verified it resolves and builds under the native build system).

- SessionMetadataStore protocol in NucleicCore so the pipeline stays storage-
  agnostic (tests/UI can swap in a fake).
- GRDBMetadataStore: WAL-mode DatabasePool on disk, in-memory DatabaseQueue for
  tests; DatabaseMigrator v1 creates project/session/approval tables + indices per
  §4 (device table deferred to M4). Domain types map through 1:1 row records;
  approval input/decision stored as canonical JSON.
- SessionController gains an optional metadataStore sink at pipeline step 3,
  persisting session metadata + approvals at boundaries (best-effort; never breaks
  the canonical transcript or fan-out).

6 store tests (round-trips, scoped upsert, approval pending→resolved, on-disk
reopen, and end-to-end controller→store persistence). Full suite 75 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-12 01:10:28 -07:00