Repository navigation
feat(mobile): define storefront presentation contract and paywall design - #21354
Conversation
…#21363) * fix: keep Anthropic BYOK notes from failing conversation processing The notes worker-loop clone only accepted ChatOpenAI, so a provider-switched Anthropic key raised TypeError and the conversation returned HTTP 500. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: keep conversation notes when the model leaves quotes unescaped JsonOutputParser drops the whole summary when a notes completion quotes a phrase inside a JSON string. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: stop reporting rejected chat files as server errors A provider 4xx on a chat attachment is a typed client outcome. Logging it with a traceback made Cloud Error Reporting group chat_file.py:150 as an outage. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: let isolated file-tool tests catch provider file rejections The sanitization loader stubs chat_file, so the new typed catch needs a real exception class there or the import fails. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: preserve full PCM span ends for live owner matching * fix(monitoring): reconcile the Grafana dead-man after the Helm rollout (#21357) A Monitoring IAM gap (HTTP 403) in the dead-man step blocked the Grafana WAL rollout in prod run 38344425582. Run it after Helm so it still fails closed without holding the monitoring stack deploy hostage. Failure-Class: FC-batch-fault-domain-head-of-line-poison Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * harden(mobile): validate storefront actions and legacy upsell eligibility * feat(mobile): add flagged contextual storefront sheet and upsell moments * test(mobile): capture storefront and contextual upsell UI states * harden(mobile): declare storefront gates with registry conventions * test(mobile): make storefront actions and evidence independent of test fonts * test(mobile): scroll management action fully into the test viewport * perf(monitoring): trim unused kubelet series and successful probe logs (#21358) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * feat(monitoring): make Telegram pages actionable and batch warnings (#21365) * feat(monitoring): make Telegram pages actionable and batch warnings Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(monitoring): keep real silence links and point GPU/egress pages at their dashboards Grafana silence URLs for our rules are 256-341 characters, so the 200-character cap dropped every one. Parakeet GPU and Cloud Run egress rules linked to log dashboards that do not show the firing series. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * feat(mobile): apply paywall design review and compact budget nudge * feat(mobile): group legacy comparisons using backend presentation identity * refactor(queues): retire unused projection-repair and automatic chat-first queues (#21359) * refactor(queues): retire unused projection and automatic chat queues Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(queues): align retired index inventory contract Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(queues): update remaining field mode count Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(queues): align cleanup imports and service guide Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(firestore): count the retired projection_repairs field override Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * test(mobile): verify paywall fold in the real draggable sheet * feat(monitoring): gate alert admission with historical backtests (#21366) * feat(monitoring): gate alert admission with historical backtests Add read-only condition-chain replay, offline inventory lint and synthetic state tests. Remove the retired proactive rule, repair endpoint readiness telemetry and zero-initialize invalid-request counters. Failure-Class: FC-alert-never-provably-fired Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(monitoring): record absent-rule proof and pause admission holds Record the full 28-rule production backtest: 19 deploy candidates, eight paused fixes and one retired-rule deletion. Bound replay requests by day, preserve explicit admission holds and block unpausing without review. Failure-Class: FC-alert-never-provably-fired Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(monitoring): key alert-lint debt on firing semantics, not routing The legacy-debt baseline fingerprinted the whole rule, so the fleet-wide notification timing change (#21365) voided every baseline and failed the offline admission gate. Fingerprint only condition/data/for/noData/execErr. Failure-Class: FC-batch-fault-domain-head-of-line-poison Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * test(billing): audit and characterize Stripe entitlement ingestion (#21368) * test(billing): characterize Stripe entitlement ingestion before extraction * test(billing): characterize the one-pass stale guard across a clock rollback * feat: dark reset recovery for already-locked content (#21375) * feat: add dark bounded locked-content recovery at quota reset * refactor: share bounded unlock batches without growing domain modules * test: cover bounded unlock query shapes and atomic failure resume * feat: invalidate enforcement cache after reset recovery * docs: keep reset recovery routing within backend guide budget * docs: shorten reset recovery guide route * refactor: expose token-fenced lease release and narrow optional request * fix(monitoring): pair dashboard links with panels so Grafana accepts the rules (#21374) Grafana rejects __dashboardUid__ without __panelId__ (HTTP 400), which blocked the live rollout of #21365 for 38 rules. Dashboard-only links now use a dashboard_url annotation that the Telegram template falls back to. Failure-Class: FC-alert-never-provably-fired Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(mobile): protect legacy prices and desktop entitlements in storefront Failure-Class: none * feat(billing): fixture-only ladder Stripe price proposal (#21356) * feat(billing): add fixture-only ladder price dry-run tooling * fix(billing): validate dry-run ladder registrations and fixtures * feat(subscription): add dark at-cap choice foundation (W4) (#21361) * feat(subscription): add dark period-scoped at-cap choice contract * feat(subscription): declare at-cap choice route policies * feat(subscription): keep choice metadata independent of BYOK validation * feat(subscription): export at-cap choice app-client schema * feat(subscription): regenerate at-cap choice client contracts * fix: type budget-choice contracts and address review findings * refactor: split Dart wire schema catalog from generator * docs: record internal-only budget choice wire generation * refactor(billing): extract the legacy Stripe entitlement state machine (#21370) * test(billing): characterize Stripe entitlement ingestion before extraction * test(billing): characterize the one-pass stale guard across a clock rollback * refactor(billing): extract pure Stripe entitlement state machine * refactor(billing): narrow resolved projection types * refactor(billing): freeze stale preservation before the Stripe lookup * billing: address state-machine review guards and effects * feat(billing): add read-only subscription drift reconciliation (#21371) * test(billing): characterize Stripe entitlement ingestion before extraction * test(billing): characterize the one-pass stale guard across a clock rollback * refactor(billing): extract pure Stripe entitlement state machine * refactor(billing): narrow resolved projection types * refactor(billing): freeze stale preservation before the Stripe lookup * feat(billing): add read-only subscription drift reconciler * test(billing): distinguish organization payment grants from subscription drift * billing: address state-machine review guards and effects * billing: correct reconciliation uncertainty and legacy grants * feat(billing): shadow entitlement versions with legacy golden contract (#21351) * feat(billing): add shadow price-keyed entitlement versions * refactor(billing): expose versions through the catalog facade * fix(billing): type the generated entitlement projection * fix(billing): retain trialing and unknown-price legacy entitlements * fix(billing): retain inactive paid shadow entitlements through period end * fix(mobile): keep storefront checkout on the upward ladder Annual subscribers could buy their own monthly price, Architect could move onto Pro, and a scheduled cancellation had no resubscribe path. Co-authored-by: Cursor <cursoragent@cursor.com> * chore: consolidate changelog for v0.12.478 * fix(mobile): don't open the required language picker over unfinished onboarding (#20798) Every signed-in launch runs the primary-language setup, and when neither the phone nor the server has a language it opened the required (undismissable) language sheet. A relaunch mid-onboarding therefore covered whichever step onboarding resumed on, consent included, on top of onboarding's own language step. Keep the server sync, but only show the required sheet once onboarding is complete. setupUserPrimaryLanguage takes an optional fetch so the prompt can be tested without the network. Fixes #20788 * feat(billing): pin loaded input rate card v1 in shadow (#21352) * feat(billing): add shadow price-keyed entitlement versions * refactor(billing): expose versions through the catalog facade * fix(billing): type the generated entitlement projection * feat(billing): pin loaded input rate card v1 in shadow * fix(billing): retain trialing and unknown-price legacy entitlements * fix(billing): price routed Free speech and incomplete Gemini usage accurately * docs(billing): align shadow usage guide with reviewed Free routing basis * fix(billing): retain inactive paid shadow entitlements through period end * fix(billing): preserve realtime reasoning and incomplete usage pricing --------- Co-authored-by: Cursor <cursoragent@cursor.com> * fix(mobile): pendant tutorial summary rows reopen a working step and return to the summary (#20805) On "You're All Set", tapping "Ask Omi a Question" reopened the question step on "Processing your question..." forever: questionSent was still true, but the recreated step had no answer and only listened for new messages. Finishing any reopened step also walked through every later step again, power cycle included, instead of returning to the summary. The reopened question step now shows the answer the provider already has (with Continue), and a step reopened from the summary returns there when finished. Fixes #20787 * feat(mobile): render the plans sheet from storefront schema v2 The copy deck owns the strings, and the client rejects any payload that is not schema_version 2. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(billing): dark unified storefront presentation contract (#21367) * feat(billing): add dark versioned unified storefront contract * refactor(codegen): extract storefront and goals schema-group metadata * chore(mobile): record internal storefront wire contract * test(billing): reuse payment router for byte-identity cases * feat(billing): recommend storefront tier from opening context * chore(codegen): refresh storefront context projections * fix(billing): block lower storefront offers for legacy subscribers * fix(mobile): a tutorial question sent by the 15 s timeout moves the tutorial on (#20806) * fix(backend): declare Cloud Run Redis ports in runtime config Keep the verified 13151 port on all four managed Cloud Run services in dev and prod. Reject omitted Redis port bindings before deploy and prove the port reaches the deploy renderer. Failure-Class: FC-runtime-binding-contract * chore: consolidate changelog for v0.12.463 * ci: accept commit-pinned image URLs as UI evidence so PR screenshots render (#21123) The gate only accepted repo-relative paths, which GitHub shows as broken images in PR descriptions, and rejected the raw.githubusercontent URL form that renders. Accept URLs pinned to a commit SHA (not branch names); the tracked-file and manifest requirements are unchanged. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * chore(app): add unreleased changelog fragment for batch speaker labeling * chore(backend): enable keep-rescue shadow mode on prod * fix(ci): stop peeling full-SHA revs; git 2.54 broke <sha>^{commit} * feat: add messaging channel foundation and shared chat turns (#21115) * feat: add channel-neutral messaging turn foundation and contract harness * chore: regenerate messaging link client contracts * feat: harden messaging admission and cross-surface continuity * chore: record internal messaging wire contract change * refactor: make messaging async storage boundaries explicit * refactor: type the shared chat turn boundary * refactor: retain outbound send classification type * test: fix messaging foundation CI harnesses * test: allow full-route contract timing * feat: expose link targets and keep private memories off channel retrieval Mint returns the configured deep link and address, sessions carry their channel fields, and channel turns drop private and restricted memories before they reach the model. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: read the private-memory filter from the surface runtime The chat turn already installs that runtime for the whole turn, so the oversized agent file does not need another copy of the flag. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> * feat: add Telegram and Linq iMessage adapters (#21116) * feat: add channel-neutral messaging turn foundation and contract harness * chore: regenerate messaging link client contracts * feat: harden messaging admission and cross-surface continuity * chore: record internal messaging wire contract change * refactor: make messaging async storage boundaries explicit * refactor: type the shared chat turn boundary * refactor: retain outbound send classification type * test: fix messaging foundation CI harnesses * feat: add Telegram and Linq messaging adapters * test: allow full-route contract timing * test: register messaging retry helper in prompt harness * test: isolate messaging imports in backend unit harnesses * feat: expose link targets and keep private memories off channel retrieval Mint returns the configured deep link and address, sessions carry their channel fields, and channel turns drop private and restricted memories before they reach the model. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: read the private-memory filter from the surface runtime The chat turn already installs that runtime for the whole turn, so the oversized agent file does not need another copy of the flag. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> * chore: consolidate changelog for v0.12.464 * feat(app): Chat apps screens for Telegram and iMessage (stacked on #21115) (#21118) * feat: add channel-neutral messaging turn foundation and contract harness * chore: regenerate messaging link client contracts * feat: harden messaging admission and cross-surface continuity * chore: record internal messaging wire contract change * refactor: make messaging async storage boundaries explicit * refactor: type the shared chat turn boundary * refactor: retain outbound send classification type * test: fix messaging foundation CI harnesses * test: allow full-route contract timing * feat(app): chat apps screens for connecting Telegram and iMessage Adds the flag-gated Chat apps surface on Integrations: connect Telegram through a one-time t.me deep link, connect iMessage by texting a one-time code, manage a link (show its chats in the app, disconnect), the WhatsApp waitlist, the free-plan upsell, and a read-only history of a chat app's sessions. Uses only the Stage A /v1/messaging routes through a typed executeApi client. The mobile-chat-apps PostHog flag fails closed; existing links keep the entry reachable for disconnect. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: expose link targets and keep private memories off channel retrieval Mint returns the configured deep link and address, sessions carry their channel fields, and channel turns drop private and restricted memories before they reach the model. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: read the private-memory filter from the surface runtime The chat turn already installs that runtime for the whole turn, so the oversized agent file does not need another copy of the flag. Co-authored-by: Cursor <cursoragent@cursor.com> * feat: use minted link targets and real chat-app switches Prefer the link-proof deep link and address, show a Telegram username when the link has one, and turn voice notes and private memories into per-link switches. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: keep the Messages brand-color exceptions on the color lines The formatter had moved the allow comments off the hex literals, so the mobile UI contract counted them as new colors. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: keep Messages brand colors on one allowed line The UI contract only honors an allow comment on the same line as Color(0x). Co-authored-by: Cursor <cursoragent@cursor.com> * fix: open minted chat-app links through the public launcher The connect sheets were calling test-only helpers, which the analyzer ratchet rejects. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(l10n): restore main's ARB key order after the restack merge The three-way ARB merge re-serialized every locale in a different key order, turning 102 new strings into a ~560k-line diff. Keep main's order and append the new keys, as scripts/l10n.py does; regenerate gen-l10n output. Content is unchanged: only the chat-apps keys are added. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> * feat(backend): fleet driver for the summary-vector backfill * chore(backend): rename driver batch variable * feat(backend): mirror fleet backfill state to Firestore for cross-execution resume * feat(backend): index summaries on re-finalization of completed fanouts * fix(backend): give Dream models readable evidence within token budgets (#21137) * fix(backend): project readable dream evidence within token budgets * fix(backend): preserve dream evidence depth during admission * feat(backend): harden fleet driver resume state * fix(cli): keep conversation Markdown export UTF-8 under legacy stdio (#21033) * fix: preserve UTF-8 conversation exports through legacy stdio Declare the exporter input and progress-output encoding independently of the host locale. Cover file and BOM stdin input through the real entry point under ASCII and CP1252 streams. * fix: normalize Markdown export PR files to LF * fix(onboarding): reset pendant tutorial question step on empty or no-speech press (#21069) Pressing the pendant button twice without speaking, or asking something Omi's server reports as no speech, left SinglePressStep stuck on "Processing your question..." forever: DeviceOnboardingProvider set questionSent=true on the second press and nothing ever reset it. Wire CaptureController's existing emptyFrames drop and onNoSpeech callback to DeviceOnboardingProvider.onVoiceQuestionFailed(), which resets the step and shows the existing voiceQuestionNoSpeech retry hint instead of spinning indefinitely. fixes #20789 * Fix desktop goal type enum decoding (#21091) * feat(backend): per-uid shard writes and bounded mirror state * feat(messaging): enable David-only prod channels for all paid plans (#21138) * chore: consolidate changelog for v0.12.465 * feat(backend): omit empty mirror maps and skip writes after failed reads * chore: regenerate desktop Swift OpenAPI DTOs * fix: preserve owner identity when repair lacks evidence * fix: preserve unresolved identity across a mixed voice * fix: improve dream triage recall with synthetic eval (#21145) Failure-Class: none * test(backend): completed-fanout summary indexing expectations * chore: keep main's generated Swift DTOs (GoalType normalization) * fix(desktop): generate the tolerant enum decoder that #21091 hand-wrote (#21149) #21091 taught the GoalType decoder in OmiApi.generated.swift to accept a Python-qualified value ("GoalType.scale") by editing the generated file, but generate_swift_openapi_types.py still emitted the old decoder, so `generate_swift_openapi_types.py --check` (the openapi-contract workflow and the pre-push hook) now fails on main for anyone who touches the API. The generator now emits that decoder for every generated string enum: an exact raw-value match first, so a value that itself contained a dot would keep its meaning (none of the 160 enums in the app-client spec has one today), then the last dotted component, then _unknown. OmiApi.generated.swift is regenerated from the generator, and GoalType decodes exactly as #21091 intended. Failure-Class: none * docs(onboarding): add an Omi for Mac user guide to the public docs corpus (#21147) The backend chat agent's product-info tool (get_omi_product_info_tool) recursively reads docs/doc content from GitHub, and the corpus had no macOS desktop user guide, so mobile/wearable chat could not answer desktop how-to questions such as enabling Only Meetings. This page mirrors the product guide shipped inside the Mac app (ProductKnowledge resources, #21081) and is registered in docs.json Get Started. * chore: consolidate changelog for v0.12.466 * fix(dream): tolerate invalid plan items and bound failed-record retries (#21154) * fix(dream): retain valid plan items and bound failed-record retries * perf(dream): reuse static fields for validation diagnostics * feat(app): phone battery sample v2 — self-contained intervals + schema delivery fix (#21152) * feat(app): phone battery sample v2 — self-contained drain intervals * fix(app): reject clock-corrupted phone battery intervals * fix(app): suspend-inclusive monotonic reference for Android battery intervals * feat(app): pendant audio-loss integrity — unresolved outages + per-day provenance (#21151) * feat(app): credible audio-loss accounting — persist unresolved outages, per-day provenance * fix(app): fence durable audio evidence and reconcile daily outage exposure * fix(app): rekey OmiDeviceHealthStore pbxproj entries to unique UUIDs * test(app): gate depends_on_21152 delivery tests out of shared CI runs * fix(app): buffer BLE outage writes when SharedPreferences binding is absent * test(app): enable depends_on_21152 delivery tests after #21152 merge * fix(dream): repair titles and protect multilingual feedback (#21160) * fix(dream): repair conversation headings and protect multilingual feedback * test(dream): account for explicit empty summary headers in queue excerpts * fix(dream): enforce deterministic summary and feedback guardrails (#21161) * fix(dream): enforce deterministic summary and feedback guardrails Failure-Class: FC-generated-presentation-contract-bypass * fix(dream): withhold feedback until privacy checks settle Failure-Class: FC-generated-presentation-contract-bypass * fix(dream): drop non-failure feedback deterministically Failure-Class: FC-generated-presentation-contract-bypass * fix(dream): guard task evidence, spelling targets and vocabulary noise (#21164) * fix(dream): guard task evidence, spelling targets and vocabulary noise * fix(dream): narrow validated task timestamps for type checking * fix: clip embeddings gateway inputs that OpenAI rejects with 400 Sync backfill posted raw transcript batches to /v1/embeddings. Empty strings, NUL bytes, and over-long inputs make the gateway return 400, which httpx raises from _models.py:829. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: drop redundant str check in embeddings batching Pyright rejects the isinstance guard because the batch items are already strings. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(backend): switch the dream agent to on for the owner allowlist (#21171) Owner-authorized promotion from shadow to on. Adds REVIEW_SURFACE_MODE=on on backend-sync, where passes run, because mode=on admission requires it. Canary leases stay forced to shadow. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * feat(backend): name identified people in general conversation titles (#3602) (#20646) * feat(backend): name identified people in general conversation titles (#3602) Notes v2 already binds speaker clusters to people through hard evidence (voice profile / tagged person), but the general (non-meeting) notes path never asked for those names in the title and never said which bound name is the account owner, so titles rarely named who the conversation was with. - SpeakerMap: the cluster->name map now also records the owner's clusters and words spoken per cluster. It stays a plain mapping for every caller and the rendered spk lines (shared prefix bytes) are unchanged. - ConversationPromptPrefix.title_people / owner_names: non-owner people who said at least 10% of the words, most-spoken first, plus the owner's name. A bare mapping (owner unknown) names nobody. Rich meeting notes keep their roster rules and get neither. - utils/llm/conversation_title_people.py: a static TITLE block inserted before the parser schema (cacheable, general path only, same pattern as rich_static_instructions) and a volatile PEOPLE IN THIS CONVERSATION block: name the one or two central people, never the account owner, never an unlisted or invented name. - Presentation contract: a title that names none of the identified people is led by them ("Sarah Chen: Q2 Budget Review"), language-neutral, at most two names within 70 characters, never inventing a title or a name. Each repair is counted on omi_conversation_note_presentation_total (static_repair / title_people_lead). * test(backend): preload conversation_title_people in isolated conversation_processing loads test_conversation_structure_timezone and test_action_item_date_validation stub utils.llm as an empty package and load each pure helper that conversation_processing imports from its file. The new title module was missing from that list, so both files failed at collection in CI. * feat(backend): name title people only from scoped voice and tag bindings Review follow-ups for #3602: - SpeakerMap now carries per (speaker_id_scope, speaker_id) cluster evidence. Merged transcripts reuse ids across scopes, so an id whose clusters disagree about who is speaking (two people, or a person and the owner) names nobody. The rendered spk lines are unchanged. - Title people come only from voice/tag bindings, never from a calendar name bound by elimination, so a guessed owner cluster cannot be named. - Names in Thai, kana, CJK and Hangul match without word boundaries, a Latin name next to an unspaced script still matches, and a surname of three or more characters counts as a mention. - Names are stripped before comparison and before leading a title, and MAX_LED_TITLE_CHARACTERS is renamed TWO_NAME_LEAD_MAX_CHARACTERS: it only decides when the second name is dropped. * feat(backend): keep the owner out of titles without a voice match Second review round for #3602: - The owner line comes from the real profile name whenever one exists, with or without an is_user cluster, and a person record matching the owner's name or first name is treated as the owner. - Talk share is speech time (word count only when no segment has a duration), so names in unspaced scripts reach the share threshold. - Inflected mentions (Peters, Сарой, Anną) count as naming the person; titles and names are NFC-normalized and curly apostrophes match. - A conflicting speaker id is dropped on its own instead of silencing the whole conversation, and a person named like a speaker placeholder is never a title person. - The note presentation contract label is now v2: the title lead adds a static repair reason. * feat(backend): a bare name stem is not a mention of the person The inflection rule matched a name's stem with an empty suffix, so "and" counted as naming Andy, "the" as Theo and "Joe" as Joel, and the title was never led. Inflected forms now need a real suffix: a name plus up to four letters, or, for a name ending in a vowel, its stem plus up to four. Tests pin the bare-stem negatives, zero speech time for negative or untimed segments, and that the owner's name stays out of the static prompt. * feat(backend): keep title names one line, from real talk, and free of what presentation strips - A conversation with no speech time and no words names nobody: a share of nothing is no evidence of who spoke. - Person and owner names reach the prompt as one line of plain text, so a stored name with a line break or control character cannot add a prompt line. - The title lead skips a name holding a speaker placeholder or a transcript ID: it runs after presentation sanitization and must not bring either back. - Pin the two-name lead to exactly TWO_NAME_LEAD_MAX_CHARACTERS. * feat(backend): keep model titles that already name people A title that already names any of the identified people is kept as written, however many it names. The prompt asks for the one or two most central people, and it stays the control for how many names appear: a deterministic repair cannot remove names from the model's prose without rewriting it, replacing the title loses its topic, and the name matcher's inflection rule reads ordinary words as names ("Annual" for Anna, "General" for Gene), so a count would rewrite titles that name no one extra. The repair still leads a title that names nobody. Tests pin that titles naming three listed people, and titles whose words only look like names, come back unchanged, and that a title naming nobody is still led. The test pinning the two-name threshold is renamed to say what it asserts. * test(backend): pin the general-path TITLE prompt bytes in the flag-off fixture Review asked for the flag-off fixture to carry the general-path prompt with its TITLE block. The previous commit built that expectation from the production helper, so a change to the TITLE wording would still pass. The fixture now has a general_static entry holding the literal general-path static prompt, TITLE block included, and test_actual_flag_off_request_uses_pinned_prompts compares the real request against it. legacy_static stays pinned to main's base bytes, and test_flag_off_prompt_bytes_pinned_to_base checks that general_static is legacy_static with only the TITLE block inserted before the schema. * fix(backend): normalize title identities in the shaped notes writer Normalize owner and tagged-person identities to NFC before casefolding and combining scoped talk shares. Keep display spellings unchanged, and cover owner exclusion through the real speaker map, prompt builder, and notes writer. Port the reviewed title prompt and deterministic lead to current shaped-only notes after rebasing upstream, preserving general-only behavior and the one-turn model budget. Failure-Class: none * feat: remove carried-speaker banner and keep inline correction * feat: limit capture storage banner to disk admission failures * feat: remove home header sync button * chore: add banner cleanup mobile changelog * docs(desktop): track the Silence Notifications setting UI evidence Main's UI-evidence check needs a tracked screenshot for changed UI source. Copy the existing settings screenshot from the docs-assets branch unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: keep owner and Omi labels out of inline correction * fix: limit live speaker correction buttons to turn starts * chore: include banner cleanup in both mobile store notes * feat(dream): require Review for risky agent edits (#21176) * feat(dream): require Review for risky agent edits * refactor(dream): share typed entity evidence reads * test(dream): exercise a guarded spelling proposal in transport fixture * feat(review): version agent proposal wire contract for released clients * docs(review): declare v2 route authentication and rate limits * chore(api): regenerate TypeScript clients for Review v2 * chore(api): regenerate Swift operations for Review v2 * test(dream): fence tiered writers in shadow sweep regression * fix: attribute live speaker budget blocks and schedule voices fairly * chore: consolidate changelog for v0.12.467 * test: exercise profile retries through the coalescing speaker queue * fix(messaging): restore Linq replies and release failed turns (#21172) * fix(messaging): repair Linq send envelope and failed turn cleanup * fix(messaging): initialize failure sink before worker handling * fix(messaging): fence cancelled executor work during cleanup * fix(messaging): log cancelled worker fencing * fix(messaging): match fixture provider host exactly * fix(messaging): preserve delivered replies and drain typed fallbacks * fix(messaging): release settled turn failures without sticking the chat A finished turn that raised CancelledError, and a failed deferred reentry, kept the surface lease after the work had unwound, so the next message could not run. Retain the lease only while the worker task is still cancelling, and fence a failed deferred reentry with an event so a retry does not run the turn again. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> * feat(app): show Dream suggestions in Review with Apply / Dismiss (#21183) * feat(dream): require Review for risky agent edits * refactor(dream): share typed entity evidence reads * test(dream): exercise a guarded spelling proposal in transport fixture * feat(review): version agent proposal wire contract for released clients * docs(review): declare v2 route authentication and rate limits * chore(api): regenerate TypeScript clients for Review v2 * chore(api): regenerate Swift operations for Review v2 * test(dream): fence tiered writers in shadow sweep regression * feat(app): show Dream suggestions in Review with Apply and Dismiss Review now reads the v2 queue, which adds Dream's agent_proposal items: closing or retiring tasks, merging people or memories, and memory or entity-summary edits that need the owner's consent. Cards show the question and Dream's reason; the sheet shows current vs suggested text. A 409 means the evidence changed, so the item stays gone with a clear message. Ten new strings in all 49 locales via scripts/l10n.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(app): changelog fragment for Dream suggestions in Review Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(debug-logs): serialize DebugLogManager appends to prevent interleaved lines (#21178) * fix(diagnostics): include error detail and stack in RingStorageSync failure log (#21180) * fix(sync): emit reconcile_completed_empty telemetry for empty job completions (#21182) * fix(debug-logs): serialize DebugLogManager appends to prevent interleaved lines * fix(sync): emit reconcile_completed_empty telemetry for empty job completions * fix: preserve coalesced ranges and serialize numeric speaker work * chore: consolidate changelog for v1.0.561 * test: retain the real speaker queue in teaching persistence coverage * fix: stop channel settings test pinning a wall-clock date The UTC+14 CI shard rolled to Oct 11 before the fixture's Oct 10 date could render as 'Connected Oct 10, 2026' in its own local midnight window. Assert the sentence shape (textContaining 'Connected') instead of a hardcoded formatted date, matching how the product renders linkedAt via OmiDateFormat. * perf(ci): compile generated Swift PRs and scope Siri advisory (#21186) * perf(ci): compile generated Swift PRs and scope Siri advisory * docs(ci): mark desktop CI changes as internal * test(ci): keep generated notification DTOs on compile-only PR lane * fix(channels): order replies, scope Undo, and anchor reminders to message time (#21175) * fix(channels): keep chat responsive and scope undo to action receipts Failure-Class: new * refactor(channels): load surface prompts at the shared mount boundary * refactor(channels): make lease and receipt state contracts explicit * fix(channels): preserve revocation and bound receipt retention Failure-Class: new * fix(channels): admit replies FIFO and anchor reminders to message time Failure-Class: new * refactor(channels): keep inbox index requirements in their owning module Failure-Class: new * fix(channels): preserve Telegram provider message timestamps Failure-Class: new * refactor(channels): narrow settled lease state after merge * fix(channels): bound provider clocks and stop lease-miss wake spins Linq reminders used processing time, and a busy surface lease requeued the inbox head and woke it again. Provider stamps are now bounded, a past relative reminder stays a missed phone reminder, and channel memory search uses the same empty-result wording as memory reads. Failure-Class: new Co-authored-by: Cursor <cursoragent@cursor.com> * fix(channels): accept only real ints for relative reminders The annotated isinstance check failed pyright and blocked the push. Runtime still rejects bools and non-integers from the model. Failure-Class: new Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> * chore: consolidate changelog for v0.12.468 * test(desktop): await the decided outcome in notification speech race tests The race tests inferred the outcome from isSpeaking after twenty yields. The playback service now reports where an audience-gated one-shot ended up (played, fell back to the system voice, withheld, or dropped as stale) at the point it is decided, and the tests await that. New cases cover a player failure whose fallback is re-checked, a stopped synthesis that finishes late, and a private room that still hears the notification. Only audience-gated one-shots report; other speech is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(hygiene): exclude vendored tokenizer vocabulary from deferred-work markers (#21153) A false positive surfaced by #20746, which vendors a BERT WordPiece vocabulary as part of an on-device router. Line 20579 is the token "hack", between "##illy" and "plaintiff", and the check read it as deferred work: FAIL: new deferred-work markers must reference a tracking issue (#123 or URL). - app/android/app/src/main/assets/vocab.txt:20579: hack Why it happens: comment_fragment() treats a line whose first word matches MARKER_RE as a comment fragment even with no comment syntax. That is deliberate for a line that is just "TODO", but in a 30,522-row data file a bare token that happens to start with a marker word is data, not a comment. Exactly one line of 30,522 is affected. Why the fix is an exclusion rather than a change to the PR: the file cannot be edited. Its line index is the token id, so removing or reordering a row silently renumbers every token after it and changes what the tokenizer produces. Editing the vendored asset to satisfy a linter would trade a false positive for a real correctness bug. This restores behaviour the check already documents -- --raw exists precisely so that generated and vendored files are excluded by default, and the exclusion set simply had no entry for vendored plain-text data. A glob over src/main/assets would generalise further; a specific path is narrower and easier to review, so this takes the narrow one and leaves the call to @Git-on-my-level. Verified by running the real script, both directions: before: FAIL app/android/app/src/main/assets/vocab.txt:20579: hack after: OK and the check is not weakened -- a "// HACK:" and a "// TODO:" with no issue reference in normal source are both still flagged, while "// TODO: ... (#123)" is still accepted. Co-authored-by: palontologist <palontologist@users.noreply.github.com> * fix(backend): drop a conversation's task search vectors when a cascade delete removes them (#15950) Isolate the Pinecone batch deletion in a try/except block and run reminder cancellation before vector cleanup to guarantee that vector failures do not abort the delete or skip cancelling task reminders. Co-authored-by: kduailabs <kiwidwnunder@gmail.com> * fix: recover exhausted speaker voices from bounded fresh PCM * fix: preserve fresh audio watermark across ring replacement * docs: clarify recovery proof lifecycle and type audio helpers * test: patch WAV encoding at its owning speaker audio module * fix(sync): bound mobile job polls, follow active content owners, stop mislabeling edge throttles (#21206) * fix(sync): bound mobile job polls and follow active content owners Failure-Class: new * fix(sync): recover expired owners and requeue retained phone audio Failure-Class: FC-event-wakes-bypass-request-cadence * fix(sync): generate declared owner and poll hint response decoders Declare additive sync conflict and status cadence fields in OpenAPI and regenerate the app wire contract. Decode conflict responses and project status metadata through generated types, retaining compatibility with omitted optional fields. Failure-Class: FC-event-wakes-bypass-request-cadence * fix(sync): advance replay clocks through job poll deadlines Keep terminal-state and retry-budget assertions intact while making replay time match the bounded polling contract. Reset shared test state and prove immediate restored-job admission, deferred upload hints, and singleton isolation. Failure-Class: FC-event-wakes-bypass-request-cadence * test(desktop): compare PTT and card surfaces like with like in notch sizing test testHoldingPushToTalkAgainstAMountedCardKeepsTheCardSurface compared the exact PTT surface size plus glow outsets with the mounted card's window frame. AppKit snaps window frames to whole points, so on a display with a fractional notch inset (37.5pt) the card's 197.5pt request lands as a 198pt frame and the assertion fails by 0.5pt, while the panel itself never shrinks. Compare the PTT surface with the card's own surface size instead; the check still fails when the PTT surface drops the mounted card. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: reserve fresh recovery phrases and enforce tranche allowance * refactor: keep matcher within the existing size ratchet * fix(ios): memories fetch 503/429 no longer reported as fatal — retry + graceful degradation (#21226) * fix(ios): memories fetch failures are recoverable — retry with backoff, non-fatal reporting * fix(ios): bound memories retries by caller deadline and cancel on disposal * fix: make owner recognition refusals attributable * test: await queue matcher completions before asserting owner recovery * feat(macos): continue MCP marketplace installs into connection setup (#20962) * feat(macos): continue marketplace installs into connection setup * test(macos): record marketplace setup and large-text evidence * fix(macos): refresh committed installs after setup dismissal * test(macos): record clean-head marketplace replay * fix(backend): memories fetch resilience — bounded retry, admission cap, structured 503 telemetry (#21227) * fix(backend): memories fetch resilience — bounded retry, structured 503 telemetry * fix(backend): cap memories retry admission and preserve probe state * fix: gate every memory read attempt through global admission * feat(backend): route Dream spelling fixes to Review until they are grounded (#21232) * feat(backend): route Dream spelling fixes to Review until they are grounded Prod Dream passes no longer auto-apply spelling edits; they become Review suggestions. Titles, overviews and vocabulary still auto-apply. Spelling returns to auto once the agent must cite supporting context. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(backend): allow the Dream auto-apply override in the prod env contract Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * docs(backend): C025 runbook — regionalize syncing-local to us-central1 Cost ledger card C025: prod syncing-local is multi-region, paying replication for <=8-minute staging blobs (+$46/day WoW). Runbook covers replacement bucket provisioning, intake pause + job drain (queued raw audio can outlive 8 min), 15-min signed-URL cache waitout, env flip in all four chart values, rollback, and old-bucket deletion gates. * fix: bound diagnostic writes and update observation regression guards * fix: narrow optional observer in diagnostic flush * perf(memory): hydrate graph references instead of scanning the corpus C008 cut 1+2: KG traversal hydration now reads only distinct edge-referenced memory IDs in 500-doc batches (existing authoritative by-ID seam) instead of two full memory_items corpus streams. 18k-doc scan -> 6 point reads in test. Eligibility recheck, citation-time privacy check, probe labels preserved. Privacy-critical exhaustive scans deliberately left unbounded (see report). * test(memory): keep scan-guard test under the 0.30s timing guard 18k-item corpus reproduced the prod-scale scan but ran 0.33s against test.sh's 0.30s per-test budget; 1.5k preserves the property (scan would still enumerate the corpus; by-ID path reads 6 docs). * test(memory): shrink batching test to 550 IDs under the timing guard Batching/dup property preserved (500+50, dedupe before batching); the implementation dedupes requested IDs first, so expected batches update to [500, 50]. * test: isolate memory read admission between hermetic e2e cases * fix(backend): per-UID ceiling on sync job status polling C024: prod poll volume stepped to 40M req/day (85% this route). Cap = 40 polls/UID/20s via existing atomic Redis reservation, independent of RATE_LIMIT_BOOST; 429 + Retry-After: 20 before job lookup; fail-open on Redis outage with fallback telemetry; two counters added. Shipped Dart poller cadence (<=18 req/min/instance) never hits the cap. * test: cover diagnostics exemption and isolate synthetic cursor admission * test: exhaust hydration budget after read admission * chore: consolidate changelog for v0.12.469 * fix(app): fully-charged notification — require on-charger dwell + fresh battery reading (#21233) * fix(app): fully-charged notification requires on-charger dwell and fresh battery reading A cached 100% from before plug-in was enough to fire the fully-charged notification on the charging edge. Require a continuous on-charger dwell and a battery sample timestamped after that edge. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(app): add changelog fragment for the fully-charged notification fix Co-authored-by: Cursor <cursoragent@cursor.com> * fix(app): scope fully-charged alerts per device and dwell on monotonic time A provider-wide alert flag suppressed the next pendant, and wall-clock dwell could be skipped by moving the phone clock. Mismatch logging now waits until the notification permission check has passed. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(app): keep device provider under the line-count ratchet The fully-charged presentation path had pushed the file past 1500 lines. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> * test(memory): isolate the cursor suite from the shared read-admission allowance #21227's process-wide RetryAllowance (8 tokens, 1/s refill) exhausts deterministically when the cursor suite pages thousands of rows in under a second. Fresh allowance per test + burst 64 via the existing env knob. * fix(backend): first read admission gates on time headroom, not document headroom #21227 regression: with an exhausted ListReadBudget document allowance (max_documents spent), _headroom returns 0.0 and first-attempt admission raises 503 Memory read admission exhausted — but an exhausted document budget is a normal truncated page in the list-budget contract (the charge path flags budget.truncated downstream). Restores truncated-page behavior; the retry path keeps the full budget-aware headroom check. * test(e2e): isolate the hermetic harness from the shared read-admission allowance Same mechanism as the cursor suite: 113 in-process tests exhaust the 8-token process-wide RetryAllowance and shed legitimate bursts as 503 Memory read admission exhausted. Burst 64 + fresh allowance in the e2e conftest; full harness green locally (126 passed, 3 skipped, 2 xfailed). * feat: expose deterministic Dream summary eligibility (#21236) * docs(backend): close the review blockers in the C025 cutover runbook Grok review (read-only) found two blockers in the no-copy drain: Firestore sync_backfill_pending docs carry raw_blob_paths and survive a Cloud Tasks + Redis drain (expire as sync_staged_audio_expired post-flip); failed/partial backfill jobs keep staged blobs and recover_dev_failed_sync_backfill.py resolves paths against the current bucket env. Runbook now requires the sequencer queue drained + recoverable failed/partial jobs finished or copied before the flip, accounts chat voice-message staging in the URL-cache clock, and documents the backfill clone-order hazard. * Fix closed-generation diagnostic persistence and log kinds * feat: chat edit receipts with undo, and h.omi.me item links (#21234) * feat(app): render chat edit receipts with undo Decode the new `editReceipt` chat content block (camelCase and snake_case) and render it as a chip in the memory/task family: an existing icon per kind, the summary, a tap that opens the conversation, memory, task or person, and a compact Undo while the server allows it. Undo calls POST /v1/chat/receipts/{id}/undo, treats already_undone as success, and reports failure through OmiFeedback. Unknown kinds keep their summary and undo but have no destination. Also parse h.omi.me singular item links (conversation/task/memory) into the Home routes that already open them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: open h.omi.me item links in the app, content-free web fallback https://h.omi.me/conversation/<id>, /task/<id> and /memory/<id> (singular; the plural /conversations/ and /tasks/ public share pages are unchanged): - iOS AASA and Android app-link intent filters claim the three paths, and the Flutter link handler routes them into Home. - Without the app, h.omi.me serves a minimal Omi page: "Open in the Omi App", App Store / Google Play, and a web sign-in handoff. It never reads the item; preview metadata is generic Omi only and noindex. - web/app login honours a same-origin `next` (and protected pages pass it), so the handoff lands on /conversations?id=<id> (tasks and memories land on their list pages; web/app has no single-item routes for them). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: UI evidence for edit receipts and item-link fallback; absolute title The fallback page's title is now exactly "Omi" (the root template made it "Omi | Omi"). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(app): treat refused receipt undo as terminal Match the backend undo contract: `unavailable` (window closed or ambiguous inverse), 404 and 409 report the failure and remove Undo instead of showing a false "Undone" or inviting a retry that cannot succeed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(app): changelog entry for chat edit receipts and item links Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(app): keep a refused receipt undo from coming back A rebuild from the original message dropped the terminal undo state, and the caption row overflowed once text scale grew past the fixed 32px line. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): reject sign-in return paths that normalize off-site `/.//evil.example` and a percent-encoded double slash passed the prefix check and became a protocol-relative URL after the browser parsed them. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> * feat(chat): rich channel replies and shared edit receipts (#21240) * feat(chat): publish shared edit receipt and owner undo contract * feat(chat): render channel markdown and share audited edit tools * docs(chat): clarify client integration and memory undo limits * fix(chat): sanitize task snapshot failures before writes * fix(memory): distinguish canonical audit keys in source inventory * fix(chat): type shared edits and verify injected tool config * fix(chat): align receipt summaries with native UI cards * fix(chat): fence edits and persist undo in message history * test(chat): cover receipt lookup query and record UI integration * fix(chat): keep receipt message queries at database authority * fix(chat): preserve protected notes across repeated corrections * fix(chat): decode correction notes in manual speaker responses * fix(chat): drop unsafe channel links and leftover citation markers Fullwidth and footnote markers were still delivered, and non-https destinations survived as text or hrefs. Record the desktop proxy and non-ledger memory undo limits that are not contained fixes. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> * chore: consolidate changelog for v0.12.470 * fix(ci): rebalance backend unit shards and synchronize fairness test (#21259) * Retrieve bounded Dream neighbourhoods with target and context roles (#21237) * feat: expose deterministic Dream summary eligibility * feat: retrieve bounded Dream target neighbourhoods * test: migrate Dream canary and report retrieval fixtures * harden: enforce Dream context and speech admission contracts * fix: wire Dream retrieval providers and isolate embedding work * fix: retrieve nearest Dream context without photos and preserve vocabulary matches * fix(dream): deduplicate context reads and tolerate unavailable search * fix(dream): initialize the per-pass control cache before dispatch * test(dream): migrate report fixtures to cached reads * test(dream): validate Typesense bindings and preserve upstream app bytes * fix(ci): distribute the growing backend suite across six shards * chore(ci): refresh measured six-shard runner cost forecast * chore(ci): record measured fanout cost in aggregate forecast caps * fix(ci): run metadata preflight for Dream stack bases * chore(dream): extract shared CI changes into standalone PR * fix(dream): reclaim unused context budget and diagnose retrieval (#21263) * fix(dream): reclaim unused context capacity and report retrieval paths * fix(dream): type diagnostics and keep vector availability checks local * fix(dream): bound reclaimed targets and retain unavailable queue liveness * fix(dream): settle permanent misses and bound transient retries and reads * fix(dream): retain user-authored titles in retrieval queries * chore: consolidate changelog for v0.12.471 * fix(dream): run ordinary-text search and retrieve non-target neighbours (#21275) * fix(dream): search ordinary speech and exclude temporal targets * fix(dream): exclude pending targets and bound query matching * fix(dream): read section evidence and reserve a context slice * test(dream): bind verified reader and stop redundant budget probes * perf(dream): avoid duplicate triage and schema sizing * fix(dream): expire unreadable speech and inspect oldest available refs * fix: restore live matcher FIFO scheduling and query cadence * Require independent support for Dream spelling auto-apply (#21238) * feat: expose deterministic Dream summary eligibility * feat: retrieve bounded Dream target neighbourhoods * feat: require independent support for Dream spelling auto-apply * harden: type Dream dry-run diagnostic fields * test: ground the synthetic Dream transport spelling fixture * test: migrate Dream canary and report retrieval fixtures * test: migrate Dream canary and report retrieval fixtures * harden: enforce Dream context and speech admission contracts * harden: enforce Dream context and speech admission contracts * fix: wire Dream retrieval providers and isolate embedding work * fix: wire Dream retrieval providers and isolate embedding work * fix: retain actionable Dream spelling answers and canonical provenance * fix: atomically admit new Dream spelling cards without wasting edit budget * fix: retrieve nearest Dream context without photos and preserve vocabulary matches * fix: retrieve nearest Dream context without photos and preserve vocabulary matches * test: inject synthetic turns into Dream edit-budget regression * fix(dream): deduplicate context reads and tolerate unavailable search * fix(dream): prevent vocabulary laundering and preserve capped questions * fix(dream): initialize the per-pass control cache before dispatch * test(dream): migrate report fixtures to cached reads * test(dream): use authored vocabulary and cached report fixtures * fix(dream): require resolvable independent support chains * test(dream): validate Typesense bindings and preserve upstream app bytes * test(dream): make legacy agent support explicitly user authored * fix(dream): defer capped spelling targets until UTC reset * test(dream): witness deferred completion query branches * fix(ci): distribute the growing backend suite across six shards * chore(ci): refresh measured six-shard runner cost forecast * chore(ci): record measured fanout cost in aggregate forecast caps * fix(ci): run metadata preflight for Dream stack bases * chore(dream): extract shared CI changes into standalone PR * Sample older Dream cleanup targets with relevance and review cooldowns (#21239) * feat: expose deterministic Dream summary eligibility * feat: retrieve bounded Dream target neighbourhoods * feat: require independent support for Dream spelling auto-apply * harden: type Dream dry-run diagnostic fields * feat: seed bounded older Dream cleanup with review cooldowns * test: ground the synthetic Dream transport spelling fixture * test: migrate Dream canary and report retrieval fixtures * test: migrate Dream canary and report retrieval fixtures * test: migrate Dream canary and report retrieval fixtures * test: isolate Dream report backfill and review metadata * harden: count dirty Dream targets in source diagnostics * harden: enforce Dream context and speech admission contracts * harden: enforce Dream context and speech admission contracts * harden: enforce Dream context and speech admission contracts * fix: wire Dream retrieval providers and isolate embedding work * fix: wire Dream retrieval providers and isolate embedding work * fix: retain actionable Dream spelling answers and canonical provenance * fix: wire Dream retrieval providers and isolate embedding work * fix: retain actionable Dream spelling answers and canonical provenance * fix: bootstrap legacy Dream cleanup and bound cooldown reads * fix: atomically admit new Dream spelling cards without wasting edit budget * fix: atomically admit new Dream spelling cards without wasting edit budget * fix: retrieve nearest Dream context without photos and preserve vocabulary matches * fix: retrieve nearest Dream context without photos and preserve vocabulary matches * fix: retrieve nearest Dream context without photos and preserve vocabulary matches * test: inject synthetic turns into Dream edit-budget regression * test: inject synthetic turns into Dream edit-budget regression * fix(dream): deduplicate context reads and tolerate unavailable search * fix(dream): prevent vocabulary laundering and preserve capped questions * fix(dream): initialize the per-pass control cache before dispatch * fix(dream): bound idle cleanup admission and historical sampling * test(dream): migrate report fixtures to cached reads * test(dream): use authored vocabulary and cached report fixtures * fix(dream): return idle without run history and update admission fixtures * fix(dream): require resolvable independent support chains * test(dream): validate Typesense bindings and preserve upstream app bytes * test(dream): make legacy agent support explicitly user authored * test(dream): supply dirty versions to legacy admission fixtures * fix(dream): defer capped spelling targets until UTC reset * fix(dream): sample real record ID spaces and honor seed deferrals * test(dream): witness deferred completion query branches * fix(ci): distribute the growing backend suite across six shards * chore(ci): refresh measured six-shard runner cost forecast * chore(ci): record measured fanout cost in aggregate forecast caps * fix(ci): run metadata preflight for Dream stack bases * test(ci): await completed speaker matches in fairness regression * chore(dream): extract shared CI changes into standalone PR * chore(dream): move speaker fairness regression fix to CI PR * chore: consolidate changelog for v0.12.472 * Schedule Dream fairly within shared reserved background capacity (#21241) * feat: expose deterministic Dream summary eligibility * feat: retrieve bounded Dream target neighbourhoods * feat: require independent support for Dream spelling auto-apply * harden: type Dream dry-run diagnostic fields * feat: seed bounded older Dream cleanup with review cooldowns * test: ground the synthetic Dream transport spelling fixture * feat: schedule Dream fairly within reserved background capacity * test: migrate Dream canary and report retrieval fixtures * test: exercise full Dream fixtures through reserved admission * test: migrate Dream canary and report retrieval fixtures * test: migrate Dream canary and report retrieval fixtures * test: isolate Dream report backfill and review metadata * test: isolate manual Dream report from stochastic backfill * harden: count dirty Dream targets in source diagnostics * harden: retain dirty source counts through scheduling * harden: enforce Dream context and speech admission contracts * harden: enforce Dream context and speech admission contracts * harden: enforce Dream context and speech admission contracts * harden: enforce Dream context and speech admission contracts * fix: wire Dream retrieval providers and isolate embedding work * fix: wire Dream retrieval providers and isolate embedding work * fix: retain actionable Dream spelling answers and canonical provenance * fix: wire Dream retrieval providers and isolate embedding work * fix: wire Dream retrieval providers and isolate embedding work * fix: retain actionable Dream spelling answers and canonical provenance * fix: retain actionable Dream spelling answers and canonical provenance * fix: bootstrap legacy Dream cleanup and bound cooldown reads * fix: bootstrap legacy Dream cleanup and bound cooldown reads * fix: report Dream capacity waits as retryable manual responses * fix: atomically admit new Dream spelling cards without wasting edit budget * fix: atomically admit new Dream spelling cards without wasting edit budget * fix: refresh Dream subscription priority and release declined slots * fix: atomically admit new Dream spelling cards without wasting edit budget * fix: retrieve nearest Dream context without photos and preserve vocabulary matches * fix: retrieve nearest Dream context without photos and preserve vocabulary matches * fix: retrieve nearest Dream context without photos and preserve vocabulary matches * fix: retrieve nearest Dream context without photos and preserve vocabulary matches * test: inject synthetic turns into Dream edit-budget regression * test: inject synthetic turns into Dream edit-budget regression * test: inject synthetic turns into Dream edit-budget regression * harden: project only current plan fields for Dream scheduling * fix(dream): deduplicate context reads and tolerate unavailable search * fix(dream): prevent vocabulary laundering and preserve capped questions * fix(dream): initialize the per-pass control cache before dispatch * fix(dream): bound idle cleanup admission and historical sampling * test(dream): migrate report fixtures to cached reads * test(dream): use authored vocabulary and cached report fixtures * fix(dream): return idle without run history and update admission fixtures * fix(dream): require resolvable independent support chains * fix(dream): isolate scheduler failures and preserve capacity-wait rotation * test(dream): validate Typesense bindings and preserve upstream app bytes * test(dream): include explicit reservation scope in runtime fixtures * chore(dream): classify reservation evidence project co…
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
|
Hey @Git-on-my-level 👋 Thank you so much for taking the time to contribute to Omi! We truly appreciate you putting in the effort to submit this pull request. After careful review, we've decided not to merge this particular PR. Please don't take this personally — we genuinely try to merge as many contributions as possible, but sometimes we have to make tough calls based on:
Before your next PR, please skim:
If this was declined for direction or taste, maintainers should cite an invariant ID or open a proposed one — ask if that citation is missing. Your contribution is still valuable to us, and we'd love to see you contribute again in the future! If you'd like feedback on how to improve this PR or want to discuss alternative approaches, please don't hesitate to reach out. Thank you for being part of the Omi community! |
|
Reopening: closed in error. #21363 merged into this branch (lane/sub-w6-paywall), not main; this PR is what lands the mobile storefront on main. |
Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # app/lib/backend/schema/gen/storefront_wire.g.dart # app/lib/l10n/app_ar.arb # app/lib/l10n/app_be.arb # app/lib/l10n/app_bg.arb # app/lib/l10n/app_bn.arb # app/lib/l10n/app_bs.arb # app/lib/l10n/app_ca.arb # app/lib/l10n/app_cs.arb # app/lib/l10n/app_da.arb # app/lib/l10n/app_de.arb # app/lib/l10n/app_el.arb # app/lib/l10n/app_en.arb # app/lib/l10n/app_es.arb # app/lib/l10n/app_et.arb # app/lib/l10n/app_fa.arb # app/lib/l10n/app_fi.arb # app/lib/l10n/app_fr.arb # app/lib/l10n/app_he.arb # app/lib/l10n/app_hi.arb # app/lib/l10n/app_hr.arb # app/lib/l10n/app_hu.arb # app/lib/l10n/app_id.arb # app/lib/l10n/app_it.arb # app/lib/l10n/app_ja.arb # app/lib/l10n/app_kn.arb # app/lib/l10n/app_ko.arb # app/lib/l10n/app_localizations.dart # app/lib/l10n/app_localizations_ar.dart # app/lib/l10n/app_localizations_be.dart # app/lib/l10n/app_localizations_bg.dart # app/lib/l10n/app_localizations_bn.dart # app/lib/l10n/app_localizations_bs.dart # app/lib/l10n/app_localizations_ca.dart # app/lib/l10n/app_localizations_cs.dart # app/lib/l10n/app_localizations_da.dart # app/lib/l10n/app_localizations_de.dart # app/lib/l10n/app_localizations_el.dart # app/lib/l10n/app_localizations_en.dart # app/lib/l10n/app_localizations_es.dart # app/lib/l10n/app_localizations_et.dart # app/lib/l10n/app_localizations_fa.dart # app/lib/l10n/app_localizations_fi.dart # app/lib/l10n/app_localizations_fr.dart # app/lib/l10n/app_localizations_he.dart # app/lib/l10n/app_localizations_hi.dart # app/lib/l10n/app_localizations_hr.dart # app/lib/l10n/app_localizations_hu.dart # app/lib/l10n/app_localizations_id.dart # app/lib/l10n/app_localizations_it.dart # app/lib/l10n/app_localizations_ja.dart # app/lib/l10n/app_localizations_kn.dart # app/lib/l10n/app_localizations_ko.dart # app/lib/l10n/app_localizations_lt.dart # app/lib/l10n/app_localizations_lv.dart # app/lib/l10n/app_localizations_mk.dart # app/lib/l10n/app_localizations_mr.dart # app/lib/l10n/app_localizations_ms.dart # app/lib/l10n/app_localizations_nl.dart # app/lib/l10n/app_localizations_no.dart # app/lib/l10n/app_localizations_pl.dart # app/lib/l10n/app_localizations_pt.dart # app/lib/l10n/app_localizations_ro.dart # app/lib/l10n/app_localizations_ru.dart # app/lib/l10n/app_localizations_sk.dart # app/lib/l10n/app_localizations_sl.dart # app/lib/l10n/app_localizations_sr.dart # app/lib/l10n/app_localizations_sv.dart # app/lib/l10n/app_localizations_ta.dart # app/lib/l10n/app_localizations_te.dart # app/lib/l10n/app_localizations_th.dart # app/lib/l10n/app_localizations_tl.dart # app/lib/l10n/app_localizations_tr.dart # app/lib/l10n/app_localizations_uk.dart # app/lib/l10n/app_localizations_ur.dart # app/lib/l10n/app_localizations_vi.dart # app/lib/l10n/app_localizations_zh.dart # app/lib/l10n/app_lt.arb # app/lib/l10n/app_lv.arb # app/lib/l10n/app_mk.arb # app/lib/l10n/app_mr.arb # app/lib/l10n/app_ms.arb # app/lib/l10n/app_nl.arb # app/lib/l10n/app_no.arb # app/lib/l10n/app_pl.arb # app/lib/l10n/app_pt.arb # app/lib/l10n/app_ro.arb # app/lib/l10n/app_ru.arb # app/lib/l10n/app_sk.arb # app/lib/l10n/app_sl.arb # app/lib/l10n/app_sr.arb # app/lib/l10n/app_sv.arb # app/lib/l10n/app_ta.arb # app/lib/l10n/app_te.arb # app/lib/l10n/app_th.arb # app/lib/l10n/app_tl.arb # app/lib/l10n/app_tr.arb # app/lib/l10n/app_uk.arb # app/lib/l10n/app_ur.arb # app/lib/l10n/app_vi.arb # app/lib/l10n/app_zh.arb # app/test/backend/storefront_wire_test.dart # backend/config/dream_agent.py # backend/config/storefront_ladder.json # backend/database/dream_store.py # backend/docs/listen_pusher_pipeline.mdx # backend/docs/storefront-contract.md # backend/models/storefront.py # backend/routers/storefront.py # backend/scripts/check_app_client_openapi_compatibility.py # backend/scripts/dart_schema_groups.py # backend/tests/unit/test_dream_report.py # backend/tests/unit/test_storefront.py # backend/utils/dream_agent.py # backend/utils/dream_metrics.py # backend/utils/storefront.py # config/feature-flags.yaml # desktop/macos/CHANGELOG.json # desktop/macos/Desktop/Sources/Generated/OmiApi.generated.swift # desktop/windows/src/renderer/src/lib/omiApi.generated.ts # docs/api-reference/app-client-openapi.json # web/admin/lib/services/omi-api/omiApi.generated.ts # web/app/src/lib/omiApi.generated.ts # web/personas-open-source/src/lib/omiApi.generated.ts
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Superseded: the schema-1 contract below is replaced by schema_version 2 (backend #21397, client #21363). This PR lands via #21363.
What changed and why
Add the mobile presentation-only storefront model and hermetic fixtures for Ladder v1 (W6, spec §5–§7). The backend owns tier order, names, recommendation, outcome copy, prices, savings, usage context and entitlement decisions. Add a typed checkout seam that rejects a presentation price if it differs from the existing catalog-selected ID; existing callers are unchanged. No payment rail or entitlement behavior changes.
Paywall design contract
Omi is a wearable memory companion: sell continuity and useful follow-through, without making captured memories feel hostage. One contextual sheet explains the exact budget/MCP limit or Pro feature the person reached for, then offers a calm comparison. No unsolicited acquisition popups. North star: ARPMAU; guardrails: activated 30-day paid conversion and payer net MRR retention. Client funnel events measure the journey; backend billing events measure revenue.
Render backend-ordered Free / Plus / Pro / Architect as stacked, readable cards. The backend recommended tier gets a neutral border and label and is never preselected. Recommendation is contextual and backend-owned: Pro at proactive-agent or screen-context gates, and for Plus users or legacy plans priced at or above Plus; Plus for Free users at usage-budget or MCP daily caps. Without context, Plus for Free users and Pro otherwise. The client passes the opening source and only renders the returned recommended flag. Each card leads with a concrete outcome, then server-computed ≈ recording hours, followed by honest billed amount and monthly equivalent. Never say “unlimited”, infer hours from old minute caps, invent icons, or disturb filter icons/chat composer.
Monthly is the initial billing choice. Annual is an explicit opt-in. Display savings only when the backend computes them from live monthly/annual prices; show full billed amount, monthly equivalent and renewal terms together. Checkout forwards the exact price ID through the existing purchase calls. No new iOS external links or purchase copy;
showSubscriptionUIremains authoritative.Show the current plan separately, including legacy plans absent from the new-buyer ladder. Never preselect a replacement, alter legacy entitlements or auto-migrate a subscriber. Restore/refresh and existing payment management remain reachable. Close stays visible outside the scrolling content during every state and payment attempt.
Use the existing Omi sheet, button, spinner and page-state primitives, neutral light/dark tokens, scalable type and ≥44 pt controls. Stack cards and wrap billing controls on small screens; scroll rather than truncate prices or renewal terms. Screen readers announce card headings, recommendation, current-plan state and the billing selection. No decorative hero competes with the decision.
Loading shows a bounded spinner; empty means no offers, with refresh and manage still reachable; errors offer Try Again without substituting stale or invented prices. A malformed/unsupported payload cannot enable checkout. At 80–<100% show a nonblocking budget banner; W4 owns the 100% choice. Pro gate decisions come from the backend, with legacy accounts exempt. MCP context uses the same sheet.
Integration boundary
W5's endpoint is not on this base. This is a provisional client contract and test fixture, not live catalog data. The JSON fixture includes synthetic price IDs and illustrative hours/prices exclusively under
app/test/. A later adapter owns endpoint/field mapping in one file. W5 must implement the coordinator’s contextual recommendation ruling; W6 passes source and renders the returned flag without client-side inference. No fixture can be used as a checkout catalog.Product invariants affected
none
How it was verified
cd app && flutter test test/models/plan_storefront_test.dart— 4 tests pass.make preflight— result recorded in final report.bash app/test.shlaunched with empty API base; result recorded in final report.Tests
Tests preserve exact IDs and ordering, computed server savings, future plan identity, hidden UI default, malformed schema/price rejection, duplicate tiers and the legacy/100% nudge boundary.
UI evidence
UI-Evidence: none -- typed model, hermetic fixture and nonvisual checkout validation seam only; no rendered surface changes.
Failure class (fixes)
Failure-Class: none
Reviewer risks