fix(desktop-windows): wire AutoCreatedTasksStep as step 14 of onboarding - #11
Open
formed2forge wants to merge 64 commits into
Open
fix(desktop-windows): wire AutoCreatedTasksStep as step 14 of onboarding#11formed2forge wants to merge 64 commits into
formed2forge wants to merge 64 commits into
Conversation
Remove unused imports, drop a duplicate MCP scope key and dead deploy helper, share the duplicated Levenshtein helper, and narrow bare excepts around datetime parsing. Update webhook/app-integration tests that stubbed the removed imports. Verification: BACKEND_UNIT_TEST_FILE_LIST covering test_memory_ingestion_text, test_verify_output_grounding, test_mcp_data_endpoints, test_async_app_integrations, test_async_webhooks — all passed. Co-authored-by: Max Carter 祁明思 <max@tsc.hk>
Delete unused memory/conversation UI remnants (including unused_element ignores and commented-out blocks) and share identical quick-edit, duration, and status pill helpers across sibling pages. Behavior preserved; Flutter SDK unavailable in this cloud VM so app tests were not executed here. Co-authored-by: Max Carter 祁明思 <max@tsc.hk>
Replace eval() with json.loads for Firebase service-account JSON, unify cm-builds under x-auth-token with HTTP status checks, and point onboarding sync tooling at desktop/macos/Desktop instead of the removed desktop/Desktop layout. Co-authored-by: Max Carter 祁明思 <max@tsc.hk>
Co-authored-by: Max Carter 祁明思 <max@tsc.hk>
Avoid growing the memory_ingestion package past its grandfathered source-file count by placing the shared Levenshtein helper in the existing ids module instead of adding a new text.py file. Co-authored-by: Max Carter 祁明思 <max@tsc.hk>
Delete the new text.py module so memory_ingestion stays within its grandfathered source-file count, and point pipeline/verify_output at ids. Co-authored-by: Max Carter 祁明思 <max@tsc.hk>
- scripts/install_onboarding_figma_sync.sh: point launchd WatchPaths at the real Resources bundle (desktop/macos/Desktop/Sources/Resources). - scripts/run_onboarding_figma_sync.sh: remove redundant -path clause for OnboardingLoadingAnimation.swift (already matched by -name 'Onboarding*.swift'). - app/lib/pages/memories/widgets/memory_edit_sheet.dart: showMemoryQuickEditSheet now accepts and forwards an optional onDelete callback instead of hardcoding a no-op. - app/lib/pages/phone_calls/active_call_banner.dart: ActiveCallTopBar uses the shared formatPhoneCallDuration helper, completing the duration-format dedup. - backend/tests/unit/test_async_app_integrations.py: test_no_threading_used inspects _async_trigger_realtime_audio_bytes bytecode (not the wrapper) and removes the brittle hasattr(app_integrations, 'threading') check.
Re-run dart format after flutter pub get so the pinned language version applies; 6 changed files now match the repo formatter. pubspec.lock picks up the same transitive meta/test_api bumps CI's pub get resolves. Failure-Class: none
Failure-Class: none
Rewrite the store-link helper as sequential ifs so Frontend Lint prettier/prettier passes after the hygiene indent change.
Hygiene cleanup dropped webhook_url_from_setting from utils.webhooks, so first-time setup treated raw Redis values as URLs and audio-bytes sends raised NameError. Parse stored settings through the helper again so ',5' and whitespace-only values stay disabled, and audio delivery can extract the endpoint. Failure-Class: none
Reproduce the revoked process-wide owner state, then establish and restore the test owner through RuntimeOwnerAuthorityTestFixture so suite order cannot turn authenticated capture into anonymous capture. Verification: RewindCaptureExclusionGenerationTests 9/9 passed; deterministic contamination recovery passed 50/50 runs; the repaired test passed inside two 5,769-test process runs (each full run retained one unrelated baseline failure). Failure-Class: FC-hand-listed-test-isolation-membership
formed2forge
force-pushed
the
fix/windows-onboarding-autocreated-tasks-step
branch
from
August 26, 2026 13:34
f15d0ca to
421ab31
Compare
BasedHardware#11303) <!-- CURSOR_AGENT_PR_BODY_BEGIN --> ## What changed and why High-confidence, behavior-preserving hygiene cleanup: remove proven dead code/unused imports, share identical helpers, narrow bare excepts around datetime parsing, and fix broken script paths. Net ~500 lines removed; no intentional product behavior change. ## Product invariants affected - INV-DATA-1 (merge from main brought app/lib/main.dart into diff; no intentional behavior change) - INV-MEM-4 (merge from main brought memory_ingestion files into diff; no intentional behavior change) - INV-MEM-1 (path match on memory ingestion / memories UI files; no intentional behavior change) ## How it was verified Focused backend unit tests via `bash test.sh` (file list below), script syntax checks, product line-count baseline ratchet, and `scripts/pr-preflight --pr-body-file` (27 checks passed). App Flutter SDK is unavailable on this Linux cloud VM; Dart brace balance was checked manually and pre-push used the documented Flutter/Dart skip hatches. ``` BACKEND_UNIT_TEST_FILE_LIST=... bash test.sh # test_memory_ingestion_edit_distance: 2 passed # test_verify_output_grounding: 26 passed # test_async_app_integrations: 18 passed # test_mcp_data_endpoints: 67 passed # test_async_webhooks: 22 passed scripts/pr-preflight --pr-body-file /tmp/pr-body.md # PR preflight passed: 27 checks ``` ## Tests - Added `backend/tests/unit/test_memory_ingestion_edit_distance.py` proving pipeline and verify_output share `ids.edit_distance` - Updated `test_async_webhooks` / `test_async_app_integrations` fixtures for removed unused imports - No Flutter tests run here (SDK absent); CI remains the app authority ## Failure class (fixes) Failure-Class: none ## Scoped cleanups - Backend: unused imports, duplicate MCP scope key, shared Levenshtein helper, bare-except narrowing, dead `_inject_config_map` - App: dead memory/conversation UI remnants; shared quick-edit / duration / status-pill helpers - Scripts: `eval` → `json.loads`, unified `cm-builds`, onboarding paths → `desktop/macos/Desktop` - CI: downward product file line-count baseline ratchet for shrunk backend files <!-- CURSOR_AGENT_PR_BODY_END --> <div><a href="https://cursor.com/agents/bc-6f28e0a4-53fb-41b4-8046-c6e2dbebd2bd?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-6f28e0a4-53fb-41b4-8046-c6e2dbebd2bd&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div>
Unused import introduced by BYOK refactor (BasedHardware#11454). Causes pyright error in CI.\n\nFailure-Class: none
…asedHardware#12274) The manual development backend deploy ran its post-promotion smoke against https://api.omi.dev, which has never resolved: the omi.dev zone exists but the api record is NXDOMAIN, so smoke_what_matters_now.py could not reach anything and failed with "could not reach the deployed backend". That failed the deploy after traffic had already shifted and triggered the traffic restore, rolling the promotion back. Development's real public API host is api.omiapi.com, which matches the rest of the development domain family (parakeet.omiapi.com, nllb.omiapi.com, pusher.omiapi.com in backend/deploy/runtime_env/dev.overlay.yaml) exactly as production uses the omi.me family. It serves /ready 200 today. Making api.omi.dev real was rejected: the codebase already uses it as the canonical fake hostname in mobile production-routing tests, so giving it a live record would undermine those fixtures. This defect was introduced by 933fdf7 on the same day as the probe-signer defect fixed in BasedHardware#12264, and stayed invisible because the probe failed first. Run 33008079463 is the first development deploy to reach this step. The step is guarded to the manual development lane; the production smoke keeps its own api.omi.me path untouched. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…asedHardware#11454's contract (BasedHardware#12277) * chore(desktop): unbreak the local Swift build and formatter gate on Xcode 26 Two pre-existing blockers on `main` that fail the pre-push gate for any desktop PR when the local toolchain is newer than the pinned Xcode 16.4: - `AppState+Permissions.swift` has swift-format drift, and `desktop-swift-format-lint` runs `lint-scope` over every first-party Swift file. Formatter output only. - `AppState+Transcription.swift:848` captures `alertPresenter` implicitly in an escaping closure. Swift 6.2 (Xcode 26.x) rejects this; Xcode 16.4 accepts it. Writing `self.` is what the diagnostic asks for and is the same strong capture the implicit form already produced, so behavior is unchanged on both toolchains. Failure-Class: none * chore(desktop): dodge the Xcode 16.4 SILGen segfault on alertPresenter Every desktop CI lane (Static & Test Contracts, Release Compile, Build & Tests aggregate) has been red since main's d49f978 landed `var alertPresenter: any DesktopAlertPresenting = AppKitSheetAlertPresenter()`: the pinned Xcode 16.4 toolchain segfaults (signal 11) in silgen emitStoredPropertyInitialization while lowering that existential-erasure default initializer. Reproduced on main itself (d49f978, fb67ca9, 50cf064 all failed; d06e220 passed only because its desktop jobs were path-filter skipped) and on unrelated PRs (BasedHardware#12269, BasedHardware#12272), so this is not specific to this branch. Move the initializer from the stored-property default position into init(). Identical semantics on both toolchains - AppState is @mainactor with a single designated init, and the alert tests overwrite the presenter immediately after construction. Xcode 26.6 parses, swift-format lint passes, changelog gate passes. Failure-Class: none * test(desktop): enroll BYOK fingerprints so paywall/agent tests match BasedHardware#11454's contract BasedHardware#11454 replaced the old "all keys present in UserDefaults" check for isByokActive with a stricter one: the selected provider's *current* key must match a fingerprint already persisted via APIKeyService.persistEnrolledFingerprints (set by activateBYOK reconciliation after BYOKValidator confirms the key). Seven tests across BYOKPaywallTests and AgentRuntimeProcessTests still set up state the old way — raw UserDefaults keys, no enrollment — and started failing the moment CI could actually reach them (BasedHardware#12276): main's own contract job caught this on BasedHardware#11454 before merge, but a compiler crash landed 8 minutes earlier (tracked separately in BasedHardware#12275) blocked every real desktop Swift test run afterward, so it went unnoticed. This is a test-only fix that transcribes BasedHardware#11454's already-stated and already-tested enrollment contract into the tests that never learned about it; no Sources change. - BYOKPaywallTests: add `enroll(_:)`, calling `persistEnrolledFingerprints` with the SHA-256 fingerprint of the provider's current key, exactly as `activateBYOK` reconciliation would after successful validation. Five tests were asserting on raw key presence: testByokActiveRequiresSelectedLLMKey (needs a *second* enrollment after setAllBYOKKeys() rewrites openrouter's key and invalidates the first fingerprint), testBuildHeadersAttachSelectedLLMByokKey, testBuildHeadersSuppressesOnlyInvalidByokHeader, testPaywallFlagSuppressedWhenByokActive, and testRemovingDeepgramKeyLeavesSelectedLLMByokActive (both of the last two now select the provider explicitly rather than relying on legacy first-match inference, since every provider's key is set and the test must enroll the same provider it selects). - AgentRuntimeProcessTests: enroll the selected provider in testUsableByokEnvironmentIncludesAllKeysWhenAllProvidersAreUsable and testUsableByokEnvironmentSuppressesAllKeysWhenOneProviderIsKnownBad — usableBYOKEnvironment() gates on isByokActive before the CredentialHealthManager suppression these tests exercise. Both existing `defer` blocks now also save/restore the enrollment map. Not touched: testBuildHeadersCanExplicitlyExcludeByokKeys and testLowLevelTransportDefaultsToExcludingByokKeys currently pass vacuously — neither enrolls a provider, so isByokActive is false and headers come back nil regardless of includeBYOK. Flagging for whoever owns BYOK rather than fixing here, since giving them real coverage means deciding what "excluded despite being active" should assert, and that's a product call, not a mechanical transcription of BasedHardware#11454. No assertions were weakened or removed — every fix completes test setup to match the stated contract. testPaywallFlagSuppressedWhenByokActive is the only coverage that an enrolled BYOK user is never paywalled; it was made to pass by enrolling correctly, not by loosening what it checks. Fixes: BasedHardware#12276 Failure-Class: none --------- Co-authored-by: David Zhang <9387252+Git-on-my-level@users.noreply.github.com>
Auto-generated: consolidates unreleased changelog fragments into v0.12.223 and regenerates CHANGELOG.json.
…re#12267) ## Why The 2026-08 macOS churn cohort analysis (`omi-knowledge-base/projects/macos-churn-analysis/evidence/2026-08-26-macos-churn-cohort-analysis.md`) listed `chat_agent_error` as "well-powered but cannot explain itself". Measured against PostHog (project 302298, macOS, JSON extraction rather than the map subscript — see below), August 2026 `chat_agent_error`: | property | populated | | --- | --- | | `error_class` / `surface` / `harness` | 1177 / 1197 | | `error_code` | 909 / 1197 | | `root_cause` | 178 / 1197 | `error_class` is fine — schema v2 fixed that. The remaining hole is that only one of the ~15 `telemetryAttempt.fail(...)` call sites (`ChatProvider.swift:5446`) passes a `ChatQueryErrorDetail`. Every other terminal — timeout, tool stall, session setup, bridge unavailable, attachment upload, concurrent request — reached PostHog with no `error_code` at all, and `root_cause` was hardcoded for exactly one error class (`.authentication`). A typed failure existed at the catch boundary and was collapsed to a bare class name by the time it was recorded. ## What changed `ChatQueryErrorClass` now classifies itself, so the fix lands at the one place that builds the payload instead of at 15 call sites: - `rootCause` maps every class to a bounded `ChatQueryRootCause` (subsystem attribution: provider, agent runtime, bridge process, local session, network, device resources, ...). `.authentication` keeps the already-published `provider_claude` value so existing PostHog breakdowns stay valid. - `fallbackErrorCode(watchdogFired:)` gives every class a bounded code when no `ChatQueryErrorDetail` is available. It separates `watchdog_timeout` from `bridge_timeout`, which have different owners and were previously indistinguishable. - A supplied `ChatQueryErrorDetail` still wins; the fallback only fills the gap. No raw exception text, prompt, path, or message enters the payload — the values are enum raw values, per the analytics integrity contract in `desktop/macos/AGENTS.md`. ## Proof `ChatQueryTelemetryTests`: - `testEveryFailureClassCarriesABoundedCodeAndRootCause` iterates all 16 classes and fails if any emits an empty/absent code or an out-of- vocabulary root cause. - `testRootCauseAndTimeoutCodesStayActionable` pins the compatibility value for auth and the watchdog/bridge timeout split. - `testErrorDetailCodeOverridesTheClassFallback` proves the fallback cannot shadow real detail. - `testAnalyticsPayloadUsesTypedAllowlist` (existing) still pins the exact emitted key set. 37 tests pass locally. Failure-Class: FC-typed-failure-collapsed-to-generic
Soniox streams token deltas rather than utterances: tokens flip is_final once committed and non-final ones are revised in place, so only finals are forwarded and consecutive finals from one speaker coalesce into a segment. Diarization and language identification are both requested, so auto-detect sessions need no declared language -- the token carries its own speaker and language. A declared language is passed as a hint instead.
Live tokens arrive with duration_ms null, so the previous arithmetic collapsed every segment's end onto its start. Verified against the live service with a 26s speech sample: 98 final tokens, all with duration_ms null.
streaming.py was already over the product line-count ratchet before this branch; adding a provider inline pushed it further. The Soniox protocol is self-contained, so it lives in utils/stt/soniox.py and streaming.py re-exports it for existing import sites. Imports flow one way, so there is no cycle. Also drops a redundant isinstance on a typed dict that pyright rejected, and annotates JSON-sourced tokens as Any so the remaining runtime guard is real.
…12295) ## Why Modulate's multilingual streaming endpoint is failing for our account (~90% of streams), and both Deepgram accounts we can reach are normal-tier with concurrency ceilings around 5 pods' worth each. There is currently no provider that can carry our auto-detect traffic reliably. Soniox is the only vendor I could find that does **speaker diarization and automatic language identification together in real time**: - AssemblyAI Universal-Streaming: diarization yes, but 18 languages — missing ko, cs, uk, pl, zh-TW, all of which appear in our traffic. - Speechmatics: richest diarization config and 55+ languages, but language identification is documented as batch-only, and 82% of our sessions arrive as `multi` with no declared language. - Soniox: diarization and language ID both in streaming, 60+ languages, auto-detect needs no language specified. ## What changed Adds Soniox as a selectable streaming provider. **Nothing changes unless a deployment opts in** — it is absent from `DEFAULT_MODELS_BY_SURFACE`, so it is only reachable by naming `soniox` in `STT_SERVICE_MODELS`. - `config/stt_provider_policy.py` — new `SONIOX_PROVIDER`, streaming surface only (the batch path has no Soniox client, and PTT dispatches Parakeet/Modulate alone). - `utils/stt/streaming.py` — `SafeSonioxSocket` + `process_audio_soniox`, a new `STTService.soniox`, and its own circuit breaker. - `routers/listen/receiver.py` — a Soniox primary falls back through Modulate then Deepgram, the same chain a Modulate primary uses. - charts — `SONIOX_API_KEY` passed to backend-listen, `optional: true` so the deployment is unaffected until the secret exists. ### Protocol notes Soniox streams **token deltas**, not utterances. Tokens flip `is_final` once committed and non-final ones are revised in place, so forwarding them would emit text the model later retracts. The socket forwards only finals, and coalesces consecutive finals from one speaker into a segment shaped like the other providers'. `enable_speaker_diarization` and `enable_language_identification` are both on, so each token carries its own `speaker` and `language`. Auto-detect sessions send no `language_hints`; a declared language is passed as a hint rather than a constraint. ## Testing - `test_soniox_streaming.py` — 9 cases: final-only forwarding, speaker coalescing, speaker changes, missing speaker field, error propagation, `preseconds` trimming, hint behaviour for `multi` vs declared language, and that Soniox stays out of the defaults. - Existing STT suites pass per-file: policy (21), Modulate fallback (11), live STT failure (13), death monitor (4), runtime env validator (85). - The full `tests/unit` run segfaults in a native extension on my machine; that reproduces on unmodified `main`, so files were run individually. ## Not verified **No end-to-end transcription test.** The provided key authenticates and the protocol handshake works, but the account returns: ``` error_code 402 organization_balance_exhausted "Organization balance exhausted. Please either add funds manually or enable autopay." ``` So token assembly, diarization quality and language identification are all exercised against a fake server only. Before enabling this in production, the account needs funding and a real two-speaker sample should be run through it. Soniox's own docs also warn that real-time diarization has higher attribution error than async, and that endpoint detection reduces diarization accuracy — relevant since our VAD gate finalizes aggressively. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/BasedHardware/omi/pull/12295?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> ## Line-count ratchet Line-Count-Exception: backend/utils/stt/streaming.py | 1692 -> 1707 | The Soniox client (230 lines) lives in its own module, utils/stt/soniox.py; the 15 lines remaining here are the irreducible wiring a provider needs in the shared module — the STTService member, its get_model_name mapping, its circuit breaker and the _circuit_for_primary branch, the selection branch, and the re-export import.
PR BasedHardware#12295 added the SONIOX_API_KEY helm binding without registering it, so deployment-secret-boundary rejects main's tip and Release Eligibility fails -- blocking every backend deploy, not just this feature.
## Why `main` is currently undeployable. BasedHardware#12295 added the `SONIOX_API_KEY` helm binding to both backend-listen values files but did not register it in the classification ledger, so `deployment-secret-boundary` fails: ``` - backend/charts/backend-listen/prod_omi_backend_listen_values.yaml: helm_secret binding SONIOX_API_KEY is unclassified - backend/charts/backend-listen/dev_omi_backend_listen_values.yaml: helm_secret binding SONIOX_API_KEY is unclassified ``` Release Eligibility failed on `a700baa559` as a result, which blocks **all** backend deploys, not only the Soniox feature. ## What changed One line: `SONIOX_API_KEY` added to `kinds.secret` in `config/deployment-setting-classification.json`, next to `MODULATE_API_KEY` and `DEEPGRAM_API_KEY`. It is a provider credential, so `secret` is the correct classification — it must not be a `config` value or reach a public build. ## Testing `python3 .github/scripts/check_deployment_secret_boundary.py --base origin/main` → `deployment secret-boundary check passed` ## How this got missed The check runs in the shared PR preflight. On BasedHardware#12295 that preflight failed first on the line-count ratchet, so the secret-boundary failure was never surfaced; the PR was merged before the corrected run completed. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/BasedHardware/omi/pull/12296?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
…ess (BasedHardware#12268) * fix(app): surface 'pendant is recording' instead of silent success when Limitless flash sync stalls Root cause: the Limitless protocol's mode command (msg 8) makes flash-page drain and recording mutually exclusive — there is no mode that serves stored pages while a recording session is being written. When the pendant is hardware-button recording, the drain starves, the 30s stall detector in FlashPageWalSyncImpl ends the pass, WalSyncs.syncAll discards the result, and SyncProvider falls through to toCompleted() — the user sees "synced" while nothing transferred and nothing tells them to stop recording. Durable guard: - On a stall, FlashPageWalSyncImpl re-queries device status while still in batch mode and classifies the stall: newest_flash_page advanced past the enumerated end while the drain starved => recordingSuspected (the pendant is minting pages it will not serve). Exposed as FlashSyncStallReason via WalSyncs.flashStallReason; stamped on the flash_page_download_partial event. - SyncProvider maps a recordingSuspected stall with no new conversations to a user-facing error state ("Press the Pendant's button to stop recording, then sync again") instead of silent completion. Message is l10n'd across all 49 locales (pendantRecordingSyncBlocked). - Unknown stalls (plain transfer lulls) keep the existing resume-on-next-sync behavior; WAL stays 'miss' with an advanced storageOffset either way. The protocol limitation itself is not fixable app-side; this closes the silent-ops half of the failure (the user now learns why sync stopped and how to unblock it). The native Transcribe Later drain engines share the silent stall pattern (NSLog-only) — deferred as a separate surface. Verification: - flutter test test/unit/flash_page_stall_classification_test.dart test/providers/sync_provider_flash_stall_test.dart — 7/7 pass (regression test asserts the stall no longer reports success). - bash app/test.sh — 753/753 pass. - flutter gen-l10n — zero untranslated messages. - bash app/scripts/analyze_ratchet.sh — passed. - Live pendant-in-hand verification pending (pendant currently paired to the TestFlight build); code path exercised via provider-level tests through the real _performSync flow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(app): surface 'pendant is full' instead of silent success when flash sync stalls A full Limitless pendant halts recording (red LED flash) but stays armed in recording mode, and in that state the firmware serves no flash pages: an offline sync starves, the 30s stall detector ends the drain, and the result used to fall through to `toCompleted` — telling the user everything synced when nothing did. This is the real-world trigger behind the silent-stall bug (confirmed on hardware); the existing `recordingSuspected` path can never fire for it, because a full pendant cannot mint new flash pages, so the newest-page-advanced heuristic stays false. Root cause / durable guard: classify a stall with zero `free_capture_pages` as a new `FlashSyncStallReason.deviceFull` (checked before the recording heuristic, since fullness cannot be inferred from page movement). SyncProvider gains a matching error branch and a full-specific message telling the user to press the button to stop recording, then sync again — the exact recovery the firmware requires. l10n: new key `pendantFullSyncBlocked` translated across all 49 locales; `flutter gen-l10n` reports zero untranslated. Tests: extended classifyStall unit tests (zero-free = deviceFull, full takes precedence over newest-page movement, free-remaining stays unknown) and the SyncProvider regression test (deviceFull surfaces an error, not success). Verification: - `flutter test` on the stall + provider suites: 11 passed. - `scripts/analyze_ratchet.sh`: passed. `flutter gen-l10n`: 0 untranslated. - Hardware (iPhone 17 Pro, dev build): with a deterministic test harness that drove the drain into a stall and injected free_capture_pages=0, the classifier logged `deviceFull` and the full-storage message rendered on screen. Real full-pendant repro (passive, ~24h to refill flash) still pending. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(app): reflow the sync error banner so long messages aren't truncated The sync error banner clamped its message to `maxLines: 2` + ellipsis, so a long recovery message was cut mid-word ("…storage is full and i…"), hiding the very instruction the user needs to act on. It was worst at larger iOS accessibility text scales, where two lines hold even less. Found while verifying the pendant-full error on a device with enlarged system fonts. Extract the banner into a small `SyncErrorCard` widget (reviewable, testable) that drops the line clamp so the message reflows in full, and top-aligns the Row so the icon and Retry pill stay put when the text wraps to several lines. Behavior-preserving for the common short-error case. Tests: `sync_error_card_test.dart` asserts the message is never clamped (maxLines null, no ellipsis) and that the full message stays visible without a layout overflow at a 2x accessibility text scale — the regression that would have caught the original bug. Verification: - `flutter test test/widgets/sync_error_card_test.dart`: 2 passed. - `scripts/analyze_ratchet.sh`: passed (prefer_const_constructors improved by 1). - Hardware (iPhone 17 Pro, dev build, enlarged accessibility fonts): the full "Your Pendant's storage is full…press the Pendant's button…then sync again" message renders across multiple lines with no truncation (screenshot before and after the extraction confirm identical, full-message rendering). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(app): log flash-page stall classification evidence Persist a `flash_page_stall_classified` event (reason + whether the post-stall status read was null + free/newest page counters) at the point the drain stall is classified. This is the one read that decides which message the user sees, and it was previously unlogged. Rationale: the deviceFull trigger is confirmed only through injected status in a test harness — a real full pendant reporting `free_capture_pages <= 0` in a clean status read has not yet been observed (the pendant sat ~65% full all session, and one real status read during a stall came back malformed with no free-page field at all). If the next natural full event classifies as `unknown` and silently completes, this record is the difference between "assumption was wrong (full != free==0)" and "the status read failed" — turning the passive full-pendant repro into a conclusive result instead of a guess. Aligns with the repo's "silent ops is not allowed" observability rule. Verification: analyzer ratchet passes; stall + provider suites still pass (11). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(app): regenerate l10n output for pendant stall messages The rebase onto current upstream/main hand-merged the .arb sources for pendantRecordingSyncBlocked/pendantFullSyncBlocked (added new keys at the tail of each of the 49 locale files, colliding with hundreds of upstream insertions at the same position) but left the generated app_localizations*.dart getters stale, since regenerating those correctly requires the toolchain rather than a text merge. flutter gen-l10n from the merged .arb sources. * chore: register FC-drain-stall-completes-silently failure class Declares the failure-class boundary these Limitless flash-drain fixes repair: a device-storage drain stall caused by a structurally-unservable device state (protocol mode conflict, full storage still armed for recording) must be classified and surfaced as an actionable error, never silently fall through to a generic "completed, nothing new" success. Two fixes in this PR (recordingSuspected, deviceFull) share this cause and its guard (FlashSyncStallReason + SyncProvider's classified-stall branch), so this records the reusable boundary rather than treating each as an isolated bug. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…rdware#12294) * fix(desktop): keep legacy sidebars clickable Register the visible legacy sidebar as an interactive shell surface while preserving the modern settings panel corner cut-outs. Failure-Class: none * fix(desktop): ground the legacy sidebar slot Give the old Home sidebar slot one real InkGlass surface so both primary navigation and Settings share the same visible ground and mouse-hit ownership. Keep modern Settings on its existing PageGlassLane to avoid nested material. Verification: swift test --package-path Desktop --filter GlassPanelHitRegionTests (4 passed); related glass/click-through suites (21 passed); pinned swift-format lint; desktop test-quality check; named local bundle visual, AX navigation, and debug_hit_probe on both legacy menus. Failure-Class: none
…callback (BasedHardware#12269) ## Why The 2026-08 macOS churn cohort analysis flagged `Update Check Failed` as ambient noise that dwarfs real updater failures (~42% of cohort users, ~17.5 events/user in three weeks), and adopted a standing guardrail to hand-exclude Sparkle code 2001 from every reliability analysis. The recorded cause was wrong, and the real one is a defect in this file. Code 2001 is `SUDownloadError`, not "no update available" (that is 1001, and `UpdateFailureDiagnostics.reason` already maps it to `.noUpdate` and suppresses the event). What actually inflates the event is that Sparkle re-delivers `didAbortWithError` for a single check — the tracker's own doc comment says so — and the legacy event has no guard against it. `Update Check Completed` is protected: `finishFailure` consumes the attempt identity, so a second callback returns nil and emits nothing. `Update Check Failed` fires straight from the delegate with no identity of its own, so one failed check is counted once per callback. PostHog, production namespace, 2026-08-18 → 2026-08-25: | build | `Update Check Failed` | `Update Check Completed` result=failed | | --- | --- | --- | | 0.12.187 | 1590 / 307 users | 506 / 160 users | | 0.12.208 | 141 / 36 users | 3 / 2 users | | 0.12.212 | 233 / 62 users | 11 / 6 users | | 0.12.213 | 52 / 27 users | 52 / 27 users | ## What changed `UpdateCheckAttemptTracker.isDuplicateOfLastTerminal(_:)` reports whether an abort is Sparkle re-delivering a terminal already closed for the same check (no active attempt, and the closed terminal's reason/domain/code/ NSURL code all match). `didAbortWithError` skips only the legacy analytics call in that case; the local log and the view-model state are untouched. The guard keys on the closed terminal rather than on "no active attempt", so an abort with nothing closed yet is still reported instead of being swallowed. No failure code is reclassified: 1003, 2001, 3000, 4005, 4007 and everything else still emit exactly as before, once. `Update Check Completed`, the authoritative metric, is unchanged. ## Proof `UpdaterViewModelTests` (12 tests pass locally): - `testRepeatedAbortForOneCheckIsRecognizedAsADuplicate` - `testDistinctAndUntrackedFailuresAreStillReported` — a different failure after a closed check, and a first-ever abort with nothing tracked, are both still reported - `testActiveCheckIsNeverTreatedAsADuplicate` `docs/release-health-metrics.md` records that pre-fix `Update Check Failed` volume is inflated and must not be compared across the boundary. Failure-Class: FC-same-subject-counted-once-per-evidence-source
…ages, conversations, memories) (BasedHardware#12125) get_messages' session-scoped branch, plus three more read paths (chat.py, conversations.py, memories.py) filter/order Firestore collections in shapes that firestore_index_registry.py never declared. Production has composite indexes for all of them only because someone created them by hand at some point; a fresh self-host deploy gets FailedPrecondition 400 the first time any of these paths runs: - messages: chat_session_id + created_at (session-scoped message reads - a chat session's first page hits this branch, not the app-scoped one) - conversations: bare status + created_at (get_in_progress_conversation, get_action_items), and discarded + status + created_at (default GET /v1/conversations with include_discarded=false, no source/category) - memories: scoring + created_at (get_memories' default no-filter path - Firestore still needs a composite for a bare multi-field sort) Adds these to the registry and regenerates firestore.indexes.json. Failure-Class: none
…GENTS.md into README (BasedHardware#12206) * docs(desktop-windows): surface pnpm/Wayland/verification notes from AGENTS.md into README AGENTS.md already documents these for coding agents, but none of it reached a human contributor reading README's quickstart: - npm install silently corrupts package.json/pnpm-lock.yaml/pnpm-workspace.yaml - CI's pnpm-major-version-10 pin and the npx pnpm@10 workaround - the native-Wayland (niri etc.) blank/missing-window gotcha and its env var fixes - pnpm typecheck/lint/test as the local pre-PR verification commands * docs(desktop-windows): move pnpm-vs-npm warnings before the install command They were sitting after the code block a reader would copy-paste first — warn about the pinned tooling before showing the generic commands, not after.
…BasedHardware#10240) (BasedHardware#12203) Replaces the post-send polling approach in UsageLimitTriggerHost with a pre-send gate check in useChat.send(), matching Mac's AgentBridge.quotaExceeded parity: a blocked send never fires the request, shows the upgrade popup immediately, and leaves the chat history untouched. UsageLimitTriggerHost now refreshes the gate snapshot (chatQuotaGate.sync) on the busy→idle edge so the next send check reads a fresh verdict without a network round trip. Removes maybeTriggerChatQuotaPopup (no callers remain) and its tests. Failure-Class: none Claude-Session: https://claude.ai/code/session_01KADKRuaPJdho9CDE7nLXQP Co-authored-by: Tim <tim@cornwallwes.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Auto-generated: consolidates unreleased changelog fragments into v0.12.226 and regenerates CHANGELOG.json.
…ardware#12301) ## Problem Soniox closes any streaming socket that receives neither audio nor a keepalive for more than 20 seconds ([docs](https://soniox.com/docs/stt/rt/connection-keepalive)). Our VAD gates audio out during silence, and ordinary conversational pauses run well past 20s, so we starve the socket and Soniox hangs up. In a 15-minute production sample on the Soniox tier, 84% of established sockets died with `408 request_timeout`, including 86% of sessions that carried real speech. Sessions that are gated to zero bytes for their whole life fail essentially every time. This is our bug, not a provider fault — the connection is dropped because we go quiet, not because transcription fails. ## Fix The send loop now waits on the audio queue with a timeout instead of blocking indefinitely. When no audio is queued for 10s it emits the documented `{"type": "keepalive"}` frame and keeps waiting, which holds the socket open through gated silence. Audio and end-of-stream handling are unchanged. 10s is half the server's 20s window, so a single dropped or delayed keepalive still leaves room before the server gives up. ## Tests New test drives a socket that is never fed audio, with the keepalive interval patched down, and asserts keepalive frames are actually sent. The existing 10 Soniox tests still pass (11 total). ## Note Until this ships, the Soniox tier's error rate measures our own VAD gating rather than Soniox transcription quality, so the provider evaluation is not meaningful yet. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/BasedHardware/omi/pull/12301?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
Failure-Class: none
…Hardware#12084) * feat: add JIT knowledge ledger foundation * chore: refresh integration OpenAPI contract * fix: make trigger evaluation release-safe Failure-Class: none * fix: preserve lifecycle semantics in ledger apply Failure-Class: none * feat: adopt guarded JIT knowledge surfaces Route the agent preference writer through the intent-backed ledger, register a privacy-filtered entity timeline tool, render optional evidence on Windows, and add a base-ref-protected Gate F legacy-surface ratchet. Failure-Class: none * feat: add progressive JIT knowledge reads Register owner-scoped current-ledger search and explicit playbook hydration, with pre-limit semantic filtering and bounded outputs. Add a content-free planner/resume migration fixture without claiming canonical transaction completion.\n\nValidation: 141 focused backend tests passed; backend typecheck reported 0 errors; repository preflight passed 120 checks. * feat: render chat evidence on web Render bounded, fail-soft conversation evidence after authoritative answers in both web chat entry points. Unsupported, future, duplicate, and raw failure details remain inert.\n\nValidation: 337 web tests passed; web typecheck, oxlint, and Prettier passed; repository preflight passed 120 checks. * feat: require intent-backed ledger search results Apply the intent-backed requirement at the final merged canonical/history filter, with a passive historical-row regression case.\n\nValidation: 54 focused backend tests passed. * test: keep agent tool isolation stubs current * feat: gate JIT conversation retrieval * fix: make entity timeline scans deterministic Failure-Class: none * fix: honor rejected ledger projections Failure-Class: none * feat: render inert screen evidence on web * fix: reuse canonical review projection Failure-Class: none * fix(web): await recap context effect Failure-Class: none * test: amortize preference tool isolation load Failure-Class: none * feat(app): add knowledge ledger review surface Failure-Class: none * feat(macos): use canonical ledger prompt projection Failure-Class: none * test(memory): classify legacy surface inventory roles Failure-Class: none * fix(app): preserve ledger history completeness state Failure-Class: none * feat(macos): preserve canonical ledger mirror metadata Failure-Class: none * fix(app): match canonical ledger ordering Failure-Class: none * test(api): prove ledger client schema parity Failure-Class: none * feat(memory): expose bounded ledger history Failure-Class: none * chore(api): generate ledger history clients Failure-Class: none * feat(retrieval): add bounded card participants Failure-Class: none * feat(macos): project ledger trigger watchlist Failure-Class: none * fix(clients): fail closed on ledger authority Failure-Class: none * feat(macos): expose bounded trigger snapshot Failure-Class: none * fix(memory): keep closed history read only Failure-Class: none * feat(app): disclose partial ledger history Failure-Class: none * test(macos): cover ledger trigger bridge Failure-Class: none * fix(app): use neutral ledger accents Failure-Class: none * chore(api): declare ledger history route policy Failure-Class: none * test(macos): remove unsafe JSON fixture unwraps Failure-Class: none * fix(memory): satisfy typed history boundary Failure-Class: none * fix(macos): require prompt snapshot authority Failure-Class: none * test(memory): prove ledger migration on emulator Failure-Class: none * feat(retrieval): emit bounded screen evidence Failure-Class: none * test(memory): classify maintenance retirement readiness Failure-Class: none * feat(macos): adapt Rewind metadata for triggers Failure-Class: none * test(retrieval): align screen timestamp contract Failure-Class: none * feat(memory): correct ledger facts by amendment Failure-Class: none * test(memory): prove ledger correction on emulator Failure-Class: none * feat(macos): harden local trigger observations Failure-Class: none * feat(agent): search bounded historical facts Failure-Class: none * test(macos): cover trigger observation adapter * fix(memory): gate historical fact retrieval * feat(memory): add gated JIT retrieval strategy * test(memory): prove mixed-version JIT runtime parity * chore(memory): keep JIT gate exports type-safe * refactor(memory): isolate JIT prompt contract * fix(conversations): round-trip owner-scoped references Accept the conversation:<id> references emitted by JIT result cards while retaining strict UUID-only bare IDs and share links. Restrict machine IDs to a bounded safe alphabet so evidence suffixes and path-like values fail closed. Failure-Class: none * test(memory): join JIT citations to evidence envelope * fix(retrieval): enforce JIT conversation search budget Cap JIT summary searches per request and bound database hydration to the projection limit before reads. Preserve the legacy path when JIT is disabled. Failure-Class: FC-unbounded-user-collection-in-prompt * fix(memory): keep JIT retrieval request scoped * test(macos): prove future JIT evidence stays inert * fix(memory): keep JIT card citations request-global Failure-Class: new * fix(retrieval): separate JIT hydration from search Treat gated owner-scoped references as exact hydration without searching transcript text for the reference. Charge every JIT candidate search to the shared four-search request budget, including snippet-bearing requests, while keeping exact hydration free and preserving released JIT-off UUID/share-link behavior.\n\nVerified:\n- cd backend && ./.venv/bin/python -m pytest tests/unit/test_conversation_jit_processing.py tests/unit/test_conversation_exact_reference_search.py -q (58 passed)\n- cd backend && uvx --from pyright==1.1.403 pyright -p pyrightconfig.json --pythonpath .venv/bin/python (0 errors)\n- git diff --check\n\nFailure-Class: FC-unbounded-user-collection-in-prompt * fix(memory): keep repeated JIT cards index-safe * fix(retrieval): satisfy JIT card type contract * fix(retrieval): hydrate collected JIT cards * test(app): preserve answers during delayed evidence requests * test(app): exercise production evidence composition * feat(memories): restore superseded ledger facts * fix(memories): reconcile reverted ledger facts * feat(memories): append reverted ledger facts * feat(memories): synchronize revert client contract * fix(memory): name ledger revert identity * fix(memories): type and enlarge revert controls * fix(memories): fence revert retries and refreshes * fix(memories): fence ledger revert authority * test(memory): count ledger revert rate limit * feat: expose agent-controlled historical facts * feat: reopen standalone ledger facts * feat: add fail-closed JIT QA bundle routing * feat: add safe local JIT QA backend stack * fix: harden isolated JIT QA stack * feat: add explicit multi-source entity timeline * feat(backend): add JIT rollout authority * feat(backend): fence every proactive paid boundary * fix(backend): release proactive quota on cancellation Release the reserved proactive quota exactly once when cancellation interrupts paid-boundary refresh or a provider retry, then re-raise cancellation without emitting retry telemetry. Add deterministic regression coverage for both cancellation points. Failure-Class: FC-proactive-quota-cancellation | new * fix(backend): make proactive quota cancellation safe Detach in-flight Redis reservations on request cancellation and release only admitted slots once they settle. Move direct-provider fallback telemetry behind the fresh paid-boundary rollout check so late kill or unknown decisions cannot report false recovery.\n\nFailure-Class: FC-proactive-quota-cancellation | new * fix(backend): preserve quota compensation during shutdown Keep late Redis reservation compensators outside the ordinary cancellable background-task drain. Desktop and main application shutdown paths now wait for these critical compensators before cancelling ordinary work, with deterministic blocked-thread and lifecycle-order regressions.\n\nFailure-Class: FC-proactive-quota-cancellation | new * fix(backend): use expiring proactive quota leases * fix(backend): make quota finalization clock-safe * fix(backend): isolate jit rollout control plane * fix(backend): close jit control plane safely * fix(backend): emit retry recovery after quota commit * test(backend): keep rollout app contract fast * feat(jit): add guarded proactivity and first-open policies * chore(desktop): mark jit policy as internal * test(desktop): cover jit proactivity policy flow * feat(backend): wire durable JIT first-open processing * feat(desktop): fence JIT proactivity runtime admission * feat: activate authoritative JIT proactivity runtime * fix: harden JIT proactivity authority * fix: close proactive runtime authority gaps * fix(jit): make first-open effects resumable * fix(jit): fence outstanding first-open work * fix(jit): resume app usage receipts * fix(jit): make app usage retries no-op Failure-Class: none * fix(jit): allow completed usage after app deletion Failure-Class: none * fix(jit): register first-open folder query Failure-Class: none * Fix first-open import isolation * feat(memory): govern ledger slots and prompt winners * feat(macos): stage guarded ledger prompt adoption * feat(jit): adopt authoritative ledger prompts on macOS * fix(jit): close ledger adoption authority leaks * fix(jit): reauthorize every ledger migration write * fix(jit): fence ledger cutover publication * fix: keep ledger prompt rollback reversible * feat(jit): add guarded frame request retention contracts * fix(jit): close frame retention authority and evidence lifecycle * fix(jit): make frame retention retries and cleanup durable * fix(jit): make frame evidence recovery and retention complete * fix(jit): close frame retention recovery gaps * Harden temporary frame retention and deployment * fix: harden JIT frame retention and consumption * fix: close JIT frame lifecycle recovery gaps * fix: unify JIT frame authority and retention Failure-Class: FC-split-mutation-authority * docs: keep frame retention guidance lean * fix: retire duplicate frame flag bindings Failure-Class: FC-split-mutation-authority * fix: register frame keyframe queries Failure-Class: FC-split-mutation-authority * fix: serialize frame retention deploys Failure-Class: FC-split-mutation-authority * test: cover frame pixel deletion ordering * style: format cumulative Dart changes * fix(app): retain permanent conversation photo fetches * fix: bound frame vision retention and authority * fix: drain terminal frame request metadata * chore: record internal ledger adoption change * feat(memory): add dark daily sweep authority * feat(memory): harden daily sweep fences and runtime seam * feat(memory): reconcile existing standing triggers in sweep adapter * fix(memory): harden daily sweep recovery and source fences * fix(memory): close daily sweep source producers * fix(memory): close daily sweep review findings * Add dark daily memory sweep authority and recovery * fix(memory): harden daily sweep rejection repairs * test(listen): stub onboarding admission in bootstrap regression The daily sweep PR fences onboarding mode behind the server-owned backend admission (get_backend_onboarding_admission), so the bootstrap regression test now simulates an admitted session instead of failing closed on a real Firestore read. Verification: focused test passes in 1.64s (previously failed after a 4m27s Firestore timeout); full test_listen_runtime_regressions.py + test_onboarding_question_start.py: 26 passed; black --check clean. * fix(memory): close daily sweep rollout and retry cursors * fix(memory): isolate daily sweep lifecycle and retry fairness * Harden daily sweep admission and completed-day staging * fix daily memory sweep reliability boundaries * preserve daily sweep invocation tombstones * close daily sweep invocation lifecycle fences * fix: keep daily sweep lifecycle cleanup active * fix: acquire ledger snapshot client off event loop * fix(memory): preserve migration tier fence without legacy growth * test(memory): prove legacy adjudication race fences * fix(dev): allow bounded ADC readiness refresh * test: keep ledger prepush deterministic * test(memory): register prompt receipt control path * fix(memory): fence ledger writer transitions * feat(backend): preserve closed ledger history in export * feat(memory): define ledger query semantics * fix(backend): fence trigger snapshots on final authority * fix(backend): bypass stale coalesced JIT refreshes * feat(macos): mirror bounded memory evidence Decode generated v3 evidence into a domain mirror, persist canonical bounded JSON through the memory cache, and preserve it across compatibility sync and older-local conflicts. Invalid, future-shaped, oversized, and over-count payloads fail closed without hiding memory text or granting prompt authority. Tests: xcrun swift test --package-path Desktop --filter ServerMemoryV17DecodingTests Tests: xcrun swift test --package-path Desktop --filter MemoryLedgerMirrorTests Tests: python3 scripts/check_desktop_test_quality.py Failure-Class: none * fix(macos): fence and classify memory evidence Keep generated memory fields independent from malformed evidence, distinguish absent valid and invalid evidence states, preserve prior evidence on invalid payloads, and gate replacements on a monotonic server timestamp so stale active evidence cannot resurrect redacted rows. Cover populated-table migration upgrades. Tests: xcrun swift test --package-path Desktop --filter ServerMemoryV17DecodingTests Tests: xcrun swift test --package-path Desktop --filter MemoryLedgerMirrorTests Tests: python3 scripts/check_desktop_test_quality.py Failure-Class: none * fix(macos): preserve evidence fences and scrub redactions Advance evidence revisions for identical valid payloads, fence stale active responses after a local edit, and remove artifact/device pointers from redacted evidence before canonical persistence. Tests: xcrun swift test --package-path Desktop --filter ServerMemoryV17DecodingTests Tests: xcrun swift test --package-path Desktop --filter MemoryLedgerMirrorTests Tests: python3 scripts/check_desktop_test_quality.py Failure-Class: none * chore(macos): record ledger evidence mirror * feat(macos): deep-link local evidence cards to Rewind * fix(macos): fence Rewind frame evidence version * fix(macos): validate Rewind evidence card availability * fix(macos): bind task detail Rewind navigation to local leases * fix(macos): fence Rewind citation owner handoff * chore(macos): register Rewind evidence deep links * test(macos): cover Rewind evidence navigation * feat(desktop): evaluate JIT trigger watchlists locally * feat(desktop): wire authoritative JIT trigger runtime * feat(desktop): bind JIT claims to snapshot authority * fix(desktop): revalidate trigger authority at execution * fix(desktop): keep JIT execution leases live * test(memory): bind standalone reopen to direct-user writer * fix: make JIT QA sign-in self-contained Failure-Class: new Verification: bash desktop/macos/tests/test-jit-qa-target.sh; bash desktop/macos/tests/test-yolo-dev-backend.sh; repaired named-bundle Google sign-in reached authenticated onboarding. * feat(memory): complete JIT policy and native Windows parity * docs(backend): keep service map within context budget * test(macos): cover JIT client and staging flows * chore(backend): declare JIT mirror route policy * fix(backend): use strict Firestore boundary for JIT admission Failure-Class: FC-malformed-doc-read * chore(quality): register malformed-document guard surface * fix(backend): fail closed on malformed JIT authority Failure-Class: FC-malformed-doc-read * refactor(backend): name JIT workflow boundary results * test: repair JIT CI contracts * fix(backend): preserve ledger query exports Retain the explicit same-name re-exports consumed by tests and downstream callers while satisfying the enforced Pyright unused-import boundary after the main rebase. Failure-Class: none * test(backend): isolate gateway setup timing Failure-Class: none * style(memory): format direct-user evidence path Failure-Class: none * test(agent): isolate ACP process-group fallback Failure-Class: none * fix(dev-harness): preserve ownership markers in narrow CI * test(jit): refresh emulator fixtures for current contracts * test(jit): orchestrate local rollout dogfood * test(jit): harden local dogfood authority * fix(dev-harness): install PostHog for CI tests * fix(chat): project server JIT rollout into retrieval Resolve the backend-owned PostHog decision inside the bounded agent setup path and pass only its boolean result to prompt/tool configuration. Unknown or failed authority remains on the released legacy path, while callers cannot self-enroll through configurable input.\n\nVerification: backend/.venv/bin/python -m pytest -q backend/tests/unit/test_chat_async_offload.py backend/tests/unit/test_atomicity_lifecycle_regressions.py (41 passed)\n\nFailure-Class: new * fix(memory): preserve preference writer compatibility Select the agent preference write path from the canonical per-user writer control. Default compatibility mode retains the released MemoryService payload and receipt behavior; ledger mode keeps the retry-stable ledger write, and transition states fail closed.\n\nVerification: backend/.venv/bin/python -m pytest -q backend/tests/unit/test_chat_async_offload.py backend/tests/unit/test_atomicity_lifecycle_regressions.py (41 passed)\n\nFailure-Class: FC-split-mutation-authority * fix(jit): separate migration rollout authority Keep staged JIT chat and proactive exposure independent from legacy-row migration and writer cutover. Migration now requires its own default-off PostHog flag and still rechecks the shared kill switch at every mutation and publication boundary. Repair the isolated conversation-JIT fixture for main's chat-scope import. Verification: 217 focused JIT, chat-scope, migration, and lifecycle tests passed; 28 conversation-JIT fixture tests passed; independent Sol review accepted the split for QA-only dev rollout. Failure-Class: FC-split-mutation-authority * fix(photos): preserve retained image retrieval Treat an empty legacy inline marker as absent when permanent storage is authoritative, while malformed non-empty inline payloads still fail closed. Route live and retained thumbnails through the storage-aware image loader and preserve the conversation identity through the full-screen viewer.\n\nVerification: backend data-export tests 32 passed; Flutter photo-viewer tests 5 passed; focused Dart analysis clean; independent Sol review found and verified the viewer identity repair.\n\nFailure-Class: none * fix(memory): keep disabled daily sweep dark Resolve the backend-owned authority before inventory and require its literal true decision before any UID discovery, registry, cleanup, scheduler, model, or commit work. Missing, malformed, throwing, disabled, and kill-switched authority now exits without touching user data; enabled behavior is preserved.\n\nVerification: 60 focused daily-sweep job, scheduler, and inventory tests passed; independent Sol review accepted the fail-closed gate.\n\nFailure-Class: FC-split-mutation-authority * fix(jit): satisfy fail-closed type contracts * test(backend): admit full runtime contract checks * style(backend): format conversation bound test * test(backend): keep conversation router isolation current * test(backend): admit export boundary duration * fix(macos): persist failed chat turn notice Failure-Class: none * fix(macos): repair JIT rollout admission contracts Failure-Class: none * fix(windows): treat JIT screen evidence as untrusted Failure-Class: none * fix(backend): preserve explicit app failure contract Failure-Class: none * fix(app): finish photo viewer consolidation * fix(backend): make provider writes lock-free against the deletion gate The account-wide legal-hold deletion gate wrapped every GCS upload and Pinecone/Typesense upsert in an exclusive per-uid Firestore mutex with no lease: concurrent same-account writes hard-failed (dropped audio, lost vectors) and a crash between acquire and finish blocked the account's gated operations forever, with no janitor. Provider writes now use a lock-free fence that refuses only during account deletion or a live destructive operation; destructive kinds keep exclusive ownership, an abandoned gate self-expires after six hours, and releasing a gate on the failure path can no longer mask the original error. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(backend): issue onboarding admission at socket connect The completed-onboarding early exit returned False from an Optional[str] function; the listen runtime derives admission via 'is not None', so users who had already completed onboarding were admitted with a fabricated session id — the exact provenance forgery the admission exists to prevent. Separately, the 20-minute admission TTL was anchored to the app-launch state read, so a user reaching the speech-profile step late (or any client that never calls the state endpoint) silently lost onboarding questions and is_user tagging. The bootstrap now issues or refreshes the admission from the durable account state at connect time; completed accounts still can never re-enter, and issuing stays best-effort with the read failing closed. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(backend): keep the released proactivity lane open for legacy clients Gating /v1/desktop/proactivity/completions on the JIT cohort returned 403 to every non-admitted user — which is the entire deployed desktop fleet on deploy day, since shipped clients poll this route continuously and treat 403 as a plain error. Context-bucket extraction and the director would have died fleet-wide, dark cohort or not, and any environment without a PostHog key (local, self-host) would have lost the lane entirely. The route returns to merge-base admission semantics (tier quotas only); JIT admission remains enforced on the JIT reservation routes, and retiring this lane stays a later explicit operation after clients migrate. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(backend): withhold JIT tools and history reads outside the rollout Five new tools (search_knowledge, search_historical_facts, read_playbook, get_entity_timeline, look_at_frame) sat unconditionally in CORE_TOOLS, so every legacy chat request carried their schemas and the model burned tool budget on 'no entries found' answers. They are now filtered per request off the same resolved rollout boolean that gates the JIT prompt appendix. The memories-tab ledger-history endpoint likewise answered every user with a bounded 501-row provider scan that can only ever be empty outside the rollout; it now returns empty without the scan for non-admitted (and unknown/error) states. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(backend): bound rollout control-plane cost and confine sync resolution Synchronous callers resolved rollout flags via per-call asyncio.run against the shared provider singleton, crossing event loops: awaiting a Task attached to another loop raises, a timed-out asyncio.run strands a coalescer entry that then serves stale UNKNOWN forever, and the LRU cache was mutated from multiple threads. Sync resolution now runs on one long-lived control-loop thread with its own authority instance. Unknown snapshots gain a 5-second negative cache — UNKNOWN can never authorize work, and without it a fleet whose flags are simply absent pays one uncached PostHog call per conversation finalization. The screen-sync loop drops its force_refresh (one uncached decide per device per minute fleet-wide) and moves to its own rate bucket so two Macs' background sync can no longer starve conversation photo reads out of the shared 120/hour frame-requests bucket. The first-open policy's kill-switch telemetry label also reported str(Enum) instead of the value and could never match. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(backend): skip eager extraction under a non-compatibility writer mode A ledger-cutover user still ran the full L1 extraction model call at finalization, after which writer admission refused the compatibility write — the conflict retried, exhausted, and failed the entire finalization for every conversation, with the model spend already paid. Extraction now checks the canonical writer mode first and skips when the daily sweep owns memory formation; only a positively-read non-compatibility mode skips, so any control-state read failure preserves the legacy eager path. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(backend): export tolerates byte-less legacy photo rows A conversation photo row carrying the legacy empty inline marker and no storage reference failed the whole portability export forever, though it holds no durable image anywhere — there is nothing to omit. Such rows now export as metadata with a content-free gap reason. Frame requests in a retained state keep the fail-closed contract via an explicit require_bytes parameter. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(windows): harden JIT delivery, admission, and bootstrap boundaries Five verified defects: (1) the exclusive notification delivery slot leaked on any throw between reservation and commit — one SQLite hiccup during a JIT turn permanently silenced every proactive lane; the span is now try/finally-guarded and stale slots expire after ten minutes. (2) The ambient lane interpolated the raw window title into a tool-capable agent prompt; the turn now carries only the opaque context handle plus a sanitized executable name, framed as untrusted data like the nano-triage lane. (3) Google Calendar was fetched every ~60s before admission, so non-cohort users with Google connected paid ~1,440 reads a day for a refused feature; observation now gates calendar evidence on the cached authority. (4) Rollout-authority errors reset the cache and retried every frame (~1 req/s offline, forever); failures now back off from 30s to 10 minutes. (5) An unguarded JIT schema exec inside the shared database open could abort local storage for all features; the mirror bootstrap is now isolated, keeps the host-facing tables alive, and JIT stays inert when unavailable. Also re-checks the control-plane owner before committing the toast so an account switch mid-turn cannot show the previous owner's advice. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(macos): restore screen provenance, guard migrations, fence chat turns Four verified defects: (1) every pre-existing screen-derived task lost its 'Screen context / Open Rewind' source row because the new evidence policy dropped any provenance that is not rewind_frame.v1; the merge-base fallback row is restored for capture.v2/legacy refs (a test flipped to match the regression is restored to its merge-base assertions). (2) RewindDatabase published its pool before migrating, latching a failed migration into a permanent false-initialized state, and three unguarded ALTER TABLE memories migrations died with duplicate-column on machines that ran earlier builds of this branch; migration now precedes publication and the ALTERs/CREATEs are existence-guarded. (3) EventKit was queried on every context visit before the flags check; non-admitted owners now build no observation inputs. (4) A failed chat turn's reconstructed notice could be appended into a different conversation's transcript when the user switched sessions or cleared chat mid-flight; both transcript resets now revoke the active turn like selectApp already did. The pre-terminalized discard class (user Stop/watchdog) still drops the durable notice on relaunch — pinned by a characterization test in agent/tests/conversation-journal.test.ts with the least-invasive fix described there. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(testing): resolve firebase-tools from the checked-in dependency npx --prefix resolves the package bin against the current directory on some npm versions, and the admission runner deliberately launches from an isolated temp dir (firebase writes debug logs to cwd) — surfacing as 'sh: firebase: command not found' on hosts without brew node@22. Prefer the vendored node_modules binary when it matches the pin; npx remains the fallback. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(backend): keep one eager-extraction call site for the surface ratchet Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(backend): gate eager extraction at the public boundary The writer-mode skip moves from _extract_memories_inner to extract_memories: the replace-policy contract test pins the inner helper to exactly the canonical replacement path, and the public boundary is the better seam anyway — a sweep-owned user now skips parity capture and usage tracking along with the model call. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(listen): stub onboarding admission issuance in bootstrap regression The connect-time ensure call landed in a harness that only stubbed the read, so the bootstrap test paid an extra real-module exception path and grazed the 0.30s fast-unit CPU budget under fanout load. Stub the issuance like the read. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(listen): allowlist the bootstrap regression's CPU budget The full listen-runtime bootstrap test measures exactly at the 0.30s fast-unit CPU budget under a saturated pre-push fanout (CPU inflates ~2x there per the guard's own notes) while passing comfortably alone. It exercises deliberately heavyweight machinery; record it as an intentional exception rather than trimming the coverage. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(backend): keep list(CORE_TOOLS) literal through JIT tool gating The JIT-only tool filter replaced the list(CORE_TOOLS) assignment with an inline comprehension, which broke the prompt-cache structural invariant (test_prompt_cache_optimization.py::test_core_tools_used_in_both_functions). Restore the list(CORE_TOOLS) copy and apply the JIT-only filter as a conditional pass, preserving rollout semantics and tool order. * feat(jit): drop automatic goal updates from the JIT featureset Product decision (David, 2026-08-26): goals change only through explicit user action for JIT-admitted conversations. Goal progress is no longer a first-open obligation — the effect is removed from FIRST_OPEN_EFFECTS and the worker, and the policy plan can no longer express deferring it. Legacy obligations carrying a pending goal_progress row are normalized away and complete on the remaining two effects. Non-JIT (legacy eager) conversations keep today's automatic goal updates unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(sweep): one summary-spine agent pass per day, with folder backstop Replaces the per-conversation transcript extractor in the completed-day producer with a single two-phase agent run: the whole day's conversation summaries go in as one bounded spine (200 conversations / 120k chars — effectively unreachable, so heavy days no longer stall the cursor), and the agent may request up to 8 raw transcript excerpts (8k chars each) to verify specifics before finalizing. At most two provider calls per user per day, both inside the existing at-most-once invocation fence; the staged page carries the memory candidates AND folder assignments for the day's unopened, unfiled conversations, applied idempotently (first-open or user assignment always wins). Memories must cite their source conversations; uncited output is dropped. The cost gate becomes a worst-case ceiling checked before any call. The onboarding cold-start channel keeps per-conversation transcript extraction unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(sweep): harden the daily agent prompts from a real-data lab pass Iterated on one real heavy day (26 conversations) with strong- and weak-model stand-ins, an adversarial judge, and hand-verified transcript ground truths. Rules added, each pinned to an observed failure: actor binding in active voice with a personal-attribute gate (a discussed or recommended topic is never someone's attribute; judgments about named people are stored as assessments); decision-state basis labels binding the verb (decided/proposed/observed, discussed-no-outcome dropped); salience ordering (money, metrics, named-party intent, identity, and durable decisions before any operational fact; one fact per memory); never guessing the direction of an invitation/offer/commitment (verify or drop); and no deferring the whole answer to verification. The agent output schema gains a 'basis' field. The memories QoS call-site inventories now count the daily-sweep agent's call site (3 -> 4). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(sweep): tune the daily agent prompts against the real memories model Ran the assembled prompts against gpt-5.6-luna (the real 'memories' route model) on the same real day. Three refinements from observed behavior: the basis label no longer leaks into memory text (metrics read as metrics, not 'David observed that…'); the never-guess-direction trigger is mechanical (passive/verbless summary phrasing or 'Speaker' as the actor forces a transcript_request — luna confidently inverted 'Tim: Invited to New York' until this; with it, phase B verifies and corrects to the true direction), hedging is itself a request signal, and nothing high-salience may be silently dropped; and a rich-day yield anchor (8-16 memories for 15+ conversations) counters the model's over-pruning without inviting padding. Final real-model run: 11 true memories + 2 legitimate verification requests, zero fabrications, ~22k tokens (~2 calls) for a 26-conversation day. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(sweep): profile-maintaining slots, ledger lookups, cache-ready prompts The daily agent now sees the user's current profile (the same get_prompt_memories seam chat uses — the ledger render for migrated users), may run up to 4 owner-scoped prior-memory keyword lookups (provider fail-soft; hits re-read through the canonical store before disclosure) to dedup and supersede, and may name a slot for standing attributes — an occupied slot becomes an amend through the existing canonical occupancy check, so the daily run maintains the rendered profile with no second write path. Both phase prompts share a byte-identical prefix (pinned by a test) and pass a per-user prompt_cache_key through get_llm; measured against gpt-5.6-luna the provider cache is exact-match rather than prefix-based today, so this is future-proofing rather than present savings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(sweep): type the memory-searcher seam for the pyright contract CI's authoritative typecheck rejected the untyped lookup seam (memories.py: list(Any or [])). The searcher is now Optional[Callable[[str], Sequence[str]]] and results are built through a typed comprehension; behavior unchanged (absent or failing searcher still degrades to an empty result block). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: repair four main-inherited CI breakages after sync origin/main is currently red on its own tip; syncing it into this PR inherits the breakage, so the fixes ride here: - subscription.py: drop the unused get_byok_keys import (pyright reportUnusedImport fails the Backend unit suite). - AppState+Transcription.swift: explicit self for alertPresenter inside the escaping showAlert completion (strict-concurrency compile error in all three Desktop Swift lanes, shipped red on main by d49f978). - AppState+Permissions.swift: pinned swift-format drift from the same main commit (desktop-swift-format-lint). - web/app/bun.lock: add the prettier + prettier-plugin-tailwindcss entries 64db30c pinned in package.json without updating the lockfile (frozen install fails web-app-checks). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(sweep): close the second review round's findings Three parallel adversarial reviews over the post-takeover additions: - Clamp every model-controlled phase-B input (draft memories, request reasons, lookup queries/results) and add the clamped worst case to the pre-call cost ceiling, which previously under-estimated phase B. - Attest an empty consumed day when the staged page carries an older stage schema version instead of stalling the cursor forever on every deploy-boundary schema bump. - Make the folder backstop's unfiled check and write share one transaction so a concurrent first-open/user assignment always wins. - Let equal-rank sweep candidates amend sweep-authored slot occupants: the profile-maintenance path froze after a slot's first write. User statements still always win; slotless subject matches still dedup. - Neutralize ``` fences in summaries/excerpts/lookup results, and mark raw-transcript fallback rows '(unstructured transcript excerpt)' with a prompt rule refusing slots/personal attributes from them without transcript verification (test pins the marker to the rule). - Remove the dead first-open goal-authority threading left by the goals removal, and update the stale jit-first-open-runtime doc. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: repair three more main-inherited breakages All shipped red on main and only surfaced once earlier failures were cleared: - AppState.swift: move the alertPresenter default out of the stored property initializer — Xcode 16.4's SILGen segfaults (signal 11) emitting it, which failed all three Desktop Swift lanes even after the explicit-self fix. - test_byok_security.py: main's BYOK rewrite (d0e3a4e, 1da8880) changed request_has_llm_byok_key to per-provider enrollment checks and made partial headers fail closed, but left the tests targeting the old get_byok_keys()-based lenient contract (masked on main because pyright failed before pytest ran). The tests now assert the shipped strict contract their own docstrings already describe. - subscription.py: pinned-black formatting for the BYOK fallback expression (the Formatting lane rejects the file as main wrote it). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tests): stub the chat-agent gateway route pin in the chat router harness Main's a6988be made routers.chat import CHAT_AGENT_ROUTE_DIRECT / get_chat_agent_route from utils.llm.gateway_client, but the chat-router test harness (and test_chat_file_upload_unsupported's local override) stub utils.llm.gateway_client without those symbols, so every suite that loads the real router failed at import — masked on main because pyright fails its Backend unit suite before pytest runs. Ninth main-inherited repair in this sync. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tests): teach test_chat_quota's utils.byok stub the rewritten import surface utils/subscription.py now imports get_byok_uid and get_cached_byok_state (main's BYOK rewrite); the module-scoped utils.byok fake predates them, so reloading subscription under the fake raised ImportError at setup — and the polluted process took test_chat_openapi_operation_ids and test_desktop_screen_crisp down with it in CI's batched run (all three pass standalone). Tenth main-inherited repair, same pyright-masked pattern. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tests): update three more suites for main's BYOK/gateway import surface Same pyright-masked pattern as the harness and test_chat_quota repairs: - test_desktop_transcribe stubbed utils.llm as a non-package, so routers.chat's new utils.llm.gateway_client import could not resolve (50 failures); the submodule is now in its stub list. - test_paywall_reconnect_gate's BYOK escape-hatch tests never set the request uid context that the enrollment-verifying rewrite requires (middleware sets it in production); they now do, and teardown clears it. - test_chat_session_app_identity's enforce_chat_quota stub rejected the new required_llm_provider keyword. All three suites pass locally (69 + 35 + 6). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tests): enroll fingerprints in the desktop BYOK tests PR BasedHardware#11454 moved macOS BYOK activation to enrollment-verified fingerprints (isByokActive and usableBYOKEnvironment gate on persistEnrolledFingerprints), and its own test lanes shipped red: the tests store raw keys but never enroll them, so every key reads as inactive. Their teardowns already clear enrollment — the setups now enroll what they store, matching the production activation path. All 8 previously-failing cases (BYOKPaywallTests + the two AgentRuntimeProcessTests BYOK-environment cases) pass locally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(deploy): enable the daily memory sweep on development The sweep's five deployment inputs were pinned off in every environment, so cohort enrolment alone could never start it -- turning it on for a dogfood account required a second PR. Development now carries the live values: - ENABLED/MODEL_ENABLED on, so the job stops exiting at its first authority gate and the model authority can budget a route. - MODEL_NAME pinned to gpt-5.6-luna, which is the declaration interlock the runner checks against get_model('memories') before any provider call. - MAX_MODEL_COST_USD 0.80, the worst-case pre-call ceiling for a maximal day including phase B's clamped draft/reason/lookup overhead. - COHORT_ENABLED on with COHORT_FLAG daily-memory-sweep-v1, so enrolment is a per-uid PostHog boolean and an unnamed cohort stays a closed rollout. Production is deliberately untouched and stays fully pinned off. The job still cannot form a memory for anyone until that flag exists and resolves true for a uid, which remains a control-plane action rather than a deployment one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(firestore): terminate the daily-sweep occupant indexes with __name__ The six daily-sweep occupant lookups were the only declarations in the manifest without a trailing __name__ field -- 63 of 69 entries carry one, and main had none missing it. Firestore appends the terminator itself and reports the index back that way, so these six could never match the live inventory. The failure mode is not a missing index; the indexes build fine. It is that reconciliation never converges: every run reports the same six as missing, tries to create them, and fails on ALREADY_EXISTS. That takes down the Firestore schema workflow on both environments permanently, and with it the development backend deploy's readiness gate -- the same class of outage the workflow's own header records from the hourly_usage index in PR BasedHardware#11979. The derived specs previously appended their extra predicates to the base spec's index_fields, which would have placed them after the terminator, so the shared prefixes are now named explicitly and each spec ends with __name__. Verified against real Firestore: reconciliation reports zero missing indexes in both based-hardware and based-hardware-dev. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: close final JIT rollout and CI gaps Fence direct JIT tools and frame pixels, keep Windows account wipes safe after optional schema failures, and repair inherited CI regressions. Failure-Class: none --------- Co-authored-by: David Zhang <9387252+Git-on-my-level@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## What changed and why Centralize recording-size estimation on `BleAudioCodec` and use the same encoded-rate model in WAL sync and the recording detail UI. This removes the conflicting 320 kbps UI estimate for Opus FS320 and keeps displayed size aligned with transfer planning. Closes BasedHardware#3579 ## Product invariants affected none ## How it was verified - `flutter test test/unit/recording_size_estimate_test.dart` — all 4 codec/rate regression tests passed. - `bash test.sh` — 1,521 tests passed and 5 skipped; the only 3 failures were missing backend fixture paths in the initial sparse checkout. After adding `backend/`, the 5 fixture-contract tests passed. - `bash scripts/analyze_ratchet.sh` — analyzer ratchet passed. - The real recording detail screen was not exercised on hardware locally; both UI and sync now call the regression-tested shared estimator. ## Tests - [x] Added regression coverage for both Opus rates, PCM/mu-law derivation, fallback codecs, and one-minute FS320 recordings. - [x] Ran the app-wide Flutter suite plus the affected fixture-contract tests. - [ ] Physical-device recording flow (no paired Omi hardware in this environment). Failure-Class: none <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/BasedHardware/omi/pull/12303?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
Hovering the notch opens the agent menu; when the pointer leaves, the
collapse resize lands while the menu's collapse spring is still shrinking
the SwiftUI content. The hosting view forwards the content's min size as a
window constraint, and auto layout grows the panel right back - from its
pinned bottom-left origin. That pushed the top-anchored island chrome up to
240pt above the screen edge, where nothing ever brought it back: the island
'disappeared' until a Push-to-Talk press happened to resize the window.
Two guards, both mechanical. windowDidResize re-anchors any notch-mode
resize whose top edge left the screen top (auto layout growth bypasses every
programmatic resize path, so the anchor is enforced at the notification, not
at call sites) - user-resizable and mid-drag windows are never fought. And
the collapse re-asserts the idle island frame once after the spring's visual
tail, so the panel returns to size instead of keeping a stale menu-height
frame.
Fixes the hover-then-vanish report on Omi macOS Beta 0.12.226.
Verification: reproduced deterministically on a dev bundle via a cursor-free
bridge seam driving the same pointer entry point the tracking view calls -
every hover cycle left the window at {{816,1263},{430,307}} (chrome 240pt
offscreen). With the fix, 20 timing patterns including rapid x15 and
re-enter-mid-collapse all settle back to the exact idle frame
{{828,1263},{392,67}}, menu open and idle states captured. 5 new geometry
tests cover the re-anchor policy, including the reproduced bug frame.
Auto-generated: consolidates unreleased changelog fragments into v0.12.227 and regenerates CHANGELOG.json.
…rdware#12311) ## Summary On the macOS Beta, hovering over the notch island and then moving the cursor away sometimes made the island vanish entirely — it stayed gone until a Push-to-Talk press happened to bring it back. **Root cause, reproduced deterministically.** Hover opens the agent menu; on pointer exit the collapse resize (416×67, logged) lands while the menu's collapse spring is still shrinking the SwiftUI content. The hosting view forwards the content's min size as a window constraint (`sizingOptions = [.minSize, .maxSize]`), so auto layout immediately grows the panel back to menu height — **from its pinned bottom-left origin**. The island chrome is top-anchored inside the window, so it ends up to 240pt *above* the screen edge: invisible. Nothing re-anchors it; PTT "fixed" it only because its resize recomputes the frame. Reproduced on a dev bundle with a cursor-free bridge seam driving the same `updateNotchPointer` entry the tracking view calls — every hover cycle ended at `{{816,1263},{430,307}}` (top edge 1570 on a 1330-high screen). **Fix, two mechanical guards:** 1. `windowDidResize` re-anchors any notch-mode resize whose top edge left the screen top (`FloatingControlBarGeometry.notchTopReanchoredFrame`). The buggy growth comes from auto layout, which bypasses every programmatic resize path — so the invariant is enforced at the notification, not at call sites. Resizable (user-sized conversation) and mid-drag windows are never fought; an epsilon prevents setFrame churn on AppKit rounding. 2. The menu collapse re-asserts the idle island frame once after the spring's visual tail (0.45s), so the panel returns to size instead of keeping a stale menu-height frame. A re-hover cancels the re-assert. Also adds the `notch_hover` bridge action (non-prod) used to reproduce and verify without a cursor. ## Verification - **Before:** every hover cycle across 20 timing patterns left the window at `{{816,1263},{430,307}}` — chrome 240pt offscreen (the user-reported disappearance, on demand). - **After:** the same 20 patterns — five enter-hold durations × four exit delays, rapid ×15, and re-enter-mid-collapse ×10 — all settle back to the exact idle frame `{{828,1263},{392,67}}` with `notchRevealProgress=1`, alpha 1. Menu-open and post-hover idle states captured from the running bundle (`.cross-review-verify.png`). - `swift test --filter NotchTopReanchorTests` — 5 passed, including a case built from the reproduced bug frame. - `swift build -c debug` — Build complete. ## Product invariants affected - **INV-CHAT-1** — unchanged. The touched files carry chat-adjacent surfaces, but this diff only re-anchors the notch window frame and re-asserts its collapse size; no chat journaling or routing changes. Failure-Class: none Line-Count-Exception: desktop/macos/Desktop/Sources/DesktopAutomationBridge.swift | 4764 -> 4791 | one cursor-free notch_hover QA action, following the file's established registry pattern Line-Count-Exception: desktop/macos/Desktop/Sources/FloatingControlBar/FloatingControlBarWindow.swift | 5309 -> 5384 | the top-edge re-anchor and collapse re-assert live in the notch panel's owner beside the resize paths they guard; extracting the window is out of scope <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/BasedHardware/omi/pull/12311?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
TOTAL_STEPS was 14 but step 14 was unreachable: handleGoal and GoalStep onSkip both called finishToChat() directly, bypassing next(), and AutoCreatedTasksStep was imported in dead code (never rendered by renderStep). Fix: bump TOTAL_STEPS to 15, change handleGoal + GoalStep onSkip to call next(), add explicit step===13 case for GoalStep in renderStep, add step===14 default case rendering AutoCreatedTasksStep, and add finishToTasks() for the tasks-route completion path. Verified: pnpm test -- Onboarding.test.tsx passes (3 new regression tests covering onContinue→step-14, onSkip→step-14, finishToTasks route). Failure-Class: none Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KADKRuaPJdho9CDE7nLXQP
…14 wire Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KADKRuaPJdho9CDE7nLXQP
formed2forge
force-pushed
the
fix/windows-onboarding-autocreated-tasks-step
branch
from
August 27, 2026 19:04
421ab31 to
52c382c
Compare
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.
Summary
AutoCreatedTasksStepas step 14 of the Windows onboarding flow, completing the sequence after account setupfinishToChatcall that was left dangling after the step-14 wire landedTest plan
AutoCreatedTasksStepcorrectlyfinishToChat🤖 Generated with Claude Code
https://claude.ai/code/session_01XCp5LUrL4FLcaLUkdDg49p