Skip to content

fix: classify unmounted chat writes and Settings unavailability - #12916

Draft
undivisible wants to merge 110 commits into
v5from
cursor/chat-write-unavailable-2ab3
Draft

fix: classify unmounted chat writes and Settings unavailability#12916
undivisible wants to merge 110 commits into
v5from
cursor/chat-write-unavailable-2ab3

Conversation

@undivisible

@undivisible undivisible commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #12914. Production chat writes stay unadmitted, but they now use the same nested error grammar the chat client already parses. Browser Settings, conversation detail, empty libraries, capture ownership, and the PWA development selector stop looking like empty success or a generic retryable outage when the producer, coverage, or door is missing.

Granted chat.read conversation reads no longer dump every chat: row onto the first Listen-sequence page. Production GET /v1/conversations pages Listen and granted chat sessions together by updatedAt descending then id ascending, matching Worker readConversations / paginateConversations. Each page keeps the requested limit. The union cursor is a distinct HMAC policy from the Listen-sequence cursor, bound to the Listen revision and chat snapshot sequence. A Listen-sequence cursor cannot continue on the union path. The last item may be a chat: or Listen row; the position table stores that identity instead of a fake Listen sequence. Missing chat.read keeps the Listen sequence reader. Empty history still does not invent chat:chat-main. Migration 0058 is in the checksummed manifest.

HEAD continues after the union page is worker-green:

  • 2fea38f4d1 / 5e90b3d7e6 / 9c231cb7ca / 7fa813af6f / 51928e1989 / 59b1556925 / 34899a269d / 23d1fba7e8 / 0baa4b0950 / 8a71bd029b / d74db69efc / 7d8e9f0891 / 1941186ad7 / ee83ece2a4 / b3470ebc06 / 2e7944a829 / 8a6bc1b61e / f167d65b01 / 7cfd1cc9ed / 83dae78a57 / 05b6a00930 / d65bcf3fcb / f8ddd69f8b / 8d8a7186da / 78ede54213 / 9e4e6316f2 / 2360277217 / fc56024139 / 96328b6ff9 / f5637cf962 / 528e1e2d74 / a10392f4f6 / 0459fda85e / 97128c21d2 / 04524f08aa / 71247a5d85 / 874d87687d / c2650d7f4c / 8a1479b42b / 4be501c906 / 1faf7c91ac / 320d851045 / a5ae2b8d92 / 9c2280dda8 / a89cc8473a / 4d2acbb59a / 9e6fb129a1 / 9c47a5c78c / 9bf8c4beef / e2422d1543 / bff42ffece / 7ee8cd617f / 8429c828c3 / 3e1d36eec0 / ce722cbdc0 / 97c85dc5be / 1cb3a58fba / 0e507e9392 / 7614cb77f0 / 27cc153c21 / 27126e8ac6 / 51aa6489e7 / 9a64d9027f / 6ef4dcb9e4 / b90ec9df4e / 2d65d843c1 / c61c54164b / f25a1e7b6e / fba3e2150f are worker-green including Portable PostgreSQL 18.4.
  • Current HEAD e45def0560 is waiting on GitHub Validate backend worker, including Portable PostgreSQL 18.4.
  • 49f083f2cf failed GitHub Validate backend worker at Client contract regressions (run 34169977662): macOS Home memory rows preferred summary over title. Portable PostgreSQL was skipped. Repaired in 2d65d843c1.
  • Worker and production ownership without a receipt producer are nested {error:{code:"capture_ownership_unavailable",retryable:false,action:"none"}} without inventing a receipt. Production identity-verification and authorization-source 503s stay {error:{code:"unavailable"}} with retry-after: 1.
  • 9a64d9027f: wide Home search includes matching tasks instead of claiming "No results" over a complete conversation/memory miss. A task page that did not load still omits those rows.
  • 6ef4dcb9e4: wide Home search rows use the same visible conversation title and summary fallbacks as Conversations, compact Daily Recaps, and macOS Home, instead of blank gaps for untitled processing Listen/recording rows or empty summaries.
  • b90ec9df4e: compact Home Daily Recaps open the matching conversation detail instead of remaining display-only. View All still opens the conversation list.
  • 2d65d843c1: Memories page, macOS memory rows, and wide Home search use parsed visible memory text instead of blank bodies or raw entity: prefixes. Home list titles stay on the memory title; cards use the body. Search indexes that copy. Old /v3/memories still does not invent provenance digests.
  • c61c54164b: compact Home omits Open calls instead of routing that control to Conversations. Conversations stays on its tab. This does not invent a phone-calls producer.
  • f25a1e7b6e: compact Home and the Tasks page no longer label a disabled read-only task control Open. Incomplete read-only rows stay Task; completed read-only rows stay Completed; Complete/Reopen stay when writes are available. Tasks page Open task still opens the editor when writes and an edit handler are present.
  • fba3e2150f: compact Home Daily Recaps use the same Today/Yesterday/date grouping as Conversations instead of weekday-only labels, preferring startedAt then createdAt.
  • e45def0560: macOS Tasks no longer labels a disabled read-only task control Complete or Reopen. Incomplete read-only rows stay Task; completed read-only rows stay Completed; Complete/Reopen stay when writes are available.

Writes still need a source-owned entitlement producer. Settings identity/entitlements still need an owner-backed producer. This PR does not mount admission, project token claims, or invent completeness.

Why this boundary

Unmounted Hono {error:"not_found"} (string) left throwBackendError with code: unknown, so send failures looked generic. Nested non-retryable not_found is the chat client's existing grammar and does not auto-retry. A live Ask/Send control after that door still looks like a working composer. Settings 503 without a producer is unavailable, not an empty signed-in page, and Retry-After on that door advertised a transient outage. Overlaying chats onto listen-sequence pages duplicated rows, skipped listen items, or minted a cursor the listen position table cannot save. Combined Home search that ignores an incomplete task page still claims a complete miss. Worker and production ownership 503s with retryable: true or retry-after advertised a transient outage for a missing receipt producer. Native then reclassified other nested non-retryable ownership 503s as OMI_HTTP_TRANSPORT, which JavaScript treats as transient. Apps Retry that ignores nested retryable:false advertised a live Retry for a catalogue door that cannot succeed until a producer exists. Generation cancel that maps nested non-retryable 404/503 to OMI_HTTP_TRANSPORT makes Stop look like a blip. Desktop Apps gallery that ignores that same nested catalogue 503 claims a load blip instead of an unmounted catalogue. Memories later-page Load more that ignores nested retryable:false claims a load blip for a door that cannot page further. Native OMI_DEV_BACKEND_UNSUPPORTED history that falls through to connection copy keeps Check again on a closed door. Conversation-detail Load older that replaces the loaded page with a full-page error makes persisted messages look gone. A later-history error that flips the composer into the resting stage claims "I'm ready." / "Nothing's waiting on you." over a blocked older cursor. Later Rewind Load more after OMI_REWIND_UNAVAILABLE or OMI_REWIND_AUTH advertises another page against a capability door that cannot succeed. A chat-write latch that survives Old/New switch keeps Ask disabled on a plane that may have writes, and keeps the previous plane's transcript. Desktop Conversations that report "More conversations are available" without Load more advertises a first-page window the Mac cannot continue. Desktop Home that reports "More memories are available" without Load more advertises a first-page window with no Memories destination. Nested non-retryable task-write 503s and OMI_DEV_BACKEND_UNSUPPORTED throws that classify as retryable advertise Retry for a door that cannot succeed. Write-ops control_unavailable 503s must stay retryable. Native transcript GET throws of OMI_DEV_BACKEND_UNSUPPORTED or nested retryable:false that always set retryable: true advertised Check again and "Transcript could not be loaded." for a closed door. Closed capture and ownership doors that still say "Reconnect your Omi" or "reopen the app" advertise a recovery action that cannot succeed until a producer exists. Closed task-write doors that omit Retry but still say "This change was not accepted" claim the edit was invalid instead of unavailable. Native OMI_DEV_BACKEND_UNSUPPORTED send throws that already latch Ask but still say "Message not sent. Check your connection and try again" claim a blip instead of an unmounted write door. Nested non-retryable POST /v1/users/* writes that reuse saved-data copy or "Settings change could not be saved. Try again" claim a library/local blip instead of a closed account-setting door. Desktop Home mixed Conversations & memories that shows a generic Load more while only paging memories looks like it continues conversations. Nested non-retryable GET /v1/users/* slices that still say "This saved data is not available" claim a library door instead of a closed account-setting door. Conversations, Memories, and Rewind Load more controls that still say generic Load more after Home memories and Tasks named their noun look like they continue a mixed list. Conversation-detail and mobile Home Load older that still say generic Load older after desktop Home named Load earlier messages look like they continue a mixed transcript. Desktop Settings Account that falls back to "Signed in to Omi" when /v1/users/profile is nested non-retryable invents a signed-in profile line over a closed account door. Desktop Alerts that treat a still-loading account snapshot as recording storage and private cloud sync being unavailable invent a closed privacy door, and a page-level loadAccountSettings throw that leaves Account on "Loading account…" invents a load that cannot finish. Desktop Alerts that load /v1/users/training-data-opt-in but never render it hide a closed account-setting door as if that privacy control does not exist. Desktop AI & Automation that load /v1/users/developer/webhooks/status but never render it hide a closed developer door as if webhook status does not exist. Connectors My Apps that says owned apps are unavailable until the account profile loads after a nested non-retryable /v1/users/profile read invents a load that cannot finish. Connectors Installed that keeps catalogue enabled bits after a nested non-retryable /v1/apps/enabled read invents an installed list the enablement door cannot confirm. The macOS Apps gallery that drops that same enabled-list error claims "No apps are available" or uninstalled tiles over a closed enablement door. Mobile Home recaps, tasks, and mind map that say "Couldn’t load …" after a nested non-retryable library 503 claim a transient load blip instead of the mapped unavailable door. Home, Tasks, and desktop Tasks that say "Task editing is unavailable for this connection" while the task read is still loading or after that read failed invent a closed write door over an unsettled or failed library read. Explore and the macOS gallery that say "Not installed" / "Not connected" after a nested non-retryable /v1/apps/enabled invent a known-empty install state the enablement door cannot confirm. Wide Home search that says "Conversations are unavailable" / "Memories are unavailable" after a nested non-retryable library 503 claims a generic load blip instead of the mapped unavailable door. The macOS library banner that says "Some of your history isn't loaded yet" after every failed door is nested non-retryable claims a delay instead of a closed producer. Desktop Settings Current plan that omits already-loaded transcription seconds after a successful subscription read hides usage the mobile Settings Plan already shows. Desktop Settings Account that omits already-loaded company, job, and data protection after a successful profile read hides identity fields mobile Settings already shows. Desktop Settings Account that says "Signed in to Omi" when a successful profile read has no email invents a signed-in identity line over a known-empty email. Desktop Settings Account that omits already-loaded Account id and treats a null name as no Name row hides identity fields mobile Settings already shows. Wide Home search that says "Saved data is unavailable." above mapped saved-data copy when every library door failed claims a generic parent over a closed producer. Explore Install that stays live after a nested non-retryable /v1/apps/enable POST advertises a write door that cannot succeed. Settings Privacy Opt in / Update that stay live after a nested non-retryable recording-storage, training, or private-cloud-sync POST advertise a write door that cannot succeed. Conversations Refresh that stays live after a nested non-retryable first-page library read advertises a retry for a door that cannot succeed. Conversations, Memories, and Tasks that still say "More X are available" after a nested non-retryable later page omitted Load more advertise a continue door that cannot succeed. Empty searches on those same closed later pages that still say "More X are available" advertise that continue door as the miss title instead of "No loaded X match." Memories and Tasks first-page error states that showed only "Memories/Tasks unavailable" with no Refresh left retryable library failures without a recovery CTA, while a live Refresh after nested non-retryable copy would advertise a retry for a door that cannot succeed. Compact Home recaps, tasks, and mind map, and the compact Tasks tab, that showed only mapped unavailable copy with no Refresh left retryable library failures without a recovery CTA on the surface that actually hosts those errors. macOS Home that still says "More conversations/tasks are available" after a nested non-retryable later page omitted Load more on those destinations advertises a continue door that already closed. A Home search miss that still says "More conversations/memories/tasks are available" after that same closed later page advertises the continue door as the miss title instead of search-miss copy. Claiming "Nothing captured yet" / "No tasks yet" on an unfiltered empty Home that still has more items would invent an empty library over remaining pages. Wide Home search OutcomeStatus that still says "More conversations/memories are available" after that closed later page advertises the continue door in the results footer even when the empty title already omitted it. Compact Home recap and task strips, and the compact Tasks tab, that show loaded rows from an incomplete or has-more page without coverage copy look like a complete library. Wide Home search OutcomeStatus that reports only conversation and memory completeness hides task errors, incompleteness, and more-available copy behind a search that already treats tasks as part of the combined library. Compact Home mind map preview that always shows the same collapsed nodes without coverage copy looks like a complete map over an incomplete or has-more memory page. Wide Home search that hides that task OutcomeStatus whenever conversation and memory reads both failed hides task coverage behind a parent line that only describes conversations or memories. Wide Home search that says "Showing saved data. Could not refresh." when conversation and memory doors both failed invents a loaded search list over empty conversation/memory rows just because tasks still have items. Wide Home search that lists only conversations and memories claims "No results" over tasks that match the query. Wide Home search rows that keep raw empty conversation titles and summaries render untitled processing Listen/recording rows as blank gaps after Conversations, compact Daily Recaps, and macOS Home already use visible fallbacks. Compact Home Daily Recaps that stay display-only hide conversation detail the Conversations tab already opens for those same rows. Memories page, macOS memory rows, and wide Home search that keep raw empty bodies or namespaced entity: prefixes render blank or machine-id rows after canonical parseMemoryText already separates that prefix. A memory list helper that prefers summary over title hides a distinct Home headline. Compact Home Open calls that routes to Conversations invents a phone-calls door over a tab that already exists. Compact Home and Tasks page controls that say Open on a disabled read-only task invent an open action the write door cannot perform. Compact Home Daily Recaps that label cards with weekday-only dates make last-month conversations look like this week's Monday. macOS Tasks controls that say Complete on a disabled read-only task invent a write action the door cannot perform.

Verification

fba3e2150f GitHub Validate backend worker passed, including Portable PostgreSQL 18.4: https://github.com/BasedHardware/omi/actions/runs/34171480621

Current HEAD e45def0560 is waiting on that same worker gate, including Portable PostgreSQL 18.4. Local RN suite: bun run --cwd react-native test -- --runInBand (696 pass).

A GitHub “1 check success” notice is not worker-green. Remaining production-readiness is producer-gated (chat writes, Settings identity, DEV IAM, hardware). Linux cannot certify Apple Debug builds, ScreenCaptureKit, physical BLE, or iPad.

Linux cannot compile scripts/test-apple-auth. Docker/test:postgres is still unavailable in this Cloud VM.

Not verified here: applying migrations 55–58 to based-hardware-dev, Cloud Run deploy, live Firebase grants, Apple Debug builds, ScreenCaptureKit, physical BLE/iPad.

main and production deploys are untouched. The New-backend query-origin fix is already on v5 (dc0ca0524f / #12914).

Remaining (not this PR)

  • Chat POST/SSE/cancellation with a real entitlement producer
  • Settings identity/entitlement producers and attachment storage
  • Worker chat admission still uses STAGING_CHAT_LIMIT as a lifetime ceiling, not a subscription-period authority
  • DEV IAM / Cloud Run (no gcloud in this VM)
  • Hardware: physical BLE, iPad, live ScreenCaptureKit / macOS capture
  • Closed-door later-page more-available copy is now omitted on Conversations, Memories, and Tasks pages, including empty searches of loaded rows, on macOS Home after a closed later conversation or task page, on Home search misses after those closed doors, and on wide Home search OutcomeStatus including tasks. Compact Home loaded recaps/tasks/mind map and the compact Tasks tab now keep completeness and more-available copy, and omit more-available after a closed later page. The mind map preview stays collapsed. Wide Home search still shows task OutcomeStatus when conversation and memory doors both failed, and that parent line uses mapped copy instead of "Showing saved data. Could not refresh." when tasks still have rows. Wide Home search results include matching tasks and visible conversation title/summary fallbacks. Compact Home Daily Recaps open the matching conversation detail; View All still opens the list. Memories page, macOS memory rows, and wide Home search use parsed visible memory text instead of blank bodies or raw entity: prefixes; Home list titles stay on the memory title. Compact Home omits Open calls instead of routing that control to Conversations. Compact Home and the Tasks page no longer label a disabled read-only task control Open. Compact Home Daily Recaps use the same Today/Yesterday/date grouping as Conversations. macOS Tasks no longer labels a disabled read-only task control Complete or Reopen. First-page Home more-available without a Home pager stays. An unfiltered empty Home with remaining items still reports more-available. Non-mac Memories and Tasks first-page reads and compact Home/Tasks match Conversations Refresh omit/offer. Remaining production-readiness is producer-gated. Do not hide Home conversations/tasks more-available copy just because Home has no pager. Do not claim "No X yet" on an empty unfiltered page that still has more items. Do not mount chat writes or invent Settings identity. Do not add Refresh to missing Settings/Apps/Conversations content stages. Do not expand the collapsed Mind Map.
Open in Web Open in Cursor 

Review in cubic

Production chat POST, SSE, cancellation, and attachments now return nested
not_found without consulting grants or inventing admission. The app maps that
404 to honest send/history copy instead of a retryable outage, paginates
chat:chat-main history, and shows typed Settings 503 copy instead of an
invented profile.

Verification: backends/example-platform `bun run check:deployed` passed
(295 tests, 1 skipped real PostgreSQL without Docker). Focused React Native
Jest passed for chatClient, chatConversationHistory, pages, Conversations,
and DesktopApp.

Failure-Class: none
Authorized empty memory, conversation, and task pages with incomplete
coverage now show the existing completeness copy instead of "No X yet."
Complete empty pages and grant denials keep their current claims.

Verification: bun run --cwd react-native test -- src/ui/ui.test.tsx
src/pages/Memories.test.tsx src/pages/Conversations.test.tsx
src/pages/Tasks.test.tsx src/desktop/DesktopApp.test.tsx
(5 suites, 66 passed).

Failure-Class: none
Production memories declare unavailable accepted-work and STM coverage,
which the recall integrity contract maps to degraded completeness. Empty
libraries must show that copy instead of claiming nothing was captured.

Verification: bun run --cwd react-native test -- src/ui/ui.test.tsx
src/pages/Memories.test.tsx src/desktop/DesktopApp.test.tsx
(3 suites, 58 passed).

Failure-Class: none
Authorized empty pages with incomplete or degraded coverage now keep
that completeness visible instead of claiming a complete empty library.

Failure-Class: none
Production declares accepted-work and STM coverage unavailable. The
direct authorized memory read must emit degraded completeness with
projection_unavailable so empty libraries cannot look finished.

Verification: bun test apps/service/composition/direct-authorized-memory-read.test.ts
(7 pass). Wired into check:deployed.

Failure-Class: none
Native capture preflight uses GET /v1/device-sessions/ownership. The
production memory shell now registers that path explicitly so ownership
is not a UUID session 404. Unmounted shells stay a string not_found.

Verification: bun test apps/service/memory-service-app.test.ts
drivers/postgres/firebase-authorized-memory-service-app.test.ts
(16 pass).

Failure-Class: none
… proxy

Browser and local portable example-platform requests treated GET
/v1/chat-messages and completed recording transcript GET as
development_backend_unsupported, so mounted reads looked like a retryable
outage. The selector now forwards those GETs and still blocks writes,
SSE, attachments, ownership, and transcription POST.

Verification: bun test tests/proxy-boundary.test.ts (15 pass).

Failure-Class: none
Chat copy treated every HTTP 503 as a transient outage, so
development_backend_unsupported (retryable:false) looked like "Try again."
The client now honors the nested retryable flag and maps that selector
door to the existing unusable-backend copy. Omitted 503 retryable still
means retry.

Verification: bun run --cwd react-native test -- __tests__/chatClient.test.ts
(20 pass).

Failure-Class: none
Browser SSE treated every 503 as a disconnect and reconnected up to four
times, so development_backend_unsupported looked like a stream outage.
Non-retryable nested 503s now return immediately. Retryable 503s still
exhaust recovery without inventing a terminal frame.

Verification: bun test tests/web-adapter.test.ts tests/proxy-boundary.test.ts
(30 pass).

Failure-Class: none
Authenticated Worker GET /v1/settings used STAGING_DISPLAY_NAME and
STAGING_EMAIL as every account's profile. Settings now returns empty
identity strings so the page shows unavailable identity instead of a
shared staging name. D1 admission usage and the Worker chat limit stay
the signed-in entitlement.

Verification: bun test test/worker.contract.test.ts (56 pass);
bun x vitest run test/account.integration.test.ts (6 pass);
bun run --cwd react-native test -- src/pages/pages.test.tsx (22 pass).

Failure-Class: none
Capture uploads treated every HTTP 503 as transient, so ownership
unavailable and development_backend_unsupported looked like a retryable
outage. Library reads did the same for nested retryable:false. Nested
false is now honored; omitted 503 retryable still retries. Apple native
already refuses capture_ownership_unavailable without a transport retry.

Verification: bun run --cwd react-native test --
__tests__/deviceSessionClient.test.ts __tests__/desktopReadClient.test.ts
(72 pass).

Failure-Class: none
Recording transcript errors always offered Check again, so a blocked
transcribe POST or nested retryable:false 503 looked retryable. Those
doors now show unavailable copy without a reload control. Omitted 5xx
retryable still retries, including capture 500s.

Verification: bun run --cwd react-native test --
src/recordingTranscript.test.tsx __tests__/deviceSessionClient.test.ts
(42 pass).

Failure-Class: none
Conversation detail always offered Check again on history errors, so a
nested retryable:false 503 looked retryable. Those doors now show
unavailable copy without a reload control. Grant denials still offer
Check again. Nested retryable is honored instead of listing selector
codes.

Verification: bun run --cwd react-native test --
src/chatConversationHistory.test.tsx (6 pass).

Failure-Class: none
Saved-data Home Retry and the desktop Try again banner treated every
unavailable phase as retryable, so nested retryable:false library 503s
looked like a connection outage. Those doors now keep the unavailable
copy without a retry control. Mixed retryable failures and unknown
outcomes still retry.

Verification: bun run --cwd react-native test --
src/chatConversationHistory.test.tsx src/desktop/DesktopApp.test.tsx
__tests__/desktopReadClient.test.ts (90 pass).

Failure-Class: none
Browser Settings Retry, conversation/task Load more, and Home older-chat
load treated nested retryable:false as a transient outage. Those doors
now keep unavailable copy without a retry control. Omitted 503 retryable
and grant denials still retry. Loaded first pages stay on screen.

Verification: bun run --cwd react-native test --
src/pages/pages.test.tsx src/chatConversationHistory.test.tsx
__tests__/useDesktopReads.test.tsx __tests__/chatClient.test.ts
src/desktop/DesktopApp.test.tsx (112 pass).

Failure-Class: none
Worker GET /v1/chat-messages mixed every account session into Home and
left named chat: conversations as title-only detail. Default history is
now the same main-session grouping as chat:chat-main; optional
chatSessionId returns that named session only. Production still rejects
the extra query key instead of serving main history for another session.

Verification: bun test test/worker.contract.test.ts (56 pass);
bun x vitest run test/d1-chat.integration.test.ts (19 pass);
bun test apps/service/routes/chat-messages.test.ts (12 pass);
bun run --cwd react-native test -- src/chatConversationHistory.test.tsx
__tests__/chatClient.test.ts (29 pass).

Failure-Class: none
Worker GET /v1/conversations and local task GET returned a 200 empty
complete library when D1 was missing, so a mis-bound Worker looked like
an empty account. Those handlers now return retryable 503 like chat
history. Authenticated fetch of an unready Worker still uses the same
predicate as /ready. Empty D1 with a bound database still returns an
honest empty page.

Verification: bun test test/worker.contract.test.ts (57 pass).

Failure-Class: none
Production Listen and processing recordings can arrive with an empty
title. Mobile conversation rows and detail used that empty string, so
the list looked blank. They now use the same visible fallback desktop
already showed, and search/index that label.

Verification: bun run --cwd react-native test --
src/pages/Conversations.test.tsx __tests__/desktopReadClient.test.ts
src/desktop/DesktopApp.test.tsx (89 pass).

Failure-Class: none
Android and Apple example-platform selectors still wrapped GET settings,
chat history, and UUID transcript as development_backend_unsupported
after the PWA proxy already forwarded those mounted doors. Native
allowlists now match the JS proxy. Writes, SSE, ownership, and
transcription POST stay blocked.

Verification: scripts/test-android-http passed, including the new
settings/chat/transcript allow and deny cases. bun run --cwd react-native
test -- __tests__/macOSNativeBoundary.test.ts (29 pass). Apple
OmiBackendDisposalTest was updated to the same cases but cannot compile
on Linux.

Failure-Class: none
Empty production Listen/recording summaries still rendered as a blank
list subtitle and detail gap after titles gained a visible fallback.
Mobile, desktop rows, and search now use the same class of copy.

Verification: bun run --cwd react-native test --
src/pages/Conversations.test.tsx __tests__/desktopReadClient.test.ts
src/desktop/DesktopApp.test.tsx (90 pass).

Failure-Class: none
Conversation detail only loads recording transcripts for omi recording
ids and chat history for chat: ids. Listen and microphone rows left a
blank gap after the overview. They now say a full transcript is not
available yet instead of inventing a Listen producer. Search indexes
the same visible title and summary fallbacks.

Verification: bun run --cwd react-native test --
src/pages/Conversations.test.tsx __tests__/desktopReadClient.test.ts
src/desktop/DesktopApp.test.tsx src/recordingTranscript.test.tsx
src/chatConversationHistory.test.tsx __tests__/legacyOmiReads.test.ts
(129 pass).

Failure-Class: none
The mobile Apps tab without ConnectorsPage content said "No apps
connected yet," which looked like a successful empty catalogue.
Missing apps content now reports unavailable, matching missing
conversation content. Mounted ConnectorsPage is unchanged.

Verification: bun run --cwd react-native test --
src/mobile/MobileAppSurface.test.tsx (20 pass).

Failure-Class: none
The mobile Settings tab without SettingsPage content rendered a blank
stage. Missing settings content now reports unavailable, matching
missing conversation and apps content. The unused Worker emptyPage
helper that returned a 200 complete empty library is removed so unbound
reads cannot be wired back to silent empty success.

Verification: bun run --cwd react-native test --
src/mobile/MobileAppSurface.test.tsx (22 pass). bun test --cwd
apps/backend-worker test/worker.contract.test.ts (57 pass).

Failure-Class: none
Home Daily Recaps used raw conversation titles and "No recaps yet" for
any empty ready list. Untitled processing rows stayed blank, and
incomplete or degraded conversation pages looked like an empty recap
library. Recaps now use conversationDisplayTitle and emptyLibraryCopy.

Verification: bun run --cwd react-native test --
src/mobile/MobileAppSurface.test.tsx (25 pass).

Failure-Class: none
A ready empty Home or Tasks-tab list always said "Nothing's waiting on
you," even when the task page was incomplete or degraded. Those
surfaces now use the same empty-library completeness copy as the
shared Tasks page and keep the existing complete-empty claim.

Verification: bun run --cwd react-native test --
src/mobile/MobileAppSurface.test.tsx (28 pass).

Failure-Class: none
Production rejected chatSessionId so a named session could not be
served as main history, but listing those ids would 400 on open.
GET now filters one optional chatSessionId the same way Worker does,
the history cursor binds that session, and conversation composition
lists named chat: rows only after that GET can serve them.

Verification: bun test from backends/example-platform on
apps/service/routes/chat-messages.test.ts
apps/service/chat/history-cursor.test.ts
apps/service/composition/chat-conversation-sessions.test.ts
drivers/sqlite/service-stores/chat-stores.test.ts
drivers/postgres/chat-read-repository.test.ts
drivers/postgres/migrations/manifest.test.ts
drivers/postgres/firebase-chat-read-runtime.test.ts
(38 pass). bun run check:deployed passed (307 pass, 1 skip:
real PostgreSQL 18.4 without Docker). GitHub Validate backend
worker remains the real PostgreSQL 18.4 proof.

Failure-Class: none
…lete

Home search treated an empty match as "No results" / "Nothing captured
matches this search" even when conversation or memory pages were
incomplete or degraded. Completeness copy wins; a complete empty match
keeps the existing search-miss claim.

Verification: bun run --cwd react-native test -- src/ui/ui.test.tsx
src/desktop/DesktopApp.test.tsx (56 pass). AppOrchestrator macOS/mobile
and MobileAppSurface suites also passed (55 pass).

Failure-Class: none
Desktop Home search filtered unmatched tasks out of the list and then
used the empty-library claim "No tasks yet." Completeness copy still
wins; a complete unmatched search now says tasks do not match.

Verification: bun run --cwd react-native test --
src/desktop/DesktopApp.test.tsx (40 pass).

Failure-Class: none
Rewind showed "No captures saved yet" / "No captures match this search"
for an empty page even when a continuation cursor remained, next to
Load more. Empty claims now require a terminal page.

Verification: bun run --cwd react-native test --
src/desktop/DesktopRewind.test.tsx (17 pass).

Failure-Class: none
The conversations, tasks, and memories matrix rows now match the
Home-search coverage copy and Rewind terminal-page empty claims
already on this branch.

Verification: documentation-only; no runtime behavior change.

Failure-Class: none
Desktop Settings Current plan already showed plan and status from a
successful subscription read, but omitted transcription seconds that
mobile Settings Plan already displays. Current plan now includes those
used/limit units when both values are present. It does not invent 0 / 0
when those fields are null.

Verification: bun run --cwd react-native test -- src/desktop/DesktopApp.test.tsx (63 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on 1faf7c9 (run 34157968656).

Failure-Class: none
… Settings

Desktop Settings Account already showed name from a successful profile
read, but omitted company, job, and data protection that mobile Settings
already displays when those fields are present. Account now shows those
rows. It does not invent empty rows when they are null.

Verification: bun run --cwd react-native test -- src/desktop/DesktopApp.test.tsx (64 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on 320d851 (run 34158353196).

Failure-Class: none
Desktop Settings Account already avoided Signed in to Omi after a nested
non-retryable profile read, but a successful profile with a null email
still used that invented identity line. Account now uses Email not set
on this account, matching mobile Settings.

Verification: bun run --cwd react-native test -- src/desktop/DesktopApp.test.tsx (65 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on a5ae2b8 (run 34158706505).

Failure-Class: none
Desktop Settings Account already showed email from a successful profile
read, but omitted Account id and treated a null name as no Name row.
Account now shows those fields, using Name not set on this account when
name is null, matching mobile Settings. Nested non-retryable profile
reads still omit those rows.

Verification: bun run --cwd react-native test -- src/desktop/DesktopApp.test.tsx (66 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on 9c2280d (run 34158988868).

Failure-Class: none
…ailed

Wide Home search still said Saved data is unavailable above the mapped
saved-data copy when every conversation and memory read was nested
non-retryable. That parent line now uses the mapped copy and still
omits Retry. Mixed library failures keep the generic parent with
per-library OutcomeStatus copy.

Verification: bun run --cwd react-native test -- src/ui/ui.test.tsx src/desktop/DesktopApp.test.tsx (84 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on a89cc84 (run 34159362860).

Failure-Class: none
Explore kept a live Install control after nested non-retryable
/v1/apps/enable, which advertised a write door that cannot succeed.
Install now stays disabled and uses Apps-unavailable copy. Nested
non-retryable /v1/apps/enabled reads still keep Install because enable
POST may still work.

Verification: bun run --cwd react-native test -- src/pages/pages.test.tsx (33 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on 4d2acbb (run 34159766332).

Failure-Class: none
Settings Privacy and macOS Alerts & Privacy kept live Opt in / Update
controls after nested non-retryable recording-storage, training, or
private-cloud-sync POSTs. Those controls now stay disabled and use
account-setting unavailable copy. Each write door latches independently.
Retryable 503s still keep the control live.

Verification: bun run --cwd react-native test -- src/pages/pages.test.tsx src/desktop/DesktopApp.test.tsx (101 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on 9e6fb12 (run 34160225491).

Failure-Class: none
…read

Conversations kept a live Refresh control after a nested non-retryable
first-page library 503, which advertised a retry for a door that cannot
succeed. Refresh is now omitted for that mapped unavailable copy,
matching the macOS Home banner. Retryable first-page failures still
offer Refresh.

Verification: bun run --cwd react-native test -- src/pages/Conversations.test.tsx src/recordingTranscript.test.tsx src/chatConversationHistory.test.tsx (37 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on 9c47a5c (run 34160771459).

Failure-Class: none
Conversations and Memories still said More conversations/memories are
available after a nested non-retryable later page omitted Load more,
which advertised a continue door that cannot succeed. That status line
is now omitted when the later-page notice is mapped unavailable copy.
Retryable later-page failures still keep Load more and the more-available
copy. Home conversations and tasks still report more-available without
a Home pager.

Verification: bun run --cwd react-native test -- src/ui/ui.test.tsx src/desktop/DesktopApp.test.tsx src/pages/Memories.test.tsx src/pages/Conversations.test.tsx (102 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on 9bf8c4b (run 34161148773).

Failure-Class: none
… page

Tasks still said More tasks are available after a nested non-retryable
later page omitted Load more, which advertised a continue door that
cannot succeed. That status line is now omitted when the later-page
notice is mapped unavailable copy. Retryable later-page failures still
keep Load more and the more-available copy. Home tasks still report
more-available without a Home pager.

Verification: bun run --cwd react-native test -- src/pages/Tasks.test.tsx src/desktop/DesktopApp.test.tsx src/ui/ui.test.tsx (99 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on e2422d1 (run 34161784284).

Failure-Class: none
Conversations, Memories, and Tasks empty searches still said More X are
available after a nested non-retryable later page omitted Load more.
That empty title advertised a continue door that cannot succeed. Those
misses now use No loaded X match. An empty unfiltered page with remaining
items still reports more-available instead of claiming No X yet.

Verification: bun run --cwd react-native test -- src/ui/ui.test.tsx src/pages/Tasks.test.tsx src/pages/Conversations.test.tsx src/pages/Memories.test.tsx (49 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on bff42ff (run 34162200580).

Failure-Class: none
Conversations already omits Refresh after nested non-retryable first-page
library reads and still offers it on retryable failures. Memories and Tasks
first-page error states had no recovery CTA. Wire the same Refresh omit
for nested non-retryable copy and keep Refresh for retryable first-page
failures on the non-mac Memories and Tasks pages.

Verification: bun run --cwd react-native test -- src/pages/Memories.test.tsx src/pages/Tasks.test.tsx src/pages/Conversations.test.tsx (34 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on 7ee8cd6 (run 34162607103).

Failure-Class: none
Compact Home recaps, tasks, and mind map, and the compact Tasks tab, had
no recovery CTA on retryable first-page library failures. Refresh is now
offered there and omitted after nested non-retryable unavailable copy.
Missing Settings/Apps/Conversations content stages still have no Refresh.

Verification: bun run --cwd react-native test -- src/mobile/MobileAppSurface.test.tsx src/pages/Memories.test.tsx src/pages/Tasks.test.tsx (67 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on 8429c82 (run 34163473853).

Failure-Class: none
…or task page

macOS Home still said "More conversations/tasks are available" after a
nested non-retryable later page omitted Load more on those destinations.
Home now omits that more-available copy for the closed door. First-page
Home more-available without a Home pager is unchanged.

Verification: bun run --cwd react-native test -- src/desktop/DesktopApp.test.tsx (71 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on 3e1d36e (run 34163871299).

Failure-Class: none
Wide Home and macOS Home still titled a search miss as more-available after
a nested non-retryable later page omitted Load more. Those misses now use
search-miss copy. An unfiltered empty Home with remaining items still
reports more-available instead of claiming nothing is captured yet.

Verification: bun run --cwd react-native test -- src/ui/ui.test.tsx src/desktop/DesktopApp.test.tsx src/pages/Conversations.test.tsx src/pages/Memories.test.tsx src/pages/Tasks.test.tsx (128 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on ce722cb (run 34164338081).

Failure-Class: none
…er page

Wide Home search still showed footer "More conversations/memories are
available" after a nested non-retryable later page omitted Load more.
OutcomeStatus now honors that closed continue door. First-page Home
more-available without a closed door is unchanged.

Verification: bun run --cwd react-native test -- src/ui/ui.test.tsx src/desktop/DesktopApp.test.tsx (95 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on 97c85dc (run 34164818232).

Failure-Class: none
Compact Home recap and task strips, and the compact Tasks tab, looked
complete when the loaded page was incomplete or had more items. Those
surfaces now keep the same completeness and more-available copy. Nested
non-retryable later pages omit more-available copy.

Verification: bun run --cwd react-native test -- src/mobile/MobileAppSurface.test.tsx src/ui/ui.test.tsx (68 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on 1cb3a58 (run 34165112816).

Failure-Class: none
Wide Home search footer reported conversation and memory completeness
and hid task errors, incompleteness, and more-available copy. Search
OutcomeStatus now includes the task page and omits more-available after
a nested non-retryable later task page.

Verification: bun run --cwd react-native test -- src/ui/ui.test.tsx (20 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on 0e507e9 (run 34165452989).

Failure-Class: none
Compact Home mind map preview looked complete when the loaded memory
page was incomplete or had more items. The collapsed preview now keeps
completeness and more-available copy. Nested non-retryable later pages
omit more-available copy.

Verification: bun run --cwd react-native test -- src/mobile/MobileAppSurface.test.tsx src/ui/ui.test.tsx (70 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on 7614cb7 (run 34165750714).

Failure-Class: none
Wide Home search hid task OutcomeStatus whenever conversation and
memory reads both failed. Task completeness, more-available copy, and
mapped task errors now still show. Conversation and memory footer rows
stay behind the parent recovery line.

Verification: bun run --cwd react-native test -- src/ui/ui.test.tsx __tests__/useDesktopReads.test.tsx (49 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on 27cc153 (run 34165967212).

Failure-Class: none
…d memories failed

Wide Home search said "Showing saved data. Could not refresh." when
conversation and memory reads both failed just because tasks still had
rows. The search list is conversations and memories, so that parent
line now uses the mapped conversation/memory copy. Mixed library
failures still keep the generic refresh parent.

Verification: bun run --cwd react-native test -- src/ui/ui.test.tsx (21 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on 27126e8 (run 34166715948).

Failure-Class: none
Wide Home search listed only conversations and memories, so a query
that matched saved tasks could still say "No results". Matching task
rows now join that list. A task page that did not load still omits
those rows. macOS Home keeps tasks in its own section.

Verification: bun run --cwd react-native test -- src/ui/ui.test.tsx __tests__/desktopReadClient.test.ts (77 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on 51aa648 (run 34167259841).

Failure-Class: none
Wide Home search listed untitled processing Listen/recording rows and
empty summaries as blank gaps, even though Conversations, compact Daily
Recaps, and macOS Home already use visible title and summary fallbacks.
Those search rows now use the same copy. Search still indexes those
fallbacks.

Verification: bun run --cwd react-native test -- src/ui/ui.test.tsx (24 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on 9a64d90 (run 34167758113).

Failure-Class: none
Compact Home Daily Recap cards were display-only even though the
Conversations tab already opens those same rows. Tapping a recap now
opens that conversation detail. View All still opens the conversation
list.

Verification: bun run --cwd react-native test -- src/ui/ui.test.tsx src/mobile/MobileAppSurface.test.tsx src/pages/Conversations.test.tsx (86 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on 6ef4dcb (run 34168682309).

Failure-Class: none
Memories page, macOS memory rows, and wide Home search rendered empty
bodies as blank gaps, and old /v3/memories namespaced entity: prefixes
as raw titles. Those surfaces now use the same parsed visible text.
Search indexes that copy.

Verification: bun run --cwd react-native test -- src/ui/ui.test.tsx src/pages/Memories.test.tsx __tests__/desktopReadClient.test.ts __tests__/legacyOmiReads.test.ts (113 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on b90ec9d (run 34169316308).

Failure-Class: none
macOS Home memory rows used a helper that preferred summary over title,
so a memory with a headline and a longer body hid that headline. List
titles now use the parsed title; memory cards still use the parsed body.
Empty bodies still show visible fallback copy.

Verification: bun run --cwd react-native test -- --runInBand (693 pass) && bun run --cwd pwa test (50 pass). GitHub Validate backend worker failed Client contract regressions on 49f083f (run 34169977662).

Failure-Class: none
Compact Home Open calls routed to Conversations, which is already a tab
and is not a phone-calls producer. That control is omitted until a calls
door exists. Conversations stays on its tab.

Verification: bun run --cwd react-native test -- --runInBand (695 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on 2d65d84 (run 34170252733).

Failure-Class: none
Compact Home and the Tasks page labeled incomplete read-only tasks Open
on controls that cannot open. Incomplete read-only rows stay Task;
completed read-only rows stay Completed; Complete/Reopen stay when
writes are available. Tasks page Open task still opens the editor when
writes and an edit handler are present.

Verification: bun run --cwd react-native test -- --runInBand (695 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on c61c541 (run 34170564410).

Failure-Class: none
Compact Home Daily Recaps labeled cards with weekday-only dates, so a
conversation from last month could read as Monday. Recaps now use the
same Today/Yesterday/date grouping as Conversations, preferring
startedAt then createdAt.

Verification: bun run --cwd react-native test -- --runInBand (696 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on f25a1e7 (run 34171113179).

Failure-Class: none
macOS Tasks labeled incomplete read-only rows Complete on a disabled
control. Incomplete read-only rows stay Task; completed read-only rows
stay Completed; Complete/Reopen stay when writes are available.

Verification: bun run --cwd react-native test -- --runInBand (696 pass). GitHub Validate backend worker including Portable PostgreSQL 18.4 passed on fba3e21 (run 34171480621).

Failure-Class: none
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants