@deepseek-ai/dsh-client-ui-workspace 0.1.5-alpha.1 → 0.1.5-alpha.2

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-workspace/README.md
5
- README.md: a819b07fecb74faeb3bebb6665aaabfdc8ecfba6
6
- README.zh.md: f9565b12bccd596fa61d1f1436f82b090139f506
5
+ README.md: 857b38c2ca08ca4e9078d8f9dfcfe8462be50722
6
+ README.zh.md: cf6f2d273f00add793cc5ac588db968c22007256
package/README.md CHANGED
@@ -51,6 +51,8 @@ The value is intentionally best effort for cold Sessions. An identity-matching u
51
51
 
52
52
  -----
53
53
 
54
+ `ctx.uiWorkspace.openSession(id)` selects the Session and returns the main area to the Conversation as one UI navigation action, including when that Session was already current. `openWorkspace(id, beforeOpen?)` and `forkSession(id)` open their result only if no later navigation has superseded the request; New Session uses `openWorkspace`. The optional synchronous preparation callback runs only for a current Workspace request, so superseded requests do not move composer drafts. Navigation or owner disposal suppresses the late UI commit, not the underlying Session creation. Selection failure leaves a global panel visible. Session rows read `usePanelInfo` to suppress their selected appearance while a global panel is active; search and directory-picker focus alone do not leave that panel.
55
+
54
56
  <a id="understand-the-implementation"></a>
55
57
  ## Understand the implementation
56
58
 
package/README.zh.md CHANGED
@@ -51,6 +51,8 @@ Session 行渲染运行时的实时 `pendingInteraction` 分类:审批显示**
51
51
 
52
52
  -----
53
53
 
54
+ `ctx.uiWorkspace.openSession(id)` 将选中会话与中央区域返回会话界面作为一次 UI 导航操作,包括目标会话已经是当前会话的情况。`openWorkspace(id, beforeOpen?)` 和 `forkSession(id)` 仅在请求未被后续导航替代时打开结果;新会话使用 `openWorkspace`。可选的同步准备回调仅对仍有效的工作区请求执行,因此过期请求不会搬移 composer 草稿。后续导航或所有者释放会阻止晚到的 UI 提交,但不取消底层会话创建。选中失败时保留当前全局面板。会话行读取 `usePanelInfo`,在全局面板活跃时不显示会话选中样式;仅把焦点移到搜索框或目录选择器不会离开该面板。
55
+
54
56
  <a id="understand-the-implementation"></a>
55
57
  ## 理解实现
56
58
 
package/lib/client.js CHANGED
@@ -27,6 +27,7 @@ window.__ModuleLoader__.load({
27
27
  workspaces;
28
28
  sessions;
29
29
  connecting = /* @__PURE__ */ new Map();
30
+ lifetime = new AbortController();
30
31
  /**
31
32
  * @param ctx - Client root Context.
32
33
  * @param directoryPicker - the directory-picking Remote namespace.
@@ -57,6 +58,26 @@ window.__ModuleLoader__.load({
57
58
  this.connecting.set(workspaceId, attempt);
58
59
  return attempt;
59
60
  }
61
+ openSession(sessionId) {
62
+ this.sessions.open(sessionId);
63
+ this.ctx.layout.selectPanel(null);
64
+ }
65
+ async openWorkspace(workspaceId, beforeOpen) {
66
+ const navigation = AbortSignal.any([this.ctx.layout.beginNavigation(), this.lifetime.signal]);
67
+ const isCurrent = () => !navigation.aborted;
68
+ const sessionId = await this.connectWorkspace(workspaceId);
69
+ if (!isCurrent()) return;
70
+ beforeOpen?.(sessionId);
71
+ if (isCurrent()) this.openSession(sessionId);
72
+ }
73
+ async forkSession(sessionId) {
74
+ const navigation = AbortSignal.any([this.ctx.layout.beginNavigation(), this.lifetime.signal]);
75
+ const childId = await this.sessions.fork({
76
+ sessionId,
77
+ increaseTitle: true
78
+ });
79
+ if (!navigation.aborted) this.openSession(childId);
80
+ }
60
81
  startSession(workspaceId) {
61
82
  const workspace = this.workspaces.list.getSnapshot();
62
83
  const sessions = this.sessions.list.getSnapshot();
@@ -66,11 +87,10 @@ window.__ModuleLoader__.load({
66
87
  const target = workspaceId ?? currentWorkspaceId ?? recent;
67
88
  if (target === void 0) {
68
89
  this.sessions.clear();
90
+ this.ctx.layout.selectPanel(null);
69
91
  return;
70
92
  }
71
- this.connectWorkspace(target).then((sessionId) => {
72
- this.sessions.open(sessionId);
73
- }, (reason) => {
93
+ this.openWorkspace(target).catch((reason) => {
74
94
  console.warn("new session failed:", reason);
75
95
  });
76
96
  }
@@ -94,9 +114,8 @@ window.__ModuleLoader__.load({
94
114
  }
95
115
  watchNavigation() {
96
116
  let initial = "waiting";
97
- let disposed = false;
98
117
  const reconcile = () => {
99
- if (disposed) return;
118
+ if (this.lifetime.signal.aborted) return;
100
119
  if (this.clearArchivedCurrent()) return;
101
120
  if (initial !== "waiting") return;
102
121
  const workspace = this.workspaces.list.getSnapshot();
@@ -113,11 +132,11 @@ window.__ModuleLoader__.load({
113
132
  }
114
133
  initial = "connecting";
115
134
  this.connectWorkspace(target).then((sessionId) => {
116
- if (disposed) return;
135
+ if (this.lifetime.signal.aborted) return;
117
136
  if (this.sessions.list.getSnapshot().current === void 0) this.sessions.open(sessionId);
118
137
  initial = "done";
119
138
  }, (reason) => {
120
- if (disposed) return;
139
+ if (this.lifetime.signal.aborted) return;
121
140
  initial = "waiting";
122
141
  console.warn("initial workspace selection failed:", reason);
123
142
  });
@@ -126,7 +145,7 @@ window.__ModuleLoader__.load({
126
145
  const disposeSessions = this.sessions.list.subscribe(reconcile);
127
146
  reconcile();
128
147
  return () => {
129
- disposed = true;
148
+ this.lifetime.abort();
130
149
  disposeSessions();
131
150
  disposeWorkspaces();
132
151
  };
@@ -1442,10 +1461,11 @@ window.__ModuleLoader__.load({
1442
1461
  return e.clientY < rect.top + rect.height / 2 ? "before" : "after";
1443
1462
  }
1444
1463
  /** The scrolling session tree; unmounting drops the sessions subscription and expand-all state. */
1445
- function SessionTree({ useSessions, useSessionPendingInteraction, startSession, open, forkSession, workspaces, archivedSessionIds, workspaceReady, onRenameRequest, onDeleteRequest, onSessionRename, onSessionArchive, insertWorkspaceBefore, insertSessionBefore, orderBy, groupExpansion, setGroupExpanded, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, setSessionOrder, home, t, revealSessionId, onSessionRevealed }) {
1464
+ function SessionTree({ useSessions, useSessionPendingInteraction, startSession, open, forkSession, workspaces, archivedSessionIds, workspaceReady, usePanelInfo, onRenameRequest, onDeleteRequest, onSessionRename, onSessionArchive, insertWorkspaceBefore, insertSessionBefore, orderBy, groupExpansion, setGroupExpanded, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, setSessionOrder, home, t, revealSessionId, onSessionRevealed }) {
1465
+ const panelActive = usePanelInfo((info) => info.activePanelId !== null);
1446
1466
  const list = useSessions((s) => s);
1447
1467
  const pendingInteractions = useSessionPendingInteraction((s) => s);
1448
- const current = list.current;
1468
+ const current = panelActive ? void 0 : list.current;
1449
1469
  const revealGroup = revealSessionId === void 0 || !workspaceReady ? void 0 : owningGroupKey(workspaces, revealSessionId);
1450
1470
  const [expandedSessionGroups, setExpandedSessionGroups] = (0, react.useState)([]);
1451
1471
  const [drag, setDrag] = (0, react.useState)(null);
@@ -1762,7 +1782,8 @@ window.__ModuleLoader__.load({
1762
1782
  });
1763
1783
  }
1764
1784
  /** The flat "In one list" body: every session is one draggable top-level row. */
1765
- function FlatList({ useSessions, useSessionPendingInteraction, open, forkSession, onSessionRename, onSessionArchive, archivedSessionIds, orderBy, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, setSessionOrder, revealSessionId, onSessionRevealed, t }) {
1785
+ function FlatList({ useSessions, useSessionPendingInteraction, open, forkSession, onSessionRename, onSessionArchive, archivedSessionIds, usePanelInfo, orderBy, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, setSessionOrder, revealSessionId, onSessionRevealed, t }) {
1786
+ const panelActive = usePanelInfo((info) => info.activePanelId !== null);
1766
1787
  const list = useSessions((s) => s);
1767
1788
  const pendingInteractions = useSessionPendingInteraction((s) => s);
1768
1789
  const baseRows = (0, react.useMemo)(() => deriveFlat(list, archivedSessionIds, pendingInteractions), [
@@ -1839,7 +1860,7 @@ window.__ModuleLoader__.load({
1839
1860
  const active = drag !== null;
1840
1861
  return (0, react_jsx_runtime.jsx)(SessionNodeItem, {
1841
1862
  node,
1842
- currentId: list.current,
1863
+ currentId: panelActive ? void 0 : list.current,
1843
1864
  now,
1844
1865
  onOpen: open,
1845
1866
  onRename: onSessionRename,
@@ -1888,7 +1909,8 @@ window.__ModuleLoader__.load({
1888
1909
  });
1889
1910
  }
1890
1911
  /** Flat search body: local metadata matches plus the current Host result page. */
1891
- function SearchResults({ useSessions, useSessionPendingInteraction, open, workspaces, archivedSessionIds, query, remote, resultLimit, t }) {
1912
+ function SearchResults({ useSessions, useSessionPendingInteraction, open, workspaces, archivedSessionIds, query, remote, resultLimit, usePanelInfo, t }) {
1913
+ const panelActive = usePanelInfo((info) => info.activePanelId !== null);
1892
1914
  const list = useSessions((s) => s);
1893
1915
  const pendingInteractions = useSessionPendingInteraction((s) => s);
1894
1916
  const currentRemote = remote.query === query ? remote : {
@@ -1919,7 +1941,7 @@ window.__ModuleLoader__.load({
1919
1941
  "aria-label": t("search.results.aria"),
1920
1942
  children: results.items.map((result) => (0, react_jsx_runtime.jsx)(SearchResultItem, {
1921
1943
  result,
1922
- currentId: list.current,
1944
+ currentId: panelActive ? void 0 : list.current,
1923
1945
  onOpen: open,
1924
1946
  t
1925
1947
  }, result.id))
@@ -1951,7 +1973,7 @@ window.__ModuleLoader__.load({
1951
1973
  * @param props - composed slot props (shell owner share + store + injected actions).
1952
1974
  * @returns the region element tree.
1953
1975
  */
1954
- function WorkspaceBrowser({ wide, expandSidebar, useSessions, useSessionPendingInteraction, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, insertSessionBefore, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostInfo, renderSlot, t }) {
1976
+ function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionPendingInteraction, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, insertSessionBefore, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostInfo, renderSlot, t }) {
1955
1977
  const home = useHostInfo((info) => info.home);
1956
1978
  const workspaces = useWorkspaces((state) => state.items);
1957
1979
  const workspacePhase = useWorkspaces((state) => state.phase);
@@ -2333,6 +2355,7 @@ window.__ModuleLoader__.load({
2333
2355
  (0, react_jsx_runtime.jsx)("div", {
2334
2356
  className: WorkspaceBrowser_module_css_default.listArea,
2335
2357
  children: wide && (normalizedQuery !== "" ? (0, react_jsx_runtime.jsx)(SearchResults, {
2358
+ usePanelInfo,
2336
2359
  useSessions,
2337
2360
  useSessionPendingInteraction,
2338
2361
  open: openSearchResult,
@@ -2343,6 +2366,7 @@ window.__ModuleLoader__.load({
2343
2366
  resultLimit: searchResultLimit,
2344
2367
  t
2345
2368
  }) : groupBy === "flat" ? (0, react_jsx_runtime.jsx)(FlatList, {
2369
+ usePanelInfo,
2346
2370
  useSessions,
2347
2371
  useSessionPendingInteraction,
2348
2372
  open,
@@ -2359,6 +2383,7 @@ window.__ModuleLoader__.load({
2359
2383
  onSessionRevealed: acknowledgeSessionReveal,
2360
2384
  t
2361
2385
  }) : (0, react_jsx_runtime.jsx)(SessionTree, {
2386
+ usePanelInfo,
2362
2387
  useSessions,
2363
2388
  useSessionPendingInteraction,
2364
2389
  onSessionRename,
@@ -2689,7 +2714,8 @@ window.__ModuleLoader__.load({
2689
2714
  "workspaces",
2690
2715
  "locale",
2691
2716
  "remote",
2692
- "remote.directoryPicker"
2717
+ "remote.directoryPicker",
2718
+ "layout"
2693
2719
  ];
2694
2720
  /**
2695
2721
  * Register the browser and picker once their slot declarations are on the
@@ -2721,13 +2747,14 @@ window.__ModuleLoader__.load({
2721
2747
  subscribe: (listener) => ctx.on("connection/reset", listener)
2722
2748
  };
2723
2749
  const pickerFlowSource = flowSource("conversation.hero.workspace.directoryFlow");
2750
+ const openSession = (sessionId) => {
2751
+ uiWorkspace.openSession(sessionId);
2752
+ };
2724
2753
  const browserInjected = () => ({
2725
2754
  startSession: (workspaceId) => {
2726
2755
  uiWorkspace.startSession(workspaceId);
2727
2756
  },
2728
- open: (sessionId) => {
2729
- sessions.open(sessionId);
2730
- },
2757
+ open: openSession,
2731
2758
  searchSessions,
2732
2759
  searchResultLimit: sessions.searchResultLimit,
2733
2760
  renameSession: async (sessionId, title) => {
@@ -2737,12 +2764,7 @@ window.__ModuleLoader__.load({
2737
2764
  if (!result.ok) throw new Error(result.error.message);
2738
2765
  },
2739
2766
  forkSession: (sessionId) => {
2740
- sessions.fork({
2741
- sessionId,
2742
- increaseTitle: true
2743
- }).then((childId) => {
2744
- sessions.open(childId);
2745
- }).catch(() => {});
2767
+ uiWorkspace.forkSession(sessionId).catch(() => {});
2746
2768
  },
2747
2769
  renameWorkspace: async (workspaceId, title) => {
2748
2770
  await workspaces.rename(workspaceId, title);
@@ -6,6 +6,24 @@ import type { IWorkspaces, WorkspaceId } from '@deepseek-ai/dsh-api-workspace-co
6
6
  import type { SessionId } from '@deepseek-ai/dsh-session/types';
7
7
  /** Workspace archive and directory operations consumed by Client UI domains. */
8
8
  export interface UiWorkspace {
9
+ /**
10
+ * Select a Session and show its Conversation as one UI navigation action.
11
+ * @param sessionId - listed or retained Session to display.
12
+ */
13
+ openSession(sessionId: SessionId): void;
14
+ /**
15
+ * Connect a Workspace and open its Session unless a later navigation supersedes it.
16
+ * @param workspaceId - target Workspace.
17
+ * @param beforeOpen - optional synchronous preparation for the selected Session, skipped after supersession.
18
+ * @returns completion; a superseded request may create a Session but does not open it.
19
+ */
20
+ openWorkspace(workspaceId: WorkspaceId, beforeOpen?: (sessionId: SessionId) => void): Promise<void>;
21
+ /**
22
+ * Fork a Session and open the child unless a later navigation supersedes it.
23
+ * @param sessionId - source Session.
24
+ * @returns completion; a superseded request leaves its child available without selecting it.
25
+ */
26
+ forkSession(sessionId: SessionId): Promise<void>;
9
27
  /**
10
28
  * Resolve the reusable or newly created blank Session for a Workspace.
11
29
  * @param workspaceId - target Workspace.
@@ -61,6 +79,7 @@ declare class UiWorkspaceService extends Service implements UiWorkspace {
61
79
  private readonly workspaces;
62
80
  private readonly sessions;
63
81
  private readonly connecting;
82
+ private readonly lifetime;
64
83
  /**
65
84
  * @param ctx - Client root Context.
66
85
  * @param directoryPicker - the directory-picking Remote namespace.
@@ -69,6 +88,9 @@ declare class UiWorkspaceService extends Service implements UiWorkspace {
69
88
  */
70
89
  constructor(ctx: Context, directoryPicker: ClientRemote['directoryPicker'], workspaces: IWorkspaces, sessions: ISessions);
71
90
  connectWorkspace(workspaceId: WorkspaceId): Promise<SessionId>;
91
+ openSession(sessionId: SessionId): void;
92
+ openWorkspace(workspaceId: WorkspaceId, beforeOpen?: (sessionId: SessionId) => void): Promise<void>;
93
+ forkSession(sessionId: SessionId): Promise<void>;
72
94
  startSession(workspaceId?: WorkspaceId): void;
73
95
  archiveSession(sessionId: SessionId): Promise<void>;
74
96
  pickDirectory(): Promise<string | null>;
@@ -4,5 +4,5 @@ import type { WorkspaceBrowserProps } from '../contract/slots.ts';
4
4
  * @param props - composed slot props (shell owner share + store + injected actions).
5
5
  * @returns the region element tree.
6
6
  */
7
- export declare function WorkspaceBrowser({ wide, expandSidebar, useSessions, useSessionPendingInteraction, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, insertSessionBefore, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostInfo, renderSlot, t, }: WorkspaceBrowserProps): import("react").JSX.Element;
7
+ export declare function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionPendingInteraction, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, insertSessionBefore, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostInfo, renderSlot, t, }: WorkspaceBrowserProps): import("react").JSX.Element;
8
8
  //# sourceMappingURL=WorkspaceBrowser.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-ui-workspace",
3
3
  "description": "Workspace picker plugin: one WorkspacePicker registered into the sidebar and empty-state workspace slots",
4
- "version": "0.1.5-alpha.1",
4
+ "version": "0.1.5-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -34,6 +34,7 @@
34
34
  "@deepseek-ai/dsh-client-connection",
35
35
  "@deepseek-ai/dsh-client-locale",
36
36
  "@deepseek-ai/dsh-client-ui-conversation",
37
+ "@deepseek-ai/dsh-client-ui-layout",
37
38
  "@deepseek-ai/dsh-client-ui-renderer",
38
39
  "@deepseek-ai/dsh-client-ui-session",
39
40
  "@deepseek-ai/dsh-client-ui-sidebar"
@@ -42,33 +43,32 @@
42
43
  }
43
44
  },
44
45
  "license": "MIT",
45
- "dependencies": {
46
- "clsx": "^2.0.0"
47
- },
48
46
  "peerDependencies": {
49
47
  "@deepseek-ai/cordis": "^4.0.2"
50
48
  },
51
49
  "devDependencies": {
52
50
  "@types/react": "~18.3.1",
53
51
  "react": "^18.2.0",
54
- "@deepseek-ai/dsh-api-session-controller": "^0.1.5-alpha.1",
55
- "@deepseek-ai/dsh-api-workspace-controller": "^0.1.5-alpha.1",
56
- "@deepseek-ai/dsh-api-remotes": "^0.1.5-alpha.1",
57
- "@deepseek-ai/dsh-client-connection": "^0.1.5-alpha.1",
58
- "@deepseek-ai/dsh-client-locale": "^0.1.5-alpha.1",
59
- "@deepseek-ai/dsh-client-store": "^0.1.5-alpha.1",
60
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-alpha.1",
61
- "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-alpha.1",
62
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-alpha.1",
63
- "@deepseek-ai/dsh-client-test-runtime": "^0.1.5-alpha.1",
64
- "@deepseek-ai/dsh-client-ui-session": "^0.1.5-alpha.1",
65
- "@deepseek-ai/dsh-client-ui-sidebar": "^0.1.5-alpha.1",
66
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-alpha.1",
67
- "@deepseek-ai/dsh-schedule": "^0.1.5-alpha.1",
68
- "@deepseek-ai/dsh-typert-protocol": "^0.1.5-alpha.1",
69
- "@deepseek-ai/dsh-session": "^0.1.5-alpha.1",
70
- "@deepseek-ai/cordis": "^4.0.2",
71
- "@deepseek-ai/dsh-util-workspace-path": "^0.1.5-alpha.1"
52
+ "clsx": "^2.0.0",
53
+ "@deepseek-ai/dsh-api-remotes": "^0.1.5-alpha.2",
54
+ "@deepseek-ai/dsh-client-locale": "^0.1.5-alpha.2",
55
+ "@deepseek-ai/dsh-client-store": "^0.1.5-alpha.2",
56
+ "@deepseek-ai/dsh-api-session-controller": "^0.1.5-alpha.2",
57
+ "@deepseek-ai/dsh-api-workspace-controller": "^0.1.5-alpha.2",
58
+ "@deepseek-ai/dsh-client-connection": "^0.1.5-alpha.2",
59
+ "@deepseek-ai/dsh-client-test-runtime": "^0.1.5-alpha.2",
60
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-alpha.2",
61
+ "@deepseek-ai/dsh-client-ui-layout": "^0.1.5-alpha.2",
62
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-alpha.2",
63
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-alpha.2",
64
+ "@deepseek-ai/dsh-client-ui-session": "^0.1.5-alpha.2",
65
+ "@deepseek-ai/dsh-client-ui-sidebar": "^0.1.5-alpha.2",
66
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-alpha.2",
67
+ "@deepseek-ai/dsh-schedule": "^0.1.5-alpha.2",
68
+ "@deepseek-ai/dsh-typert-protocol": "^0.1.5-alpha.2",
69
+ "@deepseek-ai/dsh-session": "^0.1.5-alpha.2",
70
+ "@deepseek-ai/dsh-util-workspace-path": "^0.1.5-alpha.2",
71
+ "@deepseek-ai/cordis": "^4.0.2"
72
72
  },
73
73
  "files": [
74
74
  "lib/index.js",