Skip to content

fix(ui): stop showing healthy legacy sessions as 已阻塞; compact sidebar timestamps; localize bypass label - #452

Merged
jackwener merged 1 commit into
mainfrom
ui/blocked-session-display-semantics
Jul 3, 2026
Merged

fix(ui): stop showing healthy legacy sessions as 已阻塞; compact sidebar timestamps; localize bypass label#452
jackwener merged 1 commit into
mainfrom
ui/blocked-session-display-semantics

Conversation

@jackwener

Copy link
Copy Markdown
Member

Driven by a real-app screenshot report (2026-07-03): three healthy chat sessions piled under an 已阻塞 sidebar group with shield icons, titles crushed to ?..., and an English 'Bypass permissions' pill in an otherwise Chinese composer.

1. Blocked display semantics (the headline bug)

The #410 terminal-ledger invariant marks legacy sessions missing a terminal fact as blocked/unknown. The sidebar groups by raw status → intact, resumable conversations showed as 已阻塞 (header badge too), reading like data loss when nothing needed the user's attention.

First-principles split: session-level 已阻塞 is only worth the interruption when the user can ACT — configure a connection, re-login, confirm a permission. tool_failed/unknown mean "the last run's bookkeeping didn't close cleanly"; the failure detail already lives on the failed turn in the chat.

2. Sidebar timestamps crushed titles

>7-day sessions rendered 2026年6月20日 16:33 (~150px) next to the title. New formatCompactTimestamp in @maka/core: relative within 7 days, date-only beyond (6月20日 same year, 2025年6月20日 across years). Sidebar-only; wide surfaces keep the existing formatter (artifact-pane dedup contract still satisfied — one shared implementation in core).

3. Bypass permissions → 跳过确认

询问权限 / 自动执行 / Bypass permissions 跳过确认 — consistent zh across composer, command palette, session settings, and account settings copy.

Verification

  • npm --workspace @maka/desktop test: 1685/1685
  • npm run typecheck: clean
  • behavior spot-check: unknown/tool_failed → active (reason dropped); auth/NO_REAL_CONNECTION/permission_required → blocked kept; non-blocked statuses identity

… timestamps; localize bypass label

Three fixes from a real-app screenshot report (main window, 2026-07-03):

1. Blocked display semantics. The #410 terminal-ledger invariant marks
   legacy sessions missing a terminal fact as blocked/unknown. The
   sidebar groups by raw status, so intact, resumable conversations
   piled up under an 已阻塞 group with shield icons and the chat header
   badge read 已阻塞 — for sessions where the model had answered and
   nothing needed the user's attention. Session-level blocked is now a
   display-layer distinction: only actionable reasons
   (NO_REAL_CONNECTION / auth / permission_required) keep the blocked
   presentation; tool_failed / unknown normalize to active at the
   renderer state boundary (commitSessions / upsertSessionSummary), so
   grouping, row icon, and header badge agree. Runtime status writes
   are untouched. Regression: session-status-display-normalization.test.ts.

2. Sidebar timestamps. Sessions older than 7 days rendered the full
   medium date + time (2026年6月20日 16:33, ~150px) which crushed the
   title next to it to ~2 characters. New formatCompactTimestamp in
   @maka/core: relative inside 7 days, date-only beyond (6月20日 within
   the year, 2025年6月20日 across years). Sidebar-only; wide surfaces
   keep formatRelativeTimestamp.

3. i18n consistency. The composer permission-mode pill showed English
   'Bypass permissions' between two Chinese modes; now 跳过确认 in the
   composer, command palette, session settings actions, and the
   account settings copy.

Desktop suite: 1685/1685; typecheck clean.
@jackwener
jackwener merged commit cdaa1ac into main Jul 3, 2026
@jackwener
jackwener deleted the ui/blocked-session-display-semantics branch July 3, 2026 06:05
jackwener added a commit to sunheyi6/maka-agent that referenced this pull request Jul 3, 2026
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 added a commit that referenced this pull request Jul 3, 2026
* Add fork sync GitHub Actions workflow

* Change fork sync to hourly

* perf(desktop): cut first-paint latency — lazy heavy modules, bundle onboarding 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.

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

Two layout fixes for the first-run / ready_empty home surface.

- onboarding.css: PR-FIRSTRUN-FOLD-0 (PR #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).

* review: harden background-startup races, seed normalization, and scope

Maintainer pass on #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 (#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.

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant