# Implementation Status ## TASK-01 — Swift Package + App Shell ✅ **Completed by:** Codex agent **Fixes applied by:** Claude (LSUIElement, entitlements in codesign) ### What was built - `FchatiApp/Package.swift` — SPM package, macOS 14+, no Xcode - `FchatiApp/Sources/FchatiApp/FchatiApp.swift` — MenuBarExtra (.window style), 360pt wide, 480pt min height, three tabs (Chat / Pairing / Settings) with segmented picker - `FchatiApp/FchatiApp.entitlements` — sandbox + outgoing/incoming network - `FchatiApp/Info.plist` — bundle ID `de.diyaa.fchati`, version 0.1.0, `LSUIElement = YES` (hides Dock icon) - `FchatiApp/Tests/FchatiAppTests/AppTabTests.swift` — verifies all three tabs and their icons exist - `scripts/run-macos-app.sh` — clean → test → release build → bundle → codesign → install to /Applications → open ### Verified - `swift build` passes (debug) - `swift test` passes (1 test) - Script installs and opens `/Applications/Fchati.app` successfully ### Fixes applied after agent review - Added `LSUIElement = YES` to `Info.plist` (was missing — app was appearing in Dock) - Added `--entitlements` flag to `codesign` in the build script (sandbox permissions were not being applied) --- ## TASK-02 — KeychainStore ✅ **Completed by:** Codex agent ### What was built - `FchatiApp/Sources/FchatiApp/Storage/KeychainStore.swift` — direct Security.framework wrapper for the installation ID, auth token, peer ID, and peer name. - The installation ID is created once as a UUID and persisted in Keychain. - Token and peer properties support read, update, and deletion through Swift property syntax. - `clearAll()` removes pairing credentials while preserving the installation ID. ### Verified - `swift build` passes from `FchatiApp/`. ## TASK-03 — RelayAPI (HTTP) ✅ **Completed by:** Codex agent ### What was built - `FchatiApp/Sources/FchatiApp/Networking/RelayAPI.swift` — actor-based HTTP client for pairing creation, pairing join, file upload, and file download. - Pairing requests use JSON and decode the exact relay response payloads. - File uploads use `multipart/form-data` with the required `file` field and bearer-token authorization. - Non-success HTTP responses, transport failures, and response decoding failures map to `RelayAPIError`. - `FchatiApp/Tests/FchatiAppTests/RelayAPIResponseTests.swift` — response-decoding coverage for relay pairing and upload payloads. ### Verified - `swift build` passes from `FchatiApp/`. - `swift test` passes with the relay response tests. ## TASK-04 — WSClient (WebSocket) ✅ **Completed by:** Codex agent ### What was built - `FchatiApp/Sources/FchatiApp/Networking/WSClient.swift` — actor-based WebSocket client using `URLSessionWebSocketTask`. - Connect sends authentication immediately, requires an `auth.ok` response within 10 seconds, and exposes authenticated incoming messages through `AsyncStream`. - The client sends a ping every 30 seconds and reconnects after unexpected disconnects with delays of 2, 4, 8, 16, and 32 seconds. - Explicit disconnects cancel reconnect attempts and retain the incoming stream for a later connection. - `FchatiApp/Tests/FchatiAppTests/WSMessageTests.swift` — serialization coverage for relay WebSocket messages. ### Verified - `swift build` passes from `FchatiApp/`. - `swift test` passes with the WebSocket message test. ## TASK-05 — AppSession (State Machine) ✅ **Completed by:** Codex agent ### What was built - `FchatiApp/Sources/FchatiApp/Session/AppSession.swift` — main-actor application state for pairing, session restoration, message sending, file sending, read receipts, and incoming message handling. - Existing credentials restore the WebSocket connection at launch. - Pairing and message failures update the published state with an error description. - File uploads are converted to attachment metadata and forwarded through relay chat messages. ## TASK-08 — MessageStore (Local Persistence) ✅ **Completed by:** Codex agent ### What was built - `FchatiApp/Sources/FchatiApp/Storage/MessageStore.swift` — actor-backed JSON persistence at the required Application Support location. - Message writes are atomic, deduplicated by message ID, sorted by timestamp, and retain read-receipt state. - `FchatiApp/Tests/FchatiAppTests/MessageStoreModelTests.swift` — serialization coverage for messages and attachment metadata. ## TASK-10 — New Message Notifications ✅ **Completed by:** Codex agent ### What was built - `FchatiApp/Sources/FchatiApp/Notifications/NotificationManager.swift` — notification permission request and background message notification delivery. - Notifications use the sender name, limit the preview to 100 characters, and are suppressed while the app is active. ### Verified - `swift build` passes from `FchatiApp/`. - `swift test` passes with session storage coverage. ## TASK-06 — PairingView ✅ **Completed by:** Codex agent ### What was built - `FchatiApp/Sources/FchatiApp/Features/Pairing/PairingView.swift` — create and join pairing screens with a segmented mode selector, code copy action, loading states, and inline errors. - `FchatiApp/Sources/FchatiApp/Features/Pairing/PairingViewModel.swift` — pairing request orchestration, code normalization, and display-state management. - Join codes are uppercased automatically and accept values with or without the `FCHT-` prefix. - A connection callback allows the app shell to switch to the chat screen when pairing succeeds. - `FchatiApp/Tests/FchatiAppTests/PairingViewModelTests.swift` — normalization coverage for pairing code input. ### Verified - `swift build` passes from `FchatiApp/`. - `swift test` passes with pairing view-model coverage. ## TASK-07 — ChatView ✅ **Completed by:** Codex agent ### What was built - `FchatiApp/Sources/FchatiApp/Features/Chat/ChatView.swift` — scrollable chat UI with automatic scrolling, text submission on Return, file selection, and hold-to-record voice input. - `FchatiApp/Sources/FchatiApp/Features/Chat/ChatViewModel.swift` — message and file sending, `.m4a` recording lifecycle, temporary recording cleanup, and inline send errors. - `FchatiApp/Sources/FchatiApp/Features/Chat/MessageBubble.swift` — left and right message bubbles, Markdown body rendering, attachment metadata, and timestamps. - `FchatiApp/FchatiApp.entitlements` and `FchatiApp/Info.plist` — microphone sandbox entitlement and privacy usage description required for voice recording. - `FchatiApp/Tests/FchatiAppTests/ChatMessageBubbleTests.swift` — Markdown rendering coverage for message bodies. ### Verified - `swift build` passes from `FchatiApp/`. - `swift test` passes with chat view coverage. ## TASK-09 — SettingsView ✅ **Completed by:** Codex agent ### What was built - `FchatiApp/Sources/FchatiApp/Features/Settings/SettingsView.swift` — editable display name, published session connection state, destructive unpair action, and bundled application version display. - The view reads and writes the display name through `KeychainStore.peerName` and calls `AppSession.unpair()` to clear pairing credentials. ### Verified - `swift build` passes from `FchatiApp/`. - `swift test` passes with all current application tests. ## Integration Update ✅ **Completed by:** Codex agent ### What was updated - `FchatiApp/Sources/FchatiApp/FchatiApp.swift` now renders `PairingView`, `ChatView`, and `SettingsView` in the menu-bar tabs instead of placeholder content. - The initial tab is pairing for first-time setup. - A successful pairing switches the selected tab to chat. ### Verified - `swift build` passes from `FchatiApp/`. - `swift test` passes with all current application tests. ## Server File Cleanup ✅ **Completed by:** Codex agent ### What was built - `relay-server/src/index.js` — removes expired uploaded files at startup and once every 24 hours. The retention period defaults to 30 days and is configurable with `FILE_TTL_DAYS`. - `POST /admin/cleanup` — protected manual cleanup endpoint. It accepts an optional JSON `days` value between 1 and 3650 and returns the number of deleted files and bytes. - `relay-server/.env.example` — documents the retention setting and required `ADMIN_TOKEN` bearer secret. ### Security - The manual cleanup endpoint returns unavailable until `ADMIN_TOKEN` is configured. - Requests must use `Authorization: Bearer `. ## Pending See `TASKS.md` for all remaining tasks.