@deepseek-ai/dsh-client-ui-workflow-run 0.1.0-rc.7 → 0.1.0-rc.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/client/ui-workflow-run/README.md
5
- README.md: 3fcdc632b54be7d1f8f452c6f8b3b2e1525953aa
6
- README.zh.md: 5f0ba0c8f8a251d0ce5977d469ee60637f0454db
5
+ README.md: 5093050ac7bd7e5d8a8779c4fe9848ee4f7da28b
6
+ README.zh.md: f7b652d87dc01e5161eac8ea3fc9025136feca90
package/README.md CHANGED
@@ -12,7 +12,7 @@ Phase groups come only from members that actually started. Exact phase strings s
12
12
 
13
13
  ## Presentation and navigation
14
14
 
15
- The run and each phase derive disclosure control from their current lifecycle facts. The run stays expanded while its own status is running, failed, cancelled, or interrupted, or while any phase contains such a member; each affected phase also stays expanded. Forced-open headers are static expanded rows without button, keyboard, or `aria-expanded` promises. A phase folds once when every member completes, and the run folds once when it and every phase complete. Each clean layer then exposes an ordinary disclosure control whose local choice survives clean rerenders; new activity takes control again, and a remount derives the initial state from current data. The run uses a 32-pixel `--dsw-alias-bg-module-platform` row with persistent right/down chevrons and an inline state dot plus status text, without a badge. Phases use 32-pixel disclosure rows with title and member count in the flexible main area and a fixed precise aggregate-status tail, without another dot. Members use a 16-pixel dot slot, a truncating name area, and a fixed 64-pixel status column.
15
+ The run and each phase are controlled disclosures in every status. A mount opens running, failed, cancelled, and interrupted levels and closes fully completed levels; users can then toggle either level with the full row, Enter, or Space. Ordinary running updates preserve the current choice, the first abnormal edge opens once, normal completion closes once, and a completed phase plus the outer run open again when a new running member starts under the same phase key. If an entire new clean cycle arrives in one render while the run remains active, the phase finishes folded but the outer run opens once to expose its updated summary. Completion updates the visible status immediately but delays its automatic close while focus remains inside the content. `WorkflowRunPanel` owns the phase choices, so closing and reopening the outer run does not reset them; a renderer remount reconstructs every initial choice from durable facts. The run uses a 32-pixel `--dsw-alias-bg-module-platform` row with persistent right/down chevrons and an inline state dot plus status text, without a badge. Phases use 32-pixel disclosure rows with title and member count in the flexible main area and a fixed precise aggregate-status tail, without another dot. Members use a 16-pixel dot slot, a truncating name area, and a fixed 64-pixel status column.
16
16
 
17
17
  A member opens a child Session only while every current fact agrees: the member is running, the child id is in the ordinary Session list, the row has `origin: 'subagent'`, its `parentId` is the current Session, and the list row is still running. Underlined member text is the only visible navigation affordance; keyboard focus draws a two-pixel business-primary ring around the name area, while status copy remains `Running`. The component calls only the injected ordinary `sessions.open(id)` action; remote, addressed-only, wrong-parent, or terminal rows remain non-interactive.
18
18
 
package/README.zh.md CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  ## 展示与导航
14
14
 
15
- 运行和每个阶段都从当前生命周期事实派生 disclosure 控制。运行自身处于运行中、失败、已取消或已中断,或者任一阶段包含这些状态的成员时,运行保持展开;受影响的阶段也保持展开。强制展开的标题行只是静态展开行,不承诺按钮、键盘操作或 `aria-expanded`。阶段在全部成员完成时折叠一次;运行在自身和全部阶段都完成时折叠一次。每个干净层级随后恢复普通 disclosure 控件,其本地选择在干净状态的 rerender 中保持;新活动会重新取得控制,remount 则从当前数据派生初始状态。运行使用 32 像素 `--dsw-alias-bg-module-platform` 背景行,常驻向右/向下 chevron,并以内联状态点加状态文字表达结局,不使用胶囊。阶段使用 32 像素 disclosure 行,在可伸缩主区显示标题与成员数,在固定尾部精确显示聚合状态且不重复状态点。成员使用 16 像素状态点槽、可省略名称区和固定 64 像素状态列。
15
+ 运行和每个阶段在所有状态下都是受控 disclosure。挂载时,运行中、失败、已取消和已中断层级默认展开,全部完成的层级默认折叠;此后用户可以点击整行,或按 Enter、Space 切换任一层级。普通运行更新保留当前选择,首次异常边沿只自动展开一次,正常完成只自动折叠一次;已完成阶段在同一 phase key 下开始新的运行成员时,该 Phase 与外层运行会再次自动展开。若一个完整的新干净周期在同一次渲染中送达,且运行仍处于活动状态,Phase 保持折叠,但外层运行会自动展开一次以展示更新后的摘要。完成状态会立即更新,但只要焦点仍位于展开内容内,自动折叠就会等待焦点离开。Phase 选择由 `WorkflowRunPanel` 持有,因此关闭并重新打开外层运行不会重置它们;renderer remount 会从持久事实重建每层的初始选择。运行使用 32 像素 `--dsw-alias-bg-module-platform` 背景行,常驻向右/向下 chevron,并以内联状态点加状态文字表达结局,不使用胶囊。阶段使用 32 像素 disclosure 行,在可伸缩主区显示标题与成员数,在固定尾部精确显示聚合状态且不重复状态点。成员使用 16 像素状态点槽、可省略名称区和固定 64 像素状态列。
16
16
 
17
17
  只有所有实时事实同时成立时,成员才可打开子 Session:成员仍在运行、子 id 位于普通 Session 列表、列表行为 `origin: 'subagent'`、`parentId` 等于当前 Session,且列表行仍标记运行。带下划线的成员文字是唯一可见导航提示;键盘聚焦时,名称区显示 2 像素 business-primary 焦点环,右侧状态仍只显示“运行中”。组件只调用注入的普通 `sessions.open(id)`;远程、仅地址化、父级不符或终态的行都不可交互。
18
18
 
package/lib/client.js CHANGED
@@ -19,28 +19,28 @@ window.__ModuleLoader__.load({
19
19
  document.head.appendChild(tag);
20
20
  }
21
21
  var WorkflowRunPanel_module_css_default = {
22
- "phaseCount": "DBuyfa_phaseCount",
23
- "separator": "DBuyfa_separator",
24
- "root": "DBuyfa_root",
25
- "members": "DBuyfa_members",
22
+ "dotSlot": "DBuyfa_dotSlot",
23
+ "empty": "DBuyfa_empty",
24
+ "memberButton": "DBuyfa_memberButton",
26
25
  "memberLabel": "DBuyfa_memberLabel",
27
- "phaseTitle": "DBuyfa_phaseTitle",
28
- "runLeading": "DBuyfa_runLeading",
29
26
  "memberLabelWrap": "DBuyfa_memberLabelWrap",
30
- "phase": "DBuyfa_phase",
31
- "phaseList": "DBuyfa_phaseList",
32
- "empty": "DBuyfa_empty",
33
27
  "memberRow": "DBuyfa_memberRow",
28
+ "memberStatus": "DBuyfa_memberStatus",
29
+ "members": "DBuyfa_members",
30
+ "phase": "DBuyfa_phase",
31
+ "phaseCount": "DBuyfa_phaseCount",
32
+ "phaseHeader": "DBuyfa_phaseHeader",
34
33
  "phaseLeading": "DBuyfa_phaseLeading",
35
- "runSummary": "DBuyfa_runSummary",
36
- "statusTail": "DBuyfa_statusTail",
34
+ "phaseList": "DBuyfa_phaseList",
37
35
  "phaseStatus": "DBuyfa_phaseStatus",
38
- "runTitle": "DBuyfa_runTitle",
36
+ "phaseTitle": "DBuyfa_phaseTitle",
37
+ "root": "DBuyfa_root",
39
38
  "runHeader": "DBuyfa_runHeader",
40
- "memberButton": "DBuyfa_memberButton",
41
- "memberStatus": "DBuyfa_memberStatus",
42
- "dotSlot": "DBuyfa_dotSlot",
43
- "phaseHeader": "DBuyfa_phaseHeader"
39
+ "runLeading": "DBuyfa_runLeading",
40
+ "runSummary": "DBuyfa_runSummary",
41
+ "runTitle": "DBuyfa_runTitle",
42
+ "separator": "DBuyfa_separator",
43
+ "statusTail": "DBuyfa_statusTail"
44
44
  };
45
45
  //#endregion
46
46
  //#region lib/types/client/WorkflowRunPanel.js
@@ -75,30 +75,85 @@ window.__ModuleLoader__.load({
75
75
  function memberCount(count, t) {
76
76
  return t(count === 1 ? "run.members.one" : "run.members.other", { count });
77
77
  }
78
- function phaseRequiresExpansion(phase) {
79
- return phase.members.some((member) => member.status !== "completed");
80
- }
81
- /* v8 ignore next -- DisclosureRow requires the callback but cannot invoke it when expandable is false. */
82
- const forcedOpenToggle = () => {};
83
- function ManualDisclosure(props) {
84
- const [open, setOpen] = (0, react.useState)(false);
78
+ function StatusDisclosure(props) {
85
79
  return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DisclosureRow, {
86
80
  ...props,
87
- open,
88
- expandable: true,
89
- onToggle: () => {
90
- setOpen((value) => !value);
91
- }
81
+ expandable: true
92
82
  });
93
83
  }
94
- function StatusDisclosure({ cleanCycleKey, requiresExpansion, ...props }) {
95
- if (!requiresExpansion) return (0, react_jsx_runtime.jsx)(ManualDisclosure, { ...props }, cleanCycleKey);
96
- return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DisclosureRow, {
97
- ...props,
84
+ function abnormal(status) {
85
+ return status === "failed" || status === "cancelled" || status === "interrupted";
86
+ }
87
+ function phaseDisclosureFacts(phase) {
88
+ return {
89
+ mode: phase.members.some((member) => abnormal(member.status)) ? "abnormal" : phase.members.some((member) => member.status === "running") ? "running" : "clean",
90
+ activityCount: phase.members.length
91
+ };
92
+ }
93
+ function runDisclosureFacts(status, phases) {
94
+ return {
95
+ mode: abnormal(status) || phases.some(([, facts]) => facts.mode === "abnormal") ? "abnormal" : status === "running" || phases.some(([, facts]) => facts.mode === "running") ? "running" : "clean",
96
+ activityCount: phases.reduce((count, [, facts]) => count + facts.activityCount, 0)
97
+ };
98
+ }
99
+ function initialDisclosureState(facts) {
100
+ return {
101
+ ...facts,
102
+ open: facts.mode !== "clean",
103
+ pendingCleanCollapse: false
104
+ };
105
+ }
106
+ function advanceDisclosureState(current, facts, focusWithin) {
107
+ if (current.mode === facts.mode && current.activityCount === facts.activityCount) {
108
+ if (!current.pendingCleanCollapse || focusWithin) return current;
109
+ return {
110
+ ...current,
111
+ open: false,
112
+ pendingCleanCollapse: false
113
+ };
114
+ }
115
+ if (facts.mode === "clean") {
116
+ const deferCollapse = current.open && focusWithin;
117
+ return {
118
+ ...facts,
119
+ open: deferCollapse,
120
+ pendingCleanCollapse: deferCollapse
121
+ };
122
+ }
123
+ if (current.mode === "clean" || facts.mode === "abnormal" && current.mode !== "abnormal") return {
124
+ ...facts,
98
125
  open: true,
99
- expandable: false,
100
- onToggle: forcedOpenToggle
101
- });
126
+ pendingCleanCollapse: false
127
+ };
128
+ return {
129
+ ...facts,
130
+ open: current.open,
131
+ pendingCleanCollapse: false
132
+ };
133
+ }
134
+ function focusIsWithin(element) {
135
+ if (element === null || element === void 0) return false;
136
+ return element.contains(element.ownerDocument.activeElement);
137
+ }
138
+ function collapsePending(state) {
139
+ if (!state.pendingCleanCollapse) return state;
140
+ return {
141
+ ...state,
142
+ open: false,
143
+ pendingCleanCollapse: false
144
+ };
145
+ }
146
+ function existingPhaseState(phases, key) {
147
+ const phase = phases.get(key);
148
+ /* v8 ignore next -- mounted phase callbacks are created from this owner map. */
149
+ if (phase === void 0) throw new Error(`Missing disclosure state for phase ${key}`);
150
+ return phase;
151
+ }
152
+ function preventPendingHeaderFocus(event) {
153
+ const header = event.currentTarget.querySelector("[data-disclosure-row]");
154
+ /* v8 ignore next -- DisclosureRow always renders its header before the content. */
155
+ if (header === null) throw new Error("Missing disclosure header");
156
+ if (header.contains(event.target)) event.preventDefault();
102
157
  }
103
158
  function phaseStatusSummary(members, t) {
104
159
  const counts = /* @__PURE__ */ new Map();
@@ -122,11 +177,12 @@ window.__ModuleLoader__.load({
122
177
  }
123
178
  return result;
124
179
  }
125
- function RunHeader({ children, count, name, requiresExpansion, status, t }) {
180
+ function RunHeader({ children, count, name, onToggle, open, status, t }) {
126
181
  return (0, react_jsx_runtime.jsx)(StatusDisclosure, {
127
182
  icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {}),
128
183
  title: t("run.title", { name }),
129
- requiresExpansion,
184
+ open,
185
+ onToggle,
130
186
  expandOnRowClick: true,
131
187
  previewChevron: false,
132
188
  keepContentWhenOpen: true,
@@ -153,6 +209,8 @@ window.__ModuleLoader__.load({
153
209
  }
154
210
  function MemberRow({ member, navigable, openSession, t }) {
155
211
  const name = readableMember(member.label, t);
212
+ const [focused, setFocused] = (0, react.useState)(false);
213
+ const renderButton = navigable || focused;
156
214
  const content = (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
157
215
  (0, react_jsx_runtime.jsx)("span", {
158
216
  className: WorkflowRunPanel_module_css_default.dotSlot,
@@ -173,88 +231,205 @@ window.__ModuleLoader__.load({
173
231
  children: t(STATUS_KEYS[member.status])
174
232
  })
175
233
  ] });
176
- if (!navigable) return (0, react_jsx_runtime.jsx)("div", {
234
+ if (!renderButton) return (0, react_jsx_runtime.jsx)("div", {
177
235
  className: WorkflowRunPanel_module_css_default.memberRow,
178
236
  "data-member-status": member.status,
179
237
  children: content
180
238
  });
181
239
  return (0, react_jsx_runtime.jsx)("button", {
182
240
  type: "button",
183
- className: WorkflowRunPanel_module_css_default.memberButton,
241
+ className: navigable ? WorkflowRunPanel_module_css_default.memberButton : WorkflowRunPanel_module_css_default.memberRow,
184
242
  "data-member-status": member.status,
185
- "aria-label": t("member.open", { name }),
186
- onClick: () => {
187
- openSession(member.childId);
243
+ "aria-disabled": navigable ? void 0 : true,
244
+ "aria-label": navigable ? t("member.open", { name }) : name,
245
+ tabIndex: navigable ? void 0 : -1,
246
+ onFocus: () => {
247
+ setFocused(true);
248
+ },
249
+ onBlur: () => {
250
+ setFocused(false);
188
251
  },
252
+ onClick: navigable ? () => {
253
+ openSession(member.childId);
254
+ } : void 0,
189
255
  children: content
190
256
  });
191
257
  }
192
- function PhaseSection({ phase, navigable, openSession, t }) {
193
- return (0, react_jsx_runtime.jsx)(StatusDisclosure, {
194
- icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {}),
195
- title: readablePhase(phase.phase, t),
196
- cleanCycleKey: phase.members.length,
197
- requiresExpansion: phaseRequiresExpansion(phase),
198
- expandOnRowClick: true,
199
- previewChevron: false,
200
- keepContentWhenOpen: true,
258
+ function PhaseSection({ contentRef, onContentBlur, onToggle, open, pendingCleanCollapse, phase, navigable, openSession, t }) {
259
+ return (0, react_jsx_runtime.jsx)("div", {
201
260
  className: WorkflowRunPanel_module_css_default.phase,
202
- rowClassName: WorkflowRunPanel_module_css_default.phaseHeader,
203
- leadingClassName: WorkflowRunPanel_module_css_default.phaseLeading,
204
- titleClassName: WorkflowRunPanel_module_css_default.phaseTitle,
205
- collapsedContent: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
206
- (0, react_jsx_runtime.jsx)("span", {
207
- className: WorkflowRunPanel_module_css_default.separator,
208
- "aria-hidden": true
209
- }),
210
- (0, react_jsx_runtime.jsx)("span", {
211
- className: WorkflowRunPanel_module_css_default.phaseCount,
212
- "data-phase-count": true,
213
- children: memberCount(phase.members.length, t)
214
- }),
215
- (0, react_jsx_runtime.jsx)("span", {
216
- className: WorkflowRunPanel_module_css_default.phaseStatus,
217
- "data-phase-status-text": true,
218
- children: phaseStatusSummary(phase.members, t)
261
+ onMouseDownCapture: pendingCleanCollapse ? preventPendingHeaderFocus : void 0,
262
+ children: (0, react_jsx_runtime.jsx)(StatusDisclosure, {
263
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {}),
264
+ title: readablePhase(phase.phase, t),
265
+ open,
266
+ onToggle,
267
+ expandOnRowClick: true,
268
+ previewChevron: false,
269
+ keepContentWhenOpen: true,
270
+ rowClassName: WorkflowRunPanel_module_css_default.phaseHeader,
271
+ leadingClassName: WorkflowRunPanel_module_css_default.phaseLeading,
272
+ titleClassName: WorkflowRunPanel_module_css_default.phaseTitle,
273
+ collapsedContent: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
274
+ (0, react_jsx_runtime.jsx)("span", {
275
+ className: WorkflowRunPanel_module_css_default.separator,
276
+ "aria-hidden": true
277
+ }),
278
+ (0, react_jsx_runtime.jsx)("span", {
279
+ className: WorkflowRunPanel_module_css_default.phaseCount,
280
+ "data-phase-count": true,
281
+ children: memberCount(phase.members.length, t)
282
+ }),
283
+ (0, react_jsx_runtime.jsx)("span", {
284
+ className: WorkflowRunPanel_module_css_default.phaseStatus,
285
+ "data-phase-status-text": true,
286
+ children: phaseStatusSummary(phase.members, t)
287
+ })
288
+ ] }),
289
+ children: (0, react_jsx_runtime.jsx)("div", {
290
+ ref: contentRef,
291
+ className: WorkflowRunPanel_module_css_default.members,
292
+ onBlur: onContentBlur,
293
+ children: phase.members.map((member) => (0, react_jsx_runtime.jsx)(MemberRow, {
294
+ member,
295
+ navigable: navigable.includes(member.childId),
296
+ openSession,
297
+ t
298
+ }, member.seq))
219
299
  })
220
- ] }),
221
- children: (0, react_jsx_runtime.jsx)("div", {
222
- className: WorkflowRunPanel_module_css_default.members,
223
- children: phase.members.map((member) => (0, react_jsx_runtime.jsx)(MemberRow, {
224
- member,
225
- navigable: navigable.includes(member.childId),
226
- openSession,
227
- t
228
- }, member.seq))
229
300
  })
230
301
  });
231
302
  }
232
303
  /** Render one durable workflow run with status-driven run and phase disclosure. */
233
304
  function WorkflowRunPanel({ node, sessionId, useSessions, openSession, t }) {
234
- const totalMembers = node.data.phases.reduce((count, phase) => count + phase.members.length, 0);
235
- const requiresExpansion = node.data.status !== "completed" || node.data.phases.some(phaseRequiresExpansion);
305
+ const phaseFacts = (0, react.useMemo)(() => node.data.phases.map((phase) => [phase.key, phaseDisclosureFacts(phase)]), [node.data.phases]);
306
+ const runFacts = (0, react.useMemo)(() => runDisclosureFacts(node.data.status, phaseFacts), [node.data.status, phaseFacts]);
307
+ const totalMembers = runFacts.activityCount;
308
+ const [disclosures, setDisclosures] = (0, react.useState)(() => ({
309
+ run: initialDisclosureState(runFacts),
310
+ phases: new Map(phaseFacts.map(([key, facts]) => [key, initialDisclosureState(facts)]))
311
+ }));
312
+ const runContentRef = (0, react.useRef)(null);
313
+ const phaseContentRefs = (0, react.useRef)(/* @__PURE__ */ new Map());
236
314
  const navigable = useSessions((sessions) => navigableMembers(sessions, node.data.phases, sessionId), _deepseek_ai_dsh_client_runtime_client.shallowEqual);
315
+ (0, react.useLayoutEffect)(() => {
316
+ setDisclosures((current) => {
317
+ const phases = /* @__PURE__ */ new Map();
318
+ let phasesChanged = current.phases.size !== phaseFacts.length;
319
+ let phaseStartedCycle = false;
320
+ for (const [key, facts] of phaseFacts) {
321
+ const previous = current.phases.get(key);
322
+ const next = previous === void 0 ? initialDisclosureState(facts) : advanceDisclosureState(previous, facts, focusIsWithin(phaseContentRefs.current.get(key)));
323
+ phases.set(key, next);
324
+ if (next !== previous) phasesChanged = true;
325
+ if (previous?.mode === "clean" && (facts.mode !== "clean" || facts.activityCount !== previous.activityCount)) phaseStartedCycle = true;
326
+ }
327
+ const advancedRun = advanceDisclosureState(current.run, runFacts, focusIsWithin(runContentRef.current));
328
+ const run = phaseStartedCycle && runFacts.mode !== "clean" && !advancedRun.open ? {
329
+ ...advancedRun,
330
+ open: true,
331
+ pendingCleanCollapse: false
332
+ } : advancedRun;
333
+ return run !== current.run || phasesChanged ? {
334
+ run,
335
+ phases
336
+ } : current;
337
+ });
338
+ }, [
339
+ disclosures.run.open,
340
+ phaseFacts,
341
+ runFacts
342
+ ]);
343
+ const toggleRun = () => {
344
+ setDisclosures((current) => ({
345
+ ...current,
346
+ run: {
347
+ ...current.run,
348
+ open: !current.run.open,
349
+ pendingCleanCollapse: false
350
+ }
351
+ }));
352
+ };
353
+ const togglePhase = (key) => {
354
+ setDisclosures((current) => {
355
+ const phases = new Map(current.phases);
356
+ const phase = existingPhaseState(phases, key);
357
+ phases.set(key, {
358
+ ...phase,
359
+ open: !phase.open,
360
+ pendingCleanCollapse: false
361
+ });
362
+ return {
363
+ ...current,
364
+ phases
365
+ };
366
+ });
367
+ };
368
+ const settleRunBlur = (event) => {
369
+ if (event.currentTarget.contains(event.relatedTarget)) return;
370
+ setDisclosures((current) => {
371
+ const run = collapsePending(current.run);
372
+ return run === current.run ? current : {
373
+ ...current,
374
+ run
375
+ };
376
+ });
377
+ };
378
+ const settlePhaseBlur = (key, event) => {
379
+ if (event.currentTarget.contains(event.relatedTarget)) return;
380
+ setDisclosures((current) => {
381
+ const phase = existingPhaseState(current.phases, key);
382
+ const next = collapsePending(phase);
383
+ if (next === phase) return current;
384
+ const phases = new Map(current.phases);
385
+ phases.set(key, next);
386
+ return {
387
+ ...current,
388
+ phases
389
+ };
390
+ });
391
+ };
237
392
  return (0, react_jsx_runtime.jsx)("section", {
238
393
  className: WorkflowRunPanel_module_css_default.root,
239
394
  "data-workflow-run": true,
240
395
  "data-run-status": node.data.status,
396
+ onMouseDownCapture: disclosures.run.pendingCleanCollapse ? preventPendingHeaderFocus : void 0,
241
397
  children: (0, react_jsx_runtime.jsx)(RunHeader, {
242
398
  count: totalMembers,
243
399
  name: node.data.name,
244
- requiresExpansion,
400
+ open: disclosures.run.open,
401
+ onToggle: toggleRun,
245
402
  status: node.data.status,
246
403
  t,
247
404
  children: (0, react_jsx_runtime.jsx)("div", {
405
+ ref: runContentRef,
248
406
  className: WorkflowRunPanel_module_css_default.phaseList,
407
+ onBlur: settleRunBlur,
249
408
  children: node.data.phases.length === 0 ? (0, react_jsx_runtime.jsx)("span", {
250
409
  className: WorkflowRunPanel_module_css_default.empty,
251
410
  children: t("run.empty")
252
- }) : node.data.phases.map((phase) => (0, react_jsx_runtime.jsx)(PhaseSection, {
253
- phase,
254
- navigable,
255
- openSession,
256
- t
257
- }, phase.key))
411
+ }) : node.data.phases.map((phase) => {
412
+ const facts = phaseDisclosureFacts(phase);
413
+ const disclosure = disclosures.phases.get(phase.key) ?? initialDisclosureState(facts);
414
+ return (0, react_jsx_runtime.jsx)(PhaseSection, {
415
+ contentRef: (element) => {
416
+ if (element === null) phaseContentRefs.current.delete(phase.key);
417
+ else phaseContentRefs.current.set(phase.key, element);
418
+ },
419
+ onContentBlur: (event) => {
420
+ settlePhaseBlur(phase.key, event);
421
+ },
422
+ onToggle: () => {
423
+ togglePhase(phase.key);
424
+ },
425
+ open: disclosure.open,
426
+ pendingCleanCollapse: disclosure.pendingCleanCollapse,
427
+ phase,
428
+ navigable,
429
+ openSession,
430
+ t
431
+ }, phase.key);
432
+ })
258
433
  })
259
434
  })
260
435
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-ui-workflow-run",
3
3
  "description": "Durable workflow-run Conversation Node and nested member disclosure for dsh web",
4
- "version": "0.1.0-rc.7",
4
+ "version": "0.1.0-rc.8",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -46,34 +46,30 @@
46
46
  "lib/types/**/*.d.ts"
47
47
  ],
48
48
  "license": "MIT",
49
- "dependencies": {
50
- "react": "^18.2.0"
51
- },
52
49
  "peerDependencies": {
53
- "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.7",
54
- "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.7",
55
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.7",
56
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.7",
57
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.7",
58
- "@deepseek-ai/dsh-invariants": "^0.1.0-rc.7",
59
- "@deepseek-ai/dsh-tool-workflow": "^0.1.0-rc.7",
60
- "@deepseek-ai/dsh-workflow": "^0.1.0-rc.7",
61
- "@deepseek-ai/dsh-session": "^0.1.0-rc.7",
62
- "@deepseek-ai/cordis": "^4.0.1"
50
+ "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.8",
51
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.8",
52
+ "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8",
53
+ "@deepseek-ai/dsh-session": "^0.1.0-rc.8",
54
+ "@deepseek-ai/dsh-tool-workflow": "^0.1.0-rc.8",
55
+ "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.8",
56
+ "@deepseek-ai/cordis": "^4.0.1",
57
+ "@deepseek-ai/dsh-workflow": "^0.1.0-rc.8"
63
58
  },
64
59
  "devDependencies": {
65
60
  "@types/react": "~18.3.1",
66
- "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.7",
67
- "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.7",
68
- "@deepseek-ai/dsh-client-test-runtime": "^0.1.0-rc.7",
69
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.7",
70
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.7",
71
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.7",
72
- "@deepseek-ai/dsh-invariants": "^0.1.0-rc.7",
73
- "@deepseek-ai/dsh-tool-workflow": "^0.1.0-rc.7",
74
- "@deepseek-ai/dsh-workflow": "^0.1.0-rc.7",
75
- "@deepseek-ai/cordis": "^4.0.1",
76
- "@deepseek-ai/dsh-session": "^0.1.0-rc.7"
61
+ "react": "^18.2.0",
62
+ "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.8",
63
+ "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.8",
64
+ "@deepseek-ai/dsh-client-test-runtime": "^0.1.0-rc.8",
65
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.8",
66
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.8",
67
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.8",
68
+ "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8",
69
+ "@deepseek-ai/dsh-session": "^0.1.0-rc.8",
70
+ "@deepseek-ai/dsh-tool-workflow": "^0.1.0-rc.8",
71
+ "@deepseek-ai/dsh-workflow": "^0.1.0-rc.8",
72
+ "@deepseek-ai/cordis": "^4.0.1"
77
73
  },
78
74
  "scripts": {
79
75
  "bundle": "tsdown",