Commit Graph
523 Commits
Author SHA1 Message Date
abkslmandNucleic d80e94fc24 Network Type Transition Optimization
Nucleic-Session: 40F8C53A-F001-423A-8607-EAE181DBE14B
Co-authored-by: Nucleic <[email protected]>
2026-07-04 23:39:46 -07:00
abkslmandNucleic e15c1d9b89 Network Type Transition Optimization
Nucleic-Session: 40F8C53A-F001-423A-8607-EAE181DBE14B
Co-authored-by: Nucleic <[email protected]>
2026-07-04 23:39:41 -07:00
abkslmandNucleic b0b041f268 Network Type Transition Optimization
Nucleic-Session: 40F8C53A-F001-423A-8607-EAE181DBE14B
Co-authored-by: Nucleic <[email protected]>
2026-07-04 23:39:36 -07:00
abkslmandNucleic ce4028ffc8 Network Type Transition Optimization
Nucleic-Session: 40F8C53A-F001-423A-8607-EAE181DBE14B
Co-authored-by: Nucleic <[email protected]>
2026-07-04 23:39:26 -07:00
abkslmandNucleic f988f660fe Network Type Transition Optimization
Nucleic-Session: 40F8C53A-F001-423A-8607-EAE181DBE14B
Co-authored-by: Nucleic <[email protected]>
2026-07-04 23:39:19 -07:00
abkslmandNucleic 36e146f258 Network Type Transition Optimization
Nucleic-Session: 40F8C53A-F001-423A-8607-EAE181DBE14B
Co-authored-by: Nucleic <[email protected]>
2026-07-04 23:39:14 -07:00
abkslmandNucleic 030d814ffe Network Type Transition Optimization
Nucleic-Session: 40F8C53A-F001-423A-8607-EAE181DBE14B
Co-authored-by: Nucleic <[email protected]>
2026-07-04 23:39:08 -07:00
abkslmandNucleic 63a1c9dd9e Network Type Transition Optimization
Nucleic-Session: 40F8C53A-F001-423A-8607-EAE181DBE14B
Co-authored-by: Nucleic <[email protected]>
2026-07-04 23:38:55 -07:00
abkslmandNucleic 6ff0a3fb91 Remove Face ID Permission Text
Nucleic-Session: 3EF7F067-2199-41C2-997C-5546C1A8A5C4
Co-authored-by: Nucleic <[email protected]>
2026-07-04 23:31:40 -07:00
abkslmandNucleic fdb346f41a Remove Face ID Permission Text
Nucleic-Session: 3EF7F067-2199-41C2-997C-5546C1A8A5C4
Co-authored-by: Nucleic <[email protected]>
2026-07-04 23:31:33 -07:00
abkslmandNucleic 32ef11b000 Remove Face ID Permission Text
Nucleic-Session: 3EF7F067-2199-41C2-997C-5546C1A8A5C4
Co-authored-by: Nucleic <[email protected]>
2026-07-04 23:31:24 -07:00
abkslmandNucleic d0b63121d3 Remove Face ID Permission Text
Nucleic-Session: 3EF7F067-2199-41C2-997C-5546C1A8A5C4
Co-authored-by: Nucleic <[email protected]>
2026-07-04 23:31:18 -07:00
abkslmandNucleic 8bedbc8d98 Remove Face ID Permission Text
Nucleic-Session: 3EF7F067-2199-41C2-997C-5546C1A8A5C4
Co-authored-by: Nucleic <[email protected]>
2026-07-04 23:31:14 -07:00
abkslmandNucleic 92e7c59dd6 Remove Face ID Permission Text
Nucleic-Session: 3EF7F067-2199-41C2-997C-5546C1A8A5C4
Co-authored-by: Nucleic <[email protected]>
2026-07-04 23:31:10 -07:00
abkslmandNucleic 693ca29691 Live Activity Content Density
Nucleic-Session: B3643133-163C-4C5D-BE20-1A6B7FB83DA9
Co-authored-by: Nucleic <[email protected]>
2026-07-04 23:30:53 -07:00
abkslmandNucleic a45795184e Live Activity Content Density
Nucleic-Session: B3643133-163C-4C5D-BE20-1A6B7FB83DA9
Co-authored-by: Nucleic <[email protected]>
2026-07-04 23:30:15 -07:00
abkslmandNucleic 0dda487167 Live Activity Content Density
Nucleic-Session: B3643133-163C-4C5D-BE20-1A6B7FB83DA9
Co-authored-by: Nucleic <[email protected]>
2026-07-04 23:29:52 -07:00
abkslmandClaude Fable 5 3e25ef9559 feat(relay): wire the live Nucleic Private Relay into the desktop + iOS apps (mesh P2 complete)
The relay Worker (nucleic-edge at relay.nucleic.blakeslee.xyz) is deployed, so land the
formerly deploy-gated client side of the data path:

- NucleicProtocol/Sync/RelayTransport.swift: RelayAPI (one base URL for REST + WS,
  membership -> connection token trade), RelayWebSocket (ordered sends, ping keepalive,
  ping-confirmed connect), RelayFrameChannel (client leg, WireFraming inside WS binary,
  presence fail-fast when the room has no host), RelayPresence.
- NucleicCore/Sync/RelayAccess.swift: X25519 PoP enrollment (RelayEnrollment), room
  credential in the login Keychain (separate from the push credential), membership minting
  with re-enroll-on-401.
- NucleicCore/Sync/RelayListener.swift: host SyncListener demuxing the room socket into
  per-tag virtual FrameChannels via RelayEnvelope; presence-driven reaping; backoff redial;
  injectable RelayRoomSocket seam for tests.
- Wire: additive HostMsg.relayMembership(WireRelayMembership) pushed after every hello
  (SyncHost.register -> AppStore mint) so devices paired before the relay adopt it and the
  ~90-day token refreshes on each connect; the pairing QR also carries a bootstrap
  membership so first contact can ride the relay. Old clients ignore the unknown tag.
- AppStore: .relay joins the listener composite behind the Connection-methods checkbox
  (failure degrades to a status row), advertises relayRoomID in PeerAddresses, mints the
  QR bootstrap in beginPairing.
- Desktop UI: the Nucleic Private Relay toggle is enabled (was "coming soon"); the
  LAN-only banner offers it alongside Tailnet.
- iOS: relay is the last dial candidate in HostConnection pair + reconnect (10s handshake
  watchdog); PairedHost persists relayRoomID/relayMembershipToken/relayURL; the
  relayMembership push updates the registry in place; Settings shows Relay in Transports.

Tests: RelayTransportTests, RelayListenerTests, SyncHostTests relay push + QR bootstrap.
Full suite green (783 core + 113 protocol + 2 new); iOS simulator build succeeds. Live
smoke test against the deployed Worker passed end-to-end (PoP enroll, both token tiers,
two-socket frame round-trip through the Room DO with correct envelope tags).

Known limits: host revoke-on-unpair not wired (endpoint is admin-only); PeerClient
(Mac<->Mac) doesn't dial the relay yet.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-04 21:05:01 -07:00
abkslm b587702ff5 Merge claude/keen-shamir-122122 into dev (mesh P3 multiplexer)
The iOS [HostID: HostConnection] multiplexer: connect to all paired Macs at once, mirror the active
one, instant host switching, cross-host badge/Live Activity. Compile + demo-verified; the live
multi-connection path needs a two-Mac test (may be rolled back).

Resolves the RemoteStore.swift overlap with dev's Tailnet-node-name/auth-key change by taking the
multiplexer version (which moved the tailnet config into HostConnection); dev's authKey removal is
re-applied there.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

# Conflicts:
#	ios/NucleicRemote/NucleicRemote/Models/RemoteStore.swift
2026-07-04 18:56:54 -07:00
abkslmandNucleic 0b9afb22f3 nvrsion: Add: Adjust Tailscale Node Name format and remove “Tailscale auth key” setting to enable immediate browser login when toggle is enabled.
Nucleic-Promote: 1
Co-authored-by: Nucleic <[email protected]>
2026-07-04 18:37:15 -07:00
abkslmandClaude Opus 4.8 a24f113996 Mesh P3: wire RemoteStore onto the [HostID: HostConnection] multiplexer
Completes the multiplexer rewire begun in f335eb4. RemoteStore no longer runs an inline single
connection — it owns `connections: [HostID: HostConnection]` and connects every paired Mac at once,
so all your Macs are live simultaneously.

- reconnect() connects/reconnects all paired hosts (dropping since-unpaired ones); pair() adds a new
  connection and makes it active while the others keep running; unpair() drops the active Mac and
  switches to a remaining one.
- The flat @Published state (connectivity/sessions/dashboard/capabilities/…) mirrors the *active*
  connection via callbacks, so switchHost() is now instant — every Mac is already connected, so it
  just re-points at the target's live state (no reconnect).
- Aggregate concerns merge across hosts: the app-icon badge counts needs-you across ALL Macs, Live
  Activity summarizes all live sessions, and an approval answered from a notification is broadcast to
  every connection (the owner resolves it; the notification carries no hostID yet). Intents (send /
  open / approvals) route to the active connection.

Removed ~450 lines of connection machine from RemoteStore (now in HostConnection); kept
friendlyTransportError/showError/dismissError. Demo bypasses connections (seeds state directly).

Compiles (iOS Simulator BUILD SUCCEEDED) and the demo host switcher is runtime-verified (screenshots:
switching Macs swaps the session list + tab badge). The LIVE multi-connection path — simultaneous
dials, reconnect/teardown, the shared embedded tailnet node — is compile-verified only; it needs two
real Macs to validate.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-04 18:22:39 -07:00
abkslmandClaude Opus 4.8 4f12b18d63 Mesh P3 (multiplexer, WIP): extract HostConnection engine
Begins the simultaneous multiplexer. HostConnection.swift is the per-host connection engine lifted
out of RemoteStore: one instance owns a single Mac's SyncClient, LAN→tailnet candidate chain,
reconnect backoff, tailnet-node lifecycle, and event stream, and keeps that Mac's projection
(connectivity, sessions, dashboard, capabilities, meshPeers). It talks back to its owner through a
Callbacks struct so aggregate concerns — the badge, Live Activity, notifications, and the single open
transcript — can be merged across hosts by the coordinator.

Not yet wired: RemoteStore still runs its own inline single-connection machine. The next step swaps
RemoteStore onto a `[HostID: HostConnection]` map (connect all paired Macs, aggregate their sessions,
route intents by host) — the behavior-critical part, to be verified with two real Macs. Compiles
(iOS Simulator BUILD SUCCEEDED); no behavior change yet (the engine is unused until the rewire).

Also: RemoteStore.friendlyTransportError made non-private so the engine shares it.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-04 17:56:31 -07:00
abkslm fb6a68fd34 Merge claude/keen-shamir-122122 into dev (mesh P5 finish + P3 host switcher)
Mesh P5 feature-complete except the two-Mac memory-carry spike: moved/arrived-session
visibility, relaunch recovery, bulk hand-off, stranded-arrival activate-anyway. Plus the
iOS build fix (equivalent to dev's f021ef8) and P3 foundation: paired-host registry +
migration and a host switcher (switch active Mac; 2-host demo, simulator-verified).

Co-Authored-By: Claude Opus 4.8 <[email protected]>

# Conflicts:
#	ios/NucleicRemote/NucleicRemote/Models/RemoteStore.swift
2026-07-04 17:49:10 -07:00
abkslmandClaude Opus 4.8 7b7e13aa20 Mesh P3: host switcher (switch active Mac) + 2-host demo
The safe, verifiable slice of iOS multi-host: switch which paired Mac the phone views, reusing the
existing (proven) single-connection reconnect — no rewrite of the connection state machine. The
simultaneous [HostID: HostConnection] multiplexer stays deferred (it needs two real Macs to verify).

RemoteStore: an `activeHostID` (the paired Mac the flat projection reflects); `hostChoices` (paired
registry live, mock hosts in demo); `switchHost(to:)` — live re-points via reconnect(), demo swaps
the mock host preserving in-demo edits; `reconnect()`/`pair()` set the active host; `unpair()` now
forgets the *active* Mac and switches to a remaining one if any (identical for a single host). Demo
seeds two mock Macs ("Andrew's Mac", "Studio Mac") with distinct sessions/dashboards.

SessionsView: a host-switcher menu in the toolbar, shown only when >1 Mac.

Verified in the iOS Simulator (demo mode): the switcher lists both Macs, and switching swaps the
whole session projection + the tab badge (screenshots taken). Single-host behavior is unchanged
(one host ⇒ switcher hidden ⇒ flat state exactly as before).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-04 17:40:00 -07:00
abkslmandClaude Opus 4.8 630380085e Mesh P3 (foundation): iOS paired-host registry + migration
First step of Phase 3 (iOS multi-host), per docs/MESH_TRANSFER.md. Converts IdentityStore from a
single `nucleic.pairedHost` slot to a `nucleic.pairedHosts` registry (ordered [PairedHost], keyed by
fingerprint = hostID), with a one-time migration of the legacy single value on first read (then the
old key is removed) so an upgrade keeps its Mac.

New registry API — pairedHosts(), pairedHost(id:), upsertPairedHost(_:), removePairedHost(id:) — for
the coming RemoteStore multiplexer. A single-host bridge keeps every current caller unchanged and
behavior identical: loadPairedHost() returns the active (most-recently-paired) host, savePairedHost
upserts + makes active, clearPairedHost removes the active. RemoteStore is untouched and still holds
one connection.

Settings gains a "Paired Macs" list (the visible artifact): each registered Mac with its fingerprint,
an "Active" marker, and a per-host remove (removing the active one unpairs the live connection;
removing another just forgets it) — this also gives a removal path now that pairing a new Mac keeps
the old one in the registry instead of overwriting it.

Verified with an iOS Simulator build (BUILD SUCCEEDED). Remaining Phase 3: the RemoteStore
[HostID: HostConnection] multiplexer (simultaneous connections, (hostID, sessionID) keying),
host-qualified notifications/Live Activity, host switcher UI, demo N-hosts, and the two-Mac
tailnet spike.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-04 17:14:30 -07:00
abkslmandClaude Opus 4.8 ac379a0523 Fix iOS build: handle mesh transfer verbs/events in RemoteStore switches
The P4/P5 mesh work added ClientMsg cases (addressUpdate, transferOffer/Chunk/Commit/Cancel)
and SyncClient.Event cases (transferAccept/Reject/Ready/Committed/ChunkAck) but never updated
the iOS RemoteStore's two exhaustive switches — the demo-simulator ClientMsg handler and the
event handler — so NucleicRemote failed to compile ("switch must be exhaustive"). The app target
isn't built by `swift build`, so this landed unnoticed on the branch.

Both are inert on the phone: it's never a transfer source/destination and demo has no peer Macs,
so the new cases join the passive `break`. Verified with an iOS Simulator build (BUILD SUCCEEDED),
which also confirms this session's moved/arrived-provenance rendering edits compile.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-04 17:09:20 -07:00
abkslmandClaude Opus 4.8 47d96901a6 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 Opus 4.8 5b3f006e3b Fix iOS build: handle mesh P5 transfer/peer wire cases on the phone
Mesh P5 (Mac↔Mac session transfer) added ClientMsg cases (addressUpdate,
transferOffer/Chunk/Commit/Cancel) and SyncClient.Event cases (transferAccept,
transferReject, transferReady, transferCommitted, transferChunkAck) but didn't
update the phone's demoHandle / event-handle switches, leaving NucleicRemote
non-exhaustive and failing to build (pre-existing on dev). The phone isn't a
transfer participant, so handle all of them as no-ops. Second time mesh work has
broken the iOS target this way (cf. the listPeers fix).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-04 15:38:41 -07:00
abkslmandClaude Opus 4.8 8c115e3af1 iPad: lead the dashboard with a "Needs you" section
The Home dashboard only surfaced running sessions and rollup counts, so chats
blocking on you (an approval or your next prompt) were visible only as sidebar
washes. Add a "Needs you" card pinned above the stat cards (amber header +
border) listing sessions where status.needsYou(disposition) holds, each tapping
into its detail. Mirrors the Mac home's "Needs attention" lead. Shared by
iPhone and iPad; hidden when nothing's waiting. Verified in the iPad simulator.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-04 15:33:25 -07:00
abkslmandClaude Opus 4.8 005776685d iPad: more keyboard shortcuts, a New-chat button, and hover polish
Round out the Mac-parity keyboard/pointer affordances on the iPad split view.

Keyboard shortcuts (hidden buttons in SplitRootView carry them):
- ⌘N — start a new chat: select Home and focus the composer (also defaults the
  project). A one-shot Bool binding threads Home→StartChatComposer and is
  consumed on use, so it grabs focus on ⌘N but never on a later Home appearance
  (handles both "already on Home" via onChange and "switched from a session"
  via onAppear).
- ⌘1–4 — jump to Home / Projects / To-dos / Settings.
- ⌘R — refresh sessions.
- ⌘. — interrupt a running session (Mac's "stop"; otherwise only in the ⋯ menu).

UI:
- A visible "New chat" (compose) button in the sidebar toolbar runs the same
  action, so touch users get the affordance too (gated on control scope).

Hover (pointer):
- .hoverEffect on the transcript tool-call / tool-block rows and the Home
  in-progress session rows (native List rows already hover).

Verified in the iPad simulator: the New-chat action scrolls to + focuses the
composer with the project defaulted. Builds clean.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-04 01:18:35 -07:00
abkslmandClaude Opus 4.8 fb7aeffcf0 Fix iOS build: handle ClientMsg.listPeers in the demo simulator
The mesh/session-transfer work added `ClientMsg.listPeers`, but the phone's
demo-mode `demoHandle` switch (which lists its cases explicitly, no default)
was never updated — leaving the NucleicRemote target non-exhaustive and failing
to build (pre-existing on dev, surfaced by the iPad-port merge). Demo mode has
no real mesh peers, so treat `.listPeers` as passive alongside the other
read-only requests.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-04 00:59:15 -07:00
abkslm 8edd9734bd Merge claude/elastic-haibt-f29115 into dev (iPad remote port: adaptive sidebar+detail shell, Mac-style two-pane diff, structured commit/command transcript cards, keyboard/pointer affordances, camera-free manual pairing) 2026-07-04 00:54:14 -07:00
abkslmandClaude Opus 4.8 f8fdf98517 iPad: hardware keyboard, pointer, and a camera-free pairing path (Phase 3)
Make the remote feel native with a Magic Keyboard / trackpad, and give iPad a
pairing path that survives a blocked camera.

Keyboard shortcuts (mirroring the Mac's .commands):
- Cmd+Return sends in the session follow-up composer and the start-chat composer
  (plain Return stays a newline in the multiline fields).
- On the approval card, Return allows and Esc denies -- the approval bar replaces
  the composer, so Return is unclaimed there. Allow stays gated on the biometric.

Pointer:
- .hoverEffect on the diff file-list rows (native List rows already hover).

Camera-free pairing (ManualPairingView):
- "Enter code manually" in both the pairing intro and Settings opens a sheet to
  paste the Mac's nucleic://pair?d=... code, parsed with the same
  PairingPayload(qrString:) the scanner uses (+ a Paste-from-clipboard button).
  The iPad scanner can't run while mirrored to an external display or in some
  Stage Manager states; this always works.

Note: the Mac's RemoteAccessView currently shows only the QR, so a follow-up host
change is needed to surface a copyable pairing link for this to be end-to-end.

Builds clean.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-04 00:48:56 -07:00
abkslmandClaude Fable 5 25fb2eb8ad Mesh + session transfer: P1 done, P2 core, P4 foundation
Multi-device mesh + session-transfer program (docs/MESH_TRANSFER.md).

P1 (multi-select connection methods, Mac): SyncTransportSet with legacy
migration; CompositeSyncListener partial-failure tolerant + per-method health;
AppStore listens on all enabled methods; HostInfo.hostID = DeviceIdentity.hostID
(key hash, not display name); Settings 3 method toggles + LAN-only recommendation
banner.

P2 core (relay hardening, security-critical, tested): nucleic-edge relayEnroll.ts
X25519 proof-of-possession enroll + server-side roomId derivation; room.ts
role-routed per-peer forwarding with deviceId-tag envelope + frame cap +
one-host-per-room eviction; host-bearer minting bound to the PoP room. Swift
cross-stack contracts pinned to test vectors: RelayEnrollment (PoP proof),
RelayEnvelope (routing tag), PairingPayload relay fields. Socket transport +
iOS un-gating remain deploy-gated.

P4 foundation (peer model + listPeers, tested): PeerTypes (PeerKind/
PeerCapabilities/PeerSummary); Hello.deviceKind+clientCaps;
WireCapabilities.canListPeers; ClientMsg.listPeers -> HostMsg.peerList;
PairedDevice.kind+capabilities (decode-defaulted); ConnectionHandler records
kind at pairing; SyncHost.connectedDeviceIDs(); AppStore.peerSummaries();
SyncClient .peerList event; iOS RemoteStore.meshPeers. PeerClient + Mac<->Mac
pairing UI remain.

All additive + capability-gated; SyncProtocol.version stays 1; pre-mesh stores
and clients unaffected. Swift 724 core + 91 protocol tests green; edge 50 green.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-04 00:39:30 -07:00
abkslmandClaude Opus 4.8 15ea0f1dd2 iPad: render multi-step / destructive shell pipelines as a step list
The desktop CommandStepsCard breaks a shell pipeline (a rm/git chain) into a
step list with the destructive delete flagged, instead of a raw blob. Rather
than duplicate a parser, deliver that value by reusing the existing, tested
HostCommandSummary + HostCommandBreakdown (a general command parser whose
Invocation already carries a `destructive` flag for rm/rmdir) on the two paths
that still showed raw text:

- ToolCallCard.details: a shell tool whose command has >1 invocation or is
  destructive now renders the compact step breakdown (deletes glyphed/tinted in
  red) with the literal command under "Show command"; simple one-liners keep the
  plain input block.
- ApprovalCardView: the parsed breakdown (with its sudo/deletes risk banner) now
  covers any Bash pipeline / destructive approval, not just host_exec.

Demo transcript gains a destructive cleanup pipeline (rm -rf && git worktree
prune && git branch -D) so the step list is exercisable offline.

Verified in the iPad simulator: the pipeline expands to a step list with
`rm -rf` flagged in red.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-04 00:38:59 -07:00
abkslmandClaude Opus 4.8 149af312dc iPad: show the commit card for host-run and pending-approval commits too
Extend the git commit card beyond the Bash transcript path to the other two
surfaces a commit appears on, so all three read like the Mac:

- ApprovalCardView: a pending `git commit` approval (Bash or host_exec) renders
  the structured commit card (subject + Markdown body) with the literal command
  under "Show command", instead of a raw blob or the generic host breakdown --
  so you see exactly the message you're granting.
- HostExecToolCard: a host-run `git commit` surfaces the commit card in its
  expanded body (the `$ command` already sits in the header), rather than only
  the generic "Commit changes" purpose.

Both reuse GitCommitSummary.parse + GitCommitCard from the previous commit.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-04 00:27:20 -07:00
abkslmandClaude Opus 4.8 850ee0cd96 iPad: render git commits as a structured card in the transcript
Port the desktop GitBlockCard's commit rendering (+ CommandDisclosure) to the
remote transcript: a Bash `git commit` now shows the commit subject as a
headline and the message body as Markdown, with the raw command one tap away
under "Show command" -- instead of a raw `git commit -F - <<'EOF' ...` blob.

- GitCommitCard.swift: the card + the CommandDisclosure helper (portable
  SwiftUI, mirroring Sources/NucleicApp/{GitBlockCard,CommandDisclosure}).
- GitCommitSummary.parse (in HostCommandSummary.swift) extracts the message
  from -m/--message args or a `-F -` heredoc body, reusing the file-private
  shell Lexer already used for host-exec summaries; skips env prefixes and git
  global flags, and only fires for real `git commit` segments.
- Hooked into ToolCallCard.details, gated to shell tools so non-shell input is
  never misread as a commit.
- Demo transcript gains a git-commit call so the card is exercisable offline.

SummaryCard is intentionally not ported -- it's the host-only Apple-Intelligence
recap, which a remote has no way to generate.

Verified in the iPad simulator (demo mode); parser covered by a standalone test.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-04 00:21:32 -07:00
abkslmandClaude Opus 4.8 34aa01a724 iPad: adaptive sidebar+detail shell and Mac-style diff (remote Phases 1-2)
Grow the universal NucleicRemote app into a width-adaptive shell so a
regular-width iPad renders the macOS sidebar+detail IA while the iPhone
keeps its TabView -- both over the same RemoteStore projection (one host
authority, N renderers).

Phase 1 (adaptive shell):
- AdaptiveRootView branches on horizontalSizeClass: CompactRootView (the
  existing iPhone TabView, moved verbatim) vs SplitRootView
  (NavigationSplitView) on regular width.
- SplitSidebar: Home/Projects/To-dos/Settings destinations + sessions
  grouped under their projects (attention-sorted), connection chip footer.
- SplitDetail selects a destination or a session; a selected session reuses
  SessionDetailView keyed .id(sessionID) so switching drives open/close.
- RemoteStore.closeOpen(_:) is now id-guarded so a split-view A->B switch
  (onAppear(B) before onDisappear(A)) can't tear down B's fresh subscription.
- IdentityStore.deviceID idiom-tags the prefix (ipad-/iphone-) for new
  installs so the host lists a paired iPad correctly.

Phase 2 (width tuning + diff):
- readableColumn() caps+centers Home and the transcript on wide layouts;
  a no-op at phone/portrait width.
- SessionDiffView switches on available width (GeometryReader): a Mac-style
  two-pane diff (file list + selected file's patch) on wide/landscape, the
  phone stack otherwise. UnifiedPatch splits the combined patch per file.
  Read-only, same wire, no protocol change.
- Demo diff fixture now carries both files' patches.

iPhone layout and behavior unchanged. Builds clean; verified in the iPad
simulator (demo mode).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-03 23:59:29 -07:00
abkslmandNucleic 6a268abbb9 nvrsion: Add re-sync from dev for VERSION and adjust Appcast releases; fix permission flow by removing the “allow always” button from approval views and docs; fix build script error by removing obsolete sh dependency from Makefile.
Nucleic-Promote: 1
Co-authored-by: Nucleic <[email protected]>
2026-07-03 18:57:19 -07:00
abkslmandNucleic b0284f1da1 nvrsion: Add: Adjust side padding on “Update available” card in sidebar to match session entries’ padding, fix internal tester invitation workflow for rapid Canary channel releases, and create a “trusted tester” system requiring approval to join internal testing groups.
Nucleic-Promote: 1
Co-authored-by: Nucleic <[email protected]>
2026-07-03 17:40:28 -07:00
abkslmandClaude Fable 5 57d0ccc5f6 ios: surface the browser-login step on the pairing intro screen
A first tailnet pairing with no auth key detours through a Tailscale browser
login that can take minutes. The intro screen — the only UI an unpaired phone
has — showed just "Connecting to your Mac…" for that whole window, and if
the user closed Safari there was no way back to the login page (the Settings
tab with the login link only exists once paired). The intro's pairing status
now explains the approval step and links to the login page whenever the
embedded node is waiting on one.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-03 16:25:27 -07:00
abkslmandClaude Fable 5 e623247117 Merge branch 'claude/vigorous-nash-457773' into dev
Tailscale (Tailnet) sync transport: transport picker in Settings ▸ Remote,
interactive browser login, and LAN↔tailnet fallback.

Semantic merge fixes (both sides compiled alone but not together):
- handleTransportFailure (dev's friendly transport errors) now checks
  connectivity.isLive — .connected gained a transport payload on the branch —
  and stays silent while the branch's connect chain still has candidates to
  try, so a LAN probe failing over to the tailnet doesn't flash a red error.
- Chain-exhaustion paths keep a friendlier transport-level failure message
  when onFailed already surfaced one instead of clobbering it with the
  generic handshake error.

Verified on the merge: swift build + 37 sync/transport/store tests green
(incl. dev's FilePairedDeviceStore + the now-fixed contract tests), iOS
simulator build green.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-03 16:22:13 -07:00
abkslmandClaude Fable 5 272039cca5 Tailnet: interactive browser login + LAN↔tailnet fallback
Browser login — the auth key is now optional on both platforms. When the
embedded node starts with no key, TailnetNode asks the backend (LocalAPI
backendStatus — IPN state, deliberately not filesystem heuristics: tsnet
writes logs and a machine key on every start, registered or not) whether a
login is needed, triggers login-interactive, surfaces the auth URL through a
new statusStream()/.needsLogin, and waits for Running (4-minute deadline,
generation-fenced against stop/restart). The Mac auto-opens the login page
from Settings ▸ Remote and shows a re-open button; the iPhone auto-opens only
during user-initiated pairing (a background reconnect that suddenly needs a
login must not eject the user to Safari — Settings ▸ Tailscale carries the
link). The remote-access toggle now reflects the in-flight start instead of
snapping off for the whole login window, and toggling off mid-start is
honored at both commit points (before and after host.start).

LAN↔tailnet fallback — picking Tailnet now keeps LAN on too: the host runs
both listeners under a new CompositeSyncListener (merged accept stream; one
child ending doesn't end the rest) and the pairing QR carries both hints.
The phone builds an ordered candidate chain — LAN first (QR hint or Bonjour),
tailnet second — and walks it on pair and reconnect, so a phone that leaves
the Mac's Wi‑Fi rolls over to the tailnet and rolls back when it returns.
A per-channel 4s connect guard (readiness-checking, bound to exactly its
channel) keeps a stale LAN hint from hanging the chain; a stale-client guard
in consume() keeps a replaced client's tail events from advancing it; chain
exhaustion during pairing lands in a terminal failure instead of spinning on
"Connecting…"; routine pre-fallback handshake errors no longer flash the red
error bubble. "Connected · LAN / Tailnet" shows whichever transport won.

Multi-agent review: 11 confirmed findings (incl. the login gate being dead
code via tsnet's eager state-dir writes, and two connect-timeout races), all
fixed and re-verified. Suite green (24 sync-related tests incl. 3 new
CompositeSyncListener tests); macOS + iOS builds clean.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-03 16:14:45 -07:00
abkslmandClaude Fable 5 94a962bd20 Add Tailscale (Tailnet) as a sync transport between Mac and iPhone
Settings ▸ Remote gains a "Connect via" picker — LAN (default), Tailscale
(tailnet), or Relay (disabled, coming soon). On Tailnet, both devices run an
embedded tsnet node via TailscaleKit (tailscale/libtailscale) and sync frames
flow over the user's tailnet, so the phone can connect from anywhere the
tailnet reaches; Noise E2EE runs above the transport unchanged.

- NucleicTailnet (new target, macOS + iOS): TailnetNode wraps TailscaleKit's
  node lifecycle (auth-key login, generation-fenced start/stop since up() is
  un-cancellable) and drops to the framework's public C API for the data path
  — tailscale_dial/listen/accept hand back full-duplex socketpair fds, wrapped
  by FDFrameChannel (DispatchIO) into the shared FrameChannel seam. The Swift
  wrapper's one-way connection actors can't carry a bidirectional stream.
- Host: TailnetListener adopts SyncListener; startSyncServer is single-flight
  and honors toggle-off/picker changes at the commit point; pairing QRs carry
  transport + tailnet IP/port hints (PairingPayload additive optional fields,
  forward/backward compatible over CBOR).
- iPhone: pair/reconnect dial over whichever transport the pairing recorded;
  Settings gains a Tailscale auth-key field (Keychain, committed on editing
  end); connectivity chip shows "Connected · Tailnet".
- TailscaleKit has no SwiftPM distribution: scripts/build-tailscalekit.sh
  builds a pinned libtailscale commit into an untracked local xcframework;
  Package.swift links it only when present (everything builds without it, the
  picker then reports Tailscale support as not built in), and the script
  clears SwiftPM's content-keyed manifest cache so the toggle is picked up.
- iOS floor 17.0 → 18.1 (TailscaleKit requires the iOS 18 Swift runtime);
  package-app.sh embeds the framework in the .app like Sparkle.

703-test suite: no new failures (the 7 fake-claude/fake-grok staging issues
reproduce identically on an untouched checkout — pre-existing, tracked
separately). New coverage: FDFrameChannel over socketpairs, pairing-payload
version-skew both directions, transport-setting resolution.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-03 03:50:45 -07:00
abkslmandNucleic 22d0752329 nvrsion: Add fix for QR Code Scan Failure in AppStore, Sync, and Tests; explore connection failures over local network; update Info.plist, Models, and SettingsView.
Nucleic-Promote: 1
Co-authored-by: Nucleic <[email protected]>
2026-07-03 01:42:40 -07:00
abkslmandNucleic f668a204ab nvrsion: Add: Adjust iOS beta channel release targets to “Nucleic Remote (Beta)” in BUILD.md, Makefile, app-logo-beta.icon/icon.json, ios/NucleicRemote/NucleicRemote.xcodeproj/project.pbxproj, scripts/ios-release.sh, signing/README.md.
Nucleic-Promote: 1
Co-authored-by: Nucleic <[email protected]>
2026-07-02 22:12:15 -07:00
abkslmandNucleic b10b26ba37 nvrsion: Add: Align iOS remote banner cases (beta, canary, etc) to desktop version to fix divergence in build number hash.
Nucleic-Promote: 1
Co-authored-by: Nucleic <[email protected]>
2026-07-02 21:58:03 -07:00
abkslmandNucleic 7b5bdc9d0b nvrsion: Add: Adjust Canary Build Auto-Update to run every hour, fix Keychain permission audit by updating files: ControlAuth.swift, GitHubCredentials.swift, HeartbeatReporter.swift, KeychainOwnedAccess.swift, HostIdentityStore.swift, PushRelayClient.swift, refactor Remote Build Icon to use “bell.and.waves.left.and.right.fill” in ios/NucleicRemote/NucleicRemote/Views/BuildBanner.swift, add a Remote Release Channel Check in AppStore.swift, Sync/ConnectionHandler.swift, Sync/SyncHostBridge.swift, Sync/ReleaseChannel.swift, Sync/SyncClient.swift, Sync/WireMessages.swift, tests/SyncHostTests.swift, tests/SyncTestSupport.swift, and align build number hash in ios/NucleicRemote/NucleicRemote.xcodeproj/project.pbxproj, ios/NucleicRemote/NucleicRemote/Info.plist, ios/NucleicRemote/NucleicRemote/Views/BuildBanner.swift.
Nucleic-Promote: 1
Co-authored-by: Nucleic <[email protected]>
2026-07-02 21:54:40 -07:00
abkslmandNucleic 535c092fcc nvrsion: Add: Update Info.plist to reflect no encryption requirement for TestFlight versions.
Nucleic-Promote: 1
Co-authored-by: Nucleic <[email protected]>
2026-07-02 21:09:09 -07:00
abkslmandNucleic 1bf1f4e658 nvrsion: Add “To-do” count stat card to iOS app.
Nucleic-Promote: 1
Co-authored-by: Nucleic <[email protected]>
2026-07-02 19:37:45 -07:00