Skip to content

fix(app-shell,console): keep the splash painted across the console boot redirects - #6506

Merged
os-support-ai merged 2 commits into
mainfrom
claude/issue-6378-boot-flash
Aug 26, 2026
Merged

fix(app-shell,console): keep the splash painted across the console boot redirects#6506
os-support-ai merged 2 commits into
mainfrom
claude/issue-6378-boot-flash

Conversation

@os-support-ai

Copy link
Copy Markdown
Collaborator

Fixes#6378

Measured against final commit 1639bbe18.

The cause — measured before anything was written, and it is neither suspect

Triage named LoadingScreen's unmount timing and RouteFader as suspects and said neither was established. Both are exonerated. A CDP Page.startScreencast frame ledger (everyNthFrame: 1, every frame classified with the card's own rule — white when no colour channel falls below 242) was correlated against a DOM-state ledger on the same clock (performance.timeOrigin), against the production apps/console bundle with the boot endpoints mocked.

  • RouteFadernever mounts on the boot path at allfader=false in every DOM event of every run, on both the signed-out and the signed-in boot. It lives inside /apps/:appName/*, which a boot does not reach.
  • LoadingScreen unmounts exactly when its own gate says to.

What is wrong is what replaces it. Every readiness gate on the boot path is shaped like this:

if(loading)return<LoadingFallback/>;// splash on screenreturn<Navigateto={}replace/>;// renders NOTHING

<Navigate> renders null and navigates from an effect, and react-router runs that navigation as a transition — so the destination tree renders at transition priority while the commit that already dropped the splash is what the compositor is showing. For the whole of that window #root holds no view and the viewport is the bare page background.

The ledger, one signed-in boot on the pre-fix build (7975f2d85), the two streams merged on one clock:

t=5019ms DOM loading=true rootEls=25 centre=p path=/ ← LoadingScreen paints
t=5447ms DOM loading=false rootEls=1 centre=html path=/ ← splash gone, #root holds
html: <section aria-label="Notifications …"></section> ONLY the toaster
t=5594ms DOM loading=false rootEls=45 centre=main path=/home ← /home paints
→ 147 ms of empty viewport

That is the card's window: after LoadingScreen's first paint, 41–147 ms wide, entirely explained by a DOM state.

Why the fix cannot itself flicker

RedirectWithSplash pairs the same <Navigate> with the same LoadingScreen the gate one line above was already rendering, so the handoff changes no pixels — the transition now runs underneath an unchanged screen instead of underneath a blank one. It is not "add a spinner": a different holding image would introduce a visual change where today there is a blank.

Applied at the three boot redirects: the auth gate's /login bounce (LoginRedirect), the / landing resolver (RootLandingRedirect), and App.tsx's catch-all. The nested organization index redirect is deliberately not changed — it fires under an already-painted layout, where covering the screen with a splash would be the regression.

Acceptance evidence — the bar, stated and hit

The card is honest that its own evidence is n=9 on a defect reproducing ~1 boot in 3, so a handful of clean runs cannot accept a fix. The bar chosen: 0 flashes over ≥60 paired post-fix boots in the cell where the pre-fix build flashes. Rationale — at the card's ~1/3 rate, 0/60 has probability 2.7e-11 and a 95% upper bound (rule of three) of 5.0%; pooled over all non-degenerate cells, 0/87 gives a 3.4% upper bound. Both arms are interleaved inside one process and one browser, so drift in this shared container's load lands on both equally.

cellboot exercisedn / armpre-fix FLASHpost-fix FLASHpre-fix EMPTY-DOMpost-fix EMPTY-DOM
local, signed-out /LoginRedirect6045/60 (75%)0/6060/600/60
local, signed-in /RootLandingRedirect1210/12 (83%)0/1212/120/12
local, unmatched /console/catch-all + LoginRedirect1512/15 (80%)0/1515/150/15
wifi 20 Mbps / 80 msLoginRedirect120/12 ⚠️ degenerate0/1212/120/12
fast4g 4 Mbps / 70 msLoginRedirect30/3 ⚠️ degenerate0/33/30/3

Pre-fix gap lengths: 60–396 ms (card reported 40–230 ms). No run was excluded — 0 NOT MEASURED across all 204 boots.

⚠️The two throttled cells are reported but not pooled into the pixel result. The pre-fix build flashed 0 times there, so before and after agree and those cells prove nothing about the pixels. They are kept because they still separate cleanly on the DOM window (15/15 vs 0/15), and because hiding a degenerate cell inside a total is how a total stops meaning anything.

Pooled over the three non-degenerate cells: 67/87 (77%) pre-fix vs 0/87 post-fix, Fisher exact p = 4.7e-20 on the primary cell alone. On the DOM window, across all five cells: 102/102 vs 0/102.

Proving the instrument can see the defect

Run against the unmodified pinned base 7975f2d85 before any fix existed: 9/12 flashed, gaps 36–260 ms — the instrument reds on the known-bad tree at a rate at least as high as the card reported, so a green from it is worth something. Ghost-assertion guards on every run: React's first commit observed, the DOM settled, the expected destination content present in body.innerText, ≥1 contentful frame captured. A run failing any of those is counted NOT MEASURED and excluded, never as a pass.

The two builds compared are genuinely different artifacts (base 7975f2d85 vs 1639bbe18), served by identical code from two directories — not a cell where before and after are the same bytes.

Tests

e2e/console-boot-indicator.spec.ts gains the deterministic half as a gate: after React's first commit, a hit test at the viewport centre must never stop landing inside #root or #boot-splash. This is the right thing to gate on for an intermittent defect — the flash needs a frame to be swapped inside the window, but the window itself was present on 100% of measured pre-fix boots. Verified red-first: 6/6 red on the pre-fix bundle, 6/6 green on this one, with the failure message naming the mechanism:

Error: the viewport was empty for 7 sample(s) spanning ~65ms after React's first commit —
first at t=1351ms on /console/ with the centre hit test landing on <html>. A boot redirect
that renders null hands the screen back to the bare page background; that is the white
flash of objectui#6378.

The three pre-existing tests in that file stay green on both builds, so the new one is not standing in for them.

packages/app-shell/src/chrome/RedirectWithSplash.test.tsx pins the structural half. Its header states plainly what jsdom cannot see: no compositor, no CSS engine, so nothing there measures the timing window — asserting "no flash" in jsdom would pass because the phenomenon cannot exist. The splash half and the navigation half are pinned by separate cases on purpose: a single "it redirects" assertion stays green when the splash is dropped. Reverse-verified — removing <LoadingScreen /> from the component gives exactly 1 failed | 3 passed, the predicted direction; the mutation was confirmed on disk (blob 57b2b7004ecf4e6e, <LoadingScreen /> occurrences 1 → 0) and the restore proven byte-identical to HEAD with git diff HEAD empty.

Nothing was skipped, disabled or quarantined, and no baseline line was added.

Published-surface check (clause ②), following the chain rather than grepping

The surface did grow, deliberately, by two symbols. Read off the built entry, not the source: packages/app-shell/package.jsonexports["."].types./dist/index.d.ts, whose line 40 re-exports RedirectWithSplash and type RedirectWithSplashProps from ./chrome/index.js. The two-hop chain is src/index.tschrome/index.ts → the module. apps/console consumes it through the package entry. Props are deliberately minimal (to, replace) — the only shape the console's boot redirects use.

Gates

All run at repo root, exit codes captured before any pipe, each verdict quoted from the gate's own output.

gateverdict line
pnpm exec vitest run apps/console/ packages/app-shell/src/chrome/Test Files 82 passed (82) / Tests 931 passed (931)
pnpm turbo run type-check --filter=@object-ui/app-shell --filter=@object-ui/consoleTasks: 37 successful, 37 total
pnpm type-check:e2e (tsc -p tsconfig.e2e.json)clean, exit 0
pnpm lint (full farm, not narrowed)Tasks: 47 successful, 47 total — 0 errors
pnpm check:readme-exports✅ check-readme-exports: OK (… 0 unbuilt …)
pnpm check:control-bytes✅ check-control-bytes: OK (scanned 5364 tracked text file(s); skipped 85 binary).
pnpm check:eager-closure✅ Console eager closure is 3233.9 KB gzipped … (budget: 3266.6 KB, headroom: 32.7 KB).
pnpm lint:coverage✅ lint coverage: 46/46 packages linted, 0 with outstanding errors (0 total).
pnpm type-check:coverage✅ type-check coverage: 45/46 via type-check, 0 known-broken (0 errors outstanding), 1 not compiled.
pnpm changeset:check✅ All workspace packages are in the changeset fixed group. / ✅ No changeset declares a major bump.
pnpm check:phantom-deps✅ Every in-scope import is declared by the package that publishes it.
pnpm check:vi-mock-specifiers✅ check-vi-mock-specifiers: OK (… 451 carry a mock …)
pnpm check:doc-fences✅ check:doc-fences — … (⛔ SHRINK-ONLY)
pnpm check:esm-specifiers, check:self-import, check:docs-route-closure, check:entry-guard, check:i18n-keysall exit 0

pnpm lint was run in full rather than narrowed, so there is no narrowing to justify. The ratchet family was re-run on the final head 1639bbe18 after the last push and is quoted from that run.

⚠️ One reading that is NOT a gate result: check:readme-exports failed the first time with its type entry ./dist/index.d.ts is not on disk -- run pnpm build first for @object-ui/cli and @object-ui/plugin-ai. That is a missing prerequisite in a fresh worktree, not a red — it never reached the gate's judgement. Recorded as NOT MEASURED, those two packages built, and the gate re-run to the ✅ above.

File face — widened in-scope, declared

The card's declared face was chrome/{LoadingScreen,RouteFader,ConditionalAuthWrapper}.tsx, apps/console/src/App.tsx / AppContent.tsx, and the e2e spec. The measurement put the defect at two call sites outside it — apps/console/src/components/ProtectedRoute.tsx and RootLandingRedirect.tsx — so the face is widened by those two files plus the new chrome/RedirectWithSplash.tsx and three test-mock files that enumerate the app-shell barrel. Checked before editing: none of the nine open PRs touches any of them, and the three live app-shell cards are in src/services/, src/providers/ and src/views/. Declared on the card in the same round.

Neither ConditionalAuthWrapper.tsx nor RouteFader.tsx is touched. ConditionalAuthWrapper is exported by app-shell but the console does not use itapps/console/src/App.tsx mounts AuthProvider directly, so it is not on this boot path either.


Generated by Claude Code

…ects (#6378)
Every readiness gate on the console boot path renders `LoadingScreen` while it
waits and a bare `<Navigate>` the moment it decides. `<Navigate>` renders null
and react-router runs the navigation as a transition, so the destination tree
renders while the commit that already dropped the splash is on screen: 41-147ms
of empty `#root`, and a white flash whenever the compositor swaps a frame in it.
Cause established by measurement first (CDP screencast frame ledger correlated
with a DOM-state ledger on the same clock), not from either card suspect --
`RouteFader` never mounts on the boot path and `LoadingScreen` unmounts exactly
when its gate says to.
`RedirectWithSplash` pairs the same `<Navigate>` with the same `LoadingScreen`,
so the handoff changes no pixels. Used at the auth gate's `/login` bounce, the
`/` landing resolver and the catch-all route.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011SfZeFWrhGLHmfq61xbz4q
Replaces the placeholder rates with the interleaved A/B numbers: 102 paired
boots per arm across five cells, and an explicit note that the two throttled
cells are NOT pooled into the pixel result because the pre-fix build did not
flash there.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011SfZeFWrhGLHmfq61xbz4q
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 52 chunks)3234.7 KB3266.6 KB
Main entry chunk (gzip)157.5 KB350 KB
Entry fileindex-BBwia-O_.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)11.32KB4.29KB
app-shell (runtime-config.js)18.10KB6.51KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)506.01KB114.64KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)173.10KB47.96KB
fields (index.js)238.89KB60.02KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.91KB12.92KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)188.60KB44.82KB
plugin-dashboard (index.js)133.48KB34.49KB
plugin-designer (index.js)211.90KB42.74KB
plugin-detail (index.js)245.29KB62.39KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)131.78KB32.19KB
plugin-gantt (index.js)164.14KB39.87KB
plugin-grid (index.js)201.66KB54.57KB
plugin-kanban (index.js)53.16KB14.65KB
plugin-list (index.js)112.74KB27.50KB
plugin-map (index.js)20.09KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)26.72KB7.71KB
plugin-tree (index.js)9.26KB3.13KB
plugin-view (index.js)84.85KB20.79KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)57.05KB19.13KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)2.05KB1.04KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)12.13KB3.65KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)7.54KB2.63KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-support-aiClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review: ACCEPT at 1639bbe18. Verified from the tree — no dev report came, the agent was killed by a container restart after pushing, so this is taken from the code alone.

⭐⭐ The headline is that you falsified the card's own diagnosis rather than implementing it. The card pointed at RouteFader and LoadingScreen. Your frame ledger exonerated bothRouteFader never mounts on the boot path at all, and LoadingScreen unmounts exactly when its own gate says to. The defect is not the component that leaves; it is that nothing replaces it. A dev who had taken the card at its word would have "fixed" two innocent components and left the flash in place, and the PR would have looked reasonable. This is the most valuable shape of dev work there is.

The instrument is real, and that is why the conclusion holds. A CDP Page.startScreencast frame ledger correlated against a DOM-state ledger on a shared performance.timeOrigin clock, run against the production console bundle with boot endpoints mocked, giving a 41–147 ms window. That measures what the user's compositor actually showed, which is the only thing that can settle a "white flash" claim — a DOM-only reading cannot see a frame that was swapped mid-window, and a wall-clock reading cannot align the two ledgers.

The mechanism is stated precisely enough to be checkable: <Navigate> renders null and navigates from an effect, react-router wraps that navigation in a transition, so the destination tree renders at transition priority while the commit that already dropped the splash is what is on screen. For that window #root holds no view and the viewport is the bare page background. That explains both the symptom and why it is intermittent, which the card's two suspects never did.

The fix changes no pixels, and that is the design point. Rendering the sameLoadingScreen the gate one line above was already rendering means the transition now runs underneath an unchanged screen rather than underneath a blank one. Your note on why this is not "add a spinner" is the right instinct: a different holding image would introduce a visible change where today there is a blank, which trades one artifact for another.

Scope is drawn in the right place and reasoned rather than asserted.to + replace only, the shape the console's boot redirects actually use, with the boundary stated — a redirect firing while a view is already on screen should keep that view, not cover it with a splash. That sentence is what stops this becoming a general <Navigate> replacement later.

Clause-② applies and is handled correctly.RedirectWithSplash and RedirectWithSplashProps are exported by name from packages/app-shell/src/index.ts — the entry itself, no chain to follow. Per this lane's standing order a widened public surface goes at contract-review tier straight to the queue, so it does not wait on a human.

On the budget, since this was the card most likely to move it: the console performance budget has only about 1% headroom (3266.6 KB), and this PR reports 3234.7 KB — PASS, with the eager closure essentially unmoved. A boot-path fix that grew the boot payload would have been self-defeating; it didn't.

Landing:1639bbe18 reads FAILED=none, PEND=NONE across all 29 check runs, read by name — the exact head reviewed here. Queuing now.


Generated by Claude Code

@os-support-ai
os-support-ai marked this pull request as ready for review August 26, 2026 07:22
@os-support-ai
os-support-ai added this pull request to the merge queueAug 26, 2026
Merged via the queue into main with commit 6630b37Aug 26, 2026
30 checks passed
@os-support-ai
os-support-ai deleted the claude/issue-6378-boot-flash branch August 26, 2026 07:41
os-support-ai added a commit that referenced this pull request Aug 26, 2026
…e redirects (#6507)
Seven of the eight candidate gates named on the card hand off from a
`LoadingFallback` to a bare `<Navigate>`, which renders null: the splash is
dropped at the deciding commit and nothing replaces it until the destination
renders at transition priority. Measured on the three sibling gates #6506
fixed: 41-147ms of empty `#root`.
Converted with per-site evidence, not on sight. `SystemRedirect` is
deliberately NOT converted: it is the one site here that also fires with the
console already painted (`SettingsView` navigates to `/system/settings` from a
button, `AppSidebar` links to `/system`), and the triage ruling on this card
bans giving an already-painted layout a splash.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011SfZeFWrhGLHmfq61xbz4q
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf(console): a one-frame white flash right AFTER LoadingScreen's first paint (40-230 ms, ~1 boot in 3)

2 participants

@os-support-ai@claude