Commit Graph
8 Commits
Author SHA1 Message Date
abkslm 3da6338b0a Merge nucleic/rustic-maple-marten into dev 2026-07-12 03:00:32 -07:00
abkslmandNucleic 368978bea8 nvrsion: Add: Global Icon For Remote Projects to display globe icon next to remote projects in sidebar, matching local projects in appearance, Fix: Chat: Remove Face ID Permission Text, Refactor: Chat: Live Activity Content Density to open app directly into waiting sessions when tapped, Refactor: Chat: Live Activity Content Density to refresh live activity info significantly faster, Refactor: Chat: Live Activity Content Density to refresh live activity info significantly faster, Fix: Chat: Live Activity Content Density to refresh live activity info significantly faster, Fix: Chat: Network Type Transition Optimization to reduce delay during network type switches, Feature Enhancement Planning: All devices in a mesh group should see and be aware of each other; “pair” reframed into “join”; scanning
Nucleic-Promote: 1
Co-authored-by: Nucleic <[email protected]>
2026-07-05 00:28:21 -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
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 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
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
NucleicandClaude Opus 4.8 cc97c26256 Signing: rebrand bundle IDs to xyz.blakeslee.* and correct the Team ID
Desktop channels → xyz.blakeslee.nucleic.desktop.{dev,beta,rc,release} (the stable channel keyword is unchanged; only its bundle-id suffix is "release"). iOS remote → xyz.blakeslee.nucleic.remote, including its Keychain account namespaces, the scanner log subsystem, and the coupled APNS topic.

Correct the Apple Developer Team ID to L7UDTQ6F5W across the Xcode project, ExportOptions, the APNS config + tests, and the signing/cloud docs.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-26 16:52:14 -07:00
abkslmandClaude Opus 4.8 6f24b42004 M4: NucleicRemote iPhone app — SwiftUI client over the shared protocol
A real iOS Xcode app (ios/NucleicRemote) linking the NucleicProtocol SwiftPM
library as a local package. Builds for the iOS 27 simulator and launches to the
pairing screen.

- Transport: NWFrameChannel (NWConnection) + LANDiscovery (Bonjour _nucleic._tcp).
- Engine: drives NucleicProtocol.SyncClient (Noise XXpsk0 pair / IK reconnect,
  hello/welcome, HostMsg→Event stream).
- State: RemoteStore (ObservableObject) — the single on-device projection of host
  state; IdentityStore persists the device identity (Keychain) + pinned host.
- UI (UX_IOS): attention-first SessionsView, SessionDetailView (transcript/diff +
  status-driven action area / composer), ApprovalCardView with Face ID gate on
  high-risk approvals + allow-always menu, PairingScannerView (AVFoundation QR),
  SettingsView, connection chip. Same status glyphs/semantics as the Mac.

Add-iPhone QR display + server start live on the macOS side (follow-up); push /
Live Activity are M5 (needs the relay). gitignore keeps this .xcodeproj despite
the blanket *.xcodeproj rule.

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