feat(web): open workspace picker from composer

This commit is contained in:
NI0317
2026-08-07 14:33:18 +08:00
parent 23a4c0a51e
commit c44c6c93c8
16 changed files with 165 additions and 33 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.md
2026-07-25-web-client-session-scope-and-provide-channel.md: 3c51f06fca23a495f0fbc0cc4f1c289edea07b3b
2026-07-25-web-client-session-scope-and-provide-channel.zh.md: 06fc1005785d9d11b52839f91c3bb4b99cad7d63
2026-07-25-web-client-session-scope-and-provide-channel.md: 42eaf47f08d0b1fe5abf4389accfdbae1097a529
2026-07-25-web-client-session-scope-and-provide-channel.zh.md: c55decd8b92bbc954811cc08df3a6b28a5662258

View File

@@ -132,5 +132,5 @@ Slot scope is the closed set `root | session-maybe | session`:
- Plugins gain session context isomorphic to the host's: per-session state hangs on the actx and mounts/tears down in one piece with the scope fiber, making leaks structurally impossible; two-session isolation is structurally guaranteed by the scope filter.
- The client object layer converges to a wire mirror: session identity, lifecycle, and capability adjudication all defer to the host entity — the input system (the next layer) always faces a session with a real Agent, and providers like slash/skill uniformly address by sessionId directly.
- Blank-session governance takes zero dedicated mechanisms: state rides one derived bit, visibility rides the unified list projection (only the current blank shows, as `New Session`), reclamation rides lazy persistence's existing contract (evaporation on restart), and the ordinary ceiling rides same-Workspace reuse.
- The cost: the id→ctx handoff discipline and provide's Concurrent discipline are conventions rather than type-enforced, pinned by review and tests; fully disabled input while no workspace is picked is an experience cost the product surface accepts (the price of the single state axis).
- The cost: the id→ctx handoff discipline and provide's Concurrent discipline are conventions rather than type-enforced, pinned by review and tests. The single state axis still withholds machine faces until a Session exists; the resident textarea routes activation to the Workspace picker during that interval ([decision](../feature/2026-08-07-workspace-picker-composer-entry.md)).
- Known gaps: approval/question recovery across prune (TODO); model selection returns in live-mutation shape (the host `selectModel` trio is ready-made, awaiting its own branch).

View File

@@ -132,5 +132,5 @@ slot scope 是闭集 `root | session-maybe | session`:
- 插件获得与 host 同构的会话上下文:逐会话状态挂 actx、随 scope fiber 一次拆装,泄漏结构性不可能;双会话隔离由 scope filter 结构性保证。
- client 对象层收敛为 wire 镜像:会话身份、生命周期、能力判别全部以 host 实体为准——输入体系(下一层)面对的永远是「有真 Agent 的会话」,slash/skill 等提供方一律以 sessionId 直接寻址。
- 空会话治理零专用机制:状态靠一个派生位,可见性靠统一列表投影(仅 current blank 以 `New Session` 展示),回收靠 lazy persistence 的既有契约(重启蒸发),常规上限靠同 Workspace 复用。
- 代价:id→ctx 换乘纪律、provide 的 Concurrent 纪律都是约定而非类型强制,靠 review 与测试钉住;「未选 workspace」期间输入全禁是产品面接受的体验代价(单一状态轴换来的)。
- 代价:id→ctx 换乘纪律、provide 的 Concurrent 纪律都是约定而非类型强制,靠 review 与测试钉住。单一状态轴仍会在 Session 存在前隐藏 machine face;这段时间内,常驻 textarea 会把激活操作转到 Workspace 选择器([决策](../feature/2026-08-07-workspace-picker-composer-entry.md))。
- 已知欠账:approval/question 跨 prune 恢复(TODO);模型选择以 live-mutation 形状回归(host `selectModel` 三件套现成,等独立分支)。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-07-workspace-picker-composer-entry.md
2026-08-07-workspace-picker-composer-entry.md: 42c24f86eac554ffd642199d6fe15b5d0513db28
2026-08-07-workspace-picker-composer-entry.zh.md: 4ff38599df122ef8b866798aef3d43b7c848ea18

View File

@@ -0,0 +1,29 @@
# Agent Note: The no-Workspace composer opens the existing picker
Status: implemented
English | [中文](2026-08-07-workspace-picker-composer-entry.zh.md)
## Problem
The [session-scope decision](../architecture/2026-07-25-web-client-session-scope-and-provide-channel.md) keeps one resident composer before a Workspace exists, but its textarea was disabled and only the smaller Workspace chip could open the picker. The largest and most familiar starting affordance therefore rejected the user's first click even though a recovery action was available on the same surface.
## Decision
While no Workspace owns the new Session, the resident textarea is read-only and activates the existing `conversation.hero.workspace` picker by pointer click, Enter, or Space. It exposes menu expansion state through `aria-haspopup` and `aria-expanded`. Message submission, command, permission, model, and other Session-scoped controls remain locked until Workspace selection creates or reconnects a real Session.
Workspace selection retains the existing owner and flow. `ConversationRoot` opens the picker, `WorkspacePicker` lists or creates the Workspace, and the same textarea DOM node becomes the editable composer after the Session arrives.
## Alternatives considered
**Keep the textarea disabled and emphasize the Workspace chip.** This preserves the old control boundary but leaves the dominant composer surface inert during the first action.
**Place a transparent button over the textarea.** A button has direct trigger semantics, but it creates a second focusable element over the resident textarea and complicates the DOM-identity transition that preserves focus, IME, and draft behavior.
**Accept a draft before Workspace selection.** This would require a client-owned draft Session or another pre-Session state axis. The feature only needs a discoverable path into the existing picker.
## Consequences
The first composer click now continues the required setup flow, and keyboard users can activate the same path. The textarea accurately reports read-only state until a Session exists, while adjacent controls remain disabled. The UI introduces no new Workspace state, transport, or directory-selection flow.
Component coverage pins pointer and keyboard activation, locked adjacent controls, picker expansion, and the same-node transition to an editable textarea. The assembled Web helper begins fresh Workspace setup through the textarea, so replayed browser scenarios exercise the shipped path.

View File

@@ -0,0 +1,29 @@
# Agent Note: 未选择 Workspace 时从编辑器打开现有选择器
Status: implemented
[English](2026-08-07-workspace-picker-composer-entry.md) | 中文
## 问题
[Session scope 决策](../architecture/2026-07-25-web-client-session-scope-and-provide-channel.md)会在 Workspace 存在前保留同一个常驻编辑器,但 textarea 处于禁用状态,只有较小的 Workspace chip 能打开选择器。用户首次点击最显眼、也最熟悉的输入区域时,界面不会响应,尽管同一界面已有继续操作的入口。
## 决策
新会话尚未归属任何 Workspace 时,常驻 textarea 为只读状态,并可通过鼠标点击、Enter 或 Space 激活现有的 `conversation.hero.workspace` 选择器。它通过 `aria-haspopup` 和 `aria-expanded` 暴露菜单展开状态。消息提交、命令、权限、模型及其他 Session 作用域控件会保持锁定,直到用户选择 Workspace 并创建或重新连接真实 Session。
Workspace 选择继续使用现有 owner 和流程。`ConversationRoot` 打开选择器,`WorkspacePicker` 列出或创建 Workspace;Session 到达后,同一个 textarea DOM 节点变为可编辑状态。
## 考虑过的替代方案
**保持 textarea 禁用并突出 Workspace chip。** 这样能保留原有控件边界,但首次操作时最主要的编辑器区域仍然没有响应。
**在 textarea 上方放置透明按钮。** 按钮具备直接的触发器语义,但它会在常驻 textarea 上方增加第二个可聚焦元素,并使保留焦点、输入法和草稿行为的 DOM identity 过渡更复杂。
**在选择 Workspace 前接收草稿。** 这需要由 client 拥有的草稿 Session 或另一条 Session 前状态轴。此功能只需要提供一个更容易发现的现有选择器入口。
## 后果
用户首次点击编辑器即可继续必要的设置流程,键盘用户也能激活同一路径。textarea 会如实报告只读状态,直到 Session 存在;相邻控件仍处于禁用状态。界面没有引入新的 Workspace 状态、传输或目录选择流程。
组件测试会固定鼠标和键盘激活、相邻控件锁定、选择器展开,以及同一节点变为可编辑 textarea 的过渡。组装后的 Web helper 会通过 textarea 开始全新 Workspace 设置,因此重放浏览器场景会覆盖实际交付路径。