mirror of
https://github.com/wu736139669/hapi.git
synced 2026-10-08 19:19:42 +00:00
17ee052d9a9063b332508a47e3bd56d784c09ba7
9
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9048bfa4f1 | fix(ios): prevent reconnect notices from shifting chat | ||
|
|
bc9df82dc6 |
fix(ios): refine pairing and session list UX
- Pairing now allows direct entry/scan with inline progress and error feedback - PairingConfirmView is now deep-link-only for security confirmations - Session list rows: move thinking spinner and unread indicator to trailing edge - Extracted shared PairingAttempt state for reuse - Updated strings: "Continue" → "Pair" button label - Removed unused PendingPairing.source Claude-Session: https://claude.ai/code/session_01ViH4oaLSpTcksxDFEMQgeD |
||
|
|
e289a0a776 | feat(ios): push — APNs registration, E2E notification service extension, actions (P3) | ||
|
|
6066f20c20 |
feat(ios): zh-CN localization + catalog dedupe (A-M5)
Part 1 — Simplified Chinese localization of the iOS app layer: - ios/Hapi/Resources/Localizable.xcstrings: hand-authored String Catalog (395 keys, zh-Hans; en implicit as source). Terminology mined from web/src/lib/locales/zh-CN.ts (会话/新建会话/权限模式/允许/拒绝/工作树/ 机器/语音输入/用量/草稿夹 …). - Mechanical edits only: merged multi-part string concatenations into single LocalizedStringKey literals, converted ternary/plain-String user copy to String(localized:), switched helper params (detailRow, DashboardCard, optionPicker, notice) to LocalizedStringKey. - LocalizedNoticeMapper (app layer): display-point translation of the 27 known HapiKit-emitted strings (ChatInteractor notices, dictation errors, window-sync warnings, files fallbacks, worktree-name validation) with verbatim passthrough for server-originated text; package stays language-free. - Deliberately untranslated (web parity / non-copy): event rows (EventPresentation — web renders presentation.ts verbatim), catalog option labels (Default/Auto/Sonnet/permission modes — web shows them verbatim), tool names, code-like titles (grep(pattern:), MCP:, Skill, Task), unit suffixes (B/KB/MB, m/h/d/w), decorative separators. - Language row wired: AppLanguage gains .system (follow system, new default); explicit picks write the AppleLanguages override, Follow system removes it; footer notes a relaunch applies it (no supported in-place SwiftUI locale swap). - project.pbxproj: zh-Hans added to knownRegions. Part 2 — #39 catalog dedupe (HapiProtocol): - NewSessionCatalogs.claudeModels/claudeEfforts now derive from ClaudeModels/ClaudeEfforts (single source shared with ModelCatalog); codexReasoningEfforts stays own data (web CODEX_REASONING_EFFORT_OPTIONS minus max); effortLabel delegates to ModelCatalog.capitalizedFirst. - New CatalogTests lock test for the derived option lists. Gate: bash ios/scripts/linux-test.sh — 459 tests green. |
||
|
|
6c7746d2b0 |
feat(ios): scratchlist (A-M4b)
Per-session parked notes mirroring the Android B-M4d feature: - HapiProtocol Models/ScratchlistApi.swift: wire types for entries CRUD (idempotent create via client entryId+createdAt), attachment metadata, limits (defaults from shared/src/scratchlistAttachments.ts), upload envelope, and the typed error codes (scratchlist_at_cap, scratchlist_attachment_too_large, scratchlist_attachment_in_use, ...). - Endpoints/ScratchlistEndpoints.swift: GET/POST/PUT/DELETE entries, limits, base64 upload, raw-bytes attachment fetch, attachment delete. - Stores/ScratchlistStore.swift: @MainActor @Observable per-session cache behind the SessionScratchlistStoring seam - open/release observation, 16 ms-coalesced refetch on the scratchlistUpdatedAt SSE signal, optimistic create/update/delete with surgical entryId reconcile + rollback (refresh preserves in-flight optimistic creates), 200-entry cap pre-check + hub 409 verdict, uploadsInFlight, cached limits with offline defaults, UTF-16 text clamp at 10000. - Stores/ScratchlistAttachmentGuard.swift: pure Fits/Downscale/Reject budget verdicts ported verbatim. - SessionListStore: onScratchlistInvalidation callback fired when a session patch carries scratchlistUpdatedAt (the seam the M4b comment reserved); HubSession wires it into the store and injects the store into ChatInteractor. - ChatInteractor (additive tail section): scratchlist store property, scratchlistCount badge seam, insertComposerText, parkComposerDraft (composer clears only after the hub accepts; at-cap/failed keep the draft). - Features/Scratchlist/: sheet off the chat toolbar's note icon with count badge - entry cards (4-line preview, relative age, authed thumbnails via NSCache loader, filename chips), edit sheet (PhotosPicker -> guard -> JPEG downscale -> upload spinner tile, remove, delete/save), full-screen viewer (GeneratedImage pattern), per-entry To composer, and Park current draft in the screen header (a deliberate placement divergence from Android's composer button - the composer UI is owned by the concurrent attachments package). iOS-only import step transcodes disallowed-but-decodable rasters (HEIC) to JPEG before the guard. - Tests (36, all transcribed from or mirroring the Android suites): store CRUD optimistic/rollback/at-cap/invalidation/upload/limits with canonical wire-body asserts + the SessionListStore seam test, 9 guard verdicts, 9 interactor park/insert/badge seam tests over a fake store. Verified on Linux via a dockerized Swift 6.0 scratch copy (FIFO HTTP performer): full HapiClient build under strict concurrency, 36/36 new tests green, SessionListStore 17/17 and ChatInteractor 29/29 suites green; app-side screen model + loader typechecked against stub frameworks; SwiftUI views and the CG-based import parse-checked (macOS CI compiles them). |
||
|
|
eed3dddb94 |
feat(ios): composer, permission actions, session config (A-M3ab)
Interaction layer turning the read-only chat into a working remote control,
mirroring the merged Android B-M3ab feature-for-feature (web authority where
the ports disagreed):
- ChatInteractor (HapiClient, fully swift-test covered): optimistic composer
sends (appendOptimistic -> POST -> status settle), queue-by-default with a
long-press Send&Steer intent while a turn is active, tap-to-retry on failed
rows (steer retries degrade to queue), per-session drafts
(UserDefaultsChatDrafts, hub-scoped keys, debounced + flushed on close).
- session_inactive (409) recovery: one POST /resume (current permissionMode)
then retry; a superseding session id seeds the new window
(MessageWindowControllers.seed), migrates the draft, retargets the
optimistic row and emits sessionSuperseded -- ChatView/HomeView replace the
navigation entry in place.
- Queued bar: uninvoked sends in web sort order with Cancel (optimistic
DELETE; invoked-race ingests the authoritative row as sent), Edit
(cancel + composer prefill, newer-draft guard) and Steer (invoked answers
reconcile a missed consume); single-flight per-row op guard.
reconcileQueuedState now runs on chat open and on session-pipe gap.
- Permission actions: flavor-exact bodies mirroring PermissionFooter.tsx --
claude {} / allowTools (Bash(cmd)) / mode:acceptEdits, codex-family
decision approved / approved_for_session / abort via deny -- plus
AskUserQuestion flat answers (option cards, Other free text, no-questions
fallback, cursor stable ids) and request_user_input nested answers
(user_note suffix, required validation); optimistic Resolving /
AlreadyHandled (404/409) overrides settled by the agentState patch.
ChatPipeline now re-attaches the window row's client status so failed
user rows actually render the retry affordance (web normalize.ts parity;
the Android reference misses this overlay).
- Session config sheet (toolbar gear): catalog-driven permission-mode picker
with tones, claude static model/effort catalogs (ModelCatalog port), codex
models via new GET /sessions/:id/codex-models endpoint + wire types with
per-model reasoning efforts; optimistic detail updates
(SessionListStore.updateDetailLocal, new) rolled forward to server truth
on error.
- Lifecycle: VisibilityReporter posts POST /api/visibility per tracked
handshake subscriptionId on scene-phase flips (404 prunes); ChatSession
exposes its subscriptionId and feeds the reporter; the global SSE pipe was
already HubSession-lifetime on iOS.
- Tests: ChatInteractorTests transcribes the Android interaction suite
against the real APIClient/AuthManager/SessionListStore/window registry
with only HTTP scripted -- canonical JSON bodies asserted byte-for-byte
(send/approve/deny/config), optimistic send happy/fail/retry, 409 resume
both id paths, cancel invoked-race, steer reconcile, edit prefill,
override lifecycle, config optimistic + rollback, drafts, abort.
|
||
|
|
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. |
||
|
|
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.
|
||
|
|
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. |