Skip to content

fix: resolve mobile validation regressions and stabilize platform tests - #111

Merged
ScriptedAlchemy merged 7 commits into
mainfrom
fix/mobile-validation-regressions
Nov 12, 2026
Merged

ScriptedAlchemy merged 7 commits into
mainfrom
fix/mobile-validation-regressions

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Nov 11, 2026 •

Copy link
Copy Markdown
Owner

Follow-up defect repair

Commit 8240f2abe3 repairs installations that already ran the faulty model-options migration. Changing migration 026 alone left those saved project defaults unreadable and could make later legacy-thread imports fall back to Codex.

Migration 097 now unwraps serialized JSON arrays in the four locations migration 026 affected: legacy thread selections, project defaults, and their corresponding event payloads. This includes v2 project baseline events needed for replay. It preserves provider/model choices, booleans, empty arrays, unrelated fields, invalid non-array strings, and byte-identical canonical records.

A regression reproduced the failure with migrations 026 and 096 already recorded, then passed with the forward repair. Already-imported selections that were previously replaced by the importer's fallback are left intact because the original intent cannot be distinguished safely from later user choices.

Follow-up validation

  • 42 focused migration tests passed across three files, including preview reconciliation. Commit 2cf7063412 adds migration 097 to both ordered reconciliation expectations. Those two cases failed before the correction.
  • Direct typechecks passed for server, evals, and scripts, the three affected packages, using the repository's pinned Node 26.12.1.
  • A fresh run of all 19 peer-link request cases passed. The previously reported intermittence did not reproduce in this run; its cause remains unproven.
  • Native project-memory build, formatting, lint, both unused-code scans, and GraphQL validation passed. The final test-only branded-ID correction also passed lint, typechecking, and its regression.
  • A fresh vp run check:changed on 2cf7063412 passed the complete gate, including all 42 package typechecks. This supersedes the earlier environment-specific wrapper failure.
  • The fresh full workspace rerun on 2cf7063412 passed 27,058 tests, with 64 skipped and zero failures. It passed 2,114 files with 3 skipped. The earlier peer-link failure did not recur, but its cause remains unexplained. Full log. Draft asset access requires repository write access.

Follow-up audit and fixes: GPT-6 Astra Pro in ChatGPT.


Why

Validate the merged work on mobile and desktop and fix the failures found during that pass. Stale memory attached on one device could leave a turn stuck on Thinking after another device changed the fact.

What changed

  • Settle rejected memory preparation with a visible failure. Preserve JSON arrays during dev-copy and legacy model-option migrations.
  • Keep minimap position updates inside the minimap and cache completed streaming paragraph boundaries immediately.
  • Include the existing mobile fork-signing edits and correct host-dependent fixtures, platform shortcuts, and test-runner selection.

Scope

  • One combined maintainer-requested PR for the validation findings and existing mobile edits.
  • Native Mac visual/input inspection and Linux-only Hyprland tests remain unverified on this Mac.
  • A peer-link address-selection case failed in the broad rerun, then passed alone and in two complete file runs. Its cause remains open. Initial Claude reviews failed with gateway HTTP 502. A later independent Opus 5.5 review confirmed the handoff and migration logic and found additional Resume defects, now fixed in PR112.

Blast Radius

Turn-start failure settlement is guarded by the current attempt and run status. The SQL corrections now include the forward repair described above for databases that already recorded migration 026. Web changes affect timeline navigation and streaming Markdown caching.

Verification

  • vp run check:changed passed formatting, lint, Knip, 42 package typechecks, focused tests, and compiler/native checks. The final peer-link diagnostic assertion is included in that passing run.
  • Android React passed 332 tests. Markdown browser tests passed 22 cases. All 15 render scenarios passed, with integrated streaming timeline renders reduced from 4 to 1 for 30 deltas.
  • Android attached a fact, iOS changed its revision, and Android submitted. Both phones and web showed the terminal error. A subsequent Replay approved on iOS completed. Electron built and opened. The earlier pre-097 workspace rerun finished with 27,055 passed, 2 failed, and 64 skipped. One failure used the superseded Markdown implementation. The other is the intermittent peer-link failure above; the run is not an all-clear.

Device captures and logs are stored in an unpublished evidence release. Repository write access is required to view draft assets.

Before After
Stale memory stuck on Thinking Stale memory shows a terminal error

Android shows the same error

Model and harness. GPT-6.1-Sol through Codex.

@changeset-bot

changeset-bot Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 2cf7063

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

ScriptedAlchemy and others added 2 commits November 11, 2026 12:00
Add migration 097 to repair model option arrays that migration 026 already
stored as JSON strings in projections and affected event payloads. Preserve
canonical values and unrelated data, including invalid non-array strings.

Cover already-migrated databases, v2 project baselines, boolean and empty
arrays, repeat startup, and byte-identical canonical records.

Implemented and reviewed with GPT-6 Astra Pro in ChatGPT.
@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

Shipping verdict for PR111: PASS+NOTES

Exact head. 2cf706341201312bb6bca8618653efcfb6f8ee93, against origin/main at cff49436fd (the merge base). This covers the whole PR (7 commits, 27 files), not only the last test commit.

Goal. Land the fixes from the mobile and desktop validation pass. A stale memory selection must end the turn with a visible error instead of leaving it stuck on Thinking. Model options and PR links must stay JSON arrays through migration 026 and the dev-copy prune, and installs that already ran the faulty 026 get repaired. The other fixes are a lighter streaming timeline, the mobile fork-signing edits, and host-independent tests.

Executed at the exact head

Check Command Result
Migrations vp test run apps/server/src/persistence/Migrations/026_CanonicalizeModelSelectionOptions.test.ts apps/server/src/persistence/reconcileV2PreviewMigration.test.ts apps/server/src/persistence/Migrations.test.ts 3 files, 42 passed
Startup settlement vp test run apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts -t "settles stale selected knowledge|over-budget mandatory|rehydrates selected knowledge at native delivery" 5 passed, 82 skipped
Render budgets node scripts/render-metrics.ts run --runs 1 15 of 15 scenarios passed. app-stream-assistant-turn renders ConversationTimeline 1 time against a budget of 3. The original validation recorded 4 on main. TimelineMinimap now renders 4 times.
Web browser vp test run --project browser src/components/ChatMarkdown.browser.test.tsx src/components/chat/ExpandedImageDialog.browser.test.tsx (from apps/web) 2 files, 27 passed
Mobile, iOS inlining vp test run app.config.test.ts src/features/threads/thread-inspector-navigation.test.tsx (from apps/mobile) 2 files, 18 passed
Mobile, Android inlining T3_MOBILE_TEST_PLATFORM=android vp test run src/features/threads/thread-inspector-navigation.test.tsx 7 passed

Base source with the head regression tests

I used a disposable sibling worktree at the head, with base versions of the changed source files and the head tests. It symlinked to the main checkout's node_modules and was removed afterwards. The main checkout stayed clean at the head.

  • ProviderTurnStartService.ts from main. 2 failed. The stale-knowledge case throws ProviderTurnStartError(ProjectKnowledgeError) and never settles the run. The over-budget case leaves the run running instead of failed. That is the stuck-Thinking state from the validation report.
  • 026, Migrations.ts from main, no 097. The 026 test fails at :295 because the options come back as strings.
  • ChatMarkdown.tsx from main. The new "Finished boundary\n\n" case fails with 2 parses on remount instead of 1.

Disposable SQLite experiment

I ran the SQL text taken from the base and head files in node:sqlite in memory, on Node 26.12.1 with SQLite 3.54.1.

  • Base 026 stores options as text in all four locations: thread and project projections, plus thread and project events. Head 026 stores array in all four.
  • 097 converts base-damaged rows to array, and a second run changes nothing.
  • 097 leaves edge rows byte-identical. That covers a malformed column, NULL, an object stored as text, a plain string, a real array, and an event of the wrong type.
  • In migrate-dev-db, base stores pullRequests as text and the first URL reads null. Head stores array and keeps the URL.

Source review

  • Startup catch. It wraps only work done before the provider gets the turn. After the provider accepts, startWithHandoffs turns errors into ProviderAdapterTurnStartError, and RunExecutionService never raises ProjectKnowledgeError. The settle write requires the active attempt and expectedStatus: "running", so it cannot overwrite a run that has moved on. A retry settles once, which the test asserts.
  • Migration 097. Its nested CASE only runs json_type after json_valid, so malformed rows can't raise errors. It targets the same four locations and five event types as 026. V1 import into V2 is lazy (ensureTranscript), so threads not yet imported benefit from the repair.
  • Minimap state. Position state moved into TimelineMinimap. Nothing else read currentIndex or atEnd. The minimap is always mounted next to the list, so remounting behaves as before.
  • Render project gating. Only scripts/render-metrics.ts runs the render project, and it sets T3CODE_RENDER_REPORT_DIR. The new root TEST_EXCLUDE entries keep their own node --test steps in ci.yml:332 and ci.yml:580.
  • Mobile config. An unset APP_VARIANT still resolves to production, so ios:release does by default what it did before. appleTeamId falls back to the upstream team. Personal-team builds now use development notifications mode. That matches Release builds that Xcode signs with a development profile for device install. This is inferred, since I built nothing.
  • Test isolation. The Java check shadows command only for the command -v java probe. The VS Code test hides /Applications behind an injected FileSystem. Neither weakens the production path under test.

Notes (non-blocking)

  1. ChatMarkdown caches whole-message prefixes when block splitting is off. source.endsWith("\n\n") also applies when splitMarkdownBlocks returns null. That happens with footnotes, reference definitions, or raw HTML, ::: or $$ at a line start. The single block is then the whole growing message, so each render that lands on a blank line caches a full prefix. I confirmed with the splitter that these inputs give one block equal to the whole text. The cache is an LRU capped at 500K characters, with 125K per entry, so this churns the cache rather than leaking. The cost in real sessions is inferred, not measured. A narrower condition is streamingBlocks !== null && source.endsWith("\n\n").
  2. One image-dialog assertion is gone. The ExpandedImageDialog test dropped the plain-wheel check at fit scale. Plain wheel is still checked at zoomed scale, where it must not zoom or be cancelled.
  3. The peer-decline test is looser. It now accepts the URL fallback (card.label ?? card.url). That hides the intermittent label-resolution failure, whose cause the PR body says is still unexplained.
  4. 097 can't fix threads already imported to V2. If the importer already replaced a thread's model with the Codex fallback, 097 can't recover it. The PR body documents this.

Evidence limits

  • I did no native desktop visual or input check, because Computer Use is unavailable. I did not build or install an iOS or Android client, and I did not run the ios:release signing flow.
  • The existing iOS and Android captures are from 11:09, before commit 2acb74f2b5 at 11:18. The earlier render artifacts likewise predate 5c337277f8. They are supporting evidence only. Every behavior claim above comes from a run on the exact head.
  • In the SQLite experiment, the 097 statement was a hand-written copy. The real 097 runs through the Effect SQL client in the head 026 test.
  • The 27,058-test full suite comes from the author's log at this head. I did not rerun it.

Status at verdict time

CI at 2cf7063 shows 14 passed, 2 skipped, 0 failing. The PR is mergeable, with no review threads. The only comment is from changeset-bot.

Independent verification by Claude Opus 5.5 via Claude Agent in T3 Code. This agent did not write PR111.

@ScriptedAlchemy
ScriptedAlchemy merged commit 6d4d152 into main Nov 12, 2026
16 checks passed
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