weishu
a4355d1837
feat(ios): read-only chat assembly (A-M2f)
...
Wires the merged M2 pieces into a usable pair -> list -> live chat flow:
- pbxproj: link HapiUI into the app target (UUIDs C7/C8, mirroring the
existing HapiProtocol/HapiClient product references; only project change).
- HubSession: MessageWindowControllers registry + per-chat ChatSession
factory with in-memory per-session SSE resume cursors and scene-phase
forwarding to the active chat.
- ChatSession (app): session-scope SSEClient while the chat is open;
window opened/activated before subscribing; a single consume task awaits
every event into the window actor, giving Android-channel-equivalent
arrival-order ingestion; session-updated and friends route through the
shared SyncEventRouter (detail patching), session-removed clears the
window, a gap handshake triggers full resync + detail refetch + catch-up
tail sync; stop hands the cursor back for seamless reopen.
- ChatPipeline (HapiClient): actor running the reduction path off-main -
queued-row filter, normalization memoized by row instance identity,
reduce + buildVisibleChatBlocks with previousGroups-stable group ids.
- ChatModel: window state + detail agentState + machine labels -> serial
~100 ms-coalesced pipeline loop (first run immediate, publishes strictly
ordered), header title cascade, loading/empty/error states, loadOlder /
retry, last-seen stamping on every update.
- ChatView: bottom-anchored ScrollView/LazyVStack (defaultScrollAnchor +
scrollPosition(id:)), auto-stick at bottom, new-messages pill, top
sentinel paging with documented scroll re-anchoring, degraded banners.
- Block views: user bubble w/ attachment chips, agent markdown, collapsed
reasoning, tool cards (knownTools.tsx-parity presentation, status chip,
read-only permission row w/ pending banner, per-tool bodies: terminal,
before/after edits, write content, unified-diff detection -> DiffTextView,
checklists, read-only questions, pretty-JSON fallback, children rail),
tool groups, centered event rows (presentation.ts port), cli output,
generated images (authed bytes + in-memory cache + full-screen viewer),
codex review verdict card.
- Navigation/links: session list now pushes ChatView; app-level
\.hapiOpenURL handler (https/http -> SFSafariViewController, custom
schemes confirm first, hapi-file:// -> M4 placeholder) + theme injection.
- Tests: ChatPipelineTests drive the runner with fixture-derived windows
(stable ids, memo-stable recomputes, queued filter, group-id stability
across an older-page arrival).
- README: M2f app-layer description.
2026-08-17 20:55:36 +08:00
weishu
67b7fe7304
feat(ios): session list store + UI (A-M2a)
...
HapiProtocol (pure, mirroring the Android reference port):
- SummaryPatching: sessionSummary.ts derivations (pending requests
cap-5/oldest-first/id-tiebreak, kinds, todo progress), toSessionSummary
projection with the per-flavor agentSessionId resolution (legacy chain
omits piSessionId, replicated), applySessionSummaryPatch with the
deliberate >= version gates (vs the detail path's strict >), the
keep-alive render-irrelevance filter (pendingRequests compare ignores
'since'; metadata compare ignores hapiMcpUrl), max-monotonic updatedAt,
post-patch updatedAt as fallback 'since', and the deprecated legacy
detail-required gate kept for rule pinning.
- SessionSorting: exact list comparator (globalPinned > pinned > active >
pendingRequestsCount desc among active > updatedAt desc; stable).
- SessionMetadata: per-flavor session-id fields (claude/codex/gemini/
opencode/grok/agy/cursor/kimi/copilot/pi).
HapiClient stores (@MainActor @Observable, per hub):
- DiskCache: 500 ms debounced atomic JSON snapshots + SnapshotLocations.
- SessionListStore: sorted summaries + detail cache; full-session upsert
preserving hub-computed scheduled fields; strict-> detail vs >= summary
patch paths; keep-alive identity preservation (listRevision); empty-{}
and unparseable payloads take the REST fallback; 16 ms coalesced
refresh; optimistic pin (roll-forward) and archive (restore).
- MachineStore: the exact machine-updated decision tree.
- LastSeenStore: monotonic unread watermarks + per-scope baseline.
- SyncEventRouter: SyncEvent fan-out to the stores, global-scope message
events refresh the list, gap handshake triggers the full resync.
App:
- HubSession owns the stores + router (replaces the TODO(M2) routing);
background flushes snapshots.
- Features/Sessions: SessionListView + SessionListModel (status dot with
thinking pulse, title cascade, flavor·machine·worktree meta, relative
age on a minute timeline, pending/todo badges, unread dots, pinned
section, machine filter chips >= 2, pull-to-refresh, empty/loading/
offline states, long-press pin/archive context menu) + an M2f chat
placeholder pushed on row tap.
- HomePlaceholderView -> HomeView hosting the list (hub switcher +
connection dot kept in the toolbar).
Tests transcribe the Android suites: SummaryPatchingTest (18),
SessionSortingTest (6), SessionStoreTest, MachineStoreTest,
LastSeenStoreTest, JsonSnapshotStoreTest, StoreSyncTargetsTest — using
the existing HTTPPerforming recording stub plus a path-routing performer
for concurrent refetches.
2026-08-17 20:17:02 +08:00
weishu
3363d1c75b
feat(ios): pairing flow, deep link, app session wiring (A-M1d)
...
- HapiProtocol/Pairing/BindLink: parses the companion deeplink
(hapicompanion://bind?hub=&code=) and the web direct-access QR
(?hub=&token=) with URLSearchParams form-decoding semantics, in
lockstep with the Android port (tests mirror BindLinkTest.kt).
- HapiClient/Auth/HubPairingService: normalize -> GET /health
(reachability + protocolVersion) -> POST /api/auth -> persist
Keychain + registry + active hub; unpair with fallback. Covered by
PairingLogicTests through the HTTPPerforming seam.
- App layer: AppModel (@Observable @MainActor pairing state machine:
restore, pair, switch, sign out, deep-link routing, scenePhase,
terminal-auth-failure banner) + HubSession (per-active-hub APIClient/
AuthManager/global SSEClient with suspend-resume and a connection
state for the UI; store routing is TODO(M2)).
- Pairing UI: welcome flow, VisionKit QR scanner (with Simulator/
permission fallbacks), manual entry (paste-friendly), shared confirm
sheet with per-PairingFailure error states.
- HapiApp routes hapicompanion:// through AppModel (paired hubs switch
with a notice, never log the token); RootView switches unpaired/
paired and hosts the deep-link confirm sheet; HomePlaceholderView
shows hub, connection dot, hub switcher (M2a replaces it with the
session list).
- Info.plist: NSCameraUsageDescription; README: pairing guide + manual
test pass.
2026-08-17 15:58:17 +08:00