Skip to content

feat(desktop): focus previews with a shared composer and progress card - #5482

Open
testikun wants to merge 13 commits into
apache:mainfrom
testikun:codex/desktop-focused-preview
Open

testikun wants to merge 13 commits into
apache:mainfrom
testikun:codex/desktop-focused-preview

Conversation

@testikun

@testikun testikun commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Files and Browser can focus the preview while retaining the same mounted composer, draft and attachments. Drag the center divider left or use the focus control; restore returns to the original split width. WorkHub and focused previews share ProgressCard and the Working / Paused / Needs attention / Finished vocabulary.

Fixes #5469. Workspace-reference opening remains tracked by #2664.

Review changes

  1. Escape exits focus without stopping execution. The composer offers Escape to its preview host after local popup handling. The explicit Stop button retains its normal behavior. Native testing verified that the model continued after Escape restored the split.
  2. Preserve reading position. Completed replies first open at the beginning; live output follows only while the reader stays at the end. Collapse and minimize/restore preserve the selected offset. Focus attributes now remain stable during height measurement, avoiding the transient narrow layout that displaced the reader during restore.
  3. Explain bounded content and open the full conversation. The 16,000-character and 12-item limits show a partial-content notice. The expanded card always offers “View full conversation”, restoring the split and passing the relevant turn to existing conversation navigation.
  4. Continuous container width. The composer uses the available container width up to 960px, with no 990px viewport override. Native 990px and 991px windows retain the same composer height. Long Git branch labels shrink before reaching Send/Stop.

The default now contains just the compact status row and composer; reply details expand on demand. Main has been merged and the conflicts resolved.

Real application captures

These are actual macOS Electron window screenshots with a live provider, using the local Codex connection and gpt-5.6-terra / medium. Prompts were sent through the actual composer. The model generated the HTML with Write and produced the long reply and real Bash calls. No mocked backend or seeded reply supplied these captures. The Browser loads a real native WebContentsView.

Full illustrated acceptance notes

Compact default and completed reply

Compact status and retained draft

Completed reply starts at the beginning, with truncation notice and full-conversation action

Reading position before → after minimize/restore

The same Weather paragraph and Gear heading remain at the same position, together with the unsent draft.

Reading position before minimizing

Reading position after restoring

990px → 991px native window

990px window

991px window

Escape, full conversation, native Browser and explicit Stop

Escape restores the split while the real turn continues.

Running turn after Escape

View full conversation restores the split and targets the corresponding turn.

Full conversation

The native webpage leaves room for the composer, including at 720px.

Native Browser

720px Browser window

Explicit Stop retains received content and displays Paused.

Stopped turn retaining received text

Verification

  • Full GitHub CI passed for 0fc0491ee, including the native desktop tests and Storybook interaction checks.

  • The native WorkHub menu test closes the popup on its owning window. Preview scroll restoration runs after the parent applies its focused width; the original 360px regression assertion is retained.

  • Real native application: generated HTML, divider focus, draft retention, completed-reply first position, reply collapse, composer minimize/restore, both truncation bounds, full-conversation navigation, local-menu Escape, composer Escape during execution, explicit Stop, webpage input and window resizing.

  • Desktop production build; renderer and Storybook TypeScript; changed-file Biome; renderer architecture; locale hygiene; Astryx inventory; staged source-header checks passed.

Existing preview rendering and security limits remain unchanged. Dedicated Office/CSV/Notebook viewers remain outside this change.

AI use

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

Codex implemented and reviewed the changes, operated the real app, captured the screenshots and wrote the description. GPT-5.6 Terra generated the test document and replies. Commits include Generated-by: Codex; human review is required before merging.

Checklist

  • Regression coverage updated for the changed interactions
  • Changed-code checks and native acceptance listed above completed
  • Behavior changes illustrated with real-provider application captures

@github-actions github-actions Bot added the effort/XL Under 2500 readable lines label Sep 18, 2026
Preserve the composer and split width, allow empty browser focus, and improve progress card hit targets and native titlebar clearance.

Generated-by: Codex
Show a release hint at the divider threshold, return drag focus to the mounted composer, and support browser Escape without navigating an unsubmitted address.

Generated-by: Codex
Keep the bounded live projection when no persisted assistant reply exists, label interrupted turns, and avoid projecting an earlier turn into a newly running turn.

Generated-by: Codex
Add eleven real Electron screenshots covering split, drag focus, latest reply, reading mode, restore, native Browser, live and stopped turns, and a narrow window.

Generated-by: Codex
@testikun testikun changed the title feat(desktop): focus file and browser previews with shared composer feat(desktop): focus previews with a shared composer and progress card Sep 18, 2026
Allow only the two focused-preview layout declarations while continuing to reject palette tokens and unknown custom properties. Add a regression check for both sides of the guard.

Generated-by: Codex

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

English

The direction makes sense: focusing the document while preserving the same composer, draft, and attachments supports a useful reading-and-follow-up workflow. Sharing ProgressCard with Workhub is also a good choice. I’d address these interaction issues before merging:

  1. Make Escape predictable in focused mode.
    Escape restores the split view from the preview toolbar, but stops a running turn from the composer. Entering focus by dragging also moves keyboard focus into that composer, making an attempted exit potentially interrupt the task. After dismissing any local popup, Escape should consistently leave focused mode; stopping execution should remain an explicit action.

  2. Preserve the reply’s reading position.
    Expanding a reply scrolls directly to the bottom, including completed replies. Minimizing and restoring also loses a manually selected reading position. Open completed replies at the beginning, follow live output only while the reader remains at the end, and preserve the position across collapse/restore.

  3. Make partial content explicit and provide access to the full conversation.
    Expanded replies are capped at 16,000 characters, and the live projection retains only the last 12 items. Neither limitation is visible to the reader. Keep the bounds if needed, but show a truncation notice and a “View full conversation” action that restores the conversation at the relevant turn.

  4. Remove the layout jump around 990px.
    Increasing the viewport from 990px to 991px shrinks the card from approximately 958px to 674px and switches the composer into a taller layout. A wider window consequently obscures more of the document. Use a continuous, container-based width rule and adapt the controls to the available space.

For consistency with Workhub, I’d share the status vocabulary—working, paused, needs attention, finished—and the path from a summary to the full conversation. The secondary actions can remain different: hiding a floating progress window and minimizing an in-document composer serve different purposes.

Visually, a compact status row plus the composer would make a calmer default, with reply details expanded on demand. The document should remain the primary surface.

中文

整体方向合理:让文档成为主视图,同时保留同一个输入框、草稿和附件,适合边阅读边继续提问。与 Workhub 共享 ProgressCard 也值得保留。建议合并前修正以下交互问题:

  1. 统一聚焦模式下 Escape 的行为。
    在预览工具栏按 Escape 会还原分栏,在输入框中却会停止正在运行的任务。而通过拖动进入聚焦后,焦点恰好会自动移到输入框,用户想退出聚焦时可能误停任务。建议先关闭局部弹层,再统一退出聚焦;停止执行保留明确入口。

  2. 保留回复的阅读位置。
    展开回复会直接滚到底部,已完成的回复也如此;手动读到中段后收起、恢复,阅读位置也会丢失。建议已完成回复首次从头展示,仅在用户停留于末尾时跟随实时输出,并在收起、恢复之间保留位置。

  3. 明确说明内容不完整,并提供完整对话入口。
    展开的回复最多显示 16,000 字符,实时内容仅保留最后 12 项,但界面没有说明这些限制。可以保留容量上限,同时显示截断提示,并提供“查看完整对话”,还原分栏并定位到对应回合。

  4. 消除 990px 附近的布局跳变。
    窗口从 990px 加宽到 991px,卡片反而从约 958px 缩到 674px,输入区也变成更高的分行布局,导致更宽的窗口遮住更多正文。建议采用连续、基于容器的宽度规则,再根据实际空间调整控件布局。

与 Workhub 应统一的是“处理中、已暂停、需要查看、已结束”等状态语言,以及从摘要进入完整对话的路径。次要操作可以不同:隐藏独立进度浮窗与收起文档内输入框,本来就服务于不同场景。

视觉上,建议默认保留紧凑状态行和输入框,按需展开回复详情,让文档始终占据主要视觉空间。

Preserve reading position, make Escape exit focused previews, expose bounded content and full conversation navigation, and use continuous composer widths. Verify with the live Terra provider in the native app.

Generated-by: Codex
Keep the real native-menu lifecycle assertions while avoiding Electron close-all teardown in the test. Remove superseded preview design notes and screenshot iterations from the PR.

Generated-by: Codex
Wait until parent layout effects apply the focused width before restoring the reader offset. Keep the existing 360px regression assertion and verify collapse, minimize, and settled-turn handoff without extra state.

Generated-by: Codex
@me2seeks

Copy link
Copy Markdown
Contributor

Review(对抗式复核,基于 main @ 0117d76c5

方向是好的:让文档成为主视图,同时保留同一个输入框、草稿和附件;与 WorkHub 共享 ProgressCard 也值得保留。上一轮 review 提的 4 条交互问题,我逐条核对过,已修好的部分确认如下

  • Escape 语义composer.tsx 新增 maka-composer-escape 自定义事件,预览宿主先于「流式中断」获得处理权,顺序正确。
  • 990px 跳变:旧的 990px viewport override 已删除,改为 width: min(960px, calc(100% - 2 * var(--space-4))) 的连续容器宽度。
  • 截断提示 + 查看完整对话recent-turn-overlay.tsxtruncated 判定、role="note" 提示和「查看完整对话」按钮。

不过还有 2 个真实缺陷和 1 个无障碍回归,建议合并前处理。


P1 — 聚焦状态可能「假聚焦」:UI 说已聚焦,布局却没生效,且没有兜底

apps/desktop/src/renderer/features/workbar/controller/use-focused-preview.ts:57-60

useLayoutEffect(() => {
  const frame = surfaceRef.current?.closest('.maka-detail-with-artifacts');
  setComposerTarget(frame?.querySelector<HTMLElement>(':scope > .mainColumn .maka-chat-layout > :last-child > :last-child') ?? null);
}, [input.host.activeId]);

这个 effect 只在 activeId 变化时运行一次。如果这一次 commit 里 surfaceRef.current 为 null,或选择器没命中,composerTarget整个会话保持 null。所有布局 effect 都在 !composerTarget 时静默 return,但 focusedPreview 仍然为真,并一路传到 workbar-host.tsx:221workbar-surface.tsx:559/572,于是面板按钮变成「还原分栏」、aria-pressed=true,而实际的聚焦布局从未发生。用户处在一个「声称已聚焦但并未聚焦」的状态,只能靠点那个按钮退出,没有重试、没有断言、没有 fallback。

另外这个深层选择器本身也脆弱:它依赖 Astryx ChatLayout 的内部 DOM(root → messageArea + dockContainer → dock → dockInner → composer)。我核对过,:last-child > :last-child 实际解析到的是 .dock,而不是 composer 本身,语义已经和注释不符。上游结构一变就会静默返回 null(或拿到错误的节点),直接触发上面的坏状态。

建议:进入聚焦前断言 composerTarget 存在;用稳定的 data-testid / class 替代深层选择器;找不到宿主时不要进入聚焦态。


P1 — 键盘 End 键会意外进入聚焦模式

use-focused-preview.ts:245-256 的进入条件是「拖动后剩余对话宽度 ≤ 240px」:

drag.width = Math.max(input.host.rightResizable._minSizePx,
  Math.min(drag.frame.clientWidth - 120, drag.startWidth + delta));
if (drag.width > drag.startWidth && drag.frame.clientWidth - drag.width <= 240) {
  drag.frame.dataset.previewCollapseReady = 'true';
}

而 Astryx ResizeHandle 的键盘处理(ResizeHandle.tsx:555-560)里,按 End 会调用 _onResizeMove(_maxSizePx - _size),即 600 - 当前宽度。由于 SESSION_WORKBAR_MAX_WIDTH = 600只要窗口宽度 ≤ 840px,条件 clientWidth - 600 ≤ 240 就成立——按一下 End(一个正常的「展开面板」操作)就会静默把对话折叠进聚焦模式。这是纯键盘可触发的意外进入,恰恰是这轮想避免的情况。

附带问题:唯一能进入聚焦的键盘路径就是这个意外路径,没有任何可发现的键盘入口;而折叠提示(.maka-preview-collapse-hint)只在拖拽越过阈值时才出现——功能既难发现又易误触。建议给聚焦一个明确、可发现的键盘入口,并把「进入聚焦」与普通 resize 手势区分开。


P2 — 共享 ProgressCardrole="status" 挪进 <button>,WorkHub 无障碍回归

对比 base:原 workhub-progress-card.tsxrole="status"独立 <span>,与 IconButton 平级;新的共享组件 packages/ui/src/progress-card.tsx:52-56 把状态 span 塞进了 <Button> 内部role="status" 是一个 live region,嵌在按钮里会改变读屏语义,属于共享抽象顺带引入的回归,而不是纯收益。建议把状态文本移出按钮,或明确说明这是有意为之并补上无障碍验证。


P3 — 测试未覆盖上述两条真实路径

apps/desktop/stories/session-workbar.stories.tsx 里用的是 _onResizeEnd: noop 的 mock,既没走真实键盘 End 路径,也没测 composerTarget 为 null 的降级。建议补一个真实 ResizeHandle 键盘用例和一个宿主缺失用例。


小结:P1 两条 + P2 一条建议修完再合;P3 可作为后续补强。上面每一条都附了文件与行号,方便核对。

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

方向认可,但合并前建议处理:P1 composerTarget 可能永久为 null 导致「假聚焦」且无兜底;P1 键盘 End 在 <=840px 窗口会意外进入聚焦;P2 共享 ProgressCard 把 role="status" 移入 button,WorkHub 无障碍回归。详见上方 review 评论。

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.

feat(desktop): focus a file preview and keep the session composer in the document view

3 participants