11 KiB
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 XcodeFchatiApp/Sources/FchatiApp/FchatiApp.swift— MenuBarExtra (.window style), 360pt wide, 480pt min height, three tabs (Chat / Pairing / Settings) with segmented pickerFchatiApp/FchatiApp.entitlements— sandbox + outgoing/incoming networkFchatiApp/Info.plist— bundle IDde.diyaa.fchati, version 0.1.0,LSUIElement = YES(hides Dock icon)FchatiApp/Tests/FchatiAppTests/AppTabTests.swift— verifies all three tabs and their icons existscripts/run-macos-app.sh— clean → test → release build → bundle → codesign → install to /Applications → open
Verified
swift buildpasses (debug)swift testpasses (1 test)- Script installs and opens
/Applications/Fchati.appsuccessfully
Fixes applied after agent review
- Added
LSUIElement = YEStoInfo.plist(was missing — app was appearing in Dock) - Added
--entitlementsflag tocodesignin 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 buildpasses fromFchatiApp/.
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-datawith the requiredfilefield 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 buildpasses fromFchatiApp/.swift testpasses 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 usingURLSessionWebSocketTask.- Connect sends authentication immediately, requires an
auth.okresponse within 10 seconds, and exposes authenticated incoming messages throughAsyncStream. - 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 buildpasses fromFchatiApp/.swift testpasses 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 buildpasses fromFchatiApp/.swift testpasses 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 buildpasses fromFchatiApp/.swift testpasses 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,.m4arecording 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.entitlementsandFchatiApp/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 buildpasses fromFchatiApp/.swift testpasses 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.peerNameand callsAppSession.unpair()to clear pairing credentials.
Verified
swift buildpasses fromFchatiApp/.swift testpasses with all current application tests.
Integration Update ✅
Completed by: Codex agent
What was updated
FchatiApp/Sources/FchatiApp/FchatiApp.swiftnow rendersPairingView,ChatView, andSettingsViewin 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 buildpasses fromFchatiApp/.swift testpasses 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 withFILE_TTL_DAYS.POST /admin/cleanup— protected manual cleanup endpoint. It accepts an optional JSONdaysvalue between 1 and 3650 and returns the number of deleted files and bytes.relay-server/.env.example— documents the retention setting and requiredADMIN_TOKENbearer secret.
Security
- The manual cleanup endpoint returns unavailable until
ADMIN_TOKENis configured. - Requests must use
Authorization: Bearer <ADMIN_TOKEN>.
Deployment configuration hardening
relay-server/.env.examplenow contains a non-secret placeholder forADMIN_TOKENinstead of a committed credential. Before deployment, generate and set a unique value in the untrackedrelay-server/.envfile.- Verified the Docker Compose configuration with the example environment and confirmed a local relay instance responds successfully from
GET /health. - Extended WebSocket serialization coverage to preserve the sender timestamp used for offline message delivery.
- The relay now defaults to the production Traefik certificate resolver (
myresolver) and allows a host-specific override throughTRAEFIK_CERT_RESOLVER. - Corrected the production hostname to
fchaty.diyaa.dein the relay route and macOS application HTTP and WebSocket clients. relay-server/Dockerfileinitializes all persistent-data paths as the unprivilegednodeuser, preventing pairing, queue, and upload write failures on newly created volumes.- The macOS app now has an always-visible quit control beside the top-level tabs, in addition to the existing Settings action.
Bug Fixes — Post-Review ✅
Applied by: Claude
Fix 1 — isSent detection in ChatView
File: FchatiApp/Sources/FchatiApp/Features/Chat/ChatView.swift
Problem: MessageBubble was deciding which side to render on using message.fromName == "You". If the user ever set their display name to "You" this would misidentify received messages as sent.
Fix: Compare message.from == KeychainStore.installationID — the installation UUID is unique per device and never changes.
Fix 2 — Message timestamps for queued/offline messages
Files:
FchatiApp/Sources/FchatiApp/Networking/WSClient.swift— addedsentAt: String?field toWSMessageFchatiApp/Sources/FchatiApp/Session/AppSession.swift— sender embeds ISO-8601 timestamp; receiver parses it
Problem: When a message was delivered from the offline queue, sentAt was set to Date() (arrival time). Messages sent at 11pm but delivered after a restart showed a wrong timestamp.
Fix: The sender now includes sentAt as an ISO-8601 string in the WebSocket payload. The relay passes it through unchanged (it's a dumb pipe). The receiver parses it; falls back to Date() only if the field is missing (old clients).
Pending
Nothing remaining in the codebase. Next step: deploy relay server to production.