mirror of
https://github.com/wu736139669/hapi.git
synced 2026-10-15 20:29:55 +00:00
19bb4f6bad73d556d760b994a3f4dfbd7622bfd1
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5773ca2d93 |
merge: A-M4b iOS scratchlist
# Conflicts: # ios/Hapi/Features/Chat/ChatModel.swift # ios/Hapi/Features/Chat/ChatView.swift # ios/README.md |
||
|
|
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). |
||
|
|
05d063cc33 |
feat(ios): composer attachments + dictation (A-M3f)
Attachments (Android B-M3f semantics ported verbatim): - HapiClient/Attachments/AttachmentPolicy — pure plan matrix (>4 MB recompressible image -> 2048 px JPEG q85 with .jpg rename, 50 MB hard reject, 192 MB image read cap, 512 px q80 previewUrl data-URL thumbs, data-URL parse/round-trip). - HapiClient/Attachments/ComposerAttachments — upload-on-pick tray over an AttachmentUploading seam (APIClient conforms): uploading/ready/failed chips, retained payload for retry, remove -> best-effort delete, mid-upload removal deletes the orphan on completion, consume() -> AttachmentMetadata with JPEG data-URL previewUrl, discardAllDetached + deinit orphan cleanup (Android onCleared analogue). - ChatInteractor: tray ownership, unsettled chips refuse the send with a notice, attachments-only sends post empty text, optimistic rows carry the metadata, appendDictatedText/postNotice/discardAttachments. - App: AttachmentPreparer (capped security-scoped reads, ImageIO downscale/encode with EXIF transform, HEIC-undecodable fallback), PhotosPicker multi (videos via FileRepresentation temp files), UIImagePickerController camera capture, fileImporter; composer chip row (thumb/spinner/tap-to-retry/remove) with attachment-aware send gating; user bubbles upgrade chips to off-main-decoded previewUrl thumbnails (web-sent attachments included). Dictation (Android B-M3ce port): - HapiProtocol/Models/VoiceApi — TranscriptionResponse, TranscriptionProvidersResponse, TranscriptionProviderInfo. - HapiClient/Endpoints/VoiceEndpoints — GET /api/voice/transcription/providers + multipart POST /api/voice/transcription (file/provider/mode/language, Android part order) over MultipartFormData; DictationTranscribing conformance. - HapiClient/Voice/DictationController — idle/starting/recording/ transcribing, transcribed/noProvider/error events, provider memoized (first standard-capable entry), appendTranscript port. - App: AVAudioRecorderDictation (m4a/AAC mono 44.1 kHz 96 kbps, session activate/deactivate), mic button + recording chip (elapsed + cancel), record-permission request via AVAudioApplication. Info.plist gains NSMicrophoneUsageDescription; the camera string now covers attachment capture (modern PhotosPicker needs no photo-library permission). Tests: policy matrix, tray over the real client with exact base64 upload bodies + gated in-flight scenarios, dictation controller suite with fake recorder/transport, voice endpoint request shapes, and the interactor attachment-send matrix transcribed from the Android VM tests (wire bodies byte-for-byte). |
||
|
|
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.
|