Skip to content

fix(desktop): publish session transcripts once, behind a fade swap - #5566

Merged
Astro-Han merged 2 commits into
mainfrom
fix/desktop-cached-transcript-hasolder
Sep 21, 2026
Merged

Astro-Han merged 2 commits into
mainfrom
fix/desktop-cached-transcript-hasolder

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Switching sessions painted the locally cached transcript tail first — a prefix-truncated copy whose hasOlder flag briefly rendered a dead "载入更早的记录" control (cached generations cannot serve a Host-backed loadEarlier) — then replaced the whole publication when the bounded live answer arrived.

transcripts.open() now starts the live sessions:transcript:open first and reads the local cache only when the live open fails before any live identity is received. The cache remains the offline/reconnect stand-in, but a healthy open publishes the live bounded history as its first and only transcript. This also removes a per-open session-local:transcript disk read from the normal path.

Because the fallback path still publishes cached: generations, the provisional-state contract is enforced at the authority seam: DesktopTranscriptRangeStore.range() reports hasOlder: false for cached generations, and reading-anchor restoration waits for a live generation instead of consuming a stored bookmark through a no-op loadEarlier and marking it unavailable.

While the live read is in flight the previous transcript stays mounted in place (inert) as the cover; the swapped-in transcript fades in once on its existing key={activeSession.id} remount — the same cover-then-reveal pattern as the startup reveal (#5494), at --duration-emphasized and disabled under prefers-reduced-motion.

Mid-switch captures — BEFORE frame taken with the live sessions:transcript:open held ~1.6s; AFTER frames frozen mid-fade via the Web Animations API:

Light — before vs after

Dark — before vs after

Real-speed recording of two switches on this branch — the previous transcript holds while the live read is in flight, then the complete transcript fades in once:

switch-transition-after.mp4

Verification

  • New preload-level regression test drives the real bundled preload with a stubbed ipcRenderer: a healthy live open performs zero session-local:transcript reads and publishes only the live answer; a failed live open replays the cache once, yields the reconnect-only fallback handle, and reports hasOlder: false. Both fail on the old behavior.
  • Range-store and reading-position regression tests: cached generations never advertise loadable earlier history, and a stored anchor stays pending through a cached window then restores on the live answer. Both fail on the old behavior.
  • session-local-recovery.spec.ts (3 tests) and partial-history-notice.spec.ts pass: cached history stays readable when the live endpoint is unavailable, and earlier Turns still load only on explicit request.
  • Real-Electron DOM check: on session switch .maka-chat-session-swap computes animation-name: maka-stream-fade-in and no load-earlier control appears transiently.
  • npm run format, npm run lint, check-renderer-architecture, check:asf-headers, and the affected apps/desktop dist tests (~68 transcript/reading-position/WorkHub cases) pass.

AI use

Select exactly one:

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Devin authored the implementation, tests, and verification.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

@github-actions github-actions Bot added the effort/M Under 500 readable lines label Sep 21, 2026
@Astro-Han
Astro-Han force-pushed the fix/desktop-cached-transcript-hasolder branch from a0d9a50 to f80bc53 Compare September 21, 2026 10:21

@jackwener jackwener left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I reviewed f80bc537128f5ff31d54c3ee603177f5c8cc808e.

No P0–P2.

A healthy transcripts.open() no longer publishes the local cache first. The cache is only replayed when the live open fails before any live identity arrives. Cached generations cannot serve loadEarlier, and both range() and the reading-anchor restore treat them that way (hasOlder: false; restore waits for a live generation). While switchingSession is true the previous transcript stays mounted, inert and dimmed, until the live answer remounts.

Required check test is green on this head. I did not run a real Electron session-switch.

简体中文

我审查了 f80bc537128f5ff31d54c3ee603177f5c8cc808e。没有 P0–P2。

健康的 transcripts.open() 不再先发布本地缓存。只有 live open 在收到 live identity 之前失败时才回放缓存。缓存代不能 loadEarlierhasOlder 为 false,阅读锚点等到 live 代再恢复。切会话时上一份 transcript 留着(inert + 变暗)直到 live 答案换上。

本 head 上必需检查 test 是绿的。我没有跑真实 Electron 切会话。


Automated review notice: This comment was posted by an automated review agent operated by jackwener. It is not an independent human review and does not replace one.

Astro-Han and others added 2 commits September 21, 2026 19:28
transcripts.open replays the locally cached tail before the live history
answer so the previous content is visible while the Host reads. That
snapshot carries the replica's tail bound as hasOlder, so the view
rendered a load-earlier control for a generation that cannot serve the
read (loadEarlier no-ops on cached:), then replaced it wholesale once
the live answer arrived — the "load earlier history" flash on every
session switch.

A cached generation is provisional by contract and every live-only
affordance already gates on it. Close the two leaks: range() reports
hasOlder only for generations that can answer earlier reads, and the
reading-position restore waits for the live answer instead of
concluding a bookmarked Turn is unreachable from the cached tail.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated-by: Devin
Switching sessions used to paint the locally cached tail first — a
prefix-truncated copy whose hasOlder flag rendered a dead load-earlier
control — then replace it wholesale when the live answer arrived. The
cache now stands in only when the live open fails, which also removes a
per-open session-local disk read. The switch keeps the previous
transcript inert and dimmed while the live read is in flight, then fades
the new transcript in on its keyed remount.

Generated-by: Devin
@Astro-Han
Astro-Han force-pushed the fix/desktop-cached-transcript-hasolder branch from f80bc53 to 99cb762 Compare September 21, 2026 11:47
@Astro-Han
Astro-Han marked this pull request as ready for review September 21, 2026 12:03
@Astro-Han
Astro-Han merged commit 3c63068 into main Sep 21, 2026
2 checks passed
@Astro-Han
Astro-Han deleted the fix/desktop-cached-transcript-hasolder branch September 21, 2026 12:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/M Under 500 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants