Skip to content

fix(desktop): derive conversation execution from Host snapshots - #5217

Merged
Astro-Han merged 12 commits into
mainfrom
fix/conversation-host-projection
Sep 12, 2026
Merged

Astro-Han merged 12 commits into
mainfrom
fix/conversation-host-projection

Conversation

@Astro-Han

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

Copy link
Copy Markdown
Contributor

Summary

Starting another conversation turn could leave the working indicator attached to the previous answer. Desktop inferred execution from content and session-list refreshes instead of projecting the Host root Turn.

Use the Host execution snapshot for activity in the conversation, Side Chat, and WorkHub. Keep content buffered by Turn until durable transcript handoff. Observation loss hides running presentation while retaining content and the exact Stop target; it does not fabricate a Runtime terminal event. Ordinary sends use next_turn intent so Host decides whether to start or queue them.

Initialization and recovery now deliver execution and content together through the same observation event stream as live updates. Renderer becomes ready only after consuming that seed. The observe reply acknowledges registration and carries no content. Remove response replay, the independent readiness IPC channel/callback, and stale-response generation guards. Seed delivery is addressed to registered observer IDs, so a new subscriber in an existing window gets its complete snapshot without replaying it to other subscribers. Per-observer delivery bookkeeping prevents duplicate initial seeding during recovery.

Also close the migration gaps: reconcile the whole Turn buffer, bind compaction Stop and its pending request to the Host Turn, invalidate observation when recovery starts, suppress retained tool/compaction activity when unavailable, and remove unused single-Turn output, confirmation, phase, and terminal-effect paths.

Verification

  • Regression reproduced against the old preload: registration acknowledgement published ready before any execution or content arrived.
  • Focused Node tests passed for observation/preload, observer lifecycle, candidate restoration, observation IPC, WorkHub transcript recovery, and content readiness. Coverage includes both IPC arrival orders, running roots without content, same-window subscribers, repeated recovery, and disposal during seed consumption. Tests ran serially; no Electron windows or E2E were started for this revision.
  • Desktop main, preload, renderer, and Storybook typechecks passed. Affected-file lint, renderer architecture checks, surface inventory, and whitespace checks passed. Architecture token budgets were reduced to match deleted paths.
  • Earlier revisions passed the Desktop/UI suites and browser/E2E checks documented in the review history. Those broad suites were not rerun locally for this revision; current-head CI and external rereview remain required before merging. Repository-wide formatting remains unverified.

AI use

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

OpenAI Codex implemented and tested the change. Claude Opus and Gemini provided architecture consultations; Raft reviewers independently reviewed earlier revisions. AI-authored commits carry Generated-by trailers.

Checklist

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

Affected checks passed; repository-wide formatting remains unverified.

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

@github-actions github-actions Bot added the effort/XL Under 2500 readable lines label Sep 12, 2026
Separate observable activity from retained Stop targets in Side Chat and WorkHub. Remove unused snapshot surface and make observation type imports explicit for Knip.

Generated-by: OpenAI Codex
Allow repeated pending invalidation during failed Host restoration while requiring the observation error and excluding synthetic Runtime events.

Generated-by: OpenAI Codex

@Sun-GLiang Sun-GLiang 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.

Design verdict. Reusing the existing Host observation channel, shared execution projection and TurnView is the appropriate direction. Four P2 behavior gaps remain in content handoff, compaction Stop and unavailable-state presentation, plus one P3 cleanup of paths whose production consumers were removed. The five findings are attached inline; I would address the P2 items before merging.

AI-assisted review of 97bcd881c786e1c8964e0d3e40f59288702a5b42. Its only change since the reviewed 7012fc29f is the restored-observation test; all five affected production locations are unchanged.

Verification. 126 existing focused tests passed against the reviewed source. Additional local reproductions exercised the mounted content reconciler, the actual Side Chat hook, a held Host-subscription recovery and ChatView rendering. Changing only the content reconciler to the existing whole-buffer selector fixes the late-seed reproduction. These checks did not include a full build or Electron end-to-end rerun. The latest test check is still running at submission time; the previous pending-count test failure has been addressed by the new test-only commit.

中文

设计结论。 复用现有 Host 观察通道、共享执行投影和 TurnView 的方向合理。目前仍有 4 项 P2 功能问题,涉及内容交接、压缩停止和观察不可用时的展示,另有 1 项 P3 无用代码清理;五项均附在行内。建议合并前解决 P2 项。

本次为 AI 辅助审查,对应 97bcd881c786e1c8964e0d3e40f59288702a5b42。相比已审查的 7012fc29f,最新提交只修改恢复观察的测试,五处相关生产代码均未变化。

验证。 已审查源码的 126 项现有定向测试通过;另外使用实际挂载的内容清理器、Side Chat hook、延迟恢复的 Host 订阅和 ChatView 渲染复现问题。仅将内容清理器改为使用已有全缓冲选择器,迟到 seed 的复现即恢复正常。本轮未执行完整构建或 Electron 端到端复跑。提交时最新 test 检查仍在运行;此前 pending 数量断言的失败已由新增的测试提交处理。

Comment thread apps/desktop/src/main/runtime-host-session-observer.ts Outdated
Comment thread packages/ui/src/chat-view.tsx
Comment thread apps/desktop/src/renderer/features/workbar/tools/side-chat/use-quote-companion.ts Outdated
@orangeCatDeveloper

orangeCatDeveloper commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

I think this still needs changes before merge. I found five items: two correctness issues in the multi-Turn handoff, one clear simplification opportunity, and two presentation cleanups.

  1. A buffered older Turn can be rendered after a newer persisted Turn. createTranscriptProjection reduces every buffered Turn onto the settled transcript, while overlayLiveTurn appends a Turn that is absent from the currently loaded transcript window. If Turn A remains buffered while the user is away, and the tail loaded on return contains only the newer Turn B, the result is ordered as B then A. The synthesized startedAt: Date.now() also makes A look new. Please add a regression with settled [turn-b] and buffered [turn-a], then preserve a real ordering anchor or define a range-aware policy instead of appending missing buffered Turns at the tail.

  2. LiveTurnReconciler does not observe all state that it reconciles. It subscribes through selectLiveTurn, which returns only the active (or last) Turn, but reconcile processes the entire per-session buffer. While B is active, an update to retained Turn A preserves B's object identity, so useExternalStoreSelector suppresses the render and A is not reconciled against durable messages. A stale or partial A projection can therefore continue to replace its complete durable content until an unrelated update occurs. This component is childless and explicitly intended to follow projection changes, so subscribing to selectLiveTurns is both simpler and correct.

  3. Side Chat duplicates the shared interaction reducer. applyCompanionInteractionEvent repeats the request, acknowledgement, tool-result, and terminal branches already implemented by reduceInteractionQueues. The semantics are currently identical, including Turn-scoped terminal cleanup. Reusing the shared reducer directly (or through a thin compatibility wrapper) removes an unnecessary second event classifier and its drift risk.

  4. The architecture index contradicts the implemented state. ARCHITECTURE.md still labels the new design “not yet implemented,” although this PR and the linked design document say it is implemented.

  5. The PR description still needs the repository-template pass. It is missing the standalone Fixes/Refs line and a minimal raw before/after evidence block.

The latest test-only commit does not change the first three code paths above.

Scope readiness to the Composer submit control and wait before consecutive queued and steering submissions.

Generated-by: OpenAI Codex
Reconcile the whole live buffer, bind compaction Stop requests to Host turns, invalidate observations when recovery starts, and gate retained activity presentation on current Host observation. Remove unused migration interfaces and place reconciliation in the Conversation feature.

Generated-by: OpenAI Codex
Regenerate both Astryx surface inventories for the Conversation-owned reconciler path.

Generated-by: OpenAI Codex
Resolve overlapping E2E readiness fixes by retaining the Composer-scoped submit selector and shared admission wait.

Generated-by: OpenAI Codex

@zhiiw zhiiw 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.

Scoped review (conversation execution projection, turn-buffer handoff, observability, and the removal surface), at exact head 396cc49f (verified unchanged at publish time; test completed/success on this head; mergeable). This covers one assigned slice of the PR, not the whole diff — the aggregate review is coordinated elsewhere.

No P0–P2 in this scope. One P3 below.

Turn-buffer handoff. The boundary is packages/ui/src/live-turn-buffer.ts: content is retained per turnId and released only when the durable assistant evidence for that turn arrives (reconcileLiveTurnBuffer, including the late-callback recovery loop for terminal turns). During a handoff, the old turn's content stays on the old turn (applyLiveTurnBufferEvent routes by turnId; a newer snapshot cannot overwrite it), while the running state and Stop target belong to the new turn (tailTurnId in chat-view.tsx comes only from the Host-snapshot active turn — no tail fallback). Between Enter and Host admission the user sees the message delivery row, not a fake spinner — a deliberate honesty-over-immediacy change, covered by the design doc's acceptance table.

Observability loss. Every activity cue is availability-gated, verified site by site: the main-chat answer footer (chatTurnActivity returns undefined when available is false — application/contracts/session-execution.ts:32), the turn-internal tool timeline (activityObserved reaches tool-activity.tsx's running/isPulsing via ProcessingBlock/TurnTimelineEntry), the compaction row (shows the unobserved copy instead of a spinner, chat-turn.tsx:610-617), the sidebar pulse (selectStreamingSessionIds gates on execution?.available first), and the WorkHub/Side-Chat surfaces (same gate). The Stop target deliberately survives unavailability (activeHostTurn filters terminal states only), and the Host-side command matching still rejects a turn that ended while unobserved. Observation failures now travel as host_observation_error on the observation channel instead of synthesizing Runtime terminal events, and setExecution marks the retained snapshot available: false rather than dropping it.

The removed 616 lines. Every deleted mechanism was checked for residual references on the merged-with-main head (this matters: main brought 107 files in via the merge, so the check was re-run there): the model-wait classification, delayed-flag cues, settled-session transient cleanup, the message-id-as-turn arm/rebind/disarm machinery, confirmLiveTurn, and catalog-driven transient cleanup are all gone with zero callers — including from main's newly added surfaces, none of which call into the narrowed paths. Interaction teardown narrowed from session-wide to turn-scoped (clearInteractions takes turnId; BaseEvent.turnId is a required string, so the wipe-all fallback is unreachable). The new structures are four small single-purpose modules; I found nothing further removable.

[P3] apps/desktop/src/main/__tests__/fake-dom.ts:80 still carries a comment referencing the deleted useDelayedFlag ("schedules the turn-wait cues through the window") — the module is gone, and the comment will mislead the next reader. (The file is outside this PR's diff, so this is here rather than inline; it is a one-line deletion.)

Verification boundary, stated honestly: per the constraints of this review I read the full cumulative diff and traced the seams, but did not run the test suites locally; the hosted test job is green on this head, and the author's own E2E/Storybook evidence is cited, not independently reproduced by me.


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

简体中文

范围审查(会话执行投影、Turn 缓冲交接、观察可用性、删除面),锚 exact head 396cc49(发布前复核未漂,test 绿)。无 P0–P2。交接边界在 live-turn-buffer:内容按 turnId 归属、durable 证据到达才释放;交接中旧轮内容不被新轮覆盖,运行状态与 Stop 目标归新轮;Enter 到 Host 承认之间显示交付行而非假转圈(故意的诚实优先行为变化)。观察不可用时所有活动提示逐处核实全部收敛(主对话底部、工具时间线、compaction 行、侧栏脉冲、WorkHub/SideChat 同一 available 门),Stop 目标故意保留,Host 侧匹配校验兜底。删除 616 行在并入 main 后的新 base 上重跑引用检查全部零残留(含 main 新进的 107 文件)。一条 P3:fake-dom.ts:80 注释引用已删的 useDelayedFlag(文件不在 diff 内故写在此处)。验证边界如实:按本线约束只读全 diff 追缝,未本机跑套件,hosted test 绿,作者的 E2E/Storybook 证据为引用未独立复现。

@zhiiw zhiiw 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.

Approving at exact head 396cc49f (verified unchanged at approval time; test completed/success; mergeable).

This approval stands on my own scoped review (published as the preceding COMMENT): the conversation-execution projection, turn-buffer handoff, observability-loss behavior, and the removal surface, with no P0–P2 found there and one P3 (the stale useDelayedFlag comment in apps/desktop/src/main/__tests__/fake-dom.ts:80) noted. The coverage map, the verification boundary (full-diff reading and seam tracing, no local suite runs under this review's constraints, hosted test green on this head), and the migration-gap distinction are all in that review and remain valid.

It endorses no other reviewer's conclusion and does not address merge readiness outside that scope.


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

简体中文

本 approve 基于我自己已发布的 scoped COMMENT(结论不变:范围内无 P0–P2,保留那条 P3)。覆盖范围、验证边界、迁移缺口区分都在那条里,仍然有效。不替另一位 reviewer 背书,不表态范围外的合并就绪性。

Deliver execution and content together through the observation stream and publish readiness only after consumption. Remove invoke seed replay and independent readiness IPC; address initial delivery to registered observers and preserve cancellation and recovery ordering.

Generated-by: OpenAI Codex

@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.

Approve at a98c5776daa5ead95efee6dea1f0a1ac3558b62c. This is a re-review: I reviewed an earlier revision of this pull request and raised the readiness-ordering problem that the latest commit addresses, so this is not a first-time independent reading.

Why the earlier ordering problem is now structurally absent rather than mitigated. Readiness, the execution projection and the accumulated content now travel as one message on the single ordered session event channel. The renderer applies the execution projection, then replays the seeded events, then publishes readiness, all within one synchronous block. The registration reply no longer carries events and no longer publishes readiness. There is no longer a second delivery path whose arrival order has to be reasoned about, so the previous failure could not be reconstructed on this revision.

Boundaries I checked on this commit.

  • Initial activation ordering. Readiness strictly follows consumption in the same block; a seed that finds the subscriber already disposed stops before publishing readiness.
  • Several subscribers on one window. First-delivery state moved from the window to the individual registration, and each seed names the registrations it is for. A new subscriber's seed is filtered out by existing subscribers, and the test suite pins exactly that.
  • Joining during recovery. Recovery reuses the same seeding path for every registration on the window. A subscriber seeded by recovery while its own registration call is still suspended finds itself already recorded as delivered when that call resumes, so it is not seeded twice.
  • Cancellation and failure. A failed subscription rejects the registration wait, detaches the observer and surfaces the error to the renderer's error path. The seed loop re-checks disposal on every step.
  • Main-to-preload wiring. Both new observation messages are sent through the same channel helper as ordinary events and are part of the declared message union; the registration call's return type was narrowed to match a reply that no longer carries events.
  • Removal of the renderer's generation guard. The protection did not disappear: the renderer still rejects callbacks from superseded attempts, and the preload layer only accepts a seed naming its own registration, which is freshly created per subscription. Because the pending and seed messages now share one ordered channel, a generation counter is genuinely redundant here rather than merely unused.

Ablation, and an honest limit on it. I checked each new assertion against the code path it replaces: the assertion that a registration acknowledgement publishes nothing, the exact execution-then-content-then-ready order, the refusal to replay older state on a late acknowledgement, the isolation of a new subscriber's seed from existing subscribers, and the recovery case naming several registrations. Each one contradicts a behaviour that the previous revision had, so none of them is vacuous. However, this is a reasoned ablation, not an executed one: I did not revert the fix and observe these tests turn red, so I can state that they could not pass against the previous behaviour, not that I measured them failing.

What I did not verify. I read code and hosted check results only; I ran no tests, build, end-to-end or Electron session. Real inter-process delivery ordering, timing windows and recovery races were therefore not measured. I traced the subscription owner's failure handling only along the paths that reach seeding and readiness, not its full retry behaviour under repeated establishment failures.

Gate state. The required check is terminal and successful on this commit. Note that the approval currently recorded against this pull request was submitted on 396cc49f, which predates the seeding rewrite; this approval is the one bound to the current head. No merge was performed.


Automated review notice. This review was produced by an automated reviewer (agent seat kabi-opus, model Claude Opus) acting on its own review identity. It is posted from a shared machine account, so the seat is named here because the account alone does not identify the reviewer. It is not an independent human review and does not substitute for one. Its scope and limits are stated above.

@zhiiw zhiiw 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.

Scoped re-review (same slice as before: observation delivery, turn-buffer handoff, observability, and the three conversation surfaces' consumption boundaries) at exact head a98c5776 (verified unchanged at approval time; test completed/success on this head). This is my own second pass, not a restatement of anyone else's conclusion.

No P0–P2 in the delta. The previously noted P3 (the stale useDelayedFlag comment in fake-dom.ts) was explicitly accepted by the maintainer as wontfix and is not re-raised.

The initialization-order rework, verified rather than assumed:

  • Readiness now follows consumption: the observe reply only confirms registration (Promise<void>); the seed (execution + content events) travels as one host_observation_seed message on the same ordered channel as live updates, and ready is published only after the seed's events have been applied (preload.ts:2306 area). The invoke-reply replay loop, the separate ready IPC, the old onSeeded entry, and the reply's generation parameter are all gone.
  • New subscriptions are isolated from old ones: the seed names its observerIds, and preload drops seeds not addressed to this observer; per-registration seeded tracking (moved from the target group to the registration) means an observer admitted during recovery gets its own seed while already-seeded observers are not re-seeded, and a re-observe() of an existing observer waits for readiness and seeds only if unseeded.
  • Recovery reseeds through the same message: a replacement snapshot is delivered as a seed carrying terminal-then-active events, instead of raw broadcasts plus a separate ready emission; host_observation_pending on the same channel replaces the old seed-phase channel, so ordering between "stale" and "fresh" facts is the channel's own order.
  • The content/durable handoff is untouched: the buffer and reconcile paths from the prior round are unchanged; Side Chat's onSeeded consumer is preserved through the workbar shim (ready phase → onSeeded), and WorkHub consumes the phase callback directly.
  • The dropped generation check in live-content-seed.ts is justified by the new ordering guarantee (ready can only follow its own seed on the same ordered channel, and stale subscription attempts are already fenced by the attempt counter) — reasoning-verified, not test-verified by me.

Verification boundary, stated honestly: per this round's constraints I read the full delta and traced every seam, ran no suites locally, and rely on the hosted test job (green on this head) plus the author's lint/type/architecture checks. The initialization race windows (seed-vs-live interleavings under real reconnects) are exercised by the author's updated observer/preload tests, which I read but did not execute.

This approval covers this slice only and endorses no other reviewer's conclusion; merge remains for the coordinator once the other line closes.


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

简体中文

范围复审(同一线 B:观察交付、缓冲交接、观察可用性、三个面的消费边界),锚 exact head a98c577(复核未漂,test 绿)。增量无 P0–P2;旧 P3 维护者已明确不修,不重提。初始化收拢核实:ready 跟随消费(seed 走同一有序通道,消费完才 ready;回包只确认注册),回包重放/独立 ready IPC/旧 onSeeded/旧 generation 参数全删;seed 带 observerIds,preload 丢弃不属于自己的 seed,注册级 seeded 使恢复期新订阅拿到自己的 seed 而旧订阅不重复;恢复重放也走同一 seed 消息,pending 与事实的先后就是通道顺序。缓冲交接面本 delta 未动;Side Chat 旧消费者由 workbar 兼容垫保留,WorkHub 直连。live-content-seed 删 generation 校验的合理性是推理验证(有序通道 + attempt 计数防陈旧),我未跑测试——验证边界如实:按本轮约束只读增量追缝,hosted test 绿,作者的 observer/preload 测试我读了没跑。本 approve 只覆盖本范围,不替其他线背书,不 merge。

@Astro-Han
Astro-Han merged commit f414859 into main Sep 12, 2026
1 check passed
@Astro-Han
Astro-Han deleted the fix/conversation-host-projection branch September 12, 2026 09:53
Shouly pushed a commit to Shouly/maka that referenced this pull request Sep 13, 2026
)

Thirty-one upstream commits. The one that reaches the new renderer is apache#5170,
which gives the Renderer the transcript window: Main keeps a tail cache and
answers page requests pass-through, `loadBefore` / `loadAfter` return a page,
`loadAround` / `loadLatest` a reset, `acknowledgeTail` is new, and a batch
carries `extends` / `coversFrom` / `navigation` instead of
`evictedDurableSequences` / `completedOverlayMessageIds`. Also in: apache#5217's
observation contract (`subscribeEvents` loses `onSeeded`; readiness follows
seed consumption as the `ready` phase, and the execution projection it offers
is not consumed here yet), the memory work across composer and stream
(apache#5153), interactions cleared per Turn on abort/complete (apache#4562), Session
bundles and external agents in main/preload (apache#5197, apache#5164), Code Mode
(apache#3615, apache#5219), and the scheduled-task snooze fix (apache#5226).

Resolution per the sync policy: conflicts under the old renderer's trees,
packages/ui's deleted components, their stories, e2e specs and the main tests
that import them stay deleted; upstream's new files in those trees are dropped
(`application/contracts/settings-presentation`, `features/external-agent-settings`,
`features/session-bundle`, `workhub/ui/return-button`, `model-wheel-picker`,
the prompt-rail and live-turn-buffer tests, `workhub-return-rail.spec.ts`).
The renderer side of apache#5217 (one live Turn per Session → a buffer keyed by
Turn, `liveTurn` → `liveTurns`, `phase` gone) stays out: `packages/ui`
`live-turn-projection.ts`, `transcript-projection.ts` and their tests keep
ours and `live-turn-buffer.ts` is dropped; `session-event-handlers.ts` keeps
ours plus upstream's display-frame scheduler. `packages/ui`
`conversation-copy.ts` keeps `transcriptGap` (our gap rows use it),
`transcript-row-projection.ts` is restored, `use-pending-selection.ts` goes.
Astryx stays out of package.json and the lockfile; `@ai-sdk/provider-utils`
moves to 5.0.40 and the `@ai-sdk/code-mode` override lands.

Re-implemented for the new contract:
- `lib/ported/desktop-transcript-range-store.ts` and
  `transcript-reading-position.ts` are re-ported from upstream head (the
  previous copies were format-only ports of the old versions);
  `TranscriptReadSupersededError` lives in the latter, and
  `display-frame-scheduler.ts` joins `lib/ported`.
- `store/active-session-store.ts`: the window is the store's — the display
  follows a store subscription rather than `accept`'s return; the paging gate
  and `loadTranscriptHistory` are gone (the controller refuses a read against
  an edge it already read), `loadHistory` keeps only the gap-row indicator;
  `prefetchHistory` and `retainWindow` serve `useChatScroll`'s geometry-driven
  filling and trimming; `setReadingAnchor` only moves the bookmark; the
  bookmark re-anchors after a replica generation change, by sequence within a
  Host epoch and by Turn through the landmark index across one; a read
  superseded by an epoch change is not an error.
- `SessionView` passes `onPrefetchHistory` / `onRetainWindow`; the gap rows
  and the return-to-latest button keep their explicit commands.
- `bridge/sessions.ts` drops `onSeeded`.
- Main tests for the range store, navigation race, overlay settlement and the
  two new probes are upstream's with paths under `lib/ported`; the
  reading-position test keeps upstream's pure-module cases (send pinning,
  overlay-only bookmark, superseded read) — the shell-shaped cases live with
  the store's tests.
- `settings-sections.ts` and the copy files name core's new `external-agents`
  section id as a deferred page.
- Ported apache#5226: an edit that leaves the schedule fields alone omits
  `schedule` from its patch, so the Host keeps a snoozed fire
  (`scheduled-task-form-payload.ts`, `ScheduleFormDialog.tsx`, a test in
  `scheduled-module.test.tsx`).

Also in this tree, found while verifying the sync and not caused by it: a live
Turn's finished steps vanished after switching to another task and back,
leaving only "Working on it…". The Host re-seeds only what is still incomplete
(the streaming text, pending interactions) and Main's transcript overlay is
bootstrapped once per replica, so the steps that finished while the Session
was on screen existed only in the renderer's live projection — which the
store wiped on every selection and reseed. The projection now survives the
switch (a reseed drops only the incomplete text and thinking it replays; a
Turn that ended meanwhile is retired by the transcript it left behind).
`test:streaming-switch` drives the real app through it with a new fake-backend
scenario that settles a text step and a tool call, then holds the Turn open.

The compatible-change declaration `base64-length-allocation.json` is re-pinned
from 143 to the epoch this branch carries (147, upstream's own): upstream left
it at the epoch of its commit and its per-commit hook never re-judged it, while
our merge stages it next to the epoch bump. Its reason (Base64 byte counting
in `artifact.ts` / `session-transcript.ts` without observable change) still
holds against the protocol as merged.

Gates: build:test + build:renderer, typecheck, biome lint and format, locale
hygiene, ASF headers, renderer architecture ledger (rewritten with `--write`;
the new range store's `window` local reads as environment capabilities to the
checker), e2e budget, third-party notices, knip (same findings as before the
merge), desktop dist tests (1599), renderer state (282), Electron smoke (44
checks, no renderer errors), core-dialogue smoke, streaming-switch smoke. `packages/storage`
`workspace-identity` (git worktree ENOTEMPTY) is a parallel-run flake that
passes in isolation, as is `packages/eval` `lifecycle-boundaries` (relay
cancellation timing); `packages/runtime` `model-adapter-onerror` fails on this
machine before and after the merge (asynchronous activity after the test
ended; the file is unchanged this round).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Shouly pushed a commit to Shouly/maka that referenced this pull request Sep 13, 2026
…f0a5d)

Eight upstream commits. One reaches the new renderer's contracts: apache#4878
returns expected failures as codes across IPC — the five Session setters
(`setPermissionMode`, `setCollaborationMode`, `setOrchestrationMode`,
`setModelConfiguration`, `setThinkingLevel`) answer
`DesktopSessionUpdateResult` instead of throwing, `abandonPlanProposal`
answers `PlanControlIpcResult` like its siblings, `send` / `submitMessage`
gain an `attachment_blocked` refusal, and the attachment guard throws a typed
`AttachmentIngestBlockedError` in place of the `attachment_ingest:<code>`
message token. Also in: apache#5216 makes HTML artifacts directly openable
(`app.showArtifactInFolder` beside `openArtifactPath`, which now hands an
HTML artifact to the default app; `isArtifactUserVisible` admits HTML tool
results), apache#5198 unifies WorkHub conversation identity and Host-owned choices
(an Astryx `ChoicePanel` in packages/ui, `keyboardHint` copy, a
`preserveFocus` scroll target), apache#5249's skill picker fix
(`selectedSkillIds` in `chat-input-behavior`), apache#4815 admits structured-only
Messages (`hasMeaningfulMessageContent` in core), apache#4862's ACP live session
lifecycle in the CLI, apache#5204's workbar tab scrollbar css and apache#5180's wider
locale hygiene gate.

Resolution per the sync policy: conflicts under the old renderer's trees,
packages/ui's deleted components, stories, e2e specs and the main tests that
import them stay deleted, and upstream's new files there are dropped
(`features/workhub/model/workspace-name.ts`, packages/ui's `choice-panel.tsx`
and its `index.ts` export, the `styles/base.css` / `workhub.css` /
`maka-tokens.css` edits, `expected-error-presentation.test.ts` and the WorkHub
main tests). git's rename pairing had put upstream's
`features/session-settings/ports.ts`, `features/workhub/testing.ts` and
`platform/desktop/create-session-settings-services.ts` into
`bridge/e2e-fixture.ts`, `components/ui/skeleton.tsx` and
`lib/ported/display-frame-scheduler.ts`; all three keep ours. The renderer
architecture ledger keeps ours, rewritten with `--write`. The e2e budget and
`transcript-scroll-cost.spec.ts` keep ours; upstream's new
`expected-failure-feedback.spec.ts` is trimmed to its second case (the IPC
round trip of the setting and Plan codes), the first needing the WorkHub
surface this build does not ship.

Re-implemented for the new contracts:
- `bridge/sessions.ts` unwraps every update result and rethrows a refusal as
  `ExpectedOperationError` (new `bridge/expected-operation-error.ts`, ported
  from upstream's `operation-diagnostics.ts`), so the turn actions store and
  every caller keep awaiting a summary. `localizedShellErrorMessage` renders
  the code through the new `updateFailures` copy on every surface and
  `AttachmentIngestBlockedError` by its `code`; `sessionSettingFailureCopy`
  is upstream's.
- `ChatInput` routes an `attachment_blocked` refusal through upstream's
  `showSubmissionFeedback` and keeps the draft with the ingest reason;
  `showSkillInvocationFeedback` stays exported for the partial-success toast.
- `FilesTab` reveals through `showArtifactInFolder`; an HTML row shows "View
  in Maka", its menu offers "Open in Default App" first, and its preview's
  external action opens rather than reveals. `artifact-copy` gains
  `viewInMaka` / `openInDefaultApp` in three locales.
- `TipTapEditor` hides Skills already in the draft from the picker (apache#5249);
  the chips are atoms here, so the set is read from the document rather than
  from the `/skill:x` text upstream scans.
- `composer-state.test.ts` asserts the typed preflight error.

packages/ui: `use-chat-scroll.ts` merged cleanly (`preserveFocus`) on top of
our `holdTurn` extensions; `conversation-copy.ts` and `chat-input-behavior.ts`
take upstream's additions. apache#5217's live-turn buffer stays out as before.

The compatible-change declaration is unchanged this round. The release
checklist's baseline note records that the eight commits add no unlisted
renderer surface.

Gates: build:test + build:renderer, typecheck, biome lint and format, locale
hygiene (the widened apache#5180 gate), ASF headers, renderer architecture ledger
(rewritten with `--write`), e2e budget, third-party notices, knip (39 unused
files, unchanged from the twelfth sync), workspace dist tests (desktop 3484
of 3498 with 14 skipped, every other workspace green), Electron smoke (44
checks, no renderer errors), core-dialogue smoke, streaming-switch smoke, and
the trimmed `expected-failure-feedback` e2e case against the real preload.
`packages/runtime` `model-adapter-onerror` fails on this machine before and
after, as in the eleventh and twelfth syncs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/XL Under 2500 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants