Skip to content

v2.0.1 mega convergence - #295

Closed
0xSero wants to merge 8 commits into
mainfrom
codex/v201-mega
Closed

v2.0.1 mega convergence#295
0xSero wants to merge 8 commits into
mainfrom
codex/v201-mega

Conversation

@0xSero

@0xSero 0xSero commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Sole remaining delivery PR — v2.0.1 mega convergence (IN PROGRESS)

This draft is the single convergence PR for the v2.0.1 delivery chain. It captures the already-green #293 (C1) and #294 (C2) content onto the exact base 7122dd6e. It is a draft and under active Fable batch review. No perf, simplification, or release-identity work is claimed to be present in this PR yet.

Base: 7122dd6eba57e0c6eb923acd642db9396ade1334 (origin/main tip at capture time)
Head: codex/v201-mega @ 12a7cbb1b10045801a22218811d66e4765287c08
merge-base(codex/v201-mega, 7122dd6e) = 7122dd6e (linear, 3 commits, no extras)
Diff: 10 files, +406 / −125

C1/C2 commit → source → patch-id table

# Mega commit Source commit Subject Patch ID (--stable)
C1a 98981fa8c7ab7bcda5b3a899f10d394294e22049 b18cd9c2 (in #293; standalone origin #276) chat: refine conversation typography [skip mobile-release] 6b05176b70425697b3d30315ed8a241acf6bdb45
C1b d49db8b47e564107ed050bcfb6a28914cb728581 92a66e95 (in #293; standalone origin #277) appearance: make Studio the default theme [skip mobile-release] 6c63280c98b72a9a14bd6a38980af4c5d7629184
C2 12a7cbb1b10045801a22218811d66e4765287c08 f675fb24 (#294) release: add read-only Play status fallback [skip mobile-release] 95785c21c9cb502d3f8bed97397d27920b084c9d

Subjects preserved verbatim (including [skip mobile-release]), in original order.

Content-equivalence proof (10/10 blob match)

Every file at codex/v201-mega matches its source head byte-for-byte:

  • 9 C1 files == 92a66e95 (PR #293 head)
  • tools/scripts/list-play-tracks.py == f675fb24 (PR #294 head)

git diff --check clean (full range, per-commit, and single-file).

Evidence

  • Base CI: run 31753697269 — terminal success at 7122dd6e (Android build/tests + iOS build/LitterTests green).
  • Source PRs #293/#294: already green (targeted xcodebuild -only-testing 13/13; python3 -m py_compile + python3 tools/scripts/list-play-tracks.py --self-test OK).

Related PRs being converged

  • #293 — ui: simplify typography and semantic theme defaults (C1 batch)
  • #294 — release: add read-only Play status fallback (C2)
  • #276 — chat: refine conversation typography (standalone origin of C1a)
  • #277 — appearance: make Studio the default theme (standalone origin of C1b)

Mega plan

The v2.0.1 delivery is consolidated into this single PR on top of 7122dd6e. Downstream PERF-0a/PERF-0b, PERF-1, composer, adjudicated simplification, testing, release identity, and deployment preparation will be added as later logical commits to this same mega PR (the user requires one mega PR — no separate chain/PR).

Open LOC (lines-of-code) user-choice gate — NOT included in this PR

Fable final union certified only P10 −875 / P50 −1,990 / P90 −3,185 versus the required −49,237; the stop-condition is open. The user must choose:

  • A — revise the target to the certified bands, or
  • B — proceed while recording the 25% target as unmet, or
  • C — explicitly name live product surfaces to delete and accept functionality loss (not recommended).

Reference: FABLE_LOC_UNION_ADJUDICATION_READY.


FABLE_MEGA_C1_C2_BATCH_REVIEW_READY

0xSero added 5 commits August 13, 2026 23:48
…kip mobile-release]

LitterTests (full unit target): 223 executed, 0 failures, 2.488s.
xcresult: /Users/sero/Library/Developer/Xcode/DerivedData/Litter-eildsnwjlgvfboarwgrqgegwowll/Logs/Test/Test-Litter-2026.08.13_23-44-48--0400.xcresult

UI tests (individual, iPhone 17 Pro / iOS 26.0 simulator):
- UI1 testBodyEvaluationCountAt1500: PASS (87s)
- UI2 testConversationScrollPerformanceAt1500: PASS (376s; scroll deceleration avg 1.886s)
- UI3 testComposerKeystrokeLatency: PASS (untouched)
- UI4 testBackToListPerformanceAt300Sessions: PASS (546s; back/list avg 88.405s)
- testMidhistoryToolCompletionRendersResult: PASS (11s; 7-item tool-result visible as static text)

Anchor rationale: the canonical seven-item midhistory fixture (mh-u1/a1, mh-u2/a2,
mh-tool-K, mh-u3/a3) is the single deterministic anchor shared by the 1500-item
scale gates and the 7-item visibility check. When scenario + ITEM_COUNT=N>7 are
set, patternItem 0..(N-8) prepends N-7 deterministic items before the canonical
seven, yielding exactly N items in stable order.

Corpus: perf-0a/corpus/05-tool-file-order.json
sha256 c126251a51590d29b0c39530e5899d27a63160ef0d31a87ea0feff2b4c93556a
…obile-release]

P1-1a isolation kernel: routes conversation rebinds per-thread with a
lossless global fallback on both platforms, with zero Rust/bindings changes.

iOS: per-thread @observable ThreadRebindSignal objects (dictionary is
@ObservationIgnored so foreign-thread bumps don't fan out). ConversationDestinationScreen
.onChange now reads threadRebindSignal(for: key).revision + conversationGlobalRevision
instead of the monolithic snapshotRevision. Bump sites: equality-guarded
cacheThreadSnapshot choke point, removeThreadSnapshot (entry retained),
applySnapshot publish site (change-guarded), activeThreadChanged arm.
DEBUG seam _testHandleStoreUpdate for integration tests.

Android: new ThreadRevisionLedger (pure Kotlin, JVM-testable) with per-thread
MutableStateFlow + global StateFlow. AppModel wires bumpThread/bumpGlobal at
the same choke points. ConversationScreen re-derives thread via
remember(threadRevision, conversationGlobalRevision, threadKey) instead of
remember(snapshot, threadKey).

Signal-entry lifetime rule (both platforms): entries are never removed —
dropping/re-creating a signal on thread re-add would hand live observers a
dead object (iOS) or a stale remember-cached flow (Android, fatal).

23-variant AppStoreUpdateRecord classification:
| # | Variant | Route | Carrier |
|---|---|---|---|
| 1 | fullResync | Global (+ scoped for changed threads via bulk cache) | refreshSnapshot -> applySnapshot |
| 2 | serverChanged | Global | debounced refresh -> applySnapshot |
| 3 | serverRemoved | Global | refreshSnapshot -> applySnapshot |
| 4 | threadUpserted | Scoped | applyThreadUpsert -> cacheThreadSnapshot |
| 5 | threadMetadataChanged | Scoped | direct + batched-flush -> cacheThreadSnapshot |
| 6 | threadItemChanged | Scoped (item path); applySessionSummary bumps nothing | item upsert -> cacheThreadSnapshot; summary bypasses choke point |
| 7 | threadStreamingDelta | Scoped (identical-delta no-op stays bump-free) | apply -> cacheThreadSnapshot |
| 8 | threadRemoved | Scoped (explicit bump; signal entry retained) | removeThreadSnapshot |
| 9 | activeThreadChanged | Global (unconditional arm bump) | arm-level |
| 10 | pendingApprovalsChanged | Global | refreshSnapshot -> applySnapshot |
| 11 | pendingUserInputsChanged | Global | same; double-covered by untouched iOS :1961 / Android :286 |
| 12 | voiceSessionChanged | Global | refreshSnapshot -> applySnapshot |
| 13 | savedAppsChanged | No bump (intentional) | SavedAppsStore.reload only; no snapshot publish |
| 14 | realtimeTranscriptUpdated | No bump (intentional) | no-op arm |
| 15 | realtimeHandoffRequested | No bump (intentional) | no-op arm |
| 16 | realtimeSpeechStarted | No bump (intentional) | no-op arm |
| 17 | realtimeStarted | Global | refreshSnapshot today |
| 18 | realtimeSdp | No bump (intentional) | no-op arm |
| 19 | realtimeOutputAudioDelta | No bump (intentional) | no-op arm; per-audio-frame bump would regress |
| 20 | realtimeError | Global | refreshSnapshot today |
| 21 | realtimeClosed | Global | refreshSnapshot today |
| 22 | dynamicWidgetStreaming | Scoped | applyStreamingWidget -> cacheThreadSnapshot |
| 23 | terminalSessionsChanged | Global | refreshSnapshot -> applySnapshot |

Invariant: every variant that publishes a snapshot today maps to scoped
and/or global; the six no-bump variants publish nothing today.

LOC audit: production net +83 (iOS AppModel.swift +25, LitterApp.swift +3,
Android AppModel.kt +10, ConversationScreen.kt +2, ThreadRevisionLedger.kt
+43). Tests +138 (iOS AppSnapshotRuntimeTests +85, Android
ThreadRevisionLedgerTest +53). Both within budget (prod <=90, tests <=140).

iOS test result: 229 tests passed (223 existing + 6 new R1-R6), 0 failures,
on iPhone 17 Pro simulator (iOS 26.0), Debug config, 69s build+test.

  R1 same-thread rebind: streaming delta to A bumps A's signal +1. PASS
  R2 foreign-thread isolation: delta to B leaves A's signal and global
     unchanged while snapshotRevision still bumps (proves isolation is real). PASS
  R3 unkeyed global: pendingApprovals publish bumps global +1, scoped +0. PASS
  R4 summary-only write: byte-identical item + new summary -> snapshotRevision
     +1 but scoped +0 and global +0 (summary bypasses choke point). PASS
  R5 removal semantics: threadRemoved(B) bumps B's signal, retains entry
     (=== identity), follow-up threadUpserted(B) continues monotonically. PASS
  R6 active-thread global: activeThreadChanged(nil) bumps global +1, scoped +0. PASS

Android test result: authored, CI-pending. JVM toolchain (Java 21, Android
SDK 35/36) is present, but the generated Kotlin UniFFI bindings
(shared/rust-bridge/generated/kotlin/) are absent in this worktree.
Regenerating them requires `make bindings`, which is forbidden in this
slice. The :app:compileDebugKotlin task fails on a pre-existing unrelated
reference (ActiveTurnWidget.kt:250 HydratedConversationItemContent) before
reaching the ledger test. ThreadRevisionLedgerTest.kt is plain JVM
(coroutines + StateFlow only) and will run in CI once bindings are generated.

Android wiring limitation: JVM tests prove ledger semantics only (L1-L4:
scoped bump, global bump, instance stability, N*M concurrency). The
AppModel.kt wiring (guard placement, bump-after-publish ordering) cannot run
on plain JVM (Context + native bridge); it is verified by line-diff review
against the contract, by the iOS twin tests of the identical pattern, and
behaviorally by the later CUA stage. Compose-level recomposition scoping has
no Android counter channel at this tip (known gap; a later slice).

This is an isolation/correctness kernel only. It does NOT claim any
measured reduction of the 78.669s back/open mean, 0.632s composer, or
1.990s scroll numbers. Non-regression within CUA bounds is the only speed
statement permitted for this slice. Attribution and reduction of those
costs belong to measured follow-on slices (P1-1b onward).
…[skip mobile-release]

One-line test-only hunk: move .awaitAll() outside the map lambda in L4 so it
awaits the List<Deferred> (real N x M contention) instead of invoking awaitAll()
on a single Deferred inside the map, which cannot compile (kotlinx.coroutines
defines awaitAll only on Collection<Deferred<T>> / vararg, never on Deferred),
killing the whole L1-L4 file at compileDebugUnitTestKotlin.

Corrects the P1-1a commit body claim 'will run in CI once bindings are
generated': missing generated bindings were not the only local blocker — this
committed expression was a second blocker until this commit. Per Fable-5 ruling
/Users/sero/.claude/plans/you-are-fable-5-the-zesty-stearns.md (verdict: REJECT
as-landed, remedy = this bounded corrective commit; production kernel verified
correct and byte-frozen).

Verification:
- gradle narrowest attempt (:app:testDebugUnitTest --tests
  '...ThreadRevisionLedgerTest') still blocked BEFORE test compilation by the
  pre-existing :app:compileDebugKotlin failure in ActiveTurnWidget.kt (unresolved
  UniFFI-generated types; bindings absent in this worktree) — not worked around.
- Independent audit with the project's real Kotlin 2.0.21 compiler
  (kotlin-compiler-embeddable from Gradle cache) against a data-class
  ThreadKey stub + kotlinx-coroutines-core-jvm 1.8.1 + junit 4.13.2:
  committed defective line FAILS to compile (type-inference cascade at :50);
  this line compiles clean (exit 0, zero errors); JUnitCore run: OK (4 tests),
  L4 asserts exactly n*m = 5000 under 50 coroutines x 100 bumps on
  Dispatchers.Default. Not a claim of :app:testDebugUnitTest green — CI owns that.

Scope: the five production files of 57414ba remain byte-identical
(SHA-256 verified); no submodule pointers staged; no history rewrite.
…ndroid) [skip mobile-release]

Add DEBUG-only production-path fixture seams, burst attribution ledgers, watchdogs, beacons, trace sections, and deterministic cross-platform contract tests without changing Release behavior.

Numstat audit: iOS production +349/-1; Android non-test +270/-6 including the 175-line debug fixture; ComposerBar +11/-5; tests +317 total (iOS +240, Android +77). All hard caps are satisfied.

Verification: iOS build-for-testing succeeded; focused AppSnapshotRuntimeTests 30/30; full LitterTests 235/235; git diff --check clean. Android local compilation is blocked only by absent generated Kotlin UniFFI bindings in this checkout, so Mobile CI is the binding compile/JVM gate.

Frozen-state audit: shared/rust-bridge tree d4df00c; shared/third_party tree efaf0d6; submodule pointers were not staged.

Measurement disclosure: iOS C-1 50 Hz and Android 20 ms DEBUG beacon polling are always-on in debug; C-2 ingress-hook cost must be measured at burst rate during calibration; beacon-bridged video timing carries +/-20 ms quantization. No production performance change is included before PP-B.

Release remains gated on Mobile CI, five-gate rerun, xctrace smoke, three-run driver determinism, calibration/shakeout, PP-A, W2 review/CI, and PP-B.
@0xSero

0xSero commented Aug 18, 2026

Copy link
Copy Markdown
Owner Author

Closing as superseded by #319 (merged as 0824605d), which fixes the same stalls at the Rust store boundary rather than at display cadence. This branch has been conflicting against main for some time and carries stale release metadata. If any specific UI change here is still wanted, please re-cut it against post-#319 main.

@0xSero 0xSero closed this Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant