Pārlūkot izejas kodu

fix(ui-workspace): expose session status accessibly

ZiyaZhang 2 mēneši atpakaļ
vecāks
revīzija
61803f1a46

+ 2 - 2
packages/client/ui-sidebar/README.i18n.yaml

@@ -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 packages/client/ui-sidebar/README.md
-README.md: 93a1f15a5802f94a0ebe930dda1dbd4fbc7343c9
-README.zh.md: 8c8545a5d7d8cb4d58772abf867d7ee82c31bf1d
+README.md: d2c0c3332f2202986f1daf3a45c84cc1e65eee6d
+README.zh.md: 03cb86842d8a28f3a18250a9d77dd0a0a217d7b9

+ 1 - 1
packages/client/ui-sidebar/README.md

@@ -22,6 +22,6 @@ None; this package neither assembles nor sends a provider request.
 
 ## Known Limitations and Deferred Work
 
-- **State dots have two live data states (running/none)** — the done/error/amber sources arrive with P-II approvals and notifications; the four-color primitive is already wired.
+- **State dots have approval-waiting/running/none live states** — approval waiting is amber and outranks running; done/error notification sources remain deferred.
 - **Group-by menu ships by-workspace only** — Update/Status grouping strategies are drawn without specs and deferred.
 - **"New task completed" unread marking is local viewing state** — completion-time > last-seen never reaches the host.

+ 1 - 1
packages/client/ui-sidebar/README.zh.md

@@ -22,6 +22,6 @@ New Session 会启动运行时的页面局部前端 Session Intent;真实 Work
 
 ## 已知限制与暂缓事项
 
-- **状态点只有两种实时数据状态(running/none)**:done/error/amber 的数据源将随 P-II 审批与通知功能一并提供;四色原语已接入。
+- **状态点具有待审批/running/none 三种实时状态**:待审批使用琥珀色并优先于 running;done/error 的通知数据源仍暂缓实现。
 - **分组选单只提供按 Workspace 分组**:Update/Status 分组策略只有图稿而没有规范,暂缓实现。
 - **「New task completed」未读标记是本地查看状态**:完成时间 > 上次查看时间这一事实永远不会到达宿主。

+ 2 - 2
packages/client/ui-workspace/README.i18n.yaml

@@ -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 packages/client/ui-workspace/README.md
-README.md: 1497f816a295e2cd156af9b779bce0b42759e1c7
-README.zh.md: be496412db9790b0625b40f0bbb06c1d406af015
+README.md: 4ca836e4f1beeb164716e5fc4741253719d2700c
+README.zh.md: 2a5448a12d58184b027c99b5301510370ba63a83

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
packages/client/ui-workspace/README.md


+ 2 - 1
packages/client/ui-workspace/README.zh.md

@@ -6,7 +6,7 @@
 
 该选择器通过全局 `useWorkspaces` hook 列出真实的 Host Workspace 实体。选择 Workspace 会调用 slot owner 的 `onPick` 回调,重新定位前端 Session 对象。每个注册各自声明一个**目录流子洞**(`single` kind:`conversation.hero.workspace.directoryFlow`/`sidebar.workspaces.directoryFlow`),由组合的选择器包 client half 填入其选取交互——今天是 [`-native`](../../host/directory-picker-native/README.md) 后端的无渲染 OS 选择器驱动,`-browse` 组合下则是应用内浏览对话框。平铺显示的 **打开本地文件夹…** 操作仅在本表层的洞被占用时渲染(每次菜单渲染读取占用状态;洞为空意味着该组合没有选目录能力——seam 文档化的无流程默认行为)。本包持有触发与接纳:占用者经洞的 owner 会话(`open`/`busy`/`onPicked`/`onCancel`/`onError`)每次打开上报一个所选路径,owner 通过对象层接纳它,并等待 Workspace 列表投影刷新后才选中已提交的 Workspace;取消操作不会显示提示,错误落入可重试的文件夹对话框,其 **重新选择** 会重新打开流程。**创建新工作区** 操作保留名称对话框,并禁用列表中已有的名称,而 Host 对并发或非 UI 调用方仍具有最终决定权。运行时 Session 与 Workspace 服务负责物化。Workspace 行内的 Delete 操作会打开确认框,说明保留边界、阻止重复提交,并在失败时保持打开;成功后,该分组会被移除,其 Session 则留在 Ungrouped 下。Session 行内的 Rename 操作打开同款浏览器持有的对话框,并以该行的显示标题预填:客户端不设名称冲突规则(host 负责规范化,可能以 `title-invalid` 拒绝,错误渲染在对话框告警区);确认未修改的标题是有意允许的——这正是把当前自动标题钉住、不再被重新生成覆盖的手势。
 
-Session 行会投影 runtime 的实时 `waitingApproval` 状态:琥珀色警告点优先于蓝色运行指示器,hover 卡片在请求解决前显示 **Waiting for approval**。没有待审批请求时,运行与空闲展示保持不变。
+Session 行会把 runtime 的实时 `waitingApproval` 状态与原本显示为蓝色的进行中 Session 区分开:琥珀色警告点优先于运行指示器,随附的视觉隐藏标签会向辅助技术公开这一状态,hover 卡片则在请求解决前显示 **Waiting for approval**。没有待审批请求时,运行与空闲展示保持不变。
 
 两个目标 slot 都由其他插件声明,因此 `apply` 通过声明感知的延迟机制完成注册,并在声明该 slot 的插件恢复后重新注册。
 
@@ -21,4 +21,5 @@ Session 行会投影 runtime 的实时 `waitingApproval` 状态:琥珀色警
 ## 已知限制与暂缓事项
 
 - **没有 Session 删除与 fork 控件**:Session 菜单的 Fork 与 Delete 行仍仅提供视觉效果(Rename 已接线);删除 Workspace 注册记录不会删除 Session。
+- **待审批状态不会聚合到隐藏的祖先节点**:折叠父节点下正在等待的子 Session,或折叠分组内的任何等待行,只有在对应容器展开后才可见。
 - **原生文件夹选择依赖本地 Host 载体**:在 `-native` 组合下,仅使用 fixture(测试前置数据)的部署或远程浏览器部署无法打开本地操作系统对话框;模态框会显示平台故障,并允许重试。可远程的选取是 `-browse` 组合的应用内流程。

+ 9 - 0
packages/client/ui-workspace/src/client/rows/Rows.module.css

@@ -68,6 +68,15 @@
   color: var(--dsw-alias-label-tertiary);
 }
 
+.visuallyHidden {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  overflow: hidden;
+  clip: rect(0 0 0 0);
+  white-space: nowrap;
+}
+
 
 .folderActive {
   color: var(--dsw-alias-state-business-primary);

+ 23 - 13
packages/client/ui-workspace/src/client/rows/Rows.tsx

@@ -109,18 +109,6 @@ export function ProjectRowItem({ group, onToggle, onCreate, actions }: {
   )
 }
 
-/**
- * One session subtree: the node's own 34px row (indent by depth, expand
- * twist when it has children, running dot, relative time) plus its visible
- * children, recursively — the component tree mirrors the derived tree.
- * @param props.node - derived session node.
- * @param props.depth - 0 = directly under the group header.
- * @param props.currentId - selected session id (row highlight).
- * @param props.now - epoch ms for relative-time formatting.
- * @param props.onOpen - open a session by id.
- * @param props.onToggle - unfold/fold a subtree by id.
- * @returns the node's row followed by its children.
- */
 /** Session status presentation; approval waiting outranks the underlying running state. */
 function sessionStatus(node: SessionNode): { state: 'warning' | 'ongoing' | 'done'; label: string } {
   if (node.waitingApproval) return { state: 'warning', label: 'Waiting for approval' }
@@ -167,6 +155,21 @@ function rowHalf(e: { clientY: number; currentTarget: HTMLElement }): 'before' |
   return e.clientY < rect.top + rect.height / 2 ? 'before' : 'after'
 }
 
+/**
+ * One session subtree: the node's own 34px row (indent by depth, expand
+ * twist when it has children, status dot, relative time) plus its visible
+ * children, recursively — the component tree mirrors the derived tree.
+ * @param props.node - derived session node.
+ * @param props.depth - 0 = directly under the group header.
+ * @param props.currentId - selected session id (row highlight).
+ * @param props.now - epoch ms for relative-time formatting.
+ * @param props.onOpen - open a session by id.
+ * @param props.onRename - rename a session by id and current title.
+ * @param props.onToggle - unfold/fold a subtree by id.
+ * @param props.drag - optional root-row drag wiring.
+ * @param props.flat - omit tree indentation controls for a flat list.
+ * @returns the node's row followed by its children.
+ */
 export function SessionNodeItem({ node, depth, currentId, now, onOpen, onRename, onToggle, drag, flat = false }: {
   node: SessionNode
   depth: number
@@ -235,7 +238,14 @@ export function SessionNodeItem({ node, depth, currentId, now, onOpen, onRename,
           </button>
         )
         : null}
-      <span className={css.slot}>{(row.waitingApproval || row.running) && <StateDot state={status.state} />}</span>
+      <span className={css.slot}>
+        {status.state !== 'done' && (
+          <>
+            <StateDot state={status.state} />
+            <span className={css.visuallyHidden}>{status.label}</span>
+          </>
+        )}
+      </span>
       <span className={css.title}>{row.title}</span>
       <span className={css.time}>{formatRelativeTime(row.updatedAt, now)}</span>
       <span className={css.rowActions}>

+ 9 - 4
packages/client/ui-workspace/tests/rows.spec.tsx

@@ -191,7 +191,7 @@ describe('workspace browser rows', () => {
       // Card body: full title + relative time + running status.
       expect(screen.getAllByText('Hovered')).toHaveLength(2)
       expect(screen.getByText('1min ago')).toBeTruthy()
-      expect(screen.getByText('Running')).toBeTruthy()
+      expect(screen.getAllByText('Running')).toHaveLength(2)
       fireEvent.pointerLeave(wrapper)
       // Menu open (disabled=true) suppresses the card for the same hover.
       fireEvent.click(screen.getByRole('button', { name: 'Session actions for Hovered' }))
@@ -210,15 +210,20 @@ describe('workspace browser rows', () => {
         id: sid('approval'), title: 'Needs approval', children: [], hasChildren: false,
         expanded: false, waitingApproval: true, running: true, updatedAt: 0,
       }
-      render(<SessionNodeItem node={node} depth={0} currentId={undefined} now={0} onOpen={vi.fn()}
+      const view = render(<SessionNodeItem node={node} depth={0} currentId={undefined} now={0} onOpen={vi.fn()}
         onRename={vi.fn()} onToggle={vi.fn()} />)
       const row = screen.getByRole('treeitem')
       expect(row.querySelector('[data-state="warning"]')).toBeTruthy()
       expect(row.querySelector('[data-state="ongoing"]')).toBeNull()
+      expect(screen.getByText('Waiting for approval')).toBeTruthy()
+
+      view.rerender(<SessionNodeItem node={{ ...node, running: false }} depth={0} currentId={undefined} now={0}
+        onOpen={vi.fn()} onRename={vi.fn()} onToggle={vi.fn()} />)
+      expect(screen.getByRole('treeitem').querySelector('[data-state="warning"]')).toBeTruthy()
 
-      fireEvent.pointerEnter(row.parentElement as HTMLElement)
+      fireEvent.pointerEnter(screen.getByRole('treeitem').parentElement as HTMLElement)
       act(() => { vi.advanceTimersByTime(500) })
-      expect(screen.getByText('Waiting for approval')).toBeTruthy()
+      expect(screen.getAllByText('Waiting for approval')).toHaveLength(2)
       expect(document.querySelectorAll('[data-state="warning"]')).toHaveLength(2)
     } finally {
       vi.useRealTimers()

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels