fix(mobile): Catch up the thread feed after a long background - #6642

Open
mwolson wants to merge 335 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/mobile-thread-feed-performance-v2.1
Open

fix(mobile): Catch up the thread feed after a long background#6642
mwolson wants to merge 335 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/mobile-thread-feed-performance-v2.1

Conversation

@mwolson

@mwolsonmwolson commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

What Changed

On a large thread, every live token used to redraw the whole feed. Unchanged
rows now keep the same objects, so only the row that changed updates.

Coming back to a thread used to fetch the entire transcript. Warm opens
subscribe from the cached sequence, refresh a bounded snapshot in the
background, and replace a full socket fallback with that bounded window.

A short lock could flip a live thread to Syncing. A long lock could show old
messages as current, or sit on Syncing forever. A short lock or Home switch
stays live. After a long lock the feed shows Syncing over the cached transcript
until a socket snapshot or completion marker lands the latest content. HTTP can
refresh the visible window under that pill. Catch-up still ends on the socket
snapshot or marker. Reconnect restarts the live subscribe so the new session
actually gets a thread subscription.

T3 Account in Settings could sit on Checking after a long background, and
tapping it did nothing. The account row stays tappable. While Clerk is still
loading, the account screen shows Checking plus Retry. Retry remounts Clerk.
There is no 12-second timer and no automatic remount.

Checked on a signed iPhone, an Android tablet, the iOS Simulator, and Waydroid.
A 30-second lock stays live. A long lock shows Syncing, then catches up.

Why

The phone already had the recent history. Live updates, warm opens, and
reconnects were throwing that away or treating it as finished too soon. Catch-up
should look like Syncing until the latest rows are really there.

The Clerk hang is the same freeze after a long background: Settings looked busy
forever. Remounting Clerk from a timer tore down the whole app, so recovery is
manual Retry only.

UI Changes

Live turns no longer rebuild the whole feed. Warm opens stay on the recent
window. Short background stays live. Long background shows Syncing until latest
content lands. T3 Account stays Checking until Clerk loads, then Sign in or the
account email. Retry is on the account screen while Checking.

No before/after screenshots.

Known Limitations

On a Boox NoteAir5C, Clerk can load and still sit on Sign in. Retry remounts
Clerk and does not complete sign-in. Force-stopping the app recovers. That is
native Clerk or Android process state, not hung Checking.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

High Risk
Changes thread live/synchronizing transitions, bounded snapshot persistence, and shell “live” semantics that affect outbox delivery—race-prone areas per review notes. Broad mobile V2 migration plus removal of the thread-transfer PR reporter also affect CI signal and regression visibility.

Overview
Thread sync and mobile runtime — Mobile wires into bounded thread snapshot loading, shared orchestration cache schemas, and refreshCachedThreadOnSubscribe. Client-runtime thread state distinguishes catch-up vs completion waits, uses authoritative HTTP refresh on reconnect, and gates persistence while a bounded refresh is in flight. Shell status can promote to live when a usable cached snapshot exists. The mobile feed and related UI move to V2 projections (runtime vs session, RuntimeRequestId, checkpoint summaries derived from projection), with archive guards keyed off runtime status and approval/user-input cards respecting responseCapability.

Cloud auth and navigation — T3 Account stays navigable while Clerk is loading; the auth screen shows Checking with a manual Retry that remounts Clerk without timers. Cloud cleanup refs live on the parent so Clerk remounts do not drop in-flight sign-out work. In-memory navigation state is restored on app launch.

CI and housekeeping — The Thread Transfer Report workflow and its publisher scripts/tests are removed. CI installs build-essential so ACP process-tree fixtures compile instead of soft-skipping. Smaller updates: desktop user-data dir names in tests, composer context-strip default, mobile theme adaptive colors, README appearance doc link, marketing Cursor harness label.

Reviewed by Cursor Bugbot for commit 088b2ab. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add orchestration V2 foundation and catch up mobile thread feed after background

  • Introduces the full orchestration V2 contract layer: provider policies, model selections, checkpoints, thread projections, domain events, commands, and scheduled-task schemas across packages/contracts/src/orchestrationV2.ts and related contract modules
  • Adds V2 provider adapters for Claude, Codex, Cursor, Grok, OpenCode, and ACP Registry under apps/server/src/orchestration-v2/Adapters/, each integrating its provider SDK through the ProviderAdapterV2 service boundary
  • Adds nine database migrations (044–052) creating the V2 event log, projection tables, effect outbox, provider-session bindings, scheduled tasks, and legacy V1 import state in apps/server/src/persistence/Migrations/" - Builds core V2 server services: SQL-backed ProjectionStoreV2, EffectOutbox and EffectWorker for durable effect processing, ProviderSessionManager, ProviderRuntimeRecoveryService, checkpoint capture/rollback, thread management, and MCP orchestrator/worktree toolkits
  • Migrates mobile and web clients to V2 projection types: thread feed status resolution uses runtime and latestRun fields, pending approvals and user-input checks live response capability, and thread snapshots load through bounded HTTP routes
  • Risk: replaces the legacy EnvironmentApi.orchestration IPC property with orchestrationV2 and switches all WebSocket RPC methods to V2 constants; environments advertising a protocol version mismatch are now rejected at connection time via orchestrationProtocolCompatibilityError

Macroscope summarized 088b2ab.

@coderabbitai

coderabbitaiBot commented Aug 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c8bef765-f132-43e7-8c0c-531ac526096c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 14, 2026
Comment threadpackages/client-runtime/src/state/threadHistoryMerge.ts
@mwolson
mwolson marked this pull request as ready for review August 14, 2026 20:38
@macroscopeapp

macroscopeappBot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — Despite the mobile-fix title, this is a 966-file orchestration V2 migration that changes production persistence, APIs, provider execution, authentication, MCP capabilities, and mobile synchronization behavior. It also changes defaults and static-analysis directives while unresolved comments identify stale-data, auth, UI, and functionality risks.

Not approved because:

  • 2 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 7 times, most recently from a50460b to c610c31CompareAugust 15, 2026 15:09
Comment threadpackages/client-runtime/src/state/shell.ts Outdated
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from c610c31 to 9159bcfCompareAugust 15, 2026 15:18
Comment threadpackages/client-runtime/src/state/threads.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 96891a3 to 993407dCompareAugust 15, 2026 15:26
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 3 times, most recently from 5b5e9cb to a005644CompareAugust 15, 2026 18:38
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Aug 15, 2026
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from a005644 to 1eb46e5CompareAugust 15, 2026 19:01
Comment threadpackages/client-runtime/src/state/threads.ts
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 2 times, most recently from 541c25b to 26f73f5CompareAugust 15, 2026 19:52
Comment threadpackages/client-runtime/src/state/threads.ts Outdated
Comment threadpackages/client-runtime/src/state/threads.ts Outdated
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from 26f73f5 to 6af29ddCompareAugust 15, 2026 20:22
@mwolsonmwolson changed the title perf(orchestrator): Reduce mobile thread feed presentation workfix(mobile): Catch up the thread feed after a long backgroundAug 15, 2026
Comment threadpackages/client-runtime/src/state/threads.ts
juliusmarmingeand others added 13 commits August 31, 2026 21:33
Adapt grouped tool summaries and the floating working timer to V2 run, attempt, and queue state. Bring over the composer, keyboard, and disclosure transitions while retaining the V2 activity inspector and queue controls.
Keep OV2 web composer and grouping behavior intact; share only the existing command label parser with mobile.
Restores main features dropped by the policy replay: pingdotgg#8569 theme wiring,
pingdotgg#8850 composer banner follow-ups, pingdotgg#8855/pingdotgg#8904 composer fixes, pingdotgg#8831
settings search rework, pingdotgg#8803 workspace-mutation refresh (v2-adapted),
pingdotgg#8840 circle-alert, pingdotgg#8584 codex artifact templates, pingdotgg#8688/pingdotgg#8807/pingdotgg#8936
video + image previews (web and mobile, v2-adapted), pingdotgg#8862 Expo glass,
and the round's docs. Timeline thinking rows (pingdotgg#8984) stay on the v2
work-live system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from a6e0049 to 50a57b6CompareSeptember 1, 2026 05:36
juliusmarmingeand others added 7 commits September 1, 2026 00:24
The v2 equivalents of main's pingdotgg#8984 and pingdotgg#8922: a "Working for ..." header
anchors the active run, the trailing live tool row survives between
actions in past tense instead of vanishing, and a shimmering Thinking
row marks reasoning gaps. During workspace preparation the header shows
"Setting up worktree..." (driven by the local dispatch flag or the v2
run's preparing status, so remote viewers see it too), the composer
footer span is gone, and draft promotion waits until the run starts or
startup fails instead of navigating mid-preparation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Clerk can sit on isLoaded=false after a background freeze, which pins
Settings on Checking and blocks the account screen. Remount Clerk after a
foreground timeout, keep T3 Account tappable, and offer Retry without
clearing app data.
A cold HTTP seed still waits for synchronized, but disconnect and stream
error cleared awaitingCompletion. Reconnect then published live while
replay was still arriving. Keep the fence until synchronized or delete.
A 12-second foreground timer used to remount ClerkProvider, which rebuilds
the whole app. Checking now stays until Clerk loads or the user taps Retry.
Retry is still the only remount.
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from 5755e9e to 6ff4bf9CompareSeptember 1, 2026 23:14
Comment threadapps/mobile/src/lib/threadActivity.ts Outdated
Comment on lines +180 to +187
const next = b as typeof a;
return (
a.runId === next.runId &&
a.groupId === next.groupId &&
a.hiddenCount === next.hiddenCount &&
a.expanded === next.expanded &&
a.onlyToolActivities === next.onlyToolActivities
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumlib/threadActivity.ts:180

computeStableThreadFeedEntries reuses a stale work-toggle row when its group ID and count are unchanged, so live label, completion/failure status, and shimmer updates are not rendered. threadFeedEntryIsUnchanged must also compare summary, summaryKind, hasFailure, live, and shimmer before reusing the previous row.

Suggested change
constnext=bastypeofa;
return(
a.runId===next.runId&&
a.groupId===next.groupId&&
a.hiddenCount===next.hiddenCount&&
a.expanded===next.expanded&&
a.onlyToolActivities===next.onlyToolActivities
);
constnext=bastypeofa;
return(
a.runId===next.runId&&
a.groupId===next.groupId&&
a.hiddenCount===next.hiddenCount&&
a.expanded===next.expanded&&
a.onlyToolActivities===next.onlyToolActivities&&
a.summary===next.summary&&
a.summaryKind===next.summaryKind&&
a.hasFailure===next.hasFailure&&
a.live===next.live&&
a.shimmer===next.shimmer
);
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/mobile/src/lib/threadActivity.ts around lines 180-187:
`computeStableThreadFeedEntries` reuses a stale `work-toggle` row when its group ID and count are unchanged, so live label, completion/failure status, and shimmer updates are not rendered. `threadFeedEntryIsUnchanged` must also compare `summary`, `summaryKind`, `hasFailure`, `live`, and `shimmer` before reusing the previous row.

return "present" as const;
}
case "missing": {
yield* setDeleted();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Highstate/threads.ts:480

A stale refreshAuthoritativeSnapshot response can mark an already-reconnected thread as deleted and remove its cache. The missing branch calls setDeleted() without verifying that the response belongs to the current prepared connection/session, so a delayed 404 from the old connection clears newer live data; add a request/session fence before treating this result as authoritative.

🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/client-runtime/src/state/threads.ts around line 480:
A stale `refreshAuthoritativeSnapshot` response can mark an already-reconnected thread as deleted and remove its cache. The `missing` branch calls `setDeleted()` without verifying that the response belongs to the current prepared connection/session, so a delayed 404 from the old connection clears newer live data; add a request/session fence before treating this result as authoritative.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

a.expanded === next.expanded &&
a.onlyToolActivities === next.onlyToolActivities
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stable feed skips live work-toggle updates

Medium Severity

threadFeedEntryIsUnchanged treats a work-toggle as unchanged without comparing summary, summaryKind, hasFailure, live, or shimmer. Combined with reference-equal itemsAreEqual, LegendList keeps the old row, so the group label, failure mark, and shimmer can stay frozen through a live turn.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

DateTime.toEpochMillis(activity.projectedItem.item.updatedAt) ===
DateTime.toEpochMillis(nextActivity.projectedItem.item.updatedAt)
);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stable feed freezes activity live state

Medium Severity

threadFeedActivitiesAreUnchanged ignores lifecycleStatus and live. Presentation derives those from the active run, not only from item text or updatedAt, so an in-progress tool group can keep the previous activity objects after the run settles.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

return;
}
return cleanUpAccount(previous, previousObservedAccount ?? null);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clerk remount can wipe the account

Medium Severity

Retry remounts Clerk while observedAccountRef still holds the signed-in user. If the new instance reports loaded and signed out, the bridge treats that as a real sign-out, deactivates the relay, and runs account cleanup.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 6081201 to 95bacddCompareSeptember 2, 2026 07:19
mwolson added a commit to mwolson/t3code that referenced this pull request Sep 2, 2026
The pingdotgg#6642 row-sharing replay inserted computeStableThreadFeedEntries
after hooks that read presentedFeed. Metro rewrites const to var, so
those hooks observed presentedFeed as undefined on every render and
never updated previousPresentedFeedRef or disclosure enter ids.
Local and anchored messages have no projectedItem. Comparing sourceItemId
through it threw when the feed was rebuilt.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-cutting note (not repeated inline): the panel's row geometry now lives in apps/web/src/components/chat/threadDetailsPanelStyles.ts as class-string constants that six call sites splice into Button/SelectTrigger/ComboboxTrigger, overriding the primitive's height, radius, padding, text size and base/hover background — the hover tint needs !bg-... to win against the variant it is overriding. Since this is durable geometry shared by every panel row, a named Button size/variant (e.g. size="panel-row") would carry it inside the primitive contract and drop the !important overrides, leaving only contextual layout at the call sites. Not blocking this PR, but worth doing before more rows adopt the constants.

Posted via Macroscope — UI Consistency

inset-inline-end: var(--thread-details-panel-inset);
}

.alert-glass {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alert-glass already exists as @utility alert-glass (line 284) with these exact declarations plus a @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) opaque fallback. This copy is in @layer components, and since both background declarations are !important the cascade reverses layer order, so the components copy wins — the utility's no-backdrop-filter fallback stops applying and the banner renders translucent where blur is unsupported.

Suggest dropping this block together with the four .alert-glass[data-variant=...] rules below it and leaving the @utility as the single owner (the alert-glass class name in ProviderStatusBanner already comes from it).

Posted via Macroscope — UI Consistency

size="xs"
className="ps-[8.5px]"
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : undefined}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Toolbar mode lost the ps-[8.5px] optical padding this leading-icon button carried (same for the disabled variant above, line 1777). OpenInPicker keeps ps-[8.5px] for its matching toolbar button, so the two grouped controls in the header no longer align on their icon.

Suggested change
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : undefined}
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : "ps-[8.5px]"}

Posted via Macroscope — UI Consistency

{props.children}
</ul>
{props.hiddenCount > 0 ? (
<button

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This row rebuilds the panel row by hand (h-9, rounded-lg, px-2.5, text-[13px], gap-2.5, plus the light/dark hover tint) instead of composing Button, and unlike every other row in this file it has no focus-visible ring — so the one control that pages the list gives keyboard users no visible target, even though the comment above relies on rows being reachable by keyboard.

Suggest rendering it as <Button size="sm" variant="ghost" className={cn(THREAD_DETAILS_PANEL_ROW_CLASS, "text-muted-foreground/70 hover:text-foreground/80")}> (adding THREAD_DETAILS_PANEL_ROW_CLASS to the existing import from ./threadDetailsPanelStyles): same geometry and hover tint, with the primitive's focus ring and disabled semantics back.

Posted via Macroscope — UI Consistency

if (item.kind === "open_pr") {
void openExistingPr();
return;
}
if (item.dialogAction === "push") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveQuickAction and buildMenuItems in packages/client-runtime/src/state/gitActions.ts still emit kind: "open_pr" ("View PR") whenever the branch has an open pull request and is clean, but every handler for it was removed here: this menu item now falls through to setIsCommitDialogOpen(true) and opens the Commit dialog, runQuickAction ends at if (quickAction.action) so the quick-action button does nothing, and GitQuickActionIcon falls through to InfoIcon. openExistingPr is left with no callers.

Either keep routing open_pr, or stop emitting it now that ThreadDetailsPrRow owns the pull request row:

 if (item.disabled) return;
+ if (item.kind === "open_pr") {+ void openExistingPr();+ return;+ }
if (item.dialogAction === "push") {

Posted via Macroscope — UI Consistency

@@ -759,6 +762,182 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
outline: none;
}

.surface-subheader {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.surface-subheader has no consumers: the four subheader call sites (DiffPanelShell, PreviewChromeRow, FileBrowserPanel, FilePreviewPanel) keep their own Tailwind classes and only set the data-surface-subheader attribute that the rule below matches. As written this rule never renders and the header geometry has two competing definitions. Suggest either applying the class at those call sites or dropping the rule.

Posted via Macroscope — UI Consistency

--alert-glass-tint: var(--warning);
}

.glass-opacity-slider {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.glass-opacity-slider has no consumer, literal or dynamic: the glass-opacity input in settings/SettingsPanels.tsx uses className="settings-slider ..." and sets --settings-slider-progress/--settings-slider-fill-offset, and nothing anywhere reads --glass-slider-progress. That leaves ~110 lines of vendor pseudo-element CSS that never renders and duplicates .settings-slider below it (same track, thumb, hover/active, focus-visible and forced-colors treatment).

Suggest dropping this block and keeping .settings-slider as the single slider owner — or, if the glass slider was meant to diverge, pointing that input at this class so the rule has an owner.

Posted via Macroscope — UI Consistency

className="z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-transform data-instant:transition-none"
collisionAvoidance={collisionAvoidance}
className={cn(
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] data-instant:transition-none",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes every popover in the app transition its positional properties, while the only new consumer immediately opts out (positionerClassName="... !transition-none" in PanelLayoutControls). Base UI writes the positioner's top/left inline styles on each anchor-tracking update — scroll, resize, collision flip — so unrelated popovers will now animate toward their anchor and visibly trail it while a surface scrolls.

Keeping the primitive on transition-transform and opting in per call site via the new positionerClassName avoids changing behavior for popovers that did not ask for it:

Suggested change
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] data-instant:transition-none",
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-transform data-instant:transition-none",

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Macroscope skipped reviewing this pull request. Per-review cost limit exceeded (workspace setting).

This review would cost an estimated $249.67, which exceeds your per-review limit of $10.00.

The top 3 files driving up this estimate:

FileDiff SizeEstimate
apps/server/src/orchestration-v2/Orchestrator.ts277.03KB$13.85
apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts272.17KB$13.61
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts230.26KB$11.51

Tip

To get this pull request reviewed, you can:

  1. Comment @macroscope-app on this PR to request a manual review (monthly spend limits still apply).
  2. Exclude the file(s) above from review by adding a pattern to your .macroscope/ignore.md — note that creating this file replaces Macroscope's built-in default ignores rather than extending them.
  3. Raise your cost limit in your workspace billing settings.

Turn off this reminder going forward

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@mwolson@t3dotgg@juliusmarminge@PixPMusic@nsxdavid@Yusuf007R@maria-rcks
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(mobile): Catch up the thread feed after a long background - #6642

Open
mwolson wants to merge 335 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/mobile-thread-feed-performance-v2.1
Open

fix(mobile): Catch up the thread feed after a long background#6642
mwolson wants to merge 335 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/mobile-thread-feed-performance-v2.1

Conversation

@mwolson

@mwolsonmwolson commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

What Changed

On a large thread, every live token used to redraw the whole feed. Unchanged
rows now keep the same objects, so only the row that changed updates.

Coming back to a thread used to fetch the entire transcript. Warm opens
subscribe from the cached sequence, refresh a bounded snapshot in the
background, and replace a full socket fallback with that bounded window.

A short lock could flip a live thread to Syncing. A long lock could show old
messages as current, or sit on Syncing forever. A short lock or Home switch
stays live. After a long lock the feed shows Syncing over the cached transcript
until a socket snapshot or completion marker lands the latest content. HTTP can
refresh the visible window under that pill. Catch-up still ends on the socket
snapshot or marker. Reconnect restarts the live subscribe so the new session
actually gets a thread subscription.

T3 Account in Settings could sit on Checking after a long background, and
tapping it did nothing. The account row stays tappable. While Clerk is still
loading, the account screen shows Checking plus Retry. Retry remounts Clerk.
There is no 12-second timer and no automatic remount.

Checked on a signed iPhone, an Android tablet, the iOS Simulator, and Waydroid.
A 30-second lock stays live. A long lock shows Syncing, then catches up.

Why

The phone already had the recent history. Live updates, warm opens, and
reconnects were throwing that away or treating it as finished too soon. Catch-up
should look like Syncing until the latest rows are really there.

The Clerk hang is the same freeze after a long background: Settings looked busy
forever. Remounting Clerk from a timer tore down the whole app, so recovery is
manual Retry only.

UI Changes

Live turns no longer rebuild the whole feed. Warm opens stay on the recent
window. Short background stays live. Long background shows Syncing until latest
content lands. T3 Account stays Checking until Clerk loads, then Sign in or the
account email. Retry is on the account screen while Checking.

No before/after screenshots.

Known Limitations

On a Boox NoteAir5C, Clerk can load and still sit on Sign in. Retry remounts
Clerk and does not complete sign-in. Force-stopping the app recovers. That is
native Clerk or Android process state, not hung Checking.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

High Risk
Changes thread live/synchronizing transitions, bounded snapshot persistence, and shell “live” semantics that affect outbox delivery—race-prone areas per review notes. Broad mobile V2 migration plus removal of the thread-transfer PR reporter also affect CI signal and regression visibility.

Overview
Thread sync and mobile runtime — Mobile wires into bounded thread snapshot loading, shared orchestration cache schemas, and refreshCachedThreadOnSubscribe. Client-runtime thread state distinguishes catch-up vs completion waits, uses authoritative HTTP refresh on reconnect, and gates persistence while a bounded refresh is in flight. Shell status can promote to live when a usable cached snapshot exists. The mobile feed and related UI move to V2 projections (runtime vs session, RuntimeRequestId, checkpoint summaries derived from projection), with archive guards keyed off runtime status and approval/user-input cards respecting responseCapability.

Cloud auth and navigation — T3 Account stays navigable while Clerk is loading; the auth screen shows Checking with a manual Retry that remounts Clerk without timers. Cloud cleanup refs live on the parent so Clerk remounts do not drop in-flight sign-out work. In-memory navigation state is restored on app launch.

CI and housekeeping — The Thread Transfer Report workflow and its publisher scripts/tests are removed. CI installs build-essential so ACP process-tree fixtures compile instead of soft-skipping. Smaller updates: desktop user-data dir names in tests, composer context-strip default, mobile theme adaptive colors, README appearance doc link, marketing Cursor harness label.

Reviewed by Cursor Bugbot for commit 088b2ab. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add orchestration V2 foundation and catch up mobile thread feed after background

  • Introduces the full orchestration V2 contract layer: provider policies, model selections, checkpoints, thread projections, domain events, commands, and scheduled-task schemas across packages/contracts/src/orchestrationV2.ts and related contract modules
  • Adds V2 provider adapters for Claude, Codex, Cursor, Grok, OpenCode, and ACP Registry under apps/server/src/orchestration-v2/Adapters/, each integrating its provider SDK through the ProviderAdapterV2 service boundary
  • Adds nine database migrations (044–052) creating the V2 event log, projection tables, effect outbox, provider-session bindings, scheduled tasks, and legacy V1 import state in apps/server/src/persistence/Migrations/" - Builds core V2 server services: SQL-backed ProjectionStoreV2, EffectOutbox and EffectWorker for durable effect processing, ProviderSessionManager, ProviderRuntimeRecoveryService, checkpoint capture/rollback, thread management, and MCP orchestrator/worktree toolkits
  • Migrates mobile and web clients to V2 projection types: thread feed status resolution uses runtime and latestRun fields, pending approvals and user-input checks live response capability, and thread snapshots load through bounded HTTP routes
  • Risk: replaces the legacy EnvironmentApi.orchestration IPC property with orchestrationV2 and switches all WebSocket RPC methods to V2 constants; environments advertising a protocol version mismatch are now rejected at connection time via orchestrationProtocolCompatibilityError

Macroscope summarized 088b2ab.

@coderabbitai

coderabbitaiBot commented Aug 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c8bef765-f132-43e7-8c0c-531ac526096c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 14, 2026
Comment threadpackages/client-runtime/src/state/threadHistoryMerge.ts
@mwolson
mwolson marked this pull request as ready for review August 14, 2026 20:38
@macroscopeapp

macroscopeappBot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — Despite the mobile-fix title, this is a 966-file orchestration V2 migration that changes production persistence, APIs, provider execution, authentication, MCP capabilities, and mobile synchronization behavior. It also changes defaults and static-analysis directives while unresolved comments identify stale-data, auth, UI, and functionality risks.

Not approved because:

  • 2 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 7 times, most recently from a50460b to c610c31CompareAugust 15, 2026 15:09
Comment threadpackages/client-runtime/src/state/shell.ts Outdated
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from c610c31 to 9159bcfCompareAugust 15, 2026 15:18
Comment threadpackages/client-runtime/src/state/threads.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 96891a3 to 993407dCompareAugust 15, 2026 15:26
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 3 times, most recently from 5b5e9cb to a005644CompareAugust 15, 2026 18:38
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Aug 15, 2026
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from a005644 to 1eb46e5CompareAugust 15, 2026 19:01
Comment threadpackages/client-runtime/src/state/threads.ts
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 2 times, most recently from 541c25b to 26f73f5CompareAugust 15, 2026 19:52
Comment threadpackages/client-runtime/src/state/threads.ts Outdated
Comment threadpackages/client-runtime/src/state/threads.ts Outdated
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from 26f73f5 to 6af29ddCompareAugust 15, 2026 20:22
@mwolsonmwolson changed the title perf(orchestrator): Reduce mobile thread feed presentation workfix(mobile): Catch up the thread feed after a long backgroundAug 15, 2026
Comment threadpackages/client-runtime/src/state/threads.ts
juliusmarmingeand others added 13 commits August 31, 2026 21:33
Adapt grouped tool summaries and the floating working timer to V2 run, attempt, and queue state. Bring over the composer, keyboard, and disclosure transitions while retaining the V2 activity inspector and queue controls.
Keep OV2 web composer and grouping behavior intact; share only the existing command label parser with mobile.
Restores main features dropped by the policy replay: pingdotgg#8569 theme wiring,
pingdotgg#8850 composer banner follow-ups, pingdotgg#8855/pingdotgg#8904 composer fixes, pingdotgg#8831
settings search rework, pingdotgg#8803 workspace-mutation refresh (v2-adapted),
pingdotgg#8840 circle-alert, pingdotgg#8584 codex artifact templates, pingdotgg#8688/pingdotgg#8807/pingdotgg#8936
video + image previews (web and mobile, v2-adapted), pingdotgg#8862 Expo glass,
and the round's docs. Timeline thinking rows (pingdotgg#8984) stay on the v2
work-live system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from a6e0049 to 50a57b6CompareSeptember 1, 2026 05:36
juliusmarmingeand others added 7 commits September 1, 2026 00:24
The v2 equivalents of main's pingdotgg#8984 and pingdotgg#8922: a "Working for ..." header
anchors the active run, the trailing live tool row survives between
actions in past tense instead of vanishing, and a shimmering Thinking
row marks reasoning gaps. During workspace preparation the header shows
"Setting up worktree..." (driven by the local dispatch flag or the v2
run's preparing status, so remote viewers see it too), the composer
footer span is gone, and draft promotion waits until the run starts or
startup fails instead of navigating mid-preparation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Clerk can sit on isLoaded=false after a background freeze, which pins
Settings on Checking and blocks the account screen. Remount Clerk after a
foreground timeout, keep T3 Account tappable, and offer Retry without
clearing app data.
A cold HTTP seed still waits for synchronized, but disconnect and stream
error cleared awaitingCompletion. Reconnect then published live while
replay was still arriving. Keep the fence until synchronized or delete.
A 12-second foreground timer used to remount ClerkProvider, which rebuilds
the whole app. Checking now stays until Clerk loads or the user taps Retry.
Retry is still the only remount.
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from 5755e9e to 6ff4bf9CompareSeptember 1, 2026 23:14
Comment threadapps/mobile/src/lib/threadActivity.ts Outdated
Comment on lines +180 to +187
const next = b as typeof a;
return (
a.runId === next.runId &&
a.groupId === next.groupId &&
a.hiddenCount === next.hiddenCount &&
a.expanded === next.expanded &&
a.onlyToolActivities === next.onlyToolActivities
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumlib/threadActivity.ts:180

computeStableThreadFeedEntries reuses a stale work-toggle row when its group ID and count are unchanged, so live label, completion/failure status, and shimmer updates are not rendered. threadFeedEntryIsUnchanged must also compare summary, summaryKind, hasFailure, live, and shimmer before reusing the previous row.

Suggested change
constnext=bastypeofa;
return(
a.runId===next.runId&&
a.groupId===next.groupId&&
a.hiddenCount===next.hiddenCount&&
a.expanded===next.expanded&&
a.onlyToolActivities===next.onlyToolActivities
);
constnext=bastypeofa;
return(
a.runId===next.runId&&
a.groupId===next.groupId&&
a.hiddenCount===next.hiddenCount&&
a.expanded===next.expanded&&
a.onlyToolActivities===next.onlyToolActivities&&
a.summary===next.summary&&
a.summaryKind===next.summaryKind&&
a.hasFailure===next.hasFailure&&
a.live===next.live&&
a.shimmer===next.shimmer
);
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/mobile/src/lib/threadActivity.ts around lines 180-187:
`computeStableThreadFeedEntries` reuses a stale `work-toggle` row when its group ID and count are unchanged, so live label, completion/failure status, and shimmer updates are not rendered. `threadFeedEntryIsUnchanged` must also compare `summary`, `summaryKind`, `hasFailure`, `live`, and `shimmer` before reusing the previous row.

return "present" as const;
}
case "missing": {
yield* setDeleted();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Highstate/threads.ts:480

A stale refreshAuthoritativeSnapshot response can mark an already-reconnected thread as deleted and remove its cache. The missing branch calls setDeleted() without verifying that the response belongs to the current prepared connection/session, so a delayed 404 from the old connection clears newer live data; add a request/session fence before treating this result as authoritative.

🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/client-runtime/src/state/threads.ts around line 480:
A stale `refreshAuthoritativeSnapshot` response can mark an already-reconnected thread as deleted and remove its cache. The `missing` branch calls `setDeleted()` without verifying that the response belongs to the current prepared connection/session, so a delayed 404 from the old connection clears newer live data; add a request/session fence before treating this result as authoritative.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

a.expanded === next.expanded &&
a.onlyToolActivities === next.onlyToolActivities
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stable feed skips live work-toggle updates

Medium Severity

threadFeedEntryIsUnchanged treats a work-toggle as unchanged without comparing summary, summaryKind, hasFailure, live, or shimmer. Combined with reference-equal itemsAreEqual, LegendList keeps the old row, so the group label, failure mark, and shimmer can stay frozen through a live turn.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

DateTime.toEpochMillis(activity.projectedItem.item.updatedAt) ===
DateTime.toEpochMillis(nextActivity.projectedItem.item.updatedAt)
);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stable feed freezes activity live state

Medium Severity

threadFeedActivitiesAreUnchanged ignores lifecycleStatus and live. Presentation derives those from the active run, not only from item text or updatedAt, so an in-progress tool group can keep the previous activity objects after the run settles.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

return;
}
return cleanUpAccount(previous, previousObservedAccount ?? null);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clerk remount can wipe the account

Medium Severity

Retry remounts Clerk while observedAccountRef still holds the signed-in user. If the new instance reports loaded and signed out, the bridge treats that as a real sign-out, deactivates the relay, and runs account cleanup.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 6081201 to 95bacddCompareSeptember 2, 2026 07:19
mwolson added a commit to mwolson/t3code that referenced this pull request Sep 2, 2026
The pingdotgg#6642 row-sharing replay inserted computeStableThreadFeedEntries
after hooks that read presentedFeed. Metro rewrites const to var, so
those hooks observed presentedFeed as undefined on every render and
never updated previousPresentedFeedRef or disclosure enter ids.
Local and anchored messages have no projectedItem. Comparing sourceItemId
through it threw when the feed was rebuilt.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-cutting note (not repeated inline): the panel's row geometry now lives in apps/web/src/components/chat/threadDetailsPanelStyles.ts as class-string constants that six call sites splice into Button/SelectTrigger/ComboboxTrigger, overriding the primitive's height, radius, padding, text size and base/hover background — the hover tint needs !bg-... to win against the variant it is overriding. Since this is durable geometry shared by every panel row, a named Button size/variant (e.g. size="panel-row") would carry it inside the primitive contract and drop the !important overrides, leaving only contextual layout at the call sites. Not blocking this PR, but worth doing before more rows adopt the constants.

Posted via Macroscope — UI Consistency

inset-inline-end: var(--thread-details-panel-inset);
}

.alert-glass {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alert-glass already exists as @utility alert-glass (line 284) with these exact declarations plus a @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) opaque fallback. This copy is in @layer components, and since both background declarations are !important the cascade reverses layer order, so the components copy wins — the utility's no-backdrop-filter fallback stops applying and the banner renders translucent where blur is unsupported.

Suggest dropping this block together with the four .alert-glass[data-variant=...] rules below it and leaving the @utility as the single owner (the alert-glass class name in ProviderStatusBanner already comes from it).

Posted via Macroscope — UI Consistency

size="xs"
className="ps-[8.5px]"
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : undefined}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Toolbar mode lost the ps-[8.5px] optical padding this leading-icon button carried (same for the disabled variant above, line 1777). OpenInPicker keeps ps-[8.5px] for its matching toolbar button, so the two grouped controls in the header no longer align on their icon.

Suggested change
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : undefined}
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : "ps-[8.5px]"}

Posted via Macroscope — UI Consistency

{props.children}
</ul>
{props.hiddenCount > 0 ? (
<button

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This row rebuilds the panel row by hand (h-9, rounded-lg, px-2.5, text-[13px], gap-2.5, plus the light/dark hover tint) instead of composing Button, and unlike every other row in this file it has no focus-visible ring — so the one control that pages the list gives keyboard users no visible target, even though the comment above relies on rows being reachable by keyboard.

Suggest rendering it as <Button size="sm" variant="ghost" className={cn(THREAD_DETAILS_PANEL_ROW_CLASS, "text-muted-foreground/70 hover:text-foreground/80")}> (adding THREAD_DETAILS_PANEL_ROW_CLASS to the existing import from ./threadDetailsPanelStyles): same geometry and hover tint, with the primitive's focus ring and disabled semantics back.

Posted via Macroscope — UI Consistency

if (item.kind === "open_pr") {
void openExistingPr();
return;
}
if (item.dialogAction === "push") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveQuickAction and buildMenuItems in packages/client-runtime/src/state/gitActions.ts still emit kind: "open_pr" ("View PR") whenever the branch has an open pull request and is clean, but every handler for it was removed here: this menu item now falls through to setIsCommitDialogOpen(true) and opens the Commit dialog, runQuickAction ends at if (quickAction.action) so the quick-action button does nothing, and GitQuickActionIcon falls through to InfoIcon. openExistingPr is left with no callers.

Either keep routing open_pr, or stop emitting it now that ThreadDetailsPrRow owns the pull request row:

 if (item.disabled) return;
+ if (item.kind === "open_pr") {+ void openExistingPr();+ return;+ }
if (item.dialogAction === "push") {

Posted via Macroscope — UI Consistency

@@ -759,6 +762,182 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
outline: none;
}

.surface-subheader {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.surface-subheader has no consumers: the four subheader call sites (DiffPanelShell, PreviewChromeRow, FileBrowserPanel, FilePreviewPanel) keep their own Tailwind classes and only set the data-surface-subheader attribute that the rule below matches. As written this rule never renders and the header geometry has two competing definitions. Suggest either applying the class at those call sites or dropping the rule.

Posted via Macroscope — UI Consistency

--alert-glass-tint: var(--warning);
}

.glass-opacity-slider {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.glass-opacity-slider has no consumer, literal or dynamic: the glass-opacity input in settings/SettingsPanels.tsx uses className="settings-slider ..." and sets --settings-slider-progress/--settings-slider-fill-offset, and nothing anywhere reads --glass-slider-progress. That leaves ~110 lines of vendor pseudo-element CSS that never renders and duplicates .settings-slider below it (same track, thumb, hover/active, focus-visible and forced-colors treatment).

Suggest dropping this block and keeping .settings-slider as the single slider owner — or, if the glass slider was meant to diverge, pointing that input at this class so the rule has an owner.

Posted via Macroscope — UI Consistency

className="z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-transform data-instant:transition-none"
collisionAvoidance={collisionAvoidance}
className={cn(
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] data-instant:transition-none",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes every popover in the app transition its positional properties, while the only new consumer immediately opts out (positionerClassName="... !transition-none" in PanelLayoutControls). Base UI writes the positioner's top/left inline styles on each anchor-tracking update — scroll, resize, collision flip — so unrelated popovers will now animate toward their anchor and visibly trail it while a surface scrolls.

Keeping the primitive on transition-transform and opting in per call site via the new positionerClassName avoids changing behavior for popovers that did not ask for it:

Suggested change
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] data-instant:transition-none",
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-transform data-instant:transition-none",

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Macroscope skipped reviewing this pull request. Per-review cost limit exceeded (workspace setting).

This review would cost an estimated $249.67, which exceeds your per-review limit of $10.00.

The top 3 files driving up this estimate:

FileDiff SizeEstimate
apps/server/src/orchestration-v2/Orchestrator.ts277.03KB$13.85
apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts272.17KB$13.61
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts230.26KB$11.51

Tip

To get this pull request reviewed, you can:

  1. Comment @macroscope-app on this PR to request a manual review (monthly spend limits still apply).
  2. Exclude the file(s) above from review by adding a pattern to your .macroscope/ignore.md — note that creating this file replaces Macroscope's built-in default ignores rather than extending them.
  3. Raise your cost limit in your workspace billing settings.

Turn off this reminder going forward

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@mwolson@t3dotgg@juliusmarminge@PixPMusic@nsxdavid@Yusuf007R@maria-rcks
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(mobile): Catch up the thread feed after a long background - #6642

Open
mwolson wants to merge 335 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/mobile-thread-feed-performance-v2.1
Open

fix(mobile): Catch up the thread feed after a long background#6642
mwolson wants to merge 335 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/mobile-thread-feed-performance-v2.1

Conversation

@mwolson

@mwolsonmwolson commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

What Changed

On a large thread, every live token used to redraw the whole feed. Unchanged
rows now keep the same objects, so only the row that changed updates.

Coming back to a thread used to fetch the entire transcript. Warm opens
subscribe from the cached sequence, refresh a bounded snapshot in the
background, and replace a full socket fallback with that bounded window.

A short lock could flip a live thread to Syncing. A long lock could show old
messages as current, or sit on Syncing forever. A short lock or Home switch
stays live. After a long lock the feed shows Syncing over the cached transcript
until a socket snapshot or completion marker lands the latest content. HTTP can
refresh the visible window under that pill. Catch-up still ends on the socket
snapshot or marker. Reconnect restarts the live subscribe so the new session
actually gets a thread subscription.

T3 Account in Settings could sit on Checking after a long background, and
tapping it did nothing. The account row stays tappable. While Clerk is still
loading, the account screen shows Checking plus Retry. Retry remounts Clerk.
There is no 12-second timer and no automatic remount.

Checked on a signed iPhone, an Android tablet, the iOS Simulator, and Waydroid.
A 30-second lock stays live. A long lock shows Syncing, then catches up.

Why

The phone already had the recent history. Live updates, warm opens, and
reconnects were throwing that away or treating it as finished too soon. Catch-up
should look like Syncing until the latest rows are really there.

The Clerk hang is the same freeze after a long background: Settings looked busy
forever. Remounting Clerk from a timer tore down the whole app, so recovery is
manual Retry only.

UI Changes

Live turns no longer rebuild the whole feed. Warm opens stay on the recent
window. Short background stays live. Long background shows Syncing until latest
content lands. T3 Account stays Checking until Clerk loads, then Sign in or the
account email. Retry is on the account screen while Checking.

No before/after screenshots.

Known Limitations

On a Boox NoteAir5C, Clerk can load and still sit on Sign in. Retry remounts
Clerk and does not complete sign-in. Force-stopping the app recovers. That is
native Clerk or Android process state, not hung Checking.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

High Risk
Changes thread live/synchronizing transitions, bounded snapshot persistence, and shell “live” semantics that affect outbox delivery—race-prone areas per review notes. Broad mobile V2 migration plus removal of the thread-transfer PR reporter also affect CI signal and regression visibility.

Overview
Thread sync and mobile runtime — Mobile wires into bounded thread snapshot loading, shared orchestration cache schemas, and refreshCachedThreadOnSubscribe. Client-runtime thread state distinguishes catch-up vs completion waits, uses authoritative HTTP refresh on reconnect, and gates persistence while a bounded refresh is in flight. Shell status can promote to live when a usable cached snapshot exists. The mobile feed and related UI move to V2 projections (runtime vs session, RuntimeRequestId, checkpoint summaries derived from projection), with archive guards keyed off runtime status and approval/user-input cards respecting responseCapability.

Cloud auth and navigation — T3 Account stays navigable while Clerk is loading; the auth screen shows Checking with a manual Retry that remounts Clerk without timers. Cloud cleanup refs live on the parent so Clerk remounts do not drop in-flight sign-out work. In-memory navigation state is restored on app launch.

CI and housekeeping — The Thread Transfer Report workflow and its publisher scripts/tests are removed. CI installs build-essential so ACP process-tree fixtures compile instead of soft-skipping. Smaller updates: desktop user-data dir names in tests, composer context-strip default, mobile theme adaptive colors, README appearance doc link, marketing Cursor harness label.

Reviewed by Cursor Bugbot for commit 088b2ab. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add orchestration V2 foundation and catch up mobile thread feed after background

  • Introduces the full orchestration V2 contract layer: provider policies, model selections, checkpoints, thread projections, domain events, commands, and scheduled-task schemas across packages/contracts/src/orchestrationV2.ts and related contract modules
  • Adds V2 provider adapters for Claude, Codex, Cursor, Grok, OpenCode, and ACP Registry under apps/server/src/orchestration-v2/Adapters/, each integrating its provider SDK through the ProviderAdapterV2 service boundary
  • Adds nine database migrations (044–052) creating the V2 event log, projection tables, effect outbox, provider-session bindings, scheduled tasks, and legacy V1 import state in apps/server/src/persistence/Migrations/" - Builds core V2 server services: SQL-backed ProjectionStoreV2, EffectOutbox and EffectWorker for durable effect processing, ProviderSessionManager, ProviderRuntimeRecoveryService, checkpoint capture/rollback, thread management, and MCP orchestrator/worktree toolkits
  • Migrates mobile and web clients to V2 projection types: thread feed status resolution uses runtime and latestRun fields, pending approvals and user-input checks live response capability, and thread snapshots load through bounded HTTP routes
  • Risk: replaces the legacy EnvironmentApi.orchestration IPC property with orchestrationV2 and switches all WebSocket RPC methods to V2 constants; environments advertising a protocol version mismatch are now rejected at connection time via orchestrationProtocolCompatibilityError

Macroscope summarized 088b2ab.

@coderabbitai

coderabbitaiBot commented Aug 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c8bef765-f132-43e7-8c0c-531ac526096c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 14, 2026
Comment threadpackages/client-runtime/src/state/threadHistoryMerge.ts
@mwolson
mwolson marked this pull request as ready for review August 14, 2026 20:38
@macroscopeapp

macroscopeappBot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — Despite the mobile-fix title, this is a 966-file orchestration V2 migration that changes production persistence, APIs, provider execution, authentication, MCP capabilities, and mobile synchronization behavior. It also changes defaults and static-analysis directives while unresolved comments identify stale-data, auth, UI, and functionality risks.

Not approved because:

  • 2 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 7 times, most recently from a50460b to c610c31CompareAugust 15, 2026 15:09
Comment threadpackages/client-runtime/src/state/shell.ts Outdated
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from c610c31 to 9159bcfCompareAugust 15, 2026 15:18
Comment threadpackages/client-runtime/src/state/threads.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 96891a3 to 993407dCompareAugust 15, 2026 15:26
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 3 times, most recently from 5b5e9cb to a005644CompareAugust 15, 2026 18:38
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Aug 15, 2026
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from a005644 to 1eb46e5CompareAugust 15, 2026 19:01
Comment threadpackages/client-runtime/src/state/threads.ts
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 2 times, most recently from 541c25b to 26f73f5CompareAugust 15, 2026 19:52
Comment threadpackages/client-runtime/src/state/threads.ts Outdated
Comment threadpackages/client-runtime/src/state/threads.ts Outdated
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from 26f73f5 to 6af29ddCompareAugust 15, 2026 20:22
@mwolsonmwolson changed the title perf(orchestrator): Reduce mobile thread feed presentation workfix(mobile): Catch up the thread feed after a long backgroundAug 15, 2026
Comment threadpackages/client-runtime/src/state/threads.ts
juliusmarmingeand others added 13 commits August 31, 2026 21:33
Adapt grouped tool summaries and the floating working timer to V2 run, attempt, and queue state. Bring over the composer, keyboard, and disclosure transitions while retaining the V2 activity inspector and queue controls.
Keep OV2 web composer and grouping behavior intact; share only the existing command label parser with mobile.
Restores main features dropped by the policy replay: pingdotgg#8569 theme wiring,
pingdotgg#8850 composer banner follow-ups, pingdotgg#8855/pingdotgg#8904 composer fixes, pingdotgg#8831
settings search rework, pingdotgg#8803 workspace-mutation refresh (v2-adapted),
pingdotgg#8840 circle-alert, pingdotgg#8584 codex artifact templates, pingdotgg#8688/pingdotgg#8807/pingdotgg#8936
video + image previews (web and mobile, v2-adapted), pingdotgg#8862 Expo glass,
and the round's docs. Timeline thinking rows (pingdotgg#8984) stay on the v2
work-live system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from a6e0049 to 50a57b6CompareSeptember 1, 2026 05:36
juliusmarmingeand others added 7 commits September 1, 2026 00:24
The v2 equivalents of main's pingdotgg#8984 and pingdotgg#8922: a "Working for ..." header
anchors the active run, the trailing live tool row survives between
actions in past tense instead of vanishing, and a shimmering Thinking
row marks reasoning gaps. During workspace preparation the header shows
"Setting up worktree..." (driven by the local dispatch flag or the v2
run's preparing status, so remote viewers see it too), the composer
footer span is gone, and draft promotion waits until the run starts or
startup fails instead of navigating mid-preparation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Clerk can sit on isLoaded=false after a background freeze, which pins
Settings on Checking and blocks the account screen. Remount Clerk after a
foreground timeout, keep T3 Account tappable, and offer Retry without
clearing app data.
A cold HTTP seed still waits for synchronized, but disconnect and stream
error cleared awaitingCompletion. Reconnect then published live while
replay was still arriving. Keep the fence until synchronized or delete.
A 12-second foreground timer used to remount ClerkProvider, which rebuilds
the whole app. Checking now stays until Clerk loads or the user taps Retry.
Retry is still the only remount.
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from 5755e9e to 6ff4bf9CompareSeptember 1, 2026 23:14
Comment threadapps/mobile/src/lib/threadActivity.ts Outdated
Comment on lines +180 to +187
const next = b as typeof a;
return (
a.runId === next.runId &&
a.groupId === next.groupId &&
a.hiddenCount === next.hiddenCount &&
a.expanded === next.expanded &&
a.onlyToolActivities === next.onlyToolActivities
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumlib/threadActivity.ts:180

computeStableThreadFeedEntries reuses a stale work-toggle row when its group ID and count are unchanged, so live label, completion/failure status, and shimmer updates are not rendered. threadFeedEntryIsUnchanged must also compare summary, summaryKind, hasFailure, live, and shimmer before reusing the previous row.

Suggested change
constnext=bastypeofa;
return(
a.runId===next.runId&&
a.groupId===next.groupId&&
a.hiddenCount===next.hiddenCount&&
a.expanded===next.expanded&&
a.onlyToolActivities===next.onlyToolActivities
);
constnext=bastypeofa;
return(
a.runId===next.runId&&
a.groupId===next.groupId&&
a.hiddenCount===next.hiddenCount&&
a.expanded===next.expanded&&
a.onlyToolActivities===next.onlyToolActivities&&
a.summary===next.summary&&
a.summaryKind===next.summaryKind&&
a.hasFailure===next.hasFailure&&
a.live===next.live&&
a.shimmer===next.shimmer
);
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/mobile/src/lib/threadActivity.ts around lines 180-187:
`computeStableThreadFeedEntries` reuses a stale `work-toggle` row when its group ID and count are unchanged, so live label, completion/failure status, and shimmer updates are not rendered. `threadFeedEntryIsUnchanged` must also compare `summary`, `summaryKind`, `hasFailure`, `live`, and `shimmer` before reusing the previous row.

return "present" as const;
}
case "missing": {
yield* setDeleted();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Highstate/threads.ts:480

A stale refreshAuthoritativeSnapshot response can mark an already-reconnected thread as deleted and remove its cache. The missing branch calls setDeleted() without verifying that the response belongs to the current prepared connection/session, so a delayed 404 from the old connection clears newer live data; add a request/session fence before treating this result as authoritative.

🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/client-runtime/src/state/threads.ts around line 480:
A stale `refreshAuthoritativeSnapshot` response can mark an already-reconnected thread as deleted and remove its cache. The `missing` branch calls `setDeleted()` without verifying that the response belongs to the current prepared connection/session, so a delayed 404 from the old connection clears newer live data; add a request/session fence before treating this result as authoritative.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

a.expanded === next.expanded &&
a.onlyToolActivities === next.onlyToolActivities
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stable feed skips live work-toggle updates

Medium Severity

threadFeedEntryIsUnchanged treats a work-toggle as unchanged without comparing summary, summaryKind, hasFailure, live, or shimmer. Combined with reference-equal itemsAreEqual, LegendList keeps the old row, so the group label, failure mark, and shimmer can stay frozen through a live turn.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

DateTime.toEpochMillis(activity.projectedItem.item.updatedAt) ===
DateTime.toEpochMillis(nextActivity.projectedItem.item.updatedAt)
);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stable feed freezes activity live state

Medium Severity

threadFeedActivitiesAreUnchanged ignores lifecycleStatus and live. Presentation derives those from the active run, not only from item text or updatedAt, so an in-progress tool group can keep the previous activity objects after the run settles.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

return;
}
return cleanUpAccount(previous, previousObservedAccount ?? null);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clerk remount can wipe the account

Medium Severity

Retry remounts Clerk while observedAccountRef still holds the signed-in user. If the new instance reports loaded and signed out, the bridge treats that as a real sign-out, deactivates the relay, and runs account cleanup.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 6081201 to 95bacddCompareSeptember 2, 2026 07:19
mwolson added a commit to mwolson/t3code that referenced this pull request Sep 2, 2026
The pingdotgg#6642 row-sharing replay inserted computeStableThreadFeedEntries
after hooks that read presentedFeed. Metro rewrites const to var, so
those hooks observed presentedFeed as undefined on every render and
never updated previousPresentedFeedRef or disclosure enter ids.
Local and anchored messages have no projectedItem. Comparing sourceItemId
through it threw when the feed was rebuilt.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-cutting note (not repeated inline): the panel's row geometry now lives in apps/web/src/components/chat/threadDetailsPanelStyles.ts as class-string constants that six call sites splice into Button/SelectTrigger/ComboboxTrigger, overriding the primitive's height, radius, padding, text size and base/hover background — the hover tint needs !bg-... to win against the variant it is overriding. Since this is durable geometry shared by every panel row, a named Button size/variant (e.g. size="panel-row") would carry it inside the primitive contract and drop the !important overrides, leaving only contextual layout at the call sites. Not blocking this PR, but worth doing before more rows adopt the constants.

Posted via Macroscope — UI Consistency

inset-inline-end: var(--thread-details-panel-inset);
}

.alert-glass {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alert-glass already exists as @utility alert-glass (line 284) with these exact declarations plus a @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) opaque fallback. This copy is in @layer components, and since both background declarations are !important the cascade reverses layer order, so the components copy wins — the utility's no-backdrop-filter fallback stops applying and the banner renders translucent where blur is unsupported.

Suggest dropping this block together with the four .alert-glass[data-variant=...] rules below it and leaving the @utility as the single owner (the alert-glass class name in ProviderStatusBanner already comes from it).

Posted via Macroscope — UI Consistency

size="xs"
className="ps-[8.5px]"
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : undefined}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Toolbar mode lost the ps-[8.5px] optical padding this leading-icon button carried (same for the disabled variant above, line 1777). OpenInPicker keeps ps-[8.5px] for its matching toolbar button, so the two grouped controls in the header no longer align on their icon.

Suggested change
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : undefined}
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : "ps-[8.5px]"}

Posted via Macroscope — UI Consistency

{props.children}
</ul>
{props.hiddenCount > 0 ? (
<button

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This row rebuilds the panel row by hand (h-9, rounded-lg, px-2.5, text-[13px], gap-2.5, plus the light/dark hover tint) instead of composing Button, and unlike every other row in this file it has no focus-visible ring — so the one control that pages the list gives keyboard users no visible target, even though the comment above relies on rows being reachable by keyboard.

Suggest rendering it as <Button size="sm" variant="ghost" className={cn(THREAD_DETAILS_PANEL_ROW_CLASS, "text-muted-foreground/70 hover:text-foreground/80")}> (adding THREAD_DETAILS_PANEL_ROW_CLASS to the existing import from ./threadDetailsPanelStyles): same geometry and hover tint, with the primitive's focus ring and disabled semantics back.

Posted via Macroscope — UI Consistency

if (item.kind === "open_pr") {
void openExistingPr();
return;
}
if (item.dialogAction === "push") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveQuickAction and buildMenuItems in packages/client-runtime/src/state/gitActions.ts still emit kind: "open_pr" ("View PR") whenever the branch has an open pull request and is clean, but every handler for it was removed here: this menu item now falls through to setIsCommitDialogOpen(true) and opens the Commit dialog, runQuickAction ends at if (quickAction.action) so the quick-action button does nothing, and GitQuickActionIcon falls through to InfoIcon. openExistingPr is left with no callers.

Either keep routing open_pr, or stop emitting it now that ThreadDetailsPrRow owns the pull request row:

 if (item.disabled) return;
+ if (item.kind === "open_pr") {+ void openExistingPr();+ return;+ }
if (item.dialogAction === "push") {

Posted via Macroscope — UI Consistency

@@ -759,6 +762,182 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
outline: none;
}

.surface-subheader {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.surface-subheader has no consumers: the four subheader call sites (DiffPanelShell, PreviewChromeRow, FileBrowserPanel, FilePreviewPanel) keep their own Tailwind classes and only set the data-surface-subheader attribute that the rule below matches. As written this rule never renders and the header geometry has two competing definitions. Suggest either applying the class at those call sites or dropping the rule.

Posted via Macroscope — UI Consistency

--alert-glass-tint: var(--warning);
}

.glass-opacity-slider {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.glass-opacity-slider has no consumer, literal or dynamic: the glass-opacity input in settings/SettingsPanels.tsx uses className="settings-slider ..." and sets --settings-slider-progress/--settings-slider-fill-offset, and nothing anywhere reads --glass-slider-progress. That leaves ~110 lines of vendor pseudo-element CSS that never renders and duplicates .settings-slider below it (same track, thumb, hover/active, focus-visible and forced-colors treatment).

Suggest dropping this block and keeping .settings-slider as the single slider owner — or, if the glass slider was meant to diverge, pointing that input at this class so the rule has an owner.

Posted via Macroscope — UI Consistency

className="z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-transform data-instant:transition-none"
collisionAvoidance={collisionAvoidance}
className={cn(
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] data-instant:transition-none",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes every popover in the app transition its positional properties, while the only new consumer immediately opts out (positionerClassName="... !transition-none" in PanelLayoutControls). Base UI writes the positioner's top/left inline styles on each anchor-tracking update — scroll, resize, collision flip — so unrelated popovers will now animate toward their anchor and visibly trail it while a surface scrolls.

Keeping the primitive on transition-transform and opting in per call site via the new positionerClassName avoids changing behavior for popovers that did not ask for it:

Suggested change
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] data-instant:transition-none",
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-transform data-instant:transition-none",

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Macroscope skipped reviewing this pull request. Per-review cost limit exceeded (workspace setting).

This review would cost an estimated $249.67, which exceeds your per-review limit of $10.00.

The top 3 files driving up this estimate:

FileDiff SizeEstimate
apps/server/src/orchestration-v2/Orchestrator.ts277.03KB$13.85
apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts272.17KB$13.61
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts230.26KB$11.51

Tip

To get this pull request reviewed, you can:

  1. Comment @macroscope-app on this PR to request a manual review (monthly spend limits still apply).
  2. Exclude the file(s) above from review by adding a pattern to your .macroscope/ignore.md — note that creating this file replaces Macroscope's built-in default ignores rather than extending them.
  3. Raise your cost limit in your workspace billing settings.

Turn off this reminder going forward

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@mwolson@t3dotgg@juliusmarminge@PixPMusic@nsxdavid@Yusuf007R@maria-rcks
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(mobile): Catch up the thread feed after a long background - #6642

Open
mwolson wants to merge 335 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/mobile-thread-feed-performance-v2.1
Open

fix(mobile): Catch up the thread feed after a long background#6642
mwolson wants to merge 335 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/mobile-thread-feed-performance-v2.1

Conversation

@mwolson

@mwolsonmwolson commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

What Changed

On a large thread, every live token used to redraw the whole feed. Unchanged
rows now keep the same objects, so only the row that changed updates.

Coming back to a thread used to fetch the entire transcript. Warm opens
subscribe from the cached sequence, refresh a bounded snapshot in the
background, and replace a full socket fallback with that bounded window.

A short lock could flip a live thread to Syncing. A long lock could show old
messages as current, or sit on Syncing forever. A short lock or Home switch
stays live. After a long lock the feed shows Syncing over the cached transcript
until a socket snapshot or completion marker lands the latest content. HTTP can
refresh the visible window under that pill. Catch-up still ends on the socket
snapshot or marker. Reconnect restarts the live subscribe so the new session
actually gets a thread subscription.

T3 Account in Settings could sit on Checking after a long background, and
tapping it did nothing. The account row stays tappable. While Clerk is still
loading, the account screen shows Checking plus Retry. Retry remounts Clerk.
There is no 12-second timer and no automatic remount.

Checked on a signed iPhone, an Android tablet, the iOS Simulator, and Waydroid.
A 30-second lock stays live. A long lock shows Syncing, then catches up.

Why

The phone already had the recent history. Live updates, warm opens, and
reconnects were throwing that away or treating it as finished too soon. Catch-up
should look like Syncing until the latest rows are really there.

The Clerk hang is the same freeze after a long background: Settings looked busy
forever. Remounting Clerk from a timer tore down the whole app, so recovery is
manual Retry only.

UI Changes

Live turns no longer rebuild the whole feed. Warm opens stay on the recent
window. Short background stays live. Long background shows Syncing until latest
content lands. T3 Account stays Checking until Clerk loads, then Sign in or the
account email. Retry is on the account screen while Checking.

No before/after screenshots.

Known Limitations

On a Boox NoteAir5C, Clerk can load and still sit on Sign in. Retry remounts
Clerk and does not complete sign-in. Force-stopping the app recovers. That is
native Clerk or Android process state, not hung Checking.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

High Risk
Changes thread live/synchronizing transitions, bounded snapshot persistence, and shell “live” semantics that affect outbox delivery—race-prone areas per review notes. Broad mobile V2 migration plus removal of the thread-transfer PR reporter also affect CI signal and regression visibility.

Overview
Thread sync and mobile runtime — Mobile wires into bounded thread snapshot loading, shared orchestration cache schemas, and refreshCachedThreadOnSubscribe. Client-runtime thread state distinguishes catch-up vs completion waits, uses authoritative HTTP refresh on reconnect, and gates persistence while a bounded refresh is in flight. Shell status can promote to live when a usable cached snapshot exists. The mobile feed and related UI move to V2 projections (runtime vs session, RuntimeRequestId, checkpoint summaries derived from projection), with archive guards keyed off runtime status and approval/user-input cards respecting responseCapability.

Cloud auth and navigation — T3 Account stays navigable while Clerk is loading; the auth screen shows Checking with a manual Retry that remounts Clerk without timers. Cloud cleanup refs live on the parent so Clerk remounts do not drop in-flight sign-out work. In-memory navigation state is restored on app launch.

CI and housekeeping — The Thread Transfer Report workflow and its publisher scripts/tests are removed. CI installs build-essential so ACP process-tree fixtures compile instead of soft-skipping. Smaller updates: desktop user-data dir names in tests, composer context-strip default, mobile theme adaptive colors, README appearance doc link, marketing Cursor harness label.

Reviewed by Cursor Bugbot for commit 088b2ab. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add orchestration V2 foundation and catch up mobile thread feed after background

  • Introduces the full orchestration V2 contract layer: provider policies, model selections, checkpoints, thread projections, domain events, commands, and scheduled-task schemas across packages/contracts/src/orchestrationV2.ts and related contract modules
  • Adds V2 provider adapters for Claude, Codex, Cursor, Grok, OpenCode, and ACP Registry under apps/server/src/orchestration-v2/Adapters/, each integrating its provider SDK through the ProviderAdapterV2 service boundary
  • Adds nine database migrations (044–052) creating the V2 event log, projection tables, effect outbox, provider-session bindings, scheduled tasks, and legacy V1 import state in apps/server/src/persistence/Migrations/" - Builds core V2 server services: SQL-backed ProjectionStoreV2, EffectOutbox and EffectWorker for durable effect processing, ProviderSessionManager, ProviderRuntimeRecoveryService, checkpoint capture/rollback, thread management, and MCP orchestrator/worktree toolkits
  • Migrates mobile and web clients to V2 projection types: thread feed status resolution uses runtime and latestRun fields, pending approvals and user-input checks live response capability, and thread snapshots load through bounded HTTP routes
  • Risk: replaces the legacy EnvironmentApi.orchestration IPC property with orchestrationV2 and switches all WebSocket RPC methods to V2 constants; environments advertising a protocol version mismatch are now rejected at connection time via orchestrationProtocolCompatibilityError

Macroscope summarized 088b2ab.

@coderabbitai

coderabbitaiBot commented Aug 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c8bef765-f132-43e7-8c0c-531ac526096c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 14, 2026
Comment threadpackages/client-runtime/src/state/threadHistoryMerge.ts
@mwolson
mwolson marked this pull request as ready for review August 14, 2026 20:38
@macroscopeapp

macroscopeappBot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — Despite the mobile-fix title, this is a 966-file orchestration V2 migration that changes production persistence, APIs, provider execution, authentication, MCP capabilities, and mobile synchronization behavior. It also changes defaults and static-analysis directives while unresolved comments identify stale-data, auth, UI, and functionality risks.

Not approved because:

  • 2 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 7 times, most recently from a50460b to c610c31CompareAugust 15, 2026 15:09
Comment threadpackages/client-runtime/src/state/shell.ts Outdated
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from c610c31 to 9159bcfCompareAugust 15, 2026 15:18
Comment threadpackages/client-runtime/src/state/threads.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 96891a3 to 993407dCompareAugust 15, 2026 15:26
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 3 times, most recently from 5b5e9cb to a005644CompareAugust 15, 2026 18:38
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Aug 15, 2026
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from a005644 to 1eb46e5CompareAugust 15, 2026 19:01
Comment threadpackages/client-runtime/src/state/threads.ts
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 2 times, most recently from 541c25b to 26f73f5CompareAugust 15, 2026 19:52
Comment threadpackages/client-runtime/src/state/threads.ts Outdated
Comment threadpackages/client-runtime/src/state/threads.ts Outdated
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from 26f73f5 to 6af29ddCompareAugust 15, 2026 20:22
@mwolsonmwolson changed the title perf(orchestrator): Reduce mobile thread feed presentation workfix(mobile): Catch up the thread feed after a long backgroundAug 15, 2026
Comment threadpackages/client-runtime/src/state/threads.ts
juliusmarmingeand others added 13 commits August 31, 2026 21:33
Adapt grouped tool summaries and the floating working timer to V2 run, attempt, and queue state. Bring over the composer, keyboard, and disclosure transitions while retaining the V2 activity inspector and queue controls.
Keep OV2 web composer and grouping behavior intact; share only the existing command label parser with mobile.
Restores main features dropped by the policy replay: pingdotgg#8569 theme wiring,
pingdotgg#8850 composer banner follow-ups, pingdotgg#8855/pingdotgg#8904 composer fixes, pingdotgg#8831
settings search rework, pingdotgg#8803 workspace-mutation refresh (v2-adapted),
pingdotgg#8840 circle-alert, pingdotgg#8584 codex artifact templates, pingdotgg#8688/pingdotgg#8807/pingdotgg#8936
video + image previews (web and mobile, v2-adapted), pingdotgg#8862 Expo glass,
and the round's docs. Timeline thinking rows (pingdotgg#8984) stay on the v2
work-live system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from a6e0049 to 50a57b6CompareSeptember 1, 2026 05:36
juliusmarmingeand others added 7 commits September 1, 2026 00:24
The v2 equivalents of main's pingdotgg#8984 and pingdotgg#8922: a "Working for ..." header
anchors the active run, the trailing live tool row survives between
actions in past tense instead of vanishing, and a shimmering Thinking
row marks reasoning gaps. During workspace preparation the header shows
"Setting up worktree..." (driven by the local dispatch flag or the v2
run's preparing status, so remote viewers see it too), the composer
footer span is gone, and draft promotion waits until the run starts or
startup fails instead of navigating mid-preparation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Clerk can sit on isLoaded=false after a background freeze, which pins
Settings on Checking and blocks the account screen. Remount Clerk after a
foreground timeout, keep T3 Account tappable, and offer Retry without
clearing app data.
A cold HTTP seed still waits for synchronized, but disconnect and stream
error cleared awaitingCompletion. Reconnect then published live while
replay was still arriving. Keep the fence until synchronized or delete.
A 12-second foreground timer used to remount ClerkProvider, which rebuilds
the whole app. Checking now stays until Clerk loads or the user taps Retry.
Retry is still the only remount.
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from 5755e9e to 6ff4bf9CompareSeptember 1, 2026 23:14
Comment threadapps/mobile/src/lib/threadActivity.ts Outdated
Comment on lines +180 to +187
const next = b as typeof a;
return (
a.runId === next.runId &&
a.groupId === next.groupId &&
a.hiddenCount === next.hiddenCount &&
a.expanded === next.expanded &&
a.onlyToolActivities === next.onlyToolActivities
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumlib/threadActivity.ts:180

computeStableThreadFeedEntries reuses a stale work-toggle row when its group ID and count are unchanged, so live label, completion/failure status, and shimmer updates are not rendered. threadFeedEntryIsUnchanged must also compare summary, summaryKind, hasFailure, live, and shimmer before reusing the previous row.

Suggested change
constnext=bastypeofa;
return(
a.runId===next.runId&&
a.groupId===next.groupId&&
a.hiddenCount===next.hiddenCount&&
a.expanded===next.expanded&&
a.onlyToolActivities===next.onlyToolActivities
);
constnext=bastypeofa;
return(
a.runId===next.runId&&
a.groupId===next.groupId&&
a.hiddenCount===next.hiddenCount&&
a.expanded===next.expanded&&
a.onlyToolActivities===next.onlyToolActivities&&
a.summary===next.summary&&
a.summaryKind===next.summaryKind&&
a.hasFailure===next.hasFailure&&
a.live===next.live&&
a.shimmer===next.shimmer
);
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/mobile/src/lib/threadActivity.ts around lines 180-187:
`computeStableThreadFeedEntries` reuses a stale `work-toggle` row when its group ID and count are unchanged, so live label, completion/failure status, and shimmer updates are not rendered. `threadFeedEntryIsUnchanged` must also compare `summary`, `summaryKind`, `hasFailure`, `live`, and `shimmer` before reusing the previous row.

return "present" as const;
}
case "missing": {
yield* setDeleted();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Highstate/threads.ts:480

A stale refreshAuthoritativeSnapshot response can mark an already-reconnected thread as deleted and remove its cache. The missing branch calls setDeleted() without verifying that the response belongs to the current prepared connection/session, so a delayed 404 from the old connection clears newer live data; add a request/session fence before treating this result as authoritative.

🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/client-runtime/src/state/threads.ts around line 480:
A stale `refreshAuthoritativeSnapshot` response can mark an already-reconnected thread as deleted and remove its cache. The `missing` branch calls `setDeleted()` without verifying that the response belongs to the current prepared connection/session, so a delayed 404 from the old connection clears newer live data; add a request/session fence before treating this result as authoritative.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

a.expanded === next.expanded &&
a.onlyToolActivities === next.onlyToolActivities
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stable feed skips live work-toggle updates

Medium Severity

threadFeedEntryIsUnchanged treats a work-toggle as unchanged without comparing summary, summaryKind, hasFailure, live, or shimmer. Combined with reference-equal itemsAreEqual, LegendList keeps the old row, so the group label, failure mark, and shimmer can stay frozen through a live turn.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

DateTime.toEpochMillis(activity.projectedItem.item.updatedAt) ===
DateTime.toEpochMillis(nextActivity.projectedItem.item.updatedAt)
);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stable feed freezes activity live state

Medium Severity

threadFeedActivitiesAreUnchanged ignores lifecycleStatus and live. Presentation derives those from the active run, not only from item text or updatedAt, so an in-progress tool group can keep the previous activity objects after the run settles.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

return;
}
return cleanUpAccount(previous, previousObservedAccount ?? null);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clerk remount can wipe the account

Medium Severity

Retry remounts Clerk while observedAccountRef still holds the signed-in user. If the new instance reports loaded and signed out, the bridge treats that as a real sign-out, deactivates the relay, and runs account cleanup.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 6081201 to 95bacddCompareSeptember 2, 2026 07:19
mwolson added a commit to mwolson/t3code that referenced this pull request Sep 2, 2026
The pingdotgg#6642 row-sharing replay inserted computeStableThreadFeedEntries
after hooks that read presentedFeed. Metro rewrites const to var, so
those hooks observed presentedFeed as undefined on every render and
never updated previousPresentedFeedRef or disclosure enter ids.
Local and anchored messages have no projectedItem. Comparing sourceItemId
through it threw when the feed was rebuilt.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-cutting note (not repeated inline): the panel's row geometry now lives in apps/web/src/components/chat/threadDetailsPanelStyles.ts as class-string constants that six call sites splice into Button/SelectTrigger/ComboboxTrigger, overriding the primitive's height, radius, padding, text size and base/hover background — the hover tint needs !bg-... to win against the variant it is overriding. Since this is durable geometry shared by every panel row, a named Button size/variant (e.g. size="panel-row") would carry it inside the primitive contract and drop the !important overrides, leaving only contextual layout at the call sites. Not blocking this PR, but worth doing before more rows adopt the constants.

Posted via Macroscope — UI Consistency

inset-inline-end: var(--thread-details-panel-inset);
}

.alert-glass {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alert-glass already exists as @utility alert-glass (line 284) with these exact declarations plus a @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) opaque fallback. This copy is in @layer components, and since both background declarations are !important the cascade reverses layer order, so the components copy wins — the utility's no-backdrop-filter fallback stops applying and the banner renders translucent where blur is unsupported.

Suggest dropping this block together with the four .alert-glass[data-variant=...] rules below it and leaving the @utility as the single owner (the alert-glass class name in ProviderStatusBanner already comes from it).

Posted via Macroscope — UI Consistency

size="xs"
className="ps-[8.5px]"
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : undefined}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Toolbar mode lost the ps-[8.5px] optical padding this leading-icon button carried (same for the disabled variant above, line 1777). OpenInPicker keeps ps-[8.5px] for its matching toolbar button, so the two grouped controls in the header no longer align on their icon.

Suggested change
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : undefined}
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : "ps-[8.5px]"}

Posted via Macroscope — UI Consistency

{props.children}
</ul>
{props.hiddenCount > 0 ? (
<button

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This row rebuilds the panel row by hand (h-9, rounded-lg, px-2.5, text-[13px], gap-2.5, plus the light/dark hover tint) instead of composing Button, and unlike every other row in this file it has no focus-visible ring — so the one control that pages the list gives keyboard users no visible target, even though the comment above relies on rows being reachable by keyboard.

Suggest rendering it as <Button size="sm" variant="ghost" className={cn(THREAD_DETAILS_PANEL_ROW_CLASS, "text-muted-foreground/70 hover:text-foreground/80")}> (adding THREAD_DETAILS_PANEL_ROW_CLASS to the existing import from ./threadDetailsPanelStyles): same geometry and hover tint, with the primitive's focus ring and disabled semantics back.

Posted via Macroscope — UI Consistency

if (item.kind === "open_pr") {
void openExistingPr();
return;
}
if (item.dialogAction === "push") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveQuickAction and buildMenuItems in packages/client-runtime/src/state/gitActions.ts still emit kind: "open_pr" ("View PR") whenever the branch has an open pull request and is clean, but every handler for it was removed here: this menu item now falls through to setIsCommitDialogOpen(true) and opens the Commit dialog, runQuickAction ends at if (quickAction.action) so the quick-action button does nothing, and GitQuickActionIcon falls through to InfoIcon. openExistingPr is left with no callers.

Either keep routing open_pr, or stop emitting it now that ThreadDetailsPrRow owns the pull request row:

 if (item.disabled) return;
+ if (item.kind === "open_pr") {+ void openExistingPr();+ return;+ }
if (item.dialogAction === "push") {

Posted via Macroscope — UI Consistency

@@ -759,6 +762,182 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
outline: none;
}

.surface-subheader {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.surface-subheader has no consumers: the four subheader call sites (DiffPanelShell, PreviewChromeRow, FileBrowserPanel, FilePreviewPanel) keep their own Tailwind classes and only set the data-surface-subheader attribute that the rule below matches. As written this rule never renders and the header geometry has two competing definitions. Suggest either applying the class at those call sites or dropping the rule.

Posted via Macroscope — UI Consistency

--alert-glass-tint: var(--warning);
}

.glass-opacity-slider {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.glass-opacity-slider has no consumer, literal or dynamic: the glass-opacity input in settings/SettingsPanels.tsx uses className="settings-slider ..." and sets --settings-slider-progress/--settings-slider-fill-offset, and nothing anywhere reads --glass-slider-progress. That leaves ~110 lines of vendor pseudo-element CSS that never renders and duplicates .settings-slider below it (same track, thumb, hover/active, focus-visible and forced-colors treatment).

Suggest dropping this block and keeping .settings-slider as the single slider owner — or, if the glass slider was meant to diverge, pointing that input at this class so the rule has an owner.

Posted via Macroscope — UI Consistency

className="z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-transform data-instant:transition-none"
collisionAvoidance={collisionAvoidance}
className={cn(
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] data-instant:transition-none",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes every popover in the app transition its positional properties, while the only new consumer immediately opts out (positionerClassName="... !transition-none" in PanelLayoutControls). Base UI writes the positioner's top/left inline styles on each anchor-tracking update — scroll, resize, collision flip — so unrelated popovers will now animate toward their anchor and visibly trail it while a surface scrolls.

Keeping the primitive on transition-transform and opting in per call site via the new positionerClassName avoids changing behavior for popovers that did not ask for it:

Suggested change
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] data-instant:transition-none",
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-transform data-instant:transition-none",

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Macroscope skipped reviewing this pull request. Per-review cost limit exceeded (workspace setting).

This review would cost an estimated $249.67, which exceeds your per-review limit of $10.00.

The top 3 files driving up this estimate:

FileDiff SizeEstimate
apps/server/src/orchestration-v2/Orchestrator.ts277.03KB$13.85
apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts272.17KB$13.61
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts230.26KB$11.51

Tip

To get this pull request reviewed, you can:

  1. Comment @macroscope-app on this PR to request a manual review (monthly spend limits still apply).
  2. Exclude the file(s) above from review by adding a pattern to your .macroscope/ignore.md — note that creating this file replaces Macroscope's built-in default ignores rather than extending them.
  3. Raise your cost limit in your workspace billing settings.

Turn off this reminder going forward

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@mwolson@t3dotgg@juliusmarminge@PixPMusic@nsxdavid@Yusuf007R@maria-rcks
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(mobile): Catch up the thread feed after a long background - #6642

Open
mwolson wants to merge 335 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/mobile-thread-feed-performance-v2.1
Open

fix(mobile): Catch up the thread feed after a long background#6642
mwolson wants to merge 335 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/mobile-thread-feed-performance-v2.1

Conversation

@mwolson

@mwolsonmwolson commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

What Changed

On a large thread, every live token used to redraw the whole feed. Unchanged
rows now keep the same objects, so only the row that changed updates.

Coming back to a thread used to fetch the entire transcript. Warm opens
subscribe from the cached sequence, refresh a bounded snapshot in the
background, and replace a full socket fallback with that bounded window.

A short lock could flip a live thread to Syncing. A long lock could show old
messages as current, or sit on Syncing forever. A short lock or Home switch
stays live. After a long lock the feed shows Syncing over the cached transcript
until a socket snapshot or completion marker lands the latest content. HTTP can
refresh the visible window under that pill. Catch-up still ends on the socket
snapshot or marker. Reconnect restarts the live subscribe so the new session
actually gets a thread subscription.

T3 Account in Settings could sit on Checking after a long background, and
tapping it did nothing. The account row stays tappable. While Clerk is still
loading, the account screen shows Checking plus Retry. Retry remounts Clerk.
There is no 12-second timer and no automatic remount.

Checked on a signed iPhone, an Android tablet, the iOS Simulator, and Waydroid.
A 30-second lock stays live. A long lock shows Syncing, then catches up.

Why

The phone already had the recent history. Live updates, warm opens, and
reconnects were throwing that away or treating it as finished too soon. Catch-up
should look like Syncing until the latest rows are really there.

The Clerk hang is the same freeze after a long background: Settings looked busy
forever. Remounting Clerk from a timer tore down the whole app, so recovery is
manual Retry only.

UI Changes

Live turns no longer rebuild the whole feed. Warm opens stay on the recent
window. Short background stays live. Long background shows Syncing until latest
content lands. T3 Account stays Checking until Clerk loads, then Sign in or the
account email. Retry is on the account screen while Checking.

No before/after screenshots.

Known Limitations

On a Boox NoteAir5C, Clerk can load and still sit on Sign in. Retry remounts
Clerk and does not complete sign-in. Force-stopping the app recovers. That is
native Clerk or Android process state, not hung Checking.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

High Risk
Changes thread live/synchronizing transitions, bounded snapshot persistence, and shell “live” semantics that affect outbox delivery—race-prone areas per review notes. Broad mobile V2 migration plus removal of the thread-transfer PR reporter also affect CI signal and regression visibility.

Overview
Thread sync and mobile runtime — Mobile wires into bounded thread snapshot loading, shared orchestration cache schemas, and refreshCachedThreadOnSubscribe. Client-runtime thread state distinguishes catch-up vs completion waits, uses authoritative HTTP refresh on reconnect, and gates persistence while a bounded refresh is in flight. Shell status can promote to live when a usable cached snapshot exists. The mobile feed and related UI move to V2 projections (runtime vs session, RuntimeRequestId, checkpoint summaries derived from projection), with archive guards keyed off runtime status and approval/user-input cards respecting responseCapability.

Cloud auth and navigation — T3 Account stays navigable while Clerk is loading; the auth screen shows Checking with a manual Retry that remounts Clerk without timers. Cloud cleanup refs live on the parent so Clerk remounts do not drop in-flight sign-out work. In-memory navigation state is restored on app launch.

CI and housekeeping — The Thread Transfer Report workflow and its publisher scripts/tests are removed. CI installs build-essential so ACP process-tree fixtures compile instead of soft-skipping. Smaller updates: desktop user-data dir names in tests, composer context-strip default, mobile theme adaptive colors, README appearance doc link, marketing Cursor harness label.

Reviewed by Cursor Bugbot for commit 088b2ab. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add orchestration V2 foundation and catch up mobile thread feed after background

  • Introduces the full orchestration V2 contract layer: provider policies, model selections, checkpoints, thread projections, domain events, commands, and scheduled-task schemas across packages/contracts/src/orchestrationV2.ts and related contract modules
  • Adds V2 provider adapters for Claude, Codex, Cursor, Grok, OpenCode, and ACP Registry under apps/server/src/orchestration-v2/Adapters/, each integrating its provider SDK through the ProviderAdapterV2 service boundary
  • Adds nine database migrations (044–052) creating the V2 event log, projection tables, effect outbox, provider-session bindings, scheduled tasks, and legacy V1 import state in apps/server/src/persistence/Migrations/" - Builds core V2 server services: SQL-backed ProjectionStoreV2, EffectOutbox and EffectWorker for durable effect processing, ProviderSessionManager, ProviderRuntimeRecoveryService, checkpoint capture/rollback, thread management, and MCP orchestrator/worktree toolkits
  • Migrates mobile and web clients to V2 projection types: thread feed status resolution uses runtime and latestRun fields, pending approvals and user-input checks live response capability, and thread snapshots load through bounded HTTP routes
  • Risk: replaces the legacy EnvironmentApi.orchestration IPC property with orchestrationV2 and switches all WebSocket RPC methods to V2 constants; environments advertising a protocol version mismatch are now rejected at connection time via orchestrationProtocolCompatibilityError

Macroscope summarized 088b2ab.

@coderabbitai

coderabbitaiBot commented Aug 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c8bef765-f132-43e7-8c0c-531ac526096c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 14, 2026
Comment threadpackages/client-runtime/src/state/threadHistoryMerge.ts
@mwolson
mwolson marked this pull request as ready for review August 14, 2026 20:38
@macroscopeapp

macroscopeappBot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — Despite the mobile-fix title, this is a 966-file orchestration V2 migration that changes production persistence, APIs, provider execution, authentication, MCP capabilities, and mobile synchronization behavior. It also changes defaults and static-analysis directives while unresolved comments identify stale-data, auth, UI, and functionality risks.

Not approved because:

  • 2 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 7 times, most recently from a50460b to c610c31CompareAugust 15, 2026 15:09
Comment threadpackages/client-runtime/src/state/shell.ts Outdated
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from c610c31 to 9159bcfCompareAugust 15, 2026 15:18
Comment threadpackages/client-runtime/src/state/threads.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 96891a3 to 993407dCompareAugust 15, 2026 15:26
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 3 times, most recently from 5b5e9cb to a005644CompareAugust 15, 2026 18:38
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Aug 15, 2026
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from a005644 to 1eb46e5CompareAugust 15, 2026 19:01
Comment threadpackages/client-runtime/src/state/threads.ts
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 2 times, most recently from 541c25b to 26f73f5CompareAugust 15, 2026 19:52
Comment threadpackages/client-runtime/src/state/threads.ts Outdated
Comment threadpackages/client-runtime/src/state/threads.ts Outdated
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from 26f73f5 to 6af29ddCompareAugust 15, 2026 20:22
@mwolsonmwolson changed the title perf(orchestrator): Reduce mobile thread feed presentation workfix(mobile): Catch up the thread feed after a long backgroundAug 15, 2026
Comment threadpackages/client-runtime/src/state/threads.ts
juliusmarmingeand others added 13 commits August 31, 2026 21:33
Adapt grouped tool summaries and the floating working timer to V2 run, attempt, and queue state. Bring over the composer, keyboard, and disclosure transitions while retaining the V2 activity inspector and queue controls.
Keep OV2 web composer and grouping behavior intact; share only the existing command label parser with mobile.
Restores main features dropped by the policy replay: pingdotgg#8569 theme wiring,
pingdotgg#8850 composer banner follow-ups, pingdotgg#8855/pingdotgg#8904 composer fixes, pingdotgg#8831
settings search rework, pingdotgg#8803 workspace-mutation refresh (v2-adapted),
pingdotgg#8840 circle-alert, pingdotgg#8584 codex artifact templates, pingdotgg#8688/pingdotgg#8807/pingdotgg#8936
video + image previews (web and mobile, v2-adapted), pingdotgg#8862 Expo glass,
and the round's docs. Timeline thinking rows (pingdotgg#8984) stay on the v2
work-live system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from a6e0049 to 50a57b6CompareSeptember 1, 2026 05:36
juliusmarmingeand others added 7 commits September 1, 2026 00:24
The v2 equivalents of main's pingdotgg#8984 and pingdotgg#8922: a "Working for ..." header
anchors the active run, the trailing live tool row survives between
actions in past tense instead of vanishing, and a shimmering Thinking
row marks reasoning gaps. During workspace preparation the header shows
"Setting up worktree..." (driven by the local dispatch flag or the v2
run's preparing status, so remote viewers see it too), the composer
footer span is gone, and draft promotion waits until the run starts or
startup fails instead of navigating mid-preparation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Clerk can sit on isLoaded=false after a background freeze, which pins
Settings on Checking and blocks the account screen. Remount Clerk after a
foreground timeout, keep T3 Account tappable, and offer Retry without
clearing app data.
A cold HTTP seed still waits for synchronized, but disconnect and stream
error cleared awaitingCompletion. Reconnect then published live while
replay was still arriving. Keep the fence until synchronized or delete.
A 12-second foreground timer used to remount ClerkProvider, which rebuilds
the whole app. Checking now stays until Clerk loads or the user taps Retry.
Retry is still the only remount.
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from 5755e9e to 6ff4bf9CompareSeptember 1, 2026 23:14
Comment threadapps/mobile/src/lib/threadActivity.ts Outdated
Comment on lines +180 to +187
const next = b as typeof a;
return (
a.runId === next.runId &&
a.groupId === next.groupId &&
a.hiddenCount === next.hiddenCount &&
a.expanded === next.expanded &&
a.onlyToolActivities === next.onlyToolActivities
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumlib/threadActivity.ts:180

computeStableThreadFeedEntries reuses a stale work-toggle row when its group ID and count are unchanged, so live label, completion/failure status, and shimmer updates are not rendered. threadFeedEntryIsUnchanged must also compare summary, summaryKind, hasFailure, live, and shimmer before reusing the previous row.

Suggested change
constnext=bastypeofa;
return(
a.runId===next.runId&&
a.groupId===next.groupId&&
a.hiddenCount===next.hiddenCount&&
a.expanded===next.expanded&&
a.onlyToolActivities===next.onlyToolActivities
);
constnext=bastypeofa;
return(
a.runId===next.runId&&
a.groupId===next.groupId&&
a.hiddenCount===next.hiddenCount&&
a.expanded===next.expanded&&
a.onlyToolActivities===next.onlyToolActivities&&
a.summary===next.summary&&
a.summaryKind===next.summaryKind&&
a.hasFailure===next.hasFailure&&
a.live===next.live&&
a.shimmer===next.shimmer
);
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/mobile/src/lib/threadActivity.ts around lines 180-187:
`computeStableThreadFeedEntries` reuses a stale `work-toggle` row when its group ID and count are unchanged, so live label, completion/failure status, and shimmer updates are not rendered. `threadFeedEntryIsUnchanged` must also compare `summary`, `summaryKind`, `hasFailure`, `live`, and `shimmer` before reusing the previous row.

return "present" as const;
}
case "missing": {
yield* setDeleted();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Highstate/threads.ts:480

A stale refreshAuthoritativeSnapshot response can mark an already-reconnected thread as deleted and remove its cache. The missing branch calls setDeleted() without verifying that the response belongs to the current prepared connection/session, so a delayed 404 from the old connection clears newer live data; add a request/session fence before treating this result as authoritative.

🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/client-runtime/src/state/threads.ts around line 480:
A stale `refreshAuthoritativeSnapshot` response can mark an already-reconnected thread as deleted and remove its cache. The `missing` branch calls `setDeleted()` without verifying that the response belongs to the current prepared connection/session, so a delayed 404 from the old connection clears newer live data; add a request/session fence before treating this result as authoritative.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

a.expanded === next.expanded &&
a.onlyToolActivities === next.onlyToolActivities
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stable feed skips live work-toggle updates

Medium Severity

threadFeedEntryIsUnchanged treats a work-toggle as unchanged without comparing summary, summaryKind, hasFailure, live, or shimmer. Combined with reference-equal itemsAreEqual, LegendList keeps the old row, so the group label, failure mark, and shimmer can stay frozen through a live turn.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

DateTime.toEpochMillis(activity.projectedItem.item.updatedAt) ===
DateTime.toEpochMillis(nextActivity.projectedItem.item.updatedAt)
);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stable feed freezes activity live state

Medium Severity

threadFeedActivitiesAreUnchanged ignores lifecycleStatus and live. Presentation derives those from the active run, not only from item text or updatedAt, so an in-progress tool group can keep the previous activity objects after the run settles.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

return;
}
return cleanUpAccount(previous, previousObservedAccount ?? null);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clerk remount can wipe the account

Medium Severity

Retry remounts Clerk while observedAccountRef still holds the signed-in user. If the new instance reports loaded and signed out, the bridge treats that as a real sign-out, deactivates the relay, and runs account cleanup.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 6081201 to 95bacddCompareSeptember 2, 2026 07:19
mwolson added a commit to mwolson/t3code that referenced this pull request Sep 2, 2026
The pingdotgg#6642 row-sharing replay inserted computeStableThreadFeedEntries
after hooks that read presentedFeed. Metro rewrites const to var, so
those hooks observed presentedFeed as undefined on every render and
never updated previousPresentedFeedRef or disclosure enter ids.
Local and anchored messages have no projectedItem. Comparing sourceItemId
through it threw when the feed was rebuilt.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-cutting note (not repeated inline): the panel's row geometry now lives in apps/web/src/components/chat/threadDetailsPanelStyles.ts as class-string constants that six call sites splice into Button/SelectTrigger/ComboboxTrigger, overriding the primitive's height, radius, padding, text size and base/hover background — the hover tint needs !bg-... to win against the variant it is overriding. Since this is durable geometry shared by every panel row, a named Button size/variant (e.g. size="panel-row") would carry it inside the primitive contract and drop the !important overrides, leaving only contextual layout at the call sites. Not blocking this PR, but worth doing before more rows adopt the constants.

Posted via Macroscope — UI Consistency

inset-inline-end: var(--thread-details-panel-inset);
}

.alert-glass {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alert-glass already exists as @utility alert-glass (line 284) with these exact declarations plus a @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) opaque fallback. This copy is in @layer components, and since both background declarations are !important the cascade reverses layer order, so the components copy wins — the utility's no-backdrop-filter fallback stops applying and the banner renders translucent where blur is unsupported.

Suggest dropping this block together with the four .alert-glass[data-variant=...] rules below it and leaving the @utility as the single owner (the alert-glass class name in ProviderStatusBanner already comes from it).

Posted via Macroscope — UI Consistency

size="xs"
className="ps-[8.5px]"
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : undefined}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Toolbar mode lost the ps-[8.5px] optical padding this leading-icon button carried (same for the disabled variant above, line 1777). OpenInPicker keeps ps-[8.5px] for its matching toolbar button, so the two grouped controls in the header no longer align on their icon.

Suggested change
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : undefined}
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : "ps-[8.5px]"}

Posted via Macroscope — UI Consistency

{props.children}
</ul>
{props.hiddenCount > 0 ? (
<button

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This row rebuilds the panel row by hand (h-9, rounded-lg, px-2.5, text-[13px], gap-2.5, plus the light/dark hover tint) instead of composing Button, and unlike every other row in this file it has no focus-visible ring — so the one control that pages the list gives keyboard users no visible target, even though the comment above relies on rows being reachable by keyboard.

Suggest rendering it as <Button size="sm" variant="ghost" className={cn(THREAD_DETAILS_PANEL_ROW_CLASS, "text-muted-foreground/70 hover:text-foreground/80")}> (adding THREAD_DETAILS_PANEL_ROW_CLASS to the existing import from ./threadDetailsPanelStyles): same geometry and hover tint, with the primitive's focus ring and disabled semantics back.

Posted via Macroscope — UI Consistency

if (item.kind === "open_pr") {
void openExistingPr();
return;
}
if (item.dialogAction === "push") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveQuickAction and buildMenuItems in packages/client-runtime/src/state/gitActions.ts still emit kind: "open_pr" ("View PR") whenever the branch has an open pull request and is clean, but every handler for it was removed here: this menu item now falls through to setIsCommitDialogOpen(true) and opens the Commit dialog, runQuickAction ends at if (quickAction.action) so the quick-action button does nothing, and GitQuickActionIcon falls through to InfoIcon. openExistingPr is left with no callers.

Either keep routing open_pr, or stop emitting it now that ThreadDetailsPrRow owns the pull request row:

 if (item.disabled) return;
+ if (item.kind === "open_pr") {+ void openExistingPr();+ return;+ }
if (item.dialogAction === "push") {

Posted via Macroscope — UI Consistency

@@ -759,6 +762,182 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
outline: none;
}

.surface-subheader {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.surface-subheader has no consumers: the four subheader call sites (DiffPanelShell, PreviewChromeRow, FileBrowserPanel, FilePreviewPanel) keep their own Tailwind classes and only set the data-surface-subheader attribute that the rule below matches. As written this rule never renders and the header geometry has two competing definitions. Suggest either applying the class at those call sites or dropping the rule.

Posted via Macroscope — UI Consistency

--alert-glass-tint: var(--warning);
}

.glass-opacity-slider {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.glass-opacity-slider has no consumer, literal or dynamic: the glass-opacity input in settings/SettingsPanels.tsx uses className="settings-slider ..." and sets --settings-slider-progress/--settings-slider-fill-offset, and nothing anywhere reads --glass-slider-progress. That leaves ~110 lines of vendor pseudo-element CSS that never renders and duplicates .settings-slider below it (same track, thumb, hover/active, focus-visible and forced-colors treatment).

Suggest dropping this block and keeping .settings-slider as the single slider owner — or, if the glass slider was meant to diverge, pointing that input at this class so the rule has an owner.

Posted via Macroscope — UI Consistency

className="z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-transform data-instant:transition-none"
collisionAvoidance={collisionAvoidance}
className={cn(
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] data-instant:transition-none",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes every popover in the app transition its positional properties, while the only new consumer immediately opts out (positionerClassName="... !transition-none" in PanelLayoutControls). Base UI writes the positioner's top/left inline styles on each anchor-tracking update — scroll, resize, collision flip — so unrelated popovers will now animate toward their anchor and visibly trail it while a surface scrolls.

Keeping the primitive on transition-transform and opting in per call site via the new positionerClassName avoids changing behavior for popovers that did not ask for it:

Suggested change
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] data-instant:transition-none",
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-transform data-instant:transition-none",

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Macroscope skipped reviewing this pull request. Per-review cost limit exceeded (workspace setting).

This review would cost an estimated $249.67, which exceeds your per-review limit of $10.00.

The top 3 files driving up this estimate:

FileDiff SizeEstimate
apps/server/src/orchestration-v2/Orchestrator.ts277.03KB$13.85
apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts272.17KB$13.61
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts230.26KB$11.51

Tip

To get this pull request reviewed, you can:

  1. Comment @macroscope-app on this PR to request a manual review (monthly spend limits still apply).
  2. Exclude the file(s) above from review by adding a pattern to your .macroscope/ignore.md — note that creating this file replaces Macroscope's built-in default ignores rather than extending them.
  3. Raise your cost limit in your workspace billing settings.

Turn off this reminder going forward

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@mwolson@t3dotgg@juliusmarminge@PixPMusic@nsxdavid@Yusuf007R@maria-rcks
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(mobile): Catch up the thread feed after a long background - #6642

Open
mwolson wants to merge 335 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/mobile-thread-feed-performance-v2.1
Open

fix(mobile): Catch up the thread feed after a long background#6642
mwolson wants to merge 335 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/mobile-thread-feed-performance-v2.1

Conversation

@mwolson

@mwolsonmwolson commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

What Changed

On a large thread, every live token used to redraw the whole feed. Unchanged
rows now keep the same objects, so only the row that changed updates.

Coming back to a thread used to fetch the entire transcript. Warm opens
subscribe from the cached sequence, refresh a bounded snapshot in the
background, and replace a full socket fallback with that bounded window.

A short lock could flip a live thread to Syncing. A long lock could show old
messages as current, or sit on Syncing forever. A short lock or Home switch
stays live. After a long lock the feed shows Syncing over the cached transcript
until a socket snapshot or completion marker lands the latest content. HTTP can
refresh the visible window under that pill. Catch-up still ends on the socket
snapshot or marker. Reconnect restarts the live subscribe so the new session
actually gets a thread subscription.

T3 Account in Settings could sit on Checking after a long background, and
tapping it did nothing. The account row stays tappable. While Clerk is still
loading, the account screen shows Checking plus Retry. Retry remounts Clerk.
There is no 12-second timer and no automatic remount.

Checked on a signed iPhone, an Android tablet, the iOS Simulator, and Waydroid.
A 30-second lock stays live. A long lock shows Syncing, then catches up.

Why

The phone already had the recent history. Live updates, warm opens, and
reconnects were throwing that away or treating it as finished too soon. Catch-up
should look like Syncing until the latest rows are really there.

The Clerk hang is the same freeze after a long background: Settings looked busy
forever. Remounting Clerk from a timer tore down the whole app, so recovery is
manual Retry only.

UI Changes

Live turns no longer rebuild the whole feed. Warm opens stay on the recent
window. Short background stays live. Long background shows Syncing until latest
content lands. T3 Account stays Checking until Clerk loads, then Sign in or the
account email. Retry is on the account screen while Checking.

No before/after screenshots.

Known Limitations

On a Boox NoteAir5C, Clerk can load and still sit on Sign in. Retry remounts
Clerk and does not complete sign-in. Force-stopping the app recovers. That is
native Clerk or Android process state, not hung Checking.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

High Risk
Changes thread live/synchronizing transitions, bounded snapshot persistence, and shell “live” semantics that affect outbox delivery—race-prone areas per review notes. Broad mobile V2 migration plus removal of the thread-transfer PR reporter also affect CI signal and regression visibility.

Overview
Thread sync and mobile runtime — Mobile wires into bounded thread snapshot loading, shared orchestration cache schemas, and refreshCachedThreadOnSubscribe. Client-runtime thread state distinguishes catch-up vs completion waits, uses authoritative HTTP refresh on reconnect, and gates persistence while a bounded refresh is in flight. Shell status can promote to live when a usable cached snapshot exists. The mobile feed and related UI move to V2 projections (runtime vs session, RuntimeRequestId, checkpoint summaries derived from projection), with archive guards keyed off runtime status and approval/user-input cards respecting responseCapability.

Cloud auth and navigation — T3 Account stays navigable while Clerk is loading; the auth screen shows Checking with a manual Retry that remounts Clerk without timers. Cloud cleanup refs live on the parent so Clerk remounts do not drop in-flight sign-out work. In-memory navigation state is restored on app launch.

CI and housekeeping — The Thread Transfer Report workflow and its publisher scripts/tests are removed. CI installs build-essential so ACP process-tree fixtures compile instead of soft-skipping. Smaller updates: desktop user-data dir names in tests, composer context-strip default, mobile theme adaptive colors, README appearance doc link, marketing Cursor harness label.

Reviewed by Cursor Bugbot for commit 088b2ab. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add orchestration V2 foundation and catch up mobile thread feed after background

  • Introduces the full orchestration V2 contract layer: provider policies, model selections, checkpoints, thread projections, domain events, commands, and scheduled-task schemas across packages/contracts/src/orchestrationV2.ts and related contract modules
  • Adds V2 provider adapters for Claude, Codex, Cursor, Grok, OpenCode, and ACP Registry under apps/server/src/orchestration-v2/Adapters/, each integrating its provider SDK through the ProviderAdapterV2 service boundary
  • Adds nine database migrations (044–052) creating the V2 event log, projection tables, effect outbox, provider-session bindings, scheduled tasks, and legacy V1 import state in apps/server/src/persistence/Migrations/" - Builds core V2 server services: SQL-backed ProjectionStoreV2, EffectOutbox and EffectWorker for durable effect processing, ProviderSessionManager, ProviderRuntimeRecoveryService, checkpoint capture/rollback, thread management, and MCP orchestrator/worktree toolkits
  • Migrates mobile and web clients to V2 projection types: thread feed status resolution uses runtime and latestRun fields, pending approvals and user-input checks live response capability, and thread snapshots load through bounded HTTP routes
  • Risk: replaces the legacy EnvironmentApi.orchestration IPC property with orchestrationV2 and switches all WebSocket RPC methods to V2 constants; environments advertising a protocol version mismatch are now rejected at connection time via orchestrationProtocolCompatibilityError

Macroscope summarized 088b2ab.

@coderabbitai

coderabbitaiBot commented Aug 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c8bef765-f132-43e7-8c0c-531ac526096c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 14, 2026
Comment threadpackages/client-runtime/src/state/threadHistoryMerge.ts
@mwolson
mwolson marked this pull request as ready for review August 14, 2026 20:38
@macroscopeapp

macroscopeappBot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — Despite the mobile-fix title, this is a 966-file orchestration V2 migration that changes production persistence, APIs, provider execution, authentication, MCP capabilities, and mobile synchronization behavior. It also changes defaults and static-analysis directives while unresolved comments identify stale-data, auth, UI, and functionality risks.

Not approved because:

  • 2 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 7 times, most recently from a50460b to c610c31CompareAugust 15, 2026 15:09
Comment threadpackages/client-runtime/src/state/shell.ts Outdated
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from c610c31 to 9159bcfCompareAugust 15, 2026 15:18
Comment threadpackages/client-runtime/src/state/threads.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 96891a3 to 993407dCompareAugust 15, 2026 15:26
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 3 times, most recently from 5b5e9cb to a005644CompareAugust 15, 2026 18:38
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Aug 15, 2026
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from a005644 to 1eb46e5CompareAugust 15, 2026 19:01
Comment threadpackages/client-runtime/src/state/threads.ts
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 2 times, most recently from 541c25b to 26f73f5CompareAugust 15, 2026 19:52
Comment threadpackages/client-runtime/src/state/threads.ts Outdated
Comment threadpackages/client-runtime/src/state/threads.ts Outdated
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from 26f73f5 to 6af29ddCompareAugust 15, 2026 20:22
@mwolsonmwolson changed the title perf(orchestrator): Reduce mobile thread feed presentation workfix(mobile): Catch up the thread feed after a long backgroundAug 15, 2026
Comment threadpackages/client-runtime/src/state/threads.ts
juliusmarmingeand others added 13 commits August 31, 2026 21:33
Adapt grouped tool summaries and the floating working timer to V2 run, attempt, and queue state. Bring over the composer, keyboard, and disclosure transitions while retaining the V2 activity inspector and queue controls.
Keep OV2 web composer and grouping behavior intact; share only the existing command label parser with mobile.
Restores main features dropped by the policy replay: pingdotgg#8569 theme wiring,
pingdotgg#8850 composer banner follow-ups, pingdotgg#8855/pingdotgg#8904 composer fixes, pingdotgg#8831
settings search rework, pingdotgg#8803 workspace-mutation refresh (v2-adapted),
pingdotgg#8840 circle-alert, pingdotgg#8584 codex artifact templates, pingdotgg#8688/pingdotgg#8807/pingdotgg#8936
video + image previews (web and mobile, v2-adapted), pingdotgg#8862 Expo glass,
and the round's docs. Timeline thinking rows (pingdotgg#8984) stay on the v2
work-live system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from a6e0049 to 50a57b6CompareSeptember 1, 2026 05:36
juliusmarmingeand others added 7 commits September 1, 2026 00:24
The v2 equivalents of main's pingdotgg#8984 and pingdotgg#8922: a "Working for ..." header
anchors the active run, the trailing live tool row survives between
actions in past tense instead of vanishing, and a shimmering Thinking
row marks reasoning gaps. During workspace preparation the header shows
"Setting up worktree..." (driven by the local dispatch flag or the v2
run's preparing status, so remote viewers see it too), the composer
footer span is gone, and draft promotion waits until the run starts or
startup fails instead of navigating mid-preparation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Clerk can sit on isLoaded=false after a background freeze, which pins
Settings on Checking and blocks the account screen. Remount Clerk after a
foreground timeout, keep T3 Account tappable, and offer Retry without
clearing app data.
A cold HTTP seed still waits for synchronized, but disconnect and stream
error cleared awaitingCompletion. Reconnect then published live while
replay was still arriving. Keep the fence until synchronized or delete.
A 12-second foreground timer used to remount ClerkProvider, which rebuilds
the whole app. Checking now stays until Clerk loads or the user taps Retry.
Retry is still the only remount.
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from 5755e9e to 6ff4bf9CompareSeptember 1, 2026 23:14
Comment threadapps/mobile/src/lib/threadActivity.ts Outdated
Comment on lines +180 to +187
const next = b as typeof a;
return (
a.runId === next.runId &&
a.groupId === next.groupId &&
a.hiddenCount === next.hiddenCount &&
a.expanded === next.expanded &&
a.onlyToolActivities === next.onlyToolActivities
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumlib/threadActivity.ts:180

computeStableThreadFeedEntries reuses a stale work-toggle row when its group ID and count are unchanged, so live label, completion/failure status, and shimmer updates are not rendered. threadFeedEntryIsUnchanged must also compare summary, summaryKind, hasFailure, live, and shimmer before reusing the previous row.

Suggested change
constnext=bastypeofa;
return(
a.runId===next.runId&&
a.groupId===next.groupId&&
a.hiddenCount===next.hiddenCount&&
a.expanded===next.expanded&&
a.onlyToolActivities===next.onlyToolActivities
);
constnext=bastypeofa;
return(
a.runId===next.runId&&
a.groupId===next.groupId&&
a.hiddenCount===next.hiddenCount&&
a.expanded===next.expanded&&
a.onlyToolActivities===next.onlyToolActivities&&
a.summary===next.summary&&
a.summaryKind===next.summaryKind&&
a.hasFailure===next.hasFailure&&
a.live===next.live&&
a.shimmer===next.shimmer
);
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/mobile/src/lib/threadActivity.ts around lines 180-187:
`computeStableThreadFeedEntries` reuses a stale `work-toggle` row when its group ID and count are unchanged, so live label, completion/failure status, and shimmer updates are not rendered. `threadFeedEntryIsUnchanged` must also compare `summary`, `summaryKind`, `hasFailure`, `live`, and `shimmer` before reusing the previous row.

return "present" as const;
}
case "missing": {
yield* setDeleted();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Highstate/threads.ts:480

A stale refreshAuthoritativeSnapshot response can mark an already-reconnected thread as deleted and remove its cache. The missing branch calls setDeleted() without verifying that the response belongs to the current prepared connection/session, so a delayed 404 from the old connection clears newer live data; add a request/session fence before treating this result as authoritative.

🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/client-runtime/src/state/threads.ts around line 480:
A stale `refreshAuthoritativeSnapshot` response can mark an already-reconnected thread as deleted and remove its cache. The `missing` branch calls `setDeleted()` without verifying that the response belongs to the current prepared connection/session, so a delayed 404 from the old connection clears newer live data; add a request/session fence before treating this result as authoritative.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

a.expanded === next.expanded &&
a.onlyToolActivities === next.onlyToolActivities
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stable feed skips live work-toggle updates

Medium Severity

threadFeedEntryIsUnchanged treats a work-toggle as unchanged without comparing summary, summaryKind, hasFailure, live, or shimmer. Combined with reference-equal itemsAreEqual, LegendList keeps the old row, so the group label, failure mark, and shimmer can stay frozen through a live turn.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

DateTime.toEpochMillis(activity.projectedItem.item.updatedAt) ===
DateTime.toEpochMillis(nextActivity.projectedItem.item.updatedAt)
);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stable feed freezes activity live state

Medium Severity

threadFeedActivitiesAreUnchanged ignores lifecycleStatus and live. Presentation derives those from the active run, not only from item text or updatedAt, so an in-progress tool group can keep the previous activity objects after the run settles.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

return;
}
return cleanUpAccount(previous, previousObservedAccount ?? null);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clerk remount can wipe the account

Medium Severity

Retry remounts Clerk while observedAccountRef still holds the signed-in user. If the new instance reports loaded and signed out, the bridge treats that as a real sign-out, deactivates the relay, and runs account cleanup.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 6081201 to 95bacddCompareSeptember 2, 2026 07:19
mwolson added a commit to mwolson/t3code that referenced this pull request Sep 2, 2026
The pingdotgg#6642 row-sharing replay inserted computeStableThreadFeedEntries
after hooks that read presentedFeed. Metro rewrites const to var, so
those hooks observed presentedFeed as undefined on every render and
never updated previousPresentedFeedRef or disclosure enter ids.
Local and anchored messages have no projectedItem. Comparing sourceItemId
through it threw when the feed was rebuilt.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-cutting note (not repeated inline): the panel's row geometry now lives in apps/web/src/components/chat/threadDetailsPanelStyles.ts as class-string constants that six call sites splice into Button/SelectTrigger/ComboboxTrigger, overriding the primitive's height, radius, padding, text size and base/hover background — the hover tint needs !bg-... to win against the variant it is overriding. Since this is durable geometry shared by every panel row, a named Button size/variant (e.g. size="panel-row") would carry it inside the primitive contract and drop the !important overrides, leaving only contextual layout at the call sites. Not blocking this PR, but worth doing before more rows adopt the constants.

Posted via Macroscope — UI Consistency

inset-inline-end: var(--thread-details-panel-inset);
}

.alert-glass {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alert-glass already exists as @utility alert-glass (line 284) with these exact declarations plus a @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) opaque fallback. This copy is in @layer components, and since both background declarations are !important the cascade reverses layer order, so the components copy wins — the utility's no-backdrop-filter fallback stops applying and the banner renders translucent where blur is unsupported.

Suggest dropping this block together with the four .alert-glass[data-variant=...] rules below it and leaving the @utility as the single owner (the alert-glass class name in ProviderStatusBanner already comes from it).

Posted via Macroscope — UI Consistency

size="xs"
className="ps-[8.5px]"
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : undefined}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Toolbar mode lost the ps-[8.5px] optical padding this leading-icon button carried (same for the disabled variant above, line 1777). OpenInPicker keeps ps-[8.5px] for its matching toolbar button, so the two grouped controls in the header no longer align on their icon.

Suggested change
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : undefined}
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : "ps-[8.5px]"}

Posted via Macroscope — UI Consistency

{props.children}
</ul>
{props.hiddenCount > 0 ? (
<button

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This row rebuilds the panel row by hand (h-9, rounded-lg, px-2.5, text-[13px], gap-2.5, plus the light/dark hover tint) instead of composing Button, and unlike every other row in this file it has no focus-visible ring — so the one control that pages the list gives keyboard users no visible target, even though the comment above relies on rows being reachable by keyboard.

Suggest rendering it as <Button size="sm" variant="ghost" className={cn(THREAD_DETAILS_PANEL_ROW_CLASS, "text-muted-foreground/70 hover:text-foreground/80")}> (adding THREAD_DETAILS_PANEL_ROW_CLASS to the existing import from ./threadDetailsPanelStyles): same geometry and hover tint, with the primitive's focus ring and disabled semantics back.

Posted via Macroscope — UI Consistency

if (item.kind === "open_pr") {
void openExistingPr();
return;
}
if (item.dialogAction === "push") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveQuickAction and buildMenuItems in packages/client-runtime/src/state/gitActions.ts still emit kind: "open_pr" ("View PR") whenever the branch has an open pull request and is clean, but every handler for it was removed here: this menu item now falls through to setIsCommitDialogOpen(true) and opens the Commit dialog, runQuickAction ends at if (quickAction.action) so the quick-action button does nothing, and GitQuickActionIcon falls through to InfoIcon. openExistingPr is left with no callers.

Either keep routing open_pr, or stop emitting it now that ThreadDetailsPrRow owns the pull request row:

 if (item.disabled) return;
+ if (item.kind === "open_pr") {+ void openExistingPr();+ return;+ }
if (item.dialogAction === "push") {

Posted via Macroscope — UI Consistency

@@ -759,6 +762,182 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
outline: none;
}

.surface-subheader {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.surface-subheader has no consumers: the four subheader call sites (DiffPanelShell, PreviewChromeRow, FileBrowserPanel, FilePreviewPanel) keep their own Tailwind classes and only set the data-surface-subheader attribute that the rule below matches. As written this rule never renders and the header geometry has two competing definitions. Suggest either applying the class at those call sites or dropping the rule.

Posted via Macroscope — UI Consistency

--alert-glass-tint: var(--warning);
}

.glass-opacity-slider {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.glass-opacity-slider has no consumer, literal or dynamic: the glass-opacity input in settings/SettingsPanels.tsx uses className="settings-slider ..." and sets --settings-slider-progress/--settings-slider-fill-offset, and nothing anywhere reads --glass-slider-progress. That leaves ~110 lines of vendor pseudo-element CSS that never renders and duplicates .settings-slider below it (same track, thumb, hover/active, focus-visible and forced-colors treatment).

Suggest dropping this block and keeping .settings-slider as the single slider owner — or, if the glass slider was meant to diverge, pointing that input at this class so the rule has an owner.

Posted via Macroscope — UI Consistency

className="z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-transform data-instant:transition-none"
collisionAvoidance={collisionAvoidance}
className={cn(
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] data-instant:transition-none",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes every popover in the app transition its positional properties, while the only new consumer immediately opts out (positionerClassName="... !transition-none" in PanelLayoutControls). Base UI writes the positioner's top/left inline styles on each anchor-tracking update — scroll, resize, collision flip — so unrelated popovers will now animate toward their anchor and visibly trail it while a surface scrolls.

Keeping the primitive on transition-transform and opting in per call site via the new positionerClassName avoids changing behavior for popovers that did not ask for it:

Suggested change
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] data-instant:transition-none",
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-transform data-instant:transition-none",

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Macroscope skipped reviewing this pull request. Per-review cost limit exceeded (workspace setting).

This review would cost an estimated $249.67, which exceeds your per-review limit of $10.00.

The top 3 files driving up this estimate:

FileDiff SizeEstimate
apps/server/src/orchestration-v2/Orchestrator.ts277.03KB$13.85
apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts272.17KB$13.61
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts230.26KB$11.51

Tip

To get this pull request reviewed, you can:

  1. Comment @macroscope-app on this PR to request a manual review (monthly spend limits still apply).
  2. Exclude the file(s) above from review by adding a pattern to your .macroscope/ignore.md — note that creating this file replaces Macroscope's built-in default ignores rather than extending them.
  3. Raise your cost limit in your workspace billing settings.

Turn off this reminder going forward

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@mwolson@t3dotgg@juliusmarminge@PixPMusic@nsxdavid@Yusuf007R@maria-rcks
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(mobile): Catch up the thread feed after a long background - #6642

Open
mwolson wants to merge 335 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/mobile-thread-feed-performance-v2.1
Open

fix(mobile): Catch up the thread feed after a long background#6642
mwolson wants to merge 335 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/mobile-thread-feed-performance-v2.1

Conversation

@mwolson

@mwolsonmwolson commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

What Changed

On a large thread, every live token used to redraw the whole feed. Unchanged
rows now keep the same objects, so only the row that changed updates.

Coming back to a thread used to fetch the entire transcript. Warm opens
subscribe from the cached sequence, refresh a bounded snapshot in the
background, and replace a full socket fallback with that bounded window.

A short lock could flip a live thread to Syncing. A long lock could show old
messages as current, or sit on Syncing forever. A short lock or Home switch
stays live. After a long lock the feed shows Syncing over the cached transcript
until a socket snapshot or completion marker lands the latest content. HTTP can
refresh the visible window under that pill. Catch-up still ends on the socket
snapshot or marker. Reconnect restarts the live subscribe so the new session
actually gets a thread subscription.

T3 Account in Settings could sit on Checking after a long background, and
tapping it did nothing. The account row stays tappable. While Clerk is still
loading, the account screen shows Checking plus Retry. Retry remounts Clerk.
There is no 12-second timer and no automatic remount.

Checked on a signed iPhone, an Android tablet, the iOS Simulator, and Waydroid.
A 30-second lock stays live. A long lock shows Syncing, then catches up.

Why

The phone already had the recent history. Live updates, warm opens, and
reconnects were throwing that away or treating it as finished too soon. Catch-up
should look like Syncing until the latest rows are really there.

The Clerk hang is the same freeze after a long background: Settings looked busy
forever. Remounting Clerk from a timer tore down the whole app, so recovery is
manual Retry only.

UI Changes

Live turns no longer rebuild the whole feed. Warm opens stay on the recent
window. Short background stays live. Long background shows Syncing until latest
content lands. T3 Account stays Checking until Clerk loads, then Sign in or the
account email. Retry is on the account screen while Checking.

No before/after screenshots.

Known Limitations

On a Boox NoteAir5C, Clerk can load and still sit on Sign in. Retry remounts
Clerk and does not complete sign-in. Force-stopping the app recovers. That is
native Clerk or Android process state, not hung Checking.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

High Risk
Changes thread live/synchronizing transitions, bounded snapshot persistence, and shell “live” semantics that affect outbox delivery—race-prone areas per review notes. Broad mobile V2 migration plus removal of the thread-transfer PR reporter also affect CI signal and regression visibility.

Overview
Thread sync and mobile runtime — Mobile wires into bounded thread snapshot loading, shared orchestration cache schemas, and refreshCachedThreadOnSubscribe. Client-runtime thread state distinguishes catch-up vs completion waits, uses authoritative HTTP refresh on reconnect, and gates persistence while a bounded refresh is in flight. Shell status can promote to live when a usable cached snapshot exists. The mobile feed and related UI move to V2 projections (runtime vs session, RuntimeRequestId, checkpoint summaries derived from projection), with archive guards keyed off runtime status and approval/user-input cards respecting responseCapability.

Cloud auth and navigation — T3 Account stays navigable while Clerk is loading; the auth screen shows Checking with a manual Retry that remounts Clerk without timers. Cloud cleanup refs live on the parent so Clerk remounts do not drop in-flight sign-out work. In-memory navigation state is restored on app launch.

CI and housekeeping — The Thread Transfer Report workflow and its publisher scripts/tests are removed. CI installs build-essential so ACP process-tree fixtures compile instead of soft-skipping. Smaller updates: desktop user-data dir names in tests, composer context-strip default, mobile theme adaptive colors, README appearance doc link, marketing Cursor harness label.

Reviewed by Cursor Bugbot for commit 088b2ab. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add orchestration V2 foundation and catch up mobile thread feed after background

  • Introduces the full orchestration V2 contract layer: provider policies, model selections, checkpoints, thread projections, domain events, commands, and scheduled-task schemas across packages/contracts/src/orchestrationV2.ts and related contract modules
  • Adds V2 provider adapters for Claude, Codex, Cursor, Grok, OpenCode, and ACP Registry under apps/server/src/orchestration-v2/Adapters/, each integrating its provider SDK through the ProviderAdapterV2 service boundary
  • Adds nine database migrations (044–052) creating the V2 event log, projection tables, effect outbox, provider-session bindings, scheduled tasks, and legacy V1 import state in apps/server/src/persistence/Migrations/" - Builds core V2 server services: SQL-backed ProjectionStoreV2, EffectOutbox and EffectWorker for durable effect processing, ProviderSessionManager, ProviderRuntimeRecoveryService, checkpoint capture/rollback, thread management, and MCP orchestrator/worktree toolkits
  • Migrates mobile and web clients to V2 projection types: thread feed status resolution uses runtime and latestRun fields, pending approvals and user-input checks live response capability, and thread snapshots load through bounded HTTP routes
  • Risk: replaces the legacy EnvironmentApi.orchestration IPC property with orchestrationV2 and switches all WebSocket RPC methods to V2 constants; environments advertising a protocol version mismatch are now rejected at connection time via orchestrationProtocolCompatibilityError

Macroscope summarized 088b2ab.

@coderabbitai

coderabbitaiBot commented Aug 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c8bef765-f132-43e7-8c0c-531ac526096c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 14, 2026
Comment threadpackages/client-runtime/src/state/threadHistoryMerge.ts
@mwolson
mwolson marked this pull request as ready for review August 14, 2026 20:38
@macroscopeapp

macroscopeappBot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — Despite the mobile-fix title, this is a 966-file orchestration V2 migration that changes production persistence, APIs, provider execution, authentication, MCP capabilities, and mobile synchronization behavior. It also changes defaults and static-analysis directives while unresolved comments identify stale-data, auth, UI, and functionality risks.

Not approved because:

  • 2 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 7 times, most recently from a50460b to c610c31CompareAugust 15, 2026 15:09
Comment threadpackages/client-runtime/src/state/shell.ts Outdated
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from c610c31 to 9159bcfCompareAugust 15, 2026 15:18
Comment threadpackages/client-runtime/src/state/threads.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 96891a3 to 993407dCompareAugust 15, 2026 15:26
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 3 times, most recently from 5b5e9cb to a005644CompareAugust 15, 2026 18:38
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Aug 15, 2026
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from a005644 to 1eb46e5CompareAugust 15, 2026 19:01
Comment threadpackages/client-runtime/src/state/threads.ts
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 2 times, most recently from 541c25b to 26f73f5CompareAugust 15, 2026 19:52
Comment threadpackages/client-runtime/src/state/threads.ts Outdated
Comment threadpackages/client-runtime/src/state/threads.ts Outdated
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from 26f73f5 to 6af29ddCompareAugust 15, 2026 20:22
@mwolsonmwolson changed the title perf(orchestrator): Reduce mobile thread feed presentation workfix(mobile): Catch up the thread feed after a long backgroundAug 15, 2026
Comment threadpackages/client-runtime/src/state/threads.ts
juliusmarmingeand others added 13 commits August 31, 2026 21:33
Adapt grouped tool summaries and the floating working timer to V2 run, attempt, and queue state. Bring over the composer, keyboard, and disclosure transitions while retaining the V2 activity inspector and queue controls.
Keep OV2 web composer and grouping behavior intact; share only the existing command label parser with mobile.
Restores main features dropped by the policy replay: pingdotgg#8569 theme wiring,
pingdotgg#8850 composer banner follow-ups, pingdotgg#8855/pingdotgg#8904 composer fixes, pingdotgg#8831
settings search rework, pingdotgg#8803 workspace-mutation refresh (v2-adapted),
pingdotgg#8840 circle-alert, pingdotgg#8584 codex artifact templates, pingdotgg#8688/pingdotgg#8807/pingdotgg#8936
video + image previews (web and mobile, v2-adapted), pingdotgg#8862 Expo glass,
and the round's docs. Timeline thinking rows (pingdotgg#8984) stay on the v2
work-live system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from a6e0049 to 50a57b6CompareSeptember 1, 2026 05:36
juliusmarmingeand others added 7 commits September 1, 2026 00:24
The v2 equivalents of main's pingdotgg#8984 and pingdotgg#8922: a "Working for ..." header
anchors the active run, the trailing live tool row survives between
actions in past tense instead of vanishing, and a shimmering Thinking
row marks reasoning gaps. During workspace preparation the header shows
"Setting up worktree..." (driven by the local dispatch flag or the v2
run's preparing status, so remote viewers see it too), the composer
footer span is gone, and draft promotion waits until the run starts or
startup fails instead of navigating mid-preparation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Clerk can sit on isLoaded=false after a background freeze, which pins
Settings on Checking and blocks the account screen. Remount Clerk after a
foreground timeout, keep T3 Account tappable, and offer Retry without
clearing app data.
A cold HTTP seed still waits for synchronized, but disconnect and stream
error cleared awaitingCompletion. Reconnect then published live while
replay was still arriving. Keep the fence until synchronized or delete.
A 12-second foreground timer used to remount ClerkProvider, which rebuilds
the whole app. Checking now stays until Clerk loads or the user taps Retry.
Retry is still the only remount.
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from 5755e9e to 6ff4bf9CompareSeptember 1, 2026 23:14
Comment threadapps/mobile/src/lib/threadActivity.ts Outdated
Comment on lines +180 to +187
const next = b as typeof a;
return (
a.runId === next.runId &&
a.groupId === next.groupId &&
a.hiddenCount === next.hiddenCount &&
a.expanded === next.expanded &&
a.onlyToolActivities === next.onlyToolActivities
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumlib/threadActivity.ts:180

computeStableThreadFeedEntries reuses a stale work-toggle row when its group ID and count are unchanged, so live label, completion/failure status, and shimmer updates are not rendered. threadFeedEntryIsUnchanged must also compare summary, summaryKind, hasFailure, live, and shimmer before reusing the previous row.

Suggested change
constnext=bastypeofa;
return(
a.runId===next.runId&&
a.groupId===next.groupId&&
a.hiddenCount===next.hiddenCount&&
a.expanded===next.expanded&&
a.onlyToolActivities===next.onlyToolActivities
);
constnext=bastypeofa;
return(
a.runId===next.runId&&
a.groupId===next.groupId&&
a.hiddenCount===next.hiddenCount&&
a.expanded===next.expanded&&
a.onlyToolActivities===next.onlyToolActivities&&
a.summary===next.summary&&
a.summaryKind===next.summaryKind&&
a.hasFailure===next.hasFailure&&
a.live===next.live&&
a.shimmer===next.shimmer
);
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/mobile/src/lib/threadActivity.ts around lines 180-187:
`computeStableThreadFeedEntries` reuses a stale `work-toggle` row when its group ID and count are unchanged, so live label, completion/failure status, and shimmer updates are not rendered. `threadFeedEntryIsUnchanged` must also compare `summary`, `summaryKind`, `hasFailure`, `live`, and `shimmer` before reusing the previous row.

return "present" as const;
}
case "missing": {
yield* setDeleted();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Highstate/threads.ts:480

A stale refreshAuthoritativeSnapshot response can mark an already-reconnected thread as deleted and remove its cache. The missing branch calls setDeleted() without verifying that the response belongs to the current prepared connection/session, so a delayed 404 from the old connection clears newer live data; add a request/session fence before treating this result as authoritative.

🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/client-runtime/src/state/threads.ts around line 480:
A stale `refreshAuthoritativeSnapshot` response can mark an already-reconnected thread as deleted and remove its cache. The `missing` branch calls `setDeleted()` without verifying that the response belongs to the current prepared connection/session, so a delayed 404 from the old connection clears newer live data; add a request/session fence before treating this result as authoritative.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

a.expanded === next.expanded &&
a.onlyToolActivities === next.onlyToolActivities
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stable feed skips live work-toggle updates

Medium Severity

threadFeedEntryIsUnchanged treats a work-toggle as unchanged without comparing summary, summaryKind, hasFailure, live, or shimmer. Combined with reference-equal itemsAreEqual, LegendList keeps the old row, so the group label, failure mark, and shimmer can stay frozen through a live turn.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

DateTime.toEpochMillis(activity.projectedItem.item.updatedAt) ===
DateTime.toEpochMillis(nextActivity.projectedItem.item.updatedAt)
);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stable feed freezes activity live state

Medium Severity

threadFeedActivitiesAreUnchanged ignores lifecycleStatus and live. Presentation derives those from the active run, not only from item text or updatedAt, so an in-progress tool group can keep the previous activity objects after the run settles.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

return;
}
return cleanUpAccount(previous, previousObservedAccount ?? null);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clerk remount can wipe the account

Medium Severity

Retry remounts Clerk while observedAccountRef still holds the signed-in user. If the new instance reports loaded and signed out, the bridge treats that as a real sign-out, deactivates the relay, and runs account cleanup.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 6081201 to 95bacddCompareSeptember 2, 2026 07:19
mwolson added a commit to mwolson/t3code that referenced this pull request Sep 2, 2026
The pingdotgg#6642 row-sharing replay inserted computeStableThreadFeedEntries
after hooks that read presentedFeed. Metro rewrites const to var, so
those hooks observed presentedFeed as undefined on every render and
never updated previousPresentedFeedRef or disclosure enter ids.
Local and anchored messages have no projectedItem. Comparing sourceItemId
through it threw when the feed was rebuilt.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-cutting note (not repeated inline): the panel's row geometry now lives in apps/web/src/components/chat/threadDetailsPanelStyles.ts as class-string constants that six call sites splice into Button/SelectTrigger/ComboboxTrigger, overriding the primitive's height, radius, padding, text size and base/hover background — the hover tint needs !bg-... to win against the variant it is overriding. Since this is durable geometry shared by every panel row, a named Button size/variant (e.g. size="panel-row") would carry it inside the primitive contract and drop the !important overrides, leaving only contextual layout at the call sites. Not blocking this PR, but worth doing before more rows adopt the constants.

Posted via Macroscope — UI Consistency

inset-inline-end: var(--thread-details-panel-inset);
}

.alert-glass {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alert-glass already exists as @utility alert-glass (line 284) with these exact declarations plus a @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) opaque fallback. This copy is in @layer components, and since both background declarations are !important the cascade reverses layer order, so the components copy wins — the utility's no-backdrop-filter fallback stops applying and the banner renders translucent where blur is unsupported.

Suggest dropping this block together with the four .alert-glass[data-variant=...] rules below it and leaving the @utility as the single owner (the alert-glass class name in ProviderStatusBanner already comes from it).

Posted via Macroscope — UI Consistency

size="xs"
className="ps-[8.5px]"
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : undefined}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Toolbar mode lost the ps-[8.5px] optical padding this leading-icon button carried (same for the disabled variant above, line 1777). OpenInPicker keeps ps-[8.5px] for its matching toolbar button, so the two grouped controls in the header no longer align on their icon.

Suggested change
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : undefined}
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : "ps-[8.5px]"}

Posted via Macroscope — UI Consistency

{props.children}
</ul>
{props.hiddenCount > 0 ? (
<button

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This row rebuilds the panel row by hand (h-9, rounded-lg, px-2.5, text-[13px], gap-2.5, plus the light/dark hover tint) instead of composing Button, and unlike every other row in this file it has no focus-visible ring — so the one control that pages the list gives keyboard users no visible target, even though the comment above relies on rows being reachable by keyboard.

Suggest rendering it as <Button size="sm" variant="ghost" className={cn(THREAD_DETAILS_PANEL_ROW_CLASS, "text-muted-foreground/70 hover:text-foreground/80")}> (adding THREAD_DETAILS_PANEL_ROW_CLASS to the existing import from ./threadDetailsPanelStyles): same geometry and hover tint, with the primitive's focus ring and disabled semantics back.

Posted via Macroscope — UI Consistency

if (item.kind === "open_pr") {
void openExistingPr();
return;
}
if (item.dialogAction === "push") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveQuickAction and buildMenuItems in packages/client-runtime/src/state/gitActions.ts still emit kind: "open_pr" ("View PR") whenever the branch has an open pull request and is clean, but every handler for it was removed here: this menu item now falls through to setIsCommitDialogOpen(true) and opens the Commit dialog, runQuickAction ends at if (quickAction.action) so the quick-action button does nothing, and GitQuickActionIcon falls through to InfoIcon. openExistingPr is left with no callers.

Either keep routing open_pr, or stop emitting it now that ThreadDetailsPrRow owns the pull request row:

 if (item.disabled) return;
+ if (item.kind === "open_pr") {+ void openExistingPr();+ return;+ }
if (item.dialogAction === "push") {

Posted via Macroscope — UI Consistency

@@ -759,6 +762,182 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
outline: none;
}

.surface-subheader {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.surface-subheader has no consumers: the four subheader call sites (DiffPanelShell, PreviewChromeRow, FileBrowserPanel, FilePreviewPanel) keep their own Tailwind classes and only set the data-surface-subheader attribute that the rule below matches. As written this rule never renders and the header geometry has two competing definitions. Suggest either applying the class at those call sites or dropping the rule.

Posted via Macroscope — UI Consistency

--alert-glass-tint: var(--warning);
}

.glass-opacity-slider {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.glass-opacity-slider has no consumer, literal or dynamic: the glass-opacity input in settings/SettingsPanels.tsx uses className="settings-slider ..." and sets --settings-slider-progress/--settings-slider-fill-offset, and nothing anywhere reads --glass-slider-progress. That leaves ~110 lines of vendor pseudo-element CSS that never renders and duplicates .settings-slider below it (same track, thumb, hover/active, focus-visible and forced-colors treatment).

Suggest dropping this block and keeping .settings-slider as the single slider owner — or, if the glass slider was meant to diverge, pointing that input at this class so the rule has an owner.

Posted via Macroscope — UI Consistency

className="z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-transform data-instant:transition-none"
collisionAvoidance={collisionAvoidance}
className={cn(
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] data-instant:transition-none",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes every popover in the app transition its positional properties, while the only new consumer immediately opts out (positionerClassName="... !transition-none" in PanelLayoutControls). Base UI writes the positioner's top/left inline styles on each anchor-tracking update — scroll, resize, collision flip — so unrelated popovers will now animate toward their anchor and visibly trail it while a surface scrolls.

Keeping the primitive on transition-transform and opting in per call site via the new positionerClassName avoids changing behavior for popovers that did not ask for it:

Suggested change
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] data-instant:transition-none",
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-transform data-instant:transition-none",

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Macroscope skipped reviewing this pull request. Per-review cost limit exceeded (workspace setting).

This review would cost an estimated $249.67, which exceeds your per-review limit of $10.00.

The top 3 files driving up this estimate:

FileDiff SizeEstimate
apps/server/src/orchestration-v2/Orchestrator.ts277.03KB$13.85
apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts272.17KB$13.61
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts230.26KB$11.51

Tip

To get this pull request reviewed, you can:

  1. Comment @macroscope-app on this PR to request a manual review (monthly spend limits still apply).
  2. Exclude the file(s) above from review by adding a pattern to your .macroscope/ignore.md — note that creating this file replaces Macroscope's built-in default ignores rather than extending them.
  3. Raise your cost limit in your workspace billing settings.

Turn off this reminder going forward

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@mwolson@t3dotgg@juliusmarminge@PixPMusic@nsxdavid@Yusuf007R@maria-rcks
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(mobile): Catch up the thread feed after a long background - #6642

Open
mwolson wants to merge 335 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/mobile-thread-feed-performance-v2.1
Open

fix(mobile): Catch up the thread feed after a long background#6642
mwolson wants to merge 335 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/mobile-thread-feed-performance-v2.1

Conversation

@mwolson

@mwolsonmwolson commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

What Changed

On a large thread, every live token used to redraw the whole feed. Unchanged
rows now keep the same objects, so only the row that changed updates.

Coming back to a thread used to fetch the entire transcript. Warm opens
subscribe from the cached sequence, refresh a bounded snapshot in the
background, and replace a full socket fallback with that bounded window.

A short lock could flip a live thread to Syncing. A long lock could show old
messages as current, or sit on Syncing forever. A short lock or Home switch
stays live. After a long lock the feed shows Syncing over the cached transcript
until a socket snapshot or completion marker lands the latest content. HTTP can
refresh the visible window under that pill. Catch-up still ends on the socket
snapshot or marker. Reconnect restarts the live subscribe so the new session
actually gets a thread subscription.

T3 Account in Settings could sit on Checking after a long background, and
tapping it did nothing. The account row stays tappable. While Clerk is still
loading, the account screen shows Checking plus Retry. Retry remounts Clerk.
There is no 12-second timer and no automatic remount.

Checked on a signed iPhone, an Android tablet, the iOS Simulator, and Waydroid.
A 30-second lock stays live. A long lock shows Syncing, then catches up.

Why

The phone already had the recent history. Live updates, warm opens, and
reconnects were throwing that away or treating it as finished too soon. Catch-up
should look like Syncing until the latest rows are really there.

The Clerk hang is the same freeze after a long background: Settings looked busy
forever. Remounting Clerk from a timer tore down the whole app, so recovery is
manual Retry only.

UI Changes

Live turns no longer rebuild the whole feed. Warm opens stay on the recent
window. Short background stays live. Long background shows Syncing until latest
content lands. T3 Account stays Checking until Clerk loads, then Sign in or the
account email. Retry is on the account screen while Checking.

No before/after screenshots.

Known Limitations

On a Boox NoteAir5C, Clerk can load and still sit on Sign in. Retry remounts
Clerk and does not complete sign-in. Force-stopping the app recovers. That is
native Clerk or Android process state, not hung Checking.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

High Risk
Changes thread live/synchronizing transitions, bounded snapshot persistence, and shell “live” semantics that affect outbox delivery—race-prone areas per review notes. Broad mobile V2 migration plus removal of the thread-transfer PR reporter also affect CI signal and regression visibility.

Overview
Thread sync and mobile runtime — Mobile wires into bounded thread snapshot loading, shared orchestration cache schemas, and refreshCachedThreadOnSubscribe. Client-runtime thread state distinguishes catch-up vs completion waits, uses authoritative HTTP refresh on reconnect, and gates persistence while a bounded refresh is in flight. Shell status can promote to live when a usable cached snapshot exists. The mobile feed and related UI move to V2 projections (runtime vs session, RuntimeRequestId, checkpoint summaries derived from projection), with archive guards keyed off runtime status and approval/user-input cards respecting responseCapability.

Cloud auth and navigation — T3 Account stays navigable while Clerk is loading; the auth screen shows Checking with a manual Retry that remounts Clerk without timers. Cloud cleanup refs live on the parent so Clerk remounts do not drop in-flight sign-out work. In-memory navigation state is restored on app launch.

CI and housekeeping — The Thread Transfer Report workflow and its publisher scripts/tests are removed. CI installs build-essential so ACP process-tree fixtures compile instead of soft-skipping. Smaller updates: desktop user-data dir names in tests, composer context-strip default, mobile theme adaptive colors, README appearance doc link, marketing Cursor harness label.

Reviewed by Cursor Bugbot for commit 088b2ab. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add orchestration V2 foundation and catch up mobile thread feed after background

  • Introduces the full orchestration V2 contract layer: provider policies, model selections, checkpoints, thread projections, domain events, commands, and scheduled-task schemas across packages/contracts/src/orchestrationV2.ts and related contract modules
  • Adds V2 provider adapters for Claude, Codex, Cursor, Grok, OpenCode, and ACP Registry under apps/server/src/orchestration-v2/Adapters/, each integrating its provider SDK through the ProviderAdapterV2 service boundary
  • Adds nine database migrations (044–052) creating the V2 event log, projection tables, effect outbox, provider-session bindings, scheduled tasks, and legacy V1 import state in apps/server/src/persistence/Migrations/" - Builds core V2 server services: SQL-backed ProjectionStoreV2, EffectOutbox and EffectWorker for durable effect processing, ProviderSessionManager, ProviderRuntimeRecoveryService, checkpoint capture/rollback, thread management, and MCP orchestrator/worktree toolkits
  • Migrates mobile and web clients to V2 projection types: thread feed status resolution uses runtime and latestRun fields, pending approvals and user-input checks live response capability, and thread snapshots load through bounded HTTP routes
  • Risk: replaces the legacy EnvironmentApi.orchestration IPC property with orchestrationV2 and switches all WebSocket RPC methods to V2 constants; environments advertising a protocol version mismatch are now rejected at connection time via orchestrationProtocolCompatibilityError

Macroscope summarized 088b2ab.

@coderabbitai

coderabbitaiBot commented Aug 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c8bef765-f132-43e7-8c0c-531ac526096c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 14, 2026
Comment threadpackages/client-runtime/src/state/threadHistoryMerge.ts
@mwolson
mwolson marked this pull request as ready for review August 14, 2026 20:38
@macroscopeapp

macroscopeappBot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — Despite the mobile-fix title, this is a 966-file orchestration V2 migration that changes production persistence, APIs, provider execution, authentication, MCP capabilities, and mobile synchronization behavior. It also changes defaults and static-analysis directives while unresolved comments identify stale-data, auth, UI, and functionality risks.

Not approved because:

  • 2 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 7 times, most recently from a50460b to c610c31CompareAugust 15, 2026 15:09
Comment threadpackages/client-runtime/src/state/shell.ts Outdated
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from c610c31 to 9159bcfCompareAugust 15, 2026 15:18
Comment threadpackages/client-runtime/src/state/threads.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 96891a3 to 993407dCompareAugust 15, 2026 15:26
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 3 times, most recently from 5b5e9cb to a005644CompareAugust 15, 2026 18:38
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Aug 15, 2026
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from a005644 to 1eb46e5CompareAugust 15, 2026 19:01
Comment threadpackages/client-runtime/src/state/threads.ts
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch 2 times, most recently from 541c25b to 26f73f5CompareAugust 15, 2026 19:52
Comment threadpackages/client-runtime/src/state/threads.ts Outdated
Comment threadpackages/client-runtime/src/state/threads.ts Outdated
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from 26f73f5 to 6af29ddCompareAugust 15, 2026 20:22
@mwolsonmwolson changed the title perf(orchestrator): Reduce mobile thread feed presentation workfix(mobile): Catch up the thread feed after a long backgroundAug 15, 2026
Comment threadpackages/client-runtime/src/state/threads.ts
juliusmarmingeand others added 13 commits August 31, 2026 21:33
Adapt grouped tool summaries and the floating working timer to V2 run, attempt, and queue state. Bring over the composer, keyboard, and disclosure transitions while retaining the V2 activity inspector and queue controls.
Keep OV2 web composer and grouping behavior intact; share only the existing command label parser with mobile.
Restores main features dropped by the policy replay: pingdotgg#8569 theme wiring,
pingdotgg#8850 composer banner follow-ups, pingdotgg#8855/pingdotgg#8904 composer fixes, pingdotgg#8831
settings search rework, pingdotgg#8803 workspace-mutation refresh (v2-adapted),
pingdotgg#8840 circle-alert, pingdotgg#8584 codex artifact templates, pingdotgg#8688/pingdotgg#8807/pingdotgg#8936
video + image previews (web and mobile, v2-adapted), pingdotgg#8862 Expo glass,
and the round's docs. Timeline thinking rows (pingdotgg#8984) stay on the v2
work-live system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from a6e0049 to 50a57b6CompareSeptember 1, 2026 05:36
juliusmarmingeand others added 7 commits September 1, 2026 00:24
The v2 equivalents of main's pingdotgg#8984 and pingdotgg#8922: a "Working for ..." header
anchors the active run, the trailing live tool row survives between
actions in past tense instead of vanishing, and a shimmering Thinking
row marks reasoning gaps. During workspace preparation the header shows
"Setting up worktree..." (driven by the local dispatch flag or the v2
run's preparing status, so remote viewers see it too), the composer
footer span is gone, and draft promotion waits until the run starts or
startup fails instead of navigating mid-preparation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Clerk can sit on isLoaded=false after a background freeze, which pins
Settings on Checking and blocks the account screen. Remount Clerk after a
foreground timeout, keep T3 Account tappable, and offer Retry without
clearing app data.
A cold HTTP seed still waits for synchronized, but disconnect and stream
error cleared awaitingCompletion. Reconnect then published live while
replay was still arriving. Keep the fence until synchronized or delete.
A 12-second foreground timer used to remount ClerkProvider, which rebuilds
the whole app. Checking now stays until Clerk loads or the user taps Retry.
Retry is still the only remount.
@mwolson
mwolsonforce-pushed the fix/mobile-thread-feed-performance-v2.1 branch from 5755e9e to 6ff4bf9CompareSeptember 1, 2026 23:14
Comment threadapps/mobile/src/lib/threadActivity.ts Outdated
Comment on lines +180 to +187
const next = b as typeof a;
return (
a.runId === next.runId &&
a.groupId === next.groupId &&
a.hiddenCount === next.hiddenCount &&
a.expanded === next.expanded &&
a.onlyToolActivities === next.onlyToolActivities
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumlib/threadActivity.ts:180

computeStableThreadFeedEntries reuses a stale work-toggle row when its group ID and count are unchanged, so live label, completion/failure status, and shimmer updates are not rendered. threadFeedEntryIsUnchanged must also compare summary, summaryKind, hasFailure, live, and shimmer before reusing the previous row.

Suggested change
constnext=bastypeofa;
return(
a.runId===next.runId&&
a.groupId===next.groupId&&
a.hiddenCount===next.hiddenCount&&
a.expanded===next.expanded&&
a.onlyToolActivities===next.onlyToolActivities
);
constnext=bastypeofa;
return(
a.runId===next.runId&&
a.groupId===next.groupId&&
a.hiddenCount===next.hiddenCount&&
a.expanded===next.expanded&&
a.onlyToolActivities===next.onlyToolActivities&&
a.summary===next.summary&&
a.summaryKind===next.summaryKind&&
a.hasFailure===next.hasFailure&&
a.live===next.live&&
a.shimmer===next.shimmer
);
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/mobile/src/lib/threadActivity.ts around lines 180-187:
`computeStableThreadFeedEntries` reuses a stale `work-toggle` row when its group ID and count are unchanged, so live label, completion/failure status, and shimmer updates are not rendered. `threadFeedEntryIsUnchanged` must also compare `summary`, `summaryKind`, `hasFailure`, `live`, and `shimmer` before reusing the previous row.

return "present" as const;
}
case "missing": {
yield* setDeleted();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Highstate/threads.ts:480

A stale refreshAuthoritativeSnapshot response can mark an already-reconnected thread as deleted and remove its cache. The missing branch calls setDeleted() without verifying that the response belongs to the current prepared connection/session, so a delayed 404 from the old connection clears newer live data; add a request/session fence before treating this result as authoritative.

🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/client-runtime/src/state/threads.ts around line 480:
A stale `refreshAuthoritativeSnapshot` response can mark an already-reconnected thread as deleted and remove its cache. The `missing` branch calls `setDeleted()` without verifying that the response belongs to the current prepared connection/session, so a delayed 404 from the old connection clears newer live data; add a request/session fence before treating this result as authoritative.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

a.expanded === next.expanded &&
a.onlyToolActivities === next.onlyToolActivities
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stable feed skips live work-toggle updates

Medium Severity

threadFeedEntryIsUnchanged treats a work-toggle as unchanged without comparing summary, summaryKind, hasFailure, live, or shimmer. Combined with reference-equal itemsAreEqual, LegendList keeps the old row, so the group label, failure mark, and shimmer can stay frozen through a live turn.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

DateTime.toEpochMillis(activity.projectedItem.item.updatedAt) ===
DateTime.toEpochMillis(nextActivity.projectedItem.item.updatedAt)
);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stable feed freezes activity live state

Medium Severity

threadFeedActivitiesAreUnchanged ignores lifecycleStatus and live. Presentation derives those from the active run, not only from item text or updatedAt, so an in-progress tool group can keep the previous activity objects after the run settles.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

return;
}
return cleanUpAccount(previous, previousObservedAccount ?? null);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clerk remount can wipe the account

Medium Severity

Retry remounts Clerk while observedAccountRef still holds the signed-in user. If the new instance reports loaded and signed out, the bridge treats that as a real sign-out, deactivates the relay, and runs account cleanup.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6ff4bf9. Configure here.

@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 6081201 to 95bacddCompareSeptember 2, 2026 07:19
mwolson added a commit to mwolson/t3code that referenced this pull request Sep 2, 2026
The pingdotgg#6642 row-sharing replay inserted computeStableThreadFeedEntries
after hooks that read presentedFeed. Metro rewrites const to var, so
those hooks observed presentedFeed as undefined on every render and
never updated previousPresentedFeedRef or disclosure enter ids.
Local and anchored messages have no projectedItem. Comparing sourceItemId
through it threw when the feed was rebuilt.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-cutting note (not repeated inline): the panel's row geometry now lives in apps/web/src/components/chat/threadDetailsPanelStyles.ts as class-string constants that six call sites splice into Button/SelectTrigger/ComboboxTrigger, overriding the primitive's height, radius, padding, text size and base/hover background — the hover tint needs !bg-... to win against the variant it is overriding. Since this is durable geometry shared by every panel row, a named Button size/variant (e.g. size="panel-row") would carry it inside the primitive contract and drop the !important overrides, leaving only contextual layout at the call sites. Not blocking this PR, but worth doing before more rows adopt the constants.

Posted via Macroscope — UI Consistency

inset-inline-end: var(--thread-details-panel-inset);
}

.alert-glass {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alert-glass already exists as @utility alert-glass (line 284) with these exact declarations plus a @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) opaque fallback. This copy is in @layer components, and since both background declarations are !important the cascade reverses layer order, so the components copy wins — the utility's no-backdrop-filter fallback stops applying and the banner renders translucent where blur is unsupported.

Suggest dropping this block together with the four .alert-glass[data-variant=...] rules below it and leaving the @utility as the single owner (the alert-glass class name in ProviderStatusBanner already comes from it).

Posted via Macroscope — UI Consistency

size="xs"
className="ps-[8.5px]"
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : undefined}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Toolbar mode lost the ps-[8.5px] optical padding this leading-icon button carried (same for the disabled variant above, line 1777). OpenInPicker keeps ps-[8.5px] for its matching toolbar button, so the two grouped controls in the header no longer align on their icon.

Suggested change
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : undefined}
className={isPanel ? THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS : "ps-[8.5px]"}

Posted via Macroscope — UI Consistency

{props.children}
</ul>
{props.hiddenCount > 0 ? (
<button

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This row rebuilds the panel row by hand (h-9, rounded-lg, px-2.5, text-[13px], gap-2.5, plus the light/dark hover tint) instead of composing Button, and unlike every other row in this file it has no focus-visible ring — so the one control that pages the list gives keyboard users no visible target, even though the comment above relies on rows being reachable by keyboard.

Suggest rendering it as <Button size="sm" variant="ghost" className={cn(THREAD_DETAILS_PANEL_ROW_CLASS, "text-muted-foreground/70 hover:text-foreground/80")}> (adding THREAD_DETAILS_PANEL_ROW_CLASS to the existing import from ./threadDetailsPanelStyles): same geometry and hover tint, with the primitive's focus ring and disabled semantics back.

Posted via Macroscope — UI Consistency

if (item.kind === "open_pr") {
void openExistingPr();
return;
}
if (item.dialogAction === "push") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveQuickAction and buildMenuItems in packages/client-runtime/src/state/gitActions.ts still emit kind: "open_pr" ("View PR") whenever the branch has an open pull request and is clean, but every handler for it was removed here: this menu item now falls through to setIsCommitDialogOpen(true) and opens the Commit dialog, runQuickAction ends at if (quickAction.action) so the quick-action button does nothing, and GitQuickActionIcon falls through to InfoIcon. openExistingPr is left with no callers.

Either keep routing open_pr, or stop emitting it now that ThreadDetailsPrRow owns the pull request row:

 if (item.disabled) return;
+ if (item.kind === "open_pr") {+ void openExistingPr();+ return;+ }
if (item.dialogAction === "push") {

Posted via Macroscope — UI Consistency

@@ -759,6 +762,182 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
outline: none;
}

.surface-subheader {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.surface-subheader has no consumers: the four subheader call sites (DiffPanelShell, PreviewChromeRow, FileBrowserPanel, FilePreviewPanel) keep their own Tailwind classes and only set the data-surface-subheader attribute that the rule below matches. As written this rule never renders and the header geometry has two competing definitions. Suggest either applying the class at those call sites or dropping the rule.

Posted via Macroscope — UI Consistency

--alert-glass-tint: var(--warning);
}

.glass-opacity-slider {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.glass-opacity-slider has no consumer, literal or dynamic: the glass-opacity input in settings/SettingsPanels.tsx uses className="settings-slider ..." and sets --settings-slider-progress/--settings-slider-fill-offset, and nothing anywhere reads --glass-slider-progress. That leaves ~110 lines of vendor pseudo-element CSS that never renders and duplicates .settings-slider below it (same track, thumb, hover/active, focus-visible and forced-colors treatment).

Suggest dropping this block and keeping .settings-slider as the single slider owner — or, if the glass slider was meant to diverge, pointing that input at this class so the rule has an owner.

Posted via Macroscope — UI Consistency

className="z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-transform data-instant:transition-none"
collisionAvoidance={collisionAvoidance}
className={cn(
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] data-instant:transition-none",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes every popover in the app transition its positional properties, while the only new consumer immediately opts out (positionerClassName="... !transition-none" in PanelLayoutControls). Base UI writes the positioner's top/left inline styles on each anchor-tracking update — scroll, resize, collision flip — so unrelated popovers will now animate toward their anchor and visibly trail it while a surface scrolls.

Keeping the primitive on transition-transform and opting in per call site via the new positionerClassName avoids changing behavior for popovers that did not ask for it:

Suggested change
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] data-instant:transition-none",
"z-[130] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-transform data-instant:transition-none",

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Macroscope skipped reviewing this pull request. Per-review cost limit exceeded (workspace setting).

This review would cost an estimated $249.67, which exceeds your per-review limit of $10.00.

The top 3 files driving up this estimate:

FileDiff SizeEstimate
apps/server/src/orchestration-v2/Orchestrator.ts277.03KB$13.85
apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts272.17KB$13.61
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts230.26KB$11.51

Tip

To get this pull request reviewed, you can:

  1. Comment @macroscope-app on this PR to request a manual review (monthly spend limits still apply).
  2. Exclude the file(s) above from review by adding a pattern to your .macroscope/ignore.md — note that creating this file replaces Macroscope's built-in default ignores rather than extending them.
  3. Raise your cost limit in your workspace billing settings.

Turn off this reminder going forward

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@mwolson@t3dotgg@juliusmarminge@PixPMusic@nsxdavid@Yusuf007R@maria-rcks