Skip to content

Cut first-paint latency + fix home-surface layout - #456

Merged
jackwener merged 5 commits into
apache:mainfrom
sunheyi6:feat/cold-start-perf-and-home-layout
Jul 3, 2026
Merged

Cut first-paint latency + fix home-surface layout#456
jackwener merged 5 commits into
apache:mainfrom
sunheyi6:feat/cold-start-perf-and-home-layout

Conversation

@sunheyi6

@sunheyi6 sunheyi6 commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Two commits on top of main.

perf(desktop): cut first-paint latency

A coordinated cold-start pass so the window shows an interactive shell within milliseconds of launch.

Main process

  • main.ts: createWindow() now runs before runBackgroundStartup(); credential migration, connection bootstrap, telemetry/pricing, interrupted-session recovery, bot bridges, gateway and schedulers all run concurrently in the background.
  • main-window.ts: ensureBundledOfficeSkills moved off the createWindow critical path to a fire-and-forget task after loadFile.

Renderer — shrink the initial chunk via React.lazy + Suspense

  • markdown.tsx + new markdown-body.tsx: split the react-markdown + remark-* + rehype-highlight (highlight.js) pipeline behind lazy.
  • chat-view.tsx: Skills / DailyReview / PlanReminder panels.
  • app-shell.tsx: ArtifactPane / BrowserPanel.
  • app-shell-overlays.tsx: SettingsModal.
  • OnboardingHero.tsx / command-palette.tsx: import the fine-grained provider-display / settings-nav leaves instead of the full panel modules.

First-paint data — bundle into the onboarding snapshot to skip redundant IPC

  • OnboardingSnapshot now carries sessions / connections / defaultSlug.
  • app-shell.tsx seeds the sidebar + model picker from the snapshot; hoists onboardingComposerHidden to suppress the composer skeleton flash.
  • app-shell-effects.ts: only refreshShellSettings stays critical (theme + locale); the rest defer to requestAnimationFrame.

Dev / tests

  • dev:hmr simplified to node scripts/dev.mjs.
  • modal-lifecycle-contract / use-onboarding-snapshot / visible-copy-hygiene follow the refactor.

fix(ui): pin home-surface hero to top and fill window width

  • onboarding.css: PR fix(ui): keep first-run CTA above the fold; unsquash skills capability-audit strip #436 only flattened the padding stack for .maka-firstrun. Mirror it for :has(.maka-onboarding-ready) so the ready_empty hero + quickchat + checklist sit near the top instead of floating mid-window.
  • sidebar.css: #root is flex + justify-content:center for the preload skeleton; once mounted, .appFrame shrank to content max-content and got centered, leaving large L/R gutters on the narrow-content first-run hero. Pin width:100%.

Verification

  • 13 layout/onboarding contract suites green (134/136).
  • 2 failures (turn footer copy feedback, tool error copy feedback) confirmed pre-existing on main via git checkout main comparison — not introduced here.

sunheyi6 and others added 5 commits July 3, 2026 16:43
…nboarding data, defer non-critical startup

A coordinated cold-start pass so the window shows an interactive shell
within milliseconds of launch.

Main process:
- main.ts: createWindow() now runs before runBackgroundStartup();
  credential migration, connection bootstrap, telemetry/pricing,
  interrupted-session recovery, bot bridges, gateway and schedulers
  all run concurrently in the background and never block first paint.
- main-window.ts: ensureBundledOfficeSkills moved off the createWindow
  critical path to a fire-and-forget task after loadFile.

Renderer — shrink the initial chunk via React.lazy + Suspense:
- markdown.tsx + new markdown-body.tsx: split the react-markdown +
  remark-* + rehype-highlight (highlight.js) pipeline behind lazy so
  it's only parsed when a message actually renders.
- chat-view.tsx: SkillsModuleMain / DailyReviewPanel / PlanReminderPanel.
- app-shell.tsx: ArtifactPane / BrowserPanel.
- app-shell-overlays.tsx: SettingsModal.
- OnboardingHero.tsx / command-palette.tsx: import the fine-grained
  provider-display / settings-nav leaves instead of the full
  ProvidersPanel / SettingsModal modules.

First-paint data — bundle into the onboarding snapshot to skip
redundant IPC round-trips:
- onboarding-service.ts / global.d.ts / preload.ts: OnboardingSnapshot
  now carries sessions / connections / defaultSlug.
- use-onboarding-snapshot.ts: expose getSessions/getConnections/
  getDefaultSlug.
- app-shell.tsx: seed the sidebar + model picker from the snapshot on
  first load; hoist onboardingComposerHidden to suppress the composer
  skeleton flash.
- app-shell-effects.ts: only refreshShellSettings stays on the critical
  path (theme + locale); appInfo / memory / skills / planReminders /
  visual-smoke defer to requestAnimationFrame.

Dev:
- apps/desktop/package.json: dev:hmr simplified to .

Tests follow the refactor:
- modal-lifecycle-contract: tolerate the optional <Suspense> boundary
  around the lazy SettingsModal.
- use-onboarding-snapshot: fixture gains the new snapshot fields.
- visible-copy-hygiene-contract: read CodeBlock from markdown-body.tsx.
Two layout fixes for the first-run / ready_empty home surface.

- onboarding.css: PR-FIRSTRUN-FOLD-0 (PR apache#436) only flattened the
  padding stack for .maka-firstrun and intentionally kept the other
  onboarding states' centered rhythm. ready_empty ("今天想让 Maka
  帮你做什么?") is the actual landing page once a model is
  configured, so mirror the same fold-flattening for
  :has(.maka-onboarding-ready) — chatContent padding-top
  clamp(72px,10vh,116px) -> clamp(24px,3.5vh,44px), align-content
  center -> start, and onboarding-stack min-height -> 0 /
  padding-top clamp(40px,7vh,76px) -> clamp(12px,2vh,24px). The hero
  + quickchat + first-run checklist now sit near the top instead of
  floating mid-window.
- sidebar.css: #root (index.html) is display:flex + justify-content:
  center so the preload skeleton centers before React mounts. Once
  mounted, .appFrame is that flex container's only child, and without
  an explicit main-axis size it shrank to content max-content and got
  centered — so on the narrow-content first-run hero the whole app
  frame floated mid-window with large L/R gutters, while long-message
  sessions filled the width. Pin .appFrame width:100% so it always
  spans the window regardless of inner content. Preload centering is
  unaffected (it's still the only child at that point).
Maintainer pass on apache#456 (cold-start reordering + lazy splits). The
perf direction is right; this closes the races the reordering opened:

- recovery vs live send (review P0): recoverInterruptedSessions now
  re-checks runtimeKernel.hasActiveRuns immediately before BOTH
  updateStatus('active') sites, so a message sent mid-recovery owns
  the session status. Recovery-order contract rewritten to pin the
  new invariant (recovery inside runBackgroundStartup + the kernel
  guard) instead of the old recovery-before-window ordering.
- snapshot seeding bypassed display normalization (apache#452 regression):
  the onboarding-snapshot seed is a third renderer state entry —
  without normalizeSessionSummaryForDisplay legacy blocked/unknown
  sessions flash an 已阻塞 group on first paint. Now normalized, and
  a snapshot-IPC-failure fallback calls bootstrapSessions() +
  refreshConnections() so the sidebar cannot stay empty forever.
- env bootstrap connections never reached the renderer (review P1):
  ensureBootstrapConnection creates connections in background startup
  but never emitted connections:event — on a fresh env-var cold start
  the model picker stayed empty until an unrelated refresh. Both
  creation paths now emitConnectionListChanged().
- bundled-skills race (review P1): the fire-and-forget copy moved from
  main-window into runBackgroundStartup with a deferred handle;
  skills:list awaits it so an early Skills-page open cannot observe a
  half-bundled list (settled promise = zero steady-state cost).
- scope: dropped .github/workflows/sync-fork.yml (author's fork-sync
  cron; on upstream it would be a zombie hourly self-merge job).

Kept as-is after verification: dev:hmr -> dev.mjs (the launcher self-
contains incremental tsc --build for all workspaces, so the alias is
semantically equivalent and faster); lazy markdown split (fallback
renders plain text, no unstyled flash).

typecheck clean; runtime 806/806; desktop 1696/1696.
@jackwener
jackwener force-pushed the feat/cold-start-perf-and-home-layout branch from d3176c6 to 52fe46e Compare July 3, 2026 09:00
@jackwener
jackwener merged commit 41d11c8 into apache:main Jul 3, 2026
@jackwener

Copy link
Copy Markdown
Member

Merged,感谢!🎉 冷启动重排 + lazy 分割方向完全正确(首绘不再等 credential 迁移/connection bootstrap/recovery),dev.mjs 的增量并行构建也验证过语义等价于旧 dev:hmr(自含 tsc --build 全 workspace),布局两处修复符合 #436 的折叠逻辑。

Maintainer 落地时深度审查了重排打开的竞态面,直接修了五处:

  1. recovery vs 在飞消息(P0):recoverInterruptedSessions 在两个 updateStatus('active') 前即时复查 hasActiveRuns —— 用户在恢复途中发的消息拥有 session 状态;recovery 顺序契约改锁新不变量(后台运行 + kernel guard)
  2. snapshot 种子化绕过了 fix(ui): stop showing healthy legacy sessions as 已阻塞; compact sidebar timestamps; localize bypass label #452 的展示归一化:旧 blocked/unknown 会话会在首绘瞬间闪「已阻塞」分组 —— 已补 normalizeSessionSummaryForDisplay,并加了 snapshot IPC 失败时的 bootstrapSessions() 兜底(否则侧栏永远空)
  3. env bootstrap 建连接后不发事件ensureBootstrapConnection 两条创建路径补 emitConnectionListChanged()(否则纯 env-var 冷启动模型选择器一直空)
  4. bundled skills 竞态:copy 挪进 runBackgroundStartup + deferred,skills:list await 它(settled 后零开销)
  5. scope:剔除了 .github/workflows/sync-fork.yml(你 fork 的同步 cron,落到 upstream 会变成每小时自我 merge 的僵尸 job —— 和 Fix/startup white screen #429.suncode/ 一样,提 PR 前记得过一眼 diff 文件清单)

typecheck 清洁;runtime 806/806;desktop 1696/1696。

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants