Repository navigation
feat: ladder v1 plan mechanics (flagged off) - #21488
Draft
Git-on-my-level wants to merge 84 commits into
Draft
Git-on-my-level wants to merge 84 commits into
Git-on-my-level wants to merge 84 commits into
Conversation
When live hours run out (or by choice), Omi keeps recording and can transcribe on the phone. This adds the mobile UI for that, behind the default-off PostHog flag mobile-on-device-overflow: - at-cap choice sheet (transcribe on this phone / upgrade), offered from the existing freemium prompt only when the flag is on and live hours are used up - model setup (step 1) and schedule picker (step 2: while charging, every few hours, as you talk) - conversations-list banner for audio waiting on the phone, waiting chips on recording rows, finish-setup and unsupported-phone variants - provenance chips on conversation rows and a provenance section on the summary (why, Redo with Omi Cloud, keep everything on this phone) - On-Device Transcription settings page and its entry row - settings model persisted via SharedPreferencesUtil, a pure scheduling policy, an OnDeviceModel abstraction (Android Whisper, iOS unavailable until Parakeet lands), and placeholder benchmark numbers in one file With the flag off every new surface renders nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings > Plan renders GET /v2/payments/plans behind web-plan-storefront (default off): backend header, current line, legacy note, usage meter, interval toggle, tier cards, blocked lines and CTAs, verbatim. Purchases use the payload's price ids through the existing checkout-session and upgrade-subscription rails, gated by the same checkout decision as mobile. web-contextual-upsells adds the budget meter, the 80% nudge and the exhausted-budget entry (trigger=usage_budget, Architect for Pro at 100%). Unknown fields are ignored; unknown actions, intervals, usage kinds and plan ids render inert. The registry scanner now sees TypeScript PostHog flag constants, and the four web/desktop flags are registered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
At 100% the payload has no nudge_line, so the banner drew a bare button. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… payload Settings > Plan & Usage renders GET /v2/payments/plans behind desktop-plan-storefront (default off) in place of the local plan card and grid: backend header, current line, legacy note, usage meter, interval chips, tier cards, blocked lines and CTAs, verbatim. Purchases use the payload's price ids through the existing startCheckout rail (upgrade for paid subscribers, checkout session for Free and resubscribe), gated by the same checkout decision as mobile and web. desktop-contextual-upsells adds the 80% nudge and the exhausted-budget sheet (trigger=usage_budget). Tests run every backend golden through the decoder and checkout boundary, and render the review states offscreen with ImageRenderer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add gated native engines, phone-only WAL custody, charging schedulers, and hash-bound summary provenance. Co-Authored-By: Codex <noreply@openai.com>
Preserve both main localization updates and flagged on-device copy through the l10n tool. Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Regenerate the complete merged locale set and record all 22 audited UI states with SF Pro fonts. Co-Authored-By: Codex <noreply@openai.com>
Use SafeDefaults for the background charging policy and guard the dynamically loaded native bridge. Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Codex <noreply@openai.com>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
…ne/sub-ladder-cutover
…lane/sub-ladder-cutover # Conflicts: # backend/docs/listen_pusher_pipeline.mdx # config/feature-flags.yaml
# Conflicts: # backend/docs/listen_pusher_pipeline.mdx # config/feature-flags.yaml # scripts/test_check_feature_flag_registry.py
Clients can open the plans sheet from the usage meter without inventing unit copy, and the at-cap title matches on every surface. Co-authored-by: Cursor <cursoragent@cursor.com>
Settings follow the backend meter, OS-managed speech skips setup unless assets are installing, and the save-usage toggle no longer upsells Pro. Co-authored-by: Cursor <cursoragent@cursor.com>
The usage-meter push updated the branch without starting the pull_request workflows. Co-authored-by: Cursor <cursoragent@cursor.com>
…row. Main replaced the processing card with the pending row. Phone-only recordings stay on the on-device banner and out of the cloud row. Co-authored-by: Cursor <cursoragent@cursor.com>
That file's diarization assertions currently fail on main, and a local addition made pre-push rerun them. Co-authored-by: Cursor <cursoragent@cursor.com>
The exporter and the checked-in document drifted in the merge, so the contract check rejected the branch. Co-authored-by: Cursor <cursoragent@cursor.com>
Main already rejects unsafe speaker and person ids, but publishing those path constraints narrows the released contract. The handlers still reject them before any document lookup. Co-authored-by: Cursor <cursoragent@cursor.com>
The conversations page always mounts the queue banner. Loading the storefront while the flag is off constructs the API client and reads Env, which the outage page test never initializes. Co-authored-by: Cursor <cursoragent@cursor.com>
…row. Main restored the backend unit shards and moved speaker-id checks out of the released app-client contract. The conversations page still mounts the on-device banner only as a no-op while the flag is off. Co-authored-by: Cursor <cursoragent@cursor.com>
The get-more row only opens the plans sheet, an unknown engine stays loading until detection finishes, and non-English devices use the app strings for the new usage labels. Co-authored-by: Cursor <cursoragent@cursor.com>
The released app-client contract stays free of extra speaker constraints, and the marker keys and review logs follow the bounds already on main. Co-authored-by: Cursor <cursoragent@cursor.com>
Daily Recaps stay above Today. The on-device queue sits with the pending row and stays empty while the overflow flag is off. Co-authored-by: Cursor <cursoragent@cursor.com>
Below 80%, and at 100%, the line is percent and reset, so it never says about 0 hours. Co-authored-by: Cursor <cursoragent@cursor.com>
The queue banner opens the plans sheet only when it is available, a failed engine lookup stays unavailable, and non-English devices localize the meter from hours_left. Co-authored-by: Cursor <cursoragent@cursor.com>
The plans sheet, reset date, billing-period words, and usage meter now follow the locale the client sends, with English kept as the per-key fallback. Co-authored-by: Cursor <cursoragent@cursor.com>
The route inventory and contract export import the app, and the catalog now formats dates with Babel. Co-authored-by: Cursor <cursoragent@cursor.com>
…sed. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
# Conflicts: # 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/lib/pages/settings/widgets/plans_sheet.dart # backend/docs/feature-flag-registry.md # backend/routers/payment.py # config/feature-flags.yaml # desktop/macos/Desktop/Sources/MainWindow/Pages/Settings/Components/SettingsContentView+BillingHelpers.swift # desktop/macos/Desktop/Sources/MainWindow/Pages/Settings/Sections/SettingsContentView+AccountBilling.swift # web/app/src/components/settings/__tests__/SettingsSubscriptionClarity.test.tsx
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements and integrates ladder-v1 plan mechanics across backend, mobile, web and macOS behind default-off flags. Legacy prices and installed-client entitlements stay unchanged. Keep draft; do not merge or mark ready.
Integrated status
ladder_v1_{plus,pro,architect}_{month,year}), config-bound price IDs, collision/legacy guards, read-only price attestation, renewal/reconciliation recognition and interval-safe reactivation. No Stripe writes.at_capstrings, reads/writes the capability-gated monthly choice before enabling on-device processing, and opens upgrades withtrigger=usage_budget. Device audio remains in durable custody; a choice never grants cloud entitlement.lane/sub-ladder-clientsat d70df24 andlane/ondevice-overflow-ux/ feat(app): on-device overflow engines, processing and summaries (flagged off) #21466 through 55ce061 with merge commits. Includes native engines, scheduled runner, local summaries, provenance and phone-only WAL policy. Merged main through a42840d (including shipped feat: subscription clarity for everyone (pre-ladder) #21474), then overflow 55ce061. feat(app): on-device overflow engines, processing and summaries (flagged off) #21466 is fully contained; the lead will close it. Main’s shipped freemium fallback remains authoritative; Parakeet, runner, scheduling and provenance remain behindmobile-on-device-overflow. Phone-only overflow takes precedence over a still-valid cloud batch authorization (real packet-owner regression); no rebase or force push.budget_80trigger to contractusage_budgetand omitted absent triggers instead of sending an invalid empty literal; added backendpro_budget80golden and byte-identical mobile fixture. Pro sees Architect only at 100% monthly usage.proalias retirement. Existing client behavior is preserved until the audited retirement step.meter_line,reset_label) and "Get more usage" in the same card. The settings row and the queue banner's Get more usage button are shown only when the contextual plans sheet can open, and both callopenContextualPlanswithtrigger=usage_budget.openOnDeviceUpgradehas no legacy Usage page fallback. The storefront catalog now has every app locale. Flutter, web, and macOS send the UI locale aslocale. The backend string is the meter, the get-more label, and the at-cap title.hours_leftis still set only from 80% to below 100% when the estimate is positive.meter_linestill includes the hours estimate only in that band.meter_linestill includes the hours estimate only from 80% to below 100%; below that, and at the cap, it is percent and reset (meter.line_short). An unresolved engine stays loading. A failed or timed-out lookup is unavailable with retry and never opens Parakeet; Parakeet setup opens only after a positive non-OS-managed engine. SpeechAnalyzer turns on with no setup screen; a speech-asset install is a settings row ("Getting speech ready · 40%"), and the speech-model row stays hidden while the engine is OS-managed. Parakeet settings show the real 443 MB size. The save-usage toggle is "Always transcribe on this phone" / "Saves your usage. No speaker labels." Inspected renders:/Volumes/Ephemeral/scratch/ondevice-ux-renders-2026-10-15/(17-ios26-first-run-no-setup.pngremoved; it duplicated the settings screen).Flags (all default OFF)
Server:
STOREFRONT_LADDER_V1_ENABLED,LADDER_V1_LANE_METERING_ENABLED,LADDER_V1_BUDGET_ENFORCEMENT_ENABLED,LADDER_V1_PRO_GATES_ENABLED,LADDER_V1_MCP_DAILY_CAPS_ENABLED,LADDER_V1_WIRE_IDENTITY_ENABLED,LADDER_V1_RETIRE_PRO_ALIAS_ENABLED, existingHELD_CONTENT_ENABLED(plus client capability).Clients:
web-plan-storefront,web-contextual-upsells,mobile-plan-storefront,mobile-contextual-upsells,desktop-plan-storefront,desktop-contextual-upsells,mobile-on-device-overflow(debug compile opt-in only for local verification).Remaining cutover checklist
LADDER_V1_*_PRICE_IDenv IDs. Preserve all legacy prices and env vars.proalias last.chat_at_capcopy supplies the pause, reset date and upgrade path (Pro → Architect). Mobile, macOS and both web composers consume it and open plans withusage_budget. Capture, held content and on-device transcription/summaries continue. Legacy prices and flag-off admission retain their prior behavior.uses_shared_budgetalso prevents macOS from applying the older question counter below the budget cap; legacy quota responses omit both new fields.Rollback
Disable storefront/client flags to stop new sales. Independently disable budget/Pro/MCP/identity/overflow flags to restore prior admission. Keep price ID bindings for renewal/webhook recognition; restore alias acceptance if needed. Never rewrite subscriptions, usage history or captured content. Reset recovery retains its separate flag/runbook.
Final integration validation and conflicts
13e43149735b8d5987734af8c71065e71ca52f58had all 14 full CI workflows green. Head43bfb8ebe48bbf7f2609073b48156cd68512bdb3keeps Hangul meter segments together. Previous head600a3574978280b721f4eabf8804243a0bbae6eaformats the at-cap date from the reset timestamp instead of parsing the localized reset label, translates the plans-sheet chrome, and wraps CJK meter segments at the middle dot. It localizes the storefront catalog for every app locale and keeps the shared usage meter: plans-sheet-only get-more, unknown-engine loading, one usage card, and the speech-asset progress row. Speaker-label router, contract test, marker keys and review telemetry match current main; the app-client OpenAPI diff against main is the storefront fields, includingmore_label, with no extra speaker constraints. Main had replaced the home processing card with the pending row; the on-device queue stays beside it. Draft/open; not merged. No rebase or force push. feat(app): on-device overflow engines, processing and summaries (flagged off) #21466 is fully contained; the lead will close it.is_file()assertion. Current main messaging/Firestore provisioning changes are retained; OpenAPI and Dart/Swift/TypeScript artifacts were regenerated and match the tree.backend/test.sh: quota/budget/catalog/copy/metering, sync-v2 (182 tests), storage ownership, as-captured ingest and archive contracts. Legacy prices, flag-off behavior and monthly reset remain covered.bun run check: typecheck, 6 Bun smoke tests and 721 Vitest tests pass. macOSswift buildand 63 focused quota/storefront/decoder/render tests pass. iOS simulator and Android Dev debug builds pass with overflow enabled.make preflightand normal bounded push gates pass; the expanded integration/workflow scope runs 88 repository checks.actionlintpasses. No failed, pending or cancelled full workflow remains at verification. Analyzer and both overflow-enabled mobile debug builds pass..bin-named prefix directories, while retaining the exact byte comparison.Runbook:
backend/docs/ladder-v1-cutover.md. Refreshed and inspected renders:/Volumes/Ephemeral/scratch/sub-ladder-renders-2026-10-14/integrated/(real component/widget render harnesses, not live authenticated product acceptance).UI evidence:
Chat ruling renders (actual production pause components with backend goldens; all four tiers inspected):
INV-DATA-1: canonical account/data-plane authority is preserved; the headless engine reuses existing Firebase/profile initialization. Local debug fixtures are not shipped routing changes.
INV-CAP-1: captured audio remains in durable phone/WAL custody while cloud admission is held; capture retention and sync-fence regressions pass.
INV-UI-2: composer pause and contextual plans use existing Omi controls and shared confirmation; modal dismissal consumes Escape.
INV-CHAT-1: composer presentation uses the existing shared quota/provider; no new timeline, journal writer or continuity handler.
INV-UI-3: on-device provenance never invents speaker separation or named/count labels.
Failure-Class: FC-stt-allowance-not-enforced-at-dispatch
Earlier CI exposed eager Firestore imports and unnecessary executor work while ladder flags were off. Database resolution now occurs only after the relevant guard;
test_flag_off_cold_import_never_resolves_databaseand the existing async-boundary suites pin that contract.Line-Count-Exception: app/lib/pages/conversation_detail/conversation_detail_provider.dart | 1518 -> 1527 | Narrow flagged native processing and provenance integration; engine implementation remains in dedicated modules
Line-Count-Exception: app/lib/pages/settings/transcription_settings_page.dart | 1572 -> 1575 | Narrow entry to the isolated flagged on-device settings
Line-Count-Exception: app/lib/providers/conversation_provider.dart | 2121 -> 2133 | Narrow local conversation provenance and account-owned WAL integration
Line-Count-Exception: app/lib/services/capture/capture_controller.dart | 4475 -> 4495 | Narrow retained-capture overflow hooks; native runner stays separate
Line-Count-Exception: app/lib/services/wals/local_wal_sync.dart | 2690 -> 2714 | Flagged phone-only WAL exclusion from automatic and manual cloud drains
Line-Count-Exception: backend/routers/developer.py | 2752 -> 2753 | Optional on-device STT provenance reaches the existing transcript ingest seam
Line-Count-Exception: backend/routers/payment.py | 1746 -> 1752 | Narrow flagged ladder price checkout binding
Line-Count-Exception: backend/routers/sync.py | 2714 -> 2726 | Shared flagged budget admission before file consumption
Line-Count-Exception: backend/utils/subscription.py | 1950 -> 2015 | Shared flagged ladder policy at existing admission/reporting boundaries
Line-Count-Exception: backend/utils/sync/pipeline.py | 3212 -> 3215 | Validate owned scratch before the existing recursive cleanup seam; helper and regression tests remain separate
Line-Count-Exception: backend/routers/users.py | 2572 -> 2579 | Additive flagged chat pause projection at the existing quota endpoint; copy and admission live in dedicated modules
Cap enforcement follow-up (#6 / #7)
Merged main at
1239634f1bd(#21866) with a merge commit. Ladder live budgets refresh on each 60-second usage flush, including with legacy dispatch dark; funded dispatch uses the ladder budget. Sync and GPU workers reauthorize at execution/microbatch boundaries and preserve denied audio through the existing hold/defer recovery. Ladder-only activation bypasses legacy monthly-seconds dispatch.Enforced ladder totals include residual transcription seconds at the provisional unknown-provider speech rate (no silence discount), plus residual/unpriced gateway attempts at the standard-chat estimate. Coverage histograms expose residual seconds/attempts per evaluation; no historical writes. Normal live detection overshoot is about 60 seconds of speech per session plus persistence/read latency; concurrent in-flight jobs are not reserved. Flags remain OFF; PR remains draft.
Validation:
BACKEND_PYTEST_WORKERS=1 BACKEND_UNIT_TEST_FILE_LIST=<list> nice -n 10 bash backend/test.sh: targeted 205 passed; touched 578 passed; recovery 116 passed. Flutter analyzer ratchet passed (94 existing diagnostics); 49 focused storefront/checkout Flutter tests and 8 web billing tests passed. Full repository PR preflight: 89 selected checks passed. Final refresh regressions: 43 passed. CI caught duplicate coverage histogram registration during isolated auth imports; collectors now reuse the process registry (the existing observability pattern). The failing API-key suite plus ladder/STT regressions pass locally; exact-head CI is recorded in the PR checks tab. No prod/GCP or Stripe access.🤖 Generated with Claude Code