dsh-group-status 0.5.0 → 0.5.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/CHANGELOG.md +117 -0
- package/README.en.md +138 -133
- package/README.md +140 -133
- package/docs/facet-protocol.md +274 -0
- package/docs/media/glyph-completed.svg +3 -0
- package/docs/media/glyph-pending.svg +3 -0
- package/docs/media/glyph-running.svg +6 -0
- package/docs/media/screenshot-folders.png +0 -0
- package/docs/media/screenshot-hover.png +0 -0
- package/docs/upgrading.md +241 -0
- package/lib/client.js +1197 -1159
- package/lib/client.js.map +1 -1
- package/lib/index.js +31 -4
- package/lib/types/build-stamp.d.ts +4 -0
- package/lib/types/build-stamp.d.ts.map +1 -0
- package/lib/types/build-stamp.js +23 -0
- package/lib/types/build-stamp.js.map +1 -0
- package/lib/types/client/BadgeSettingsCard.js +3 -3
- package/lib/types/client/BadgeSettingsCard.js.map +1 -1
- package/lib/types/client/group-status.d.ts +5 -0
- package/lib/types/client/group-status.d.ts.map +1 -1
- package/lib/types/client/group-status.js +25 -0
- package/lib/types/client/group-status.js.map +1 -1
- package/lib/types/client/locales.d.ts +4 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +4 -0
- package/lib/types/client/locales.js.map +1 -1
- package/lib/types/client/navigation.d.ts +23 -8
- package/lib/types/client/navigation.d.ts.map +1 -1
- package/lib/types/client/navigation.js +20 -3
- package/lib/types/client/navigation.js.map +1 -1
- package/lib/types/client/rows/EmptySessions.d.ts +4 -0
- package/lib/types/client/rows/EmptySessions.d.ts.map +1 -0
- package/lib/types/client/rows/EmptySessions.js +10 -0
- package/lib/types/client/rows/EmptySessions.js.map +1 -0
- package/lib/types/client/rows/ListView.d.ts +24 -0
- package/lib/types/client/rows/ListView.d.ts.map +1 -0
- package/lib/types/client/rows/ListView.js +81 -0
- package/lib/types/client/rows/ListView.js.map +1 -0
- package/lib/types/client/rows/SessionTree.d.ts +4 -0
- package/lib/types/client/rows/SessionTree.d.ts.map +1 -0
- package/lib/types/client/rows/SessionTree.js +381 -0
- package/lib/types/client/rows/SessionTree.js.map +1 -0
- package/lib/types/client/rows/ViewOptionsMenu.d.ts +14 -0
- package/lib/types/client/rows/ViewOptionsMenu.d.ts.map +1 -0
- package/lib/types/client/rows/ViewOptionsMenu.js +50 -0
- package/lib/types/client/rows/ViewOptionsMenu.js.map +1 -0
- package/lib/types/client/rows/WorkspaceBrowser.d.ts +0 -5
- package/lib/types/client/rows/WorkspaceBrowser.d.ts.map +1 -1
- package/lib/types/client/rows/WorkspaceBrowser.js +7 -557
- package/lib/types/client/rows/WorkspaceBrowser.js.map +1 -1
- package/lib/types/client/rows/browser-shared.d.ts +105 -0
- package/lib/types/client/rows/browser-shared.d.ts.map +1 -0
- package/lib/types/client/rows/browser-shared.js +93 -0
- package/lib/types/client/rows/browser-shared.js.map +1 -0
- package/lib/types/client/session-actions/derived.d.ts.map +1 -1
- package/lib/types/client/session-actions/derived.js +13 -2
- package/lib/types/client/session-actions/derived.js.map +1 -1
- package/lib/types/client/session-watermarks.d.ts +6 -1
- package/lib/types/client/session-watermarks.d.ts.map +1 -1
- package/lib/types/client/session-watermarks.js +31 -11
- package/lib/types/client/session-watermarks.js.map +1 -1
- package/lib/types/client/toast/register.d.ts.map +1 -1
- package/lib/types/client/toast/register.js +102 -132
- package/lib/types/client/toast/register.js.map +1 -1
- package/lib/types/host-contract.d.ts +15 -1
- package/lib/types/host-contract.d.ts.map +1 -1
- package/lib/types/host-contract.js +47 -4
- package/lib/types/host-contract.js.map +1 -1
- package/lib/types/index.d.ts +6 -3
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +10 -4
- package/lib/types/index.js.map +1 -1
- package/package.json +17 -3
package/lib/client.js
CHANGED
|
@@ -857,6 +857,8 @@ window.__ModuleLoader__.load({
|
|
|
857
857
|
text: fieldText(BADGE_FIELDS.countThreshold),
|
|
858
858
|
label: t("settings.count.threshold"),
|
|
859
859
|
hint: t("settings.count.thresholdHint"),
|
|
860
|
+
decreaseLabel: t("settings.count.decrease"),
|
|
861
|
+
increaseLabel: t("settings.count.increase"),
|
|
860
862
|
state,
|
|
861
863
|
actions
|
|
862
864
|
})]
|
|
@@ -891,7 +893,7 @@ window.__ModuleLoader__.load({
|
|
|
891
893
|
});
|
|
892
894
|
}
|
|
893
895
|
/** 单行数字输入:label + 带加减按钮的数字输入 + hint。 */
|
|
894
|
-
function NumberRow({ field, text, label, hint, state, actions }) {
|
|
896
|
+
function NumberRow({ field, text, label, hint, decreaseLabel, increaseLabel, state, actions }) {
|
|
895
897
|
const disabled = !state.writable || state.saving;
|
|
896
898
|
const currentValue = Number(text) || 0;
|
|
897
899
|
const decrement = () => {
|
|
@@ -915,7 +917,7 @@ window.__ModuleLoader__.load({
|
|
|
915
917
|
className: BadgeSettingsCard_module_css_default.badgeNumberButton,
|
|
916
918
|
disabled: disabled || currentValue <= 0,
|
|
917
919
|
onClick: decrement,
|
|
918
|
-
"aria-label":
|
|
920
|
+
"aria-label": decreaseLabel,
|
|
919
921
|
children: "−"
|
|
920
922
|
}),
|
|
921
923
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -927,7 +929,7 @@ window.__ModuleLoader__.load({
|
|
|
927
929
|
className: BadgeSettingsCard_module_css_default.badgeNumberButton,
|
|
928
930
|
disabled,
|
|
929
931
|
onClick: increment,
|
|
930
|
-
"aria-label":
|
|
932
|
+
"aria-label": increaseLabel,
|
|
931
933
|
children: "+"
|
|
932
934
|
})
|
|
933
935
|
]
|
|
@@ -987,11 +989,34 @@ window.__ModuleLoader__.load({
|
|
|
987
989
|
}
|
|
988
990
|
//#endregion
|
|
989
991
|
//#region src/client/toast/register.tsx
|
|
990
|
-
/**
|
|
992
|
+
/**
|
|
993
|
+
* Wrap a business display mapping without making the slot component generic.
|
|
994
|
+
* @param display - 该键的纯文案映射;`notice` 的类型由键决定,写错字段即编译失败。
|
|
995
|
+
*/
|
|
991
996
|
function plainToast(display) {
|
|
992
997
|
return () => ({ display });
|
|
993
998
|
}
|
|
994
999
|
/**
|
|
1000
|
+
* 一条普通通知的登记数据:键与它自己的 display 成对。
|
|
1001
|
+
*
|
|
1002
|
+
* 键必须作为泛型实参先写出来,`display` 的 `notice` 才有类型——单看 display 推断不出
|
|
1003
|
+
* K,只能退回全部通知的联合。存下来的 `inject` 按 {@link KeyedToastInjected} 抹掉键
|
|
1004
|
+
* 负载:键 → 负载 的对应关系在入参这一侧已经校验过。
|
|
1005
|
+
*/
|
|
1006
|
+
function plainToastRow(key, display) {
|
|
1007
|
+
return {
|
|
1008
|
+
key,
|
|
1009
|
+
inject: plainToast(display)
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
const ARCHIVE_TOASTS = [{
|
|
1013
|
+
key: "archived",
|
|
1014
|
+
text: "toast.archived"
|
|
1015
|
+
}, {
|
|
1016
|
+
key: "stoppedAndArchived",
|
|
1017
|
+
text: "toast.stoppedAndArchived"
|
|
1018
|
+
}];
|
|
1019
|
+
/**
|
|
995
1020
|
* Register notice presentations with their business-owned display fields and actions.
|
|
996
1021
|
* @param ctx - workspace plugin context.
|
|
997
1022
|
* @param viewStore - viewing store, read only by archive notices.
|
|
@@ -1008,130 +1033,72 @@ window.__ModuleLoader__.load({
|
|
|
1008
1033
|
actions.showArchived();
|
|
1009
1034
|
}
|
|
1010
1035
|
});
|
|
1036
|
+
const plainToasts = [
|
|
1037
|
+
plainToastRow("sessionIdCopied", (_notice, t) => ({
|
|
1038
|
+
text: t("toast.sessionIdCopied"),
|
|
1039
|
+
tone: "success"
|
|
1040
|
+
})),
|
|
1041
|
+
plainToastRow("sessionIdCopyFailed", (_notice, t) => ({
|
|
1042
|
+
text: t("toast.sessionIdCopyFailed"),
|
|
1043
|
+
tone: "warning"
|
|
1044
|
+
})),
|
|
1045
|
+
plainToastRow("pinFailed", (_notice, t) => ({
|
|
1046
|
+
text: t("toast.pinFailed"),
|
|
1047
|
+
tone: "warning"
|
|
1048
|
+
})),
|
|
1049
|
+
plainToastRow("unpinFailed", (_notice, t) => ({
|
|
1050
|
+
text: t("toast.unpinFailed"),
|
|
1051
|
+
tone: "warning"
|
|
1052
|
+
})),
|
|
1053
|
+
plainToastRow("archivedNotOpenable", (_notice, t) => ({
|
|
1054
|
+
text: t("toast.archivedNotOpenable"),
|
|
1055
|
+
tone: "warning"
|
|
1056
|
+
})),
|
|
1057
|
+
plainToastRow("defaultWorkspaceFailed", (_notice, t) => ({
|
|
1058
|
+
text: t("defaultWorkspace.failed"),
|
|
1059
|
+
tone: "warning"
|
|
1060
|
+
})),
|
|
1061
|
+
plainToastRow("shortcutForkFailed", (notice, t) => ({
|
|
1062
|
+
text: t(notice.reason === "unavailable" ? "shortcut.noCompletedTurn" : "shortcut.forkFailed"),
|
|
1063
|
+
tone: "plain"
|
|
1064
|
+
})),
|
|
1065
|
+
plainToastRow("createFailed", (notice, t) => ({
|
|
1066
|
+
text: t("toast.createFailed", { message: notice.message }),
|
|
1067
|
+
tone: "warning",
|
|
1068
|
+
holdMs: 6e3
|
|
1069
|
+
})),
|
|
1070
|
+
plainToastRow("forkRequiresOpen", (notice, t, dismiss) => ({
|
|
1071
|
+
text: t("toast.forkRequiresOpen"),
|
|
1072
|
+
tone: "warning",
|
|
1073
|
+
holdMs: 6e3,
|
|
1074
|
+
actions: [{
|
|
1075
|
+
label: t("toast.openForkSource"),
|
|
1076
|
+
onClick: () => {
|
|
1077
|
+
dismiss();
|
|
1078
|
+
actions.openForkSource(notice.sessionId);
|
|
1079
|
+
}
|
|
1080
|
+
}]
|
|
1081
|
+
}))
|
|
1082
|
+
];
|
|
1011
1083
|
ctx.slots.registerFactory({
|
|
1012
1084
|
name: "workspace.toast.content",
|
|
1013
1085
|
scope: "root"
|
|
1014
1086
|
}, WorkspaceToast);
|
|
1015
1087
|
ctx.slots.inject("workspace.toast", function* () {
|
|
1016
|
-
yield ctx.slots.register({
|
|
1017
|
-
name: "workspace.toast",
|
|
1018
|
-
key: "sessionIdCopied",
|
|
1019
|
-
locale: "workspace",
|
|
1020
|
-
inject: plainToast((_notice, t) => ({
|
|
1021
|
-
text: t("toast.sessionIdCopied"),
|
|
1022
|
-
tone: "success"
|
|
1023
|
-
}))
|
|
1024
|
-
}, toastEntry);
|
|
1025
|
-
yield ctx.slots.register({
|
|
1026
|
-
name: "workspace.toast",
|
|
1027
|
-
key: "sessionIdCopyFailed",
|
|
1028
|
-
locale: "workspace",
|
|
1029
|
-
inject: plainToast((_notice, t) => ({
|
|
1030
|
-
text: t("toast.sessionIdCopyFailed"),
|
|
1031
|
-
tone: "warning"
|
|
1032
|
-
}))
|
|
1033
|
-
}, toastEntry);
|
|
1034
|
-
yield ctx.slots.register({
|
|
1035
|
-
name: "workspace.toast",
|
|
1036
|
-
key: "pinFailed",
|
|
1037
|
-
locale: "workspace",
|
|
1038
|
-
inject: plainToast((_notice, t) => ({
|
|
1039
|
-
text: t("toast.pinFailed"),
|
|
1040
|
-
tone: "warning"
|
|
1041
|
-
}))
|
|
1042
|
-
}, toastEntry);
|
|
1043
|
-
yield ctx.slots.register({
|
|
1044
|
-
name: "workspace.toast",
|
|
1045
|
-
key: "unpinFailed",
|
|
1046
|
-
locale: "workspace",
|
|
1047
|
-
inject: plainToast((_notice, t) => ({
|
|
1048
|
-
text: t("toast.unpinFailed"),
|
|
1049
|
-
tone: "warning"
|
|
1050
|
-
}))
|
|
1051
|
-
}, toastEntry);
|
|
1052
|
-
yield ctx.slots.register({
|
|
1053
|
-
name: "workspace.toast",
|
|
1054
|
-
key: "archivedNotOpenable",
|
|
1055
|
-
locale: "workspace",
|
|
1056
|
-
inject: plainToast((_notice, t) => ({
|
|
1057
|
-
text: t("toast.archivedNotOpenable"),
|
|
1058
|
-
tone: "warning"
|
|
1059
|
-
}))
|
|
1060
|
-
}, toastEntry);
|
|
1061
|
-
yield ctx.slots.register({
|
|
1062
|
-
name: "workspace.toast",
|
|
1063
|
-
key: "defaultWorkspaceFailed",
|
|
1064
|
-
locale: "workspace",
|
|
1065
|
-
inject: plainToast((_notice, t) => ({
|
|
1066
|
-
text: t("defaultWorkspace.failed"),
|
|
1067
|
-
tone: "warning"
|
|
1068
|
-
}))
|
|
1069
|
-
}, toastEntry);
|
|
1070
|
-
yield ctx.slots.register({
|
|
1071
|
-
name: "workspace.toast",
|
|
1072
|
-
key: "shortcutForkFailed",
|
|
1073
|
-
locale: "workspace",
|
|
1074
|
-
inject: plainToast((notice, t) => ({
|
|
1075
|
-
text: t(notice.reason === "unavailable" ? "shortcut.noCompletedTurn" : "shortcut.forkFailed"),
|
|
1076
|
-
tone: "plain"
|
|
1077
|
-
}))
|
|
1078
|
-
}, toastEntry);
|
|
1079
|
-
yield ctx.slots.register({
|
|
1080
|
-
name: "workspace.toast",
|
|
1081
|
-
key: "createFailed",
|
|
1082
|
-
locale: "workspace",
|
|
1083
|
-
inject: plainToast((notice, t) => ({
|
|
1084
|
-
text: t("toast.createFailed", { message: notice.message }),
|
|
1085
|
-
tone: "warning",
|
|
1086
|
-
holdMs: 6e3
|
|
1087
|
-
}))
|
|
1088
|
-
}, toastEntry);
|
|
1089
|
-
yield ctx.slots.register({
|
|
1088
|
+
for (const { key, inject } of plainToasts) yield ctx.slots.register({
|
|
1090
1089
|
name: "workspace.toast",
|
|
1091
|
-
key
|
|
1090
|
+
key,
|
|
1092
1091
|
locale: "workspace",
|
|
1093
|
-
inject
|
|
1094
|
-
text: t("toast.forkRequiresOpen"),
|
|
1095
|
-
tone: "warning",
|
|
1096
|
-
holdMs: 6e3,
|
|
1097
|
-
actions: [{
|
|
1098
|
-
label: t("toast.openForkSource"),
|
|
1099
|
-
onClick: () => {
|
|
1100
|
-
dismiss();
|
|
1101
|
-
actions.openForkSource(notice.sessionId);
|
|
1102
|
-
}
|
|
1103
|
-
}]
|
|
1104
|
-
}))
|
|
1092
|
+
inject
|
|
1105
1093
|
}, toastEntry);
|
|
1106
|
-
yield ctx.slots.register({
|
|
1107
|
-
name: "workspace.toast",
|
|
1108
|
-
key: "archived",
|
|
1109
|
-
locale: "workspace",
|
|
1110
|
-
store: viewStore,
|
|
1111
|
-
inject: () => ({
|
|
1112
|
-
display: (notice, t, dismiss) => ({
|
|
1113
|
-
text: t("toast.archived"),
|
|
1114
|
-
tone: "success",
|
|
1115
|
-
holdMs: 6e3,
|
|
1116
|
-
actions: [{
|
|
1117
|
-
label: t("toast.archivedUndo"),
|
|
1118
|
-
onClick: () => {
|
|
1119
|
-
dismiss();
|
|
1120
|
-
actions.undoArchive(notice.sessionId);
|
|
1121
|
-
}
|
|
1122
|
-
}]
|
|
1123
|
-
}),
|
|
1124
|
-
filterAction: archiveFilterAction
|
|
1125
|
-
})
|
|
1126
|
-
}, archiveToastEntry);
|
|
1127
|
-
yield ctx.slots.register({
|
|
1094
|
+
for (const { key, text } of ARCHIVE_TOASTS) yield ctx.slots.register({
|
|
1128
1095
|
name: "workspace.toast",
|
|
1129
|
-
key
|
|
1096
|
+
key,
|
|
1130
1097
|
locale: "workspace",
|
|
1131
1098
|
store: viewStore,
|
|
1132
1099
|
inject: () => ({
|
|
1133
1100
|
display: (notice, t, dismiss) => ({
|
|
1134
|
-
text: t(
|
|
1101
|
+
text: t(text),
|
|
1135
1102
|
tone: "success",
|
|
1136
1103
|
holdMs: 6e3,
|
|
1137
1104
|
actions: [{
|
|
@@ -2385,7 +2352,10 @@ window.__ModuleLoader__.load({
|
|
|
2385
2352
|
const sessions = this.sessions.list.getSnapshot();
|
|
2386
2353
|
const blank = sessions.ids.find((id) => {
|
|
2387
2354
|
const summary = sessions.byId[id];
|
|
2388
|
-
|
|
2355
|
+
if (summary === void 0 || !summary.blank || summary.cwd !== workspace.path) return false;
|
|
2356
|
+
if (!workspace.sessionIds.includes(id) || archived.includes(id)) return false;
|
|
2357
|
+
const recorded = summary.projectionValues?.agentPreset;
|
|
2358
|
+
return typeof recorded !== "string" || recorded === options.agentPreset;
|
|
2389
2359
|
});
|
|
2390
2360
|
return this.createNamed({
|
|
2391
2361
|
workspaceId: workspace.workspaceId,
|
|
@@ -2464,7 +2434,7 @@ window.__ModuleLoader__.load({
|
|
|
2464
2434
|
startSession(workspaceId, options) {
|
|
2465
2435
|
const { agentPreset, ...draft } = options ?? {};
|
|
2466
2436
|
const draftOptions = options === void 0 ? void 0 : draft;
|
|
2467
|
-
const initializeDraft = draftOptions !== void 0 && (
|
|
2437
|
+
const initializeDraft = draftOptions !== void 0 && (draft.prompt !== void 0 || draft.clearPreviousDraft === true || draft.whenDraftExists !== void 0);
|
|
2468
2438
|
const workspace = this.workspaces.list.getSnapshot();
|
|
2469
2439
|
const sessions = this.sessions.list.getSnapshot();
|
|
2470
2440
|
const current = this.mainReference?.sessionId;
|
|
@@ -2698,462 +2668,902 @@ window.__ModuleLoader__.load({
|
|
|
2698
2668
|
function workspaceDisplayTitle(title, localizedDefault) {
|
|
2699
2669
|
return title === "default-workspace" ? localizedDefault : title;
|
|
2700
2670
|
}
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
planReview: 0,
|
|
2712
|
-
question: 0,
|
|
2713
|
-
done: 0,
|
|
2714
|
-
subagents: 0,
|
|
2715
|
-
facets: /* @__PURE__ */ new Map()
|
|
2716
|
-
});
|
|
2717
|
-
/**
|
|
2718
|
-
* 合并两份分组折果:本插件自己的计数相加,外部状态来源**按 facet 各自合并**。
|
|
2719
|
-
*
|
|
2720
|
-
* 用于多层聚合:折叠的父文件夹要覆盖整棵子树,而子树里每一层都已各自折过一遍。
|
|
2721
|
-
* `FacetFold.data` 怎么合归该 facet 自己决定,本模块从不解释。
|
|
2722
|
-
*/
|
|
2723
|
-
function mergeLiveStatus(a, b) {
|
|
2724
|
-
return {
|
|
2725
|
-
running: a.running + b.running,
|
|
2726
|
-
approval: a.approval + b.approval,
|
|
2727
|
-
planReview: a.planReview + b.planReview,
|
|
2728
|
-
question: a.question + b.question,
|
|
2729
|
-
done: a.done + b.done,
|
|
2730
|
-
subagents: a.subagents + b.subagents,
|
|
2731
|
-
facets: mergeFacets(a.facets, b.facets)
|
|
2732
|
-
};
|
|
2733
|
-
}
|
|
2734
|
-
/** 按 facet id 合并两侧折果;只有一侧有的来源原样带过。 */
|
|
2735
|
-
function mergeFacets(a, b) {
|
|
2736
|
-
if (a.size === 0) return b;
|
|
2737
|
-
if (b.size === 0) return a;
|
|
2738
|
-
const merged = new Map(a);
|
|
2739
|
-
for (const [id, entry] of b) {
|
|
2740
|
-
const own = merged.get(id);
|
|
2741
|
-
merged.set(id, own === void 0 ? entry : {
|
|
2742
|
-
facet: own.facet,
|
|
2743
|
-
fold: own.facet.merge?.(own.fold, entry.fold) ?? defaultMergeFold(own.fold, entry.fold)
|
|
2744
|
-
});
|
|
2745
|
-
}
|
|
2746
|
-
return merged;
|
|
2671
|
+
//#endregion
|
|
2672
|
+
//#region \0dsh-css:src/client/WorkspacePicker.module.css.mjs
|
|
2673
|
+
const css$3 = ".S_JYgq_modalAction{min-width:72px}.S_JYgq_modalError,.S_JYgq_menuStatus{margin-top:8px;font-size:12px;line-height:18px}.S_JYgq_modalError{color:var(--dsw-alias-state-error-primary)}.S_JYgq_menuStatus{color:var(--dsw-alias-label-secondary)}";
|
|
2674
|
+
const tagId$3 = "dsh-group-status/WorkspacePicker.module.css";
|
|
2675
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
|
|
2676
|
+
const tag = document.createElement("style");
|
|
2677
|
+
tag.dataset.plugin = "dsh-group-status";
|
|
2678
|
+
tag.dataset.pluginCss = tagId$3;
|
|
2679
|
+
tag.textContent = css$3;
|
|
2680
|
+
document.head.appendChild(tag);
|
|
2747
2681
|
}
|
|
2682
|
+
var WorkspacePicker_module_css_default = {
|
|
2683
|
+
"menuStatus": "S_JYgq_menuStatus",
|
|
2684
|
+
"modalAction": "S_JYgq_modalAction",
|
|
2685
|
+
"modalError": "S_JYgq_modalError"
|
|
2686
|
+
};
|
|
2687
|
+
//#endregion
|
|
2688
|
+
//#region src/client/WorkspacePicker.tsx
|
|
2689
|
+
const ADD_WORKSPACE = "::add-workspace";
|
|
2748
2690
|
/**
|
|
2749
|
-
*
|
|
2750
|
-
*
|
|
2751
|
-
*
|
|
2752
|
-
* locale at render time.
|
|
2753
|
-
*
|
|
2754
|
-
* @param nodes - member rows of the group.
|
|
2755
|
-
* @param sessionIds - the same members' Session ids (facets address their own data by id).
|
|
2756
|
-
* @param facets - the registered status sources to fold over this group.
|
|
2757
|
-
* @param now - one clock reading for the whole pass, so sibling facets cannot disagree
|
|
2758
|
-
* about which deadlines are still ahead.
|
|
2691
|
+
* Render the pick menu plus the adoption error dialog.
|
|
2692
|
+
* @param props - owner-controlled flow props.
|
|
2693
|
+
* @returns menu + dialog elements.
|
|
2759
2694
|
*/
|
|
2760
|
-
function
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
}
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2695
|
+
function WorkspacePickFlow({ t, open, anchorRef, useWorkspaces, createWorkspace, useDirectoryFlow, renderDirectoryFlow, onPick, onClose, addOnly = false, onBusyChange, side = "bottom", selectedId }) {
|
|
2696
|
+
const workspaceSnapshot = useWorkspaces((state) => state);
|
|
2697
|
+
const workspaces = workspaceSnapshot.items;
|
|
2698
|
+
const getAnchorRect = (0, react.useCallback)(() => anchorRef?.current?.getBoundingClientRect() ?? null, [anchorRef]);
|
|
2699
|
+
const [errorOpen, setErrorOpen] = (0, react.useState)(false);
|
|
2700
|
+
const [modalError, setModalError] = (0, react.useState)(null);
|
|
2701
|
+
const [flowOpen, setFlowOpen] = (0, react.useState)(false);
|
|
2702
|
+
const [pickingFolder, setPickingFolder] = (0, react.useState)(false);
|
|
2703
|
+
const flowBusy = flowOpen || pickingFolder;
|
|
2704
|
+
(0, react.useEffect)(() => {
|
|
2705
|
+
onBusyChange?.(flowBusy);
|
|
2706
|
+
}, [flowBusy, onBusyChange]);
|
|
2707
|
+
const flowAvailable = useDirectoryFlow((occupied) => occupied);
|
|
2708
|
+
(0, react.useEffect)(() => {
|
|
2709
|
+
if (flowOpen && !flowAvailable) setFlowOpen(false);
|
|
2710
|
+
}, [flowOpen, flowAvailable]);
|
|
2711
|
+
const addEntries = flowAvailable ? [{
|
|
2712
|
+
id: ADD_WORKSPACE,
|
|
2713
|
+
label: t("menu.addWorkspace"),
|
|
2714
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutlineRegular, { size: 16 }),
|
|
2715
|
+
disabled: flowBusy
|
|
2716
|
+
}] : [];
|
|
2717
|
+
const pinAdd = !addOnly && workspaces.length > 0;
|
|
2718
|
+
const items = pinAdd ? workspaces.map((workspace) => ({
|
|
2719
|
+
id: workspace.workspaceId,
|
|
2720
|
+
label: workspaceDisplayTitle(workspace.title, t("workspace.defaultName")),
|
|
2721
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderCloseRegular, { size: 16 }),
|
|
2722
|
+
disabled: flowBusy
|
|
2723
|
+
})) : addEntries;
|
|
2724
|
+
const menuIsEmpty = items.length === 0;
|
|
2725
|
+
const closeModal = () => {
|
|
2726
|
+
setErrorOpen(false);
|
|
2727
|
+
setModalError(null);
|
|
2728
|
+
};
|
|
2729
|
+
/** Adopt a picked directory; failures land in the folder-error dialog (Choose again reopens the flow). */
|
|
2730
|
+
const adoptDirectory = (path) => createWorkspace({ path }).then((workspace) => {
|
|
2731
|
+
setFlowOpen(false);
|
|
2732
|
+
onPick(workspace.workspaceId);
|
|
2733
|
+
}).catch((reason) => {
|
|
2734
|
+
setModalError(reason instanceof Error ? reason.message : String(reason));
|
|
2735
|
+
setFlowOpen(false);
|
|
2736
|
+
setErrorOpen(true);
|
|
2737
|
+
});
|
|
2738
|
+
const openDirectoryFlow = (0, react.useCallback)(() => {
|
|
2739
|
+
onClose();
|
|
2740
|
+
setErrorOpen(false);
|
|
2741
|
+
setModalError(null);
|
|
2742
|
+
setFlowOpen(true);
|
|
2743
|
+
}, [onClose]);
|
|
2744
|
+
const listSettled = addOnly || workspaceSnapshot.phase === "ready";
|
|
2745
|
+
const addIsTheOnlyEntry = !pinAdd && listSettled && addEntries.length === 1;
|
|
2746
|
+
(0, react.useEffect)(() => {
|
|
2747
|
+
if (open && addIsTheOnlyEntry && !flowBusy) openDirectoryFlow();
|
|
2748
|
+
}, [
|
|
2749
|
+
open,
|
|
2750
|
+
addIsTheOnlyEntry,
|
|
2751
|
+
flowBusy,
|
|
2752
|
+
openDirectoryFlow
|
|
2753
|
+
]);
|
|
2754
|
+
/** Owner side of the flow conversation: adopt keeps the flow open (busy) until the Host answers. */
|
|
2755
|
+
const flowOwner = {
|
|
2756
|
+
open: flowOpen,
|
|
2757
|
+
busy: pickingFolder,
|
|
2758
|
+
onPicked: (path) => {
|
|
2759
|
+
setPickingFolder(true);
|
|
2760
|
+
adoptDirectory(path).finally(() => {
|
|
2761
|
+
setPickingFolder(false);
|
|
2762
|
+
});
|
|
2763
|
+
},
|
|
2764
|
+
onCancel: () => {
|
|
2765
|
+
setFlowOpen(false);
|
|
2766
|
+
},
|
|
2767
|
+
onError: (message) => {
|
|
2768
|
+
setFlowOpen(false);
|
|
2769
|
+
setModalError(message);
|
|
2770
|
+
setErrorOpen(true);
|
|
2789
2771
|
}
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2772
|
+
};
|
|
2773
|
+
const handleSelect = (id) => {
|
|
2774
|
+
if (id === ADD_WORKSPACE) {
|
|
2775
|
+
openDirectoryFlow();
|
|
2776
|
+
return;
|
|
2793
2777
|
}
|
|
2794
|
-
|
|
2795
|
-
}
|
|
2796
|
-
const folded = /* @__PURE__ */ new Map();
|
|
2797
|
-
for (const { facet } of facets) {
|
|
2798
|
-
let snapshot;
|
|
2799
|
-
try {
|
|
2800
|
-
snapshot = facet.getSnapshot();
|
|
2801
|
-
} catch {
|
|
2802
|
-
snapshot = void 0;
|
|
2803
|
-
}
|
|
2804
|
-
try {
|
|
2805
|
-
const result = facet.fold(snapshot, sessionIds, now);
|
|
2806
|
-
if (result !== void 0 && result.count > 0) folded.set(facet.id, {
|
|
2807
|
-
facet,
|
|
2808
|
-
fold: result
|
|
2809
|
-
});
|
|
2810
|
-
} catch {}
|
|
2811
|
-
}
|
|
2812
|
-
return {
|
|
2813
|
-
running,
|
|
2814
|
-
approval,
|
|
2815
|
-
planReview,
|
|
2816
|
-
question,
|
|
2817
|
-
done,
|
|
2818
|
-
subagents,
|
|
2819
|
-
facets: folded
|
|
2778
|
+
onPick(id);
|
|
2820
2779
|
};
|
|
2780
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2781
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
2782
|
+
open: open && !addIsTheOnlyEntry && !menuIsEmpty,
|
|
2783
|
+
anchor: null,
|
|
2784
|
+
items,
|
|
2785
|
+
...pinAdd ? { footer: addEntries } : {},
|
|
2786
|
+
selectedId,
|
|
2787
|
+
onSelect: handleSelect,
|
|
2788
|
+
onClose,
|
|
2789
|
+
side,
|
|
2790
|
+
portal: true,
|
|
2791
|
+
getAnchorRect
|
|
2792
|
+
}),
|
|
2793
|
+
open && !addIsTheOnlyEntry && !menuIsEmpty && workspaceSnapshot.phase === "pending" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2794
|
+
className: WorkspacePicker_module_css_default.menuStatus,
|
|
2795
|
+
role: "status",
|
|
2796
|
+
children: t("picker.loading")
|
|
2797
|
+
}),
|
|
2798
|
+
renderDirectoryFlow(flowOwner),
|
|
2799
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
2800
|
+
open: errorOpen,
|
|
2801
|
+
onClose: closeModal,
|
|
2802
|
+
closeLabel: t("close"),
|
|
2803
|
+
title: t("folderError.title"),
|
|
2804
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2805
|
+
variant: "outline",
|
|
2806
|
+
className: WorkspacePicker_module_css_default.modalAction,
|
|
2807
|
+
onClick: closeModal,
|
|
2808
|
+
children: t("cancel")
|
|
2809
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2810
|
+
variant: "primary",
|
|
2811
|
+
className: WorkspacePicker_module_css_default.modalAction,
|
|
2812
|
+
disabled: !flowAvailable,
|
|
2813
|
+
onClick: openDirectoryFlow,
|
|
2814
|
+
children: t("folderError.retry")
|
|
2815
|
+
})] }),
|
|
2816
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2817
|
+
className: WorkspacePicker_module_css_default.modalError,
|
|
2818
|
+
role: "alert",
|
|
2819
|
+
children: modalError
|
|
2820
|
+
})
|
|
2821
|
+
})
|
|
2822
|
+
] });
|
|
2821
2823
|
}
|
|
2822
2824
|
/**
|
|
2823
|
-
*
|
|
2824
|
-
*
|
|
2825
|
-
*
|
|
2826
|
-
*
|
|
2827
|
-
* keep counting them — so expansion is a rendering fact, never an input to the
|
|
2828
|
-
* aggregation. Short-circuiting expanded groups to `EMPTY_LIVE_STATUS` (the shape this
|
|
2829
|
-
* had before the tree-wide aggregator existed, harmless while nothing aggregated) makes
|
|
2830
|
-
* every collapsed parent undercount by the size of its expanded descendants.
|
|
2831
|
-
*
|
|
2832
|
-
* @param groups - derived groups, each with its member rows already populated.
|
|
2833
|
-
* @param facets - registered sources, already filtered by the visibility settings.
|
|
2834
|
-
* @param now - one clock reading shared by every group in the pass.
|
|
2825
|
+
* The conversation empty-state registration: adapts the owner share to the
|
|
2826
|
+
* core flow (all state and semantics live in the flow / the owner).
|
|
2827
|
+
* @param props - empty-state slot props (owner share + injected creation callback).
|
|
2828
|
+
* @returns the flow element.
|
|
2835
2829
|
*/
|
|
2836
|
-
function
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2830
|
+
function WorkspacePicker({ open, anchorRef, useWorkspaces, selectedId, onPick, onClose, createWorkspace, useDirectoryFlow, renderSlot, t }) {
|
|
2831
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspacePickFlow, {
|
|
2832
|
+
t,
|
|
2833
|
+
open,
|
|
2834
|
+
anchorRef,
|
|
2835
|
+
useWorkspaces,
|
|
2836
|
+
createWorkspace,
|
|
2837
|
+
useDirectoryFlow,
|
|
2838
|
+
renderDirectoryFlow: (owner) => renderSlot("conversation.hero.workspace.directoryFlow", owner),
|
|
2839
|
+
selectedId,
|
|
2840
|
+
onPick,
|
|
2841
|
+
onClose
|
|
2842
|
+
});
|
|
2843
|
+
}
|
|
2844
|
+
//#endregion
|
|
2845
|
+
//#region \0dsh-css:src/client/rows/WorkspaceBrowser.module.css.mjs
|
|
2846
|
+
const css$2 = ".QAYkWG_root{--dsh-session-list-edge-inset:var(--dsh-sidebar-inline-padding);--dsh-session-list-scrollbar-width:5px;--dsh-session-list-scrollbar-offset:2px;box-sizing:border-box;min-height:0;padding-right:var(--dsh-session-list-edge-inset);flex-direction:column;flex:1;display:flex}.QAYkWG_root.QAYkWG_rail{padding-right:0}.QAYkWG_iconButton{border-radius:var(--dsw-radius-sm);cursor:pointer;width:28px;height:28px;color:var(--dsw-alias-label-secondary);background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.QAYkWG_iconButton:focus-visible,.QAYkWG_searchButton:focus-visible,.QAYkWG_clearButton:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:-2px}.QAYkWG_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.QAYkWG_viewOptionsMenu{min-width:200px}.QAYkWG_sectionHeader{box-sizing:border-box;border-radius:var(--dsw-radius-md);height:36px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:flex-end;align-items:center;gap:4px;margin-bottom:4px;padding-left:4px;display:flex;overflow:hidden}.QAYkWG_root:not(.QAYkWG_rail) .QAYkWG_sectionHeader{margin-top:2px;margin-right:-4px}.QAYkWG_sectionLabel{white-space:nowrap;opacity:1;visibility:visible;min-width:0;max-width:45%;transition:max-width .18s var(--ds-ease-in-out), margin-right .18s var(--ds-ease-in-out), opacity .12s var(--ds-ease-in-out), transform .18s var(--ds-ease-in-out), visibility 0s linear;flex:none;line-height:20px;overflow:hidden}.QAYkWG_sectionLabelHidden{opacity:0;visibility:hidden;max-width:0;margin-right:-4px;transition-delay:0s,0s,0s,0s,.18s;transform:translate(-4px)}.QAYkWG_searchSlot{box-sizing:border-box;min-width:0;max-width:28px;transition:max-width .18s var(--ds-ease-in-out), padding-left .18s var(--ds-ease-in-out);flex:1;align-items:center;margin-left:auto;padding-left:0;display:flex}.QAYkWG_searchSlotExpanded{max-width:100%;padding-left:0}.QAYkWG_headerActions{opacity:1;visibility:visible;max-width:60px;transition:max-width .18s var(--ds-ease-in-out), opacity .12s var(--ds-ease-in-out), transform .18s var(--ds-ease-in-out), visibility 0s linear;flex:none;align-items:center;gap:4px;display:flex;overflow:hidden}.QAYkWG_headerActionsHidden{opacity:0;visibility:hidden;pointer-events:none;max-width:0;transition-delay:0s,0s,0s,.18s;transform:translate(4px)}.QAYkWG_search{box-sizing:border-box;border-radius:var(--dsw-radius-sm);cursor:text;width:100%;height:28px;color:var(--dsw-alias-label-secondary);transition:width .18s var(--ds-ease-in-out), padding .18s var(--ds-ease-in-out), border-color .18s var(--ds-ease-in-out), background-color .18s var(--ds-ease-in-out);background:0 0;border:none;flex:none;align-items:center;gap:0;margin:0;padding:0;display:flex;overflow:hidden}.QAYkWG_searchExpanded{border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);width:calc(100% + 4px);height:30px;color:var(--dsw-alias-label-caption);background:0 0;margin-inline:-2px;padding:0 4px 0 0}.QAYkWG_searchButton{border-radius:var(--dsw-radius-sm);cursor:pointer;width:28px;height:28px;color:inherit;background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.QAYkWG_searchExpanded .QAYkWG_searchButton{width:28px;height:30px}.QAYkWG_searchButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.QAYkWG_searchExpanded .QAYkWG_searchButton:hover{background:0 0}.QAYkWG_searchInput{opacity:0;pointer-events:none;width:0;min-width:0;color:var(--dsw-alias-label-primary);transition:opacity .12s var(--ds-ease-in-out);background:0 0;border:none;outline:none;flex:1;font-size:13px;line-height:18px}.QAYkWG_searchExpanded .QAYkWG_searchInput{opacity:1;pointer-events:auto;margin-left:-2px}.QAYkWG_searchInput::placeholder{color:var(--dsw-alias-label-tertiary)}.QAYkWG_clearButton{border-radius:var(--dsw-radius-sm);cursor:pointer;width:24px;height:24px;color:var(--dsw-alias-label-secondary);background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.QAYkWG_clearButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.QAYkWG_rail .QAYkWG_sectionHeader{justify-content:flex-start;gap:0;margin-bottom:12px;padding-left:0}.QAYkWG_rail .QAYkWG_headerActions{max-width:none}.QAYkWG_rail .QAYkWG_iconButton{border-radius:var(--dsw-radius-md);width:36px;height:36px;color:var(--dsw-alias-label-primary)}.QAYkWG_rail .QAYkWG_search{border-radius:var(--dsw-radius-md);background:0 0;border-color:#0000;gap:0;width:36px;height:36px;margin:0 0 12px;padding:0}.QAYkWG_rail .QAYkWG_searchButton{border-radius:var(--dsw-radius-md);width:36px;height:36px;color:var(--dsw-alias-label-primary)}.QAYkWG_rail .QAYkWG_searchButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.QAYkWG_listArea{min-height:0;margin-left:-4px;margin-right:calc(-1 * var(--dsh-session-list-edge-inset));flex-direction:column;flex:1;padding-left:4px;display:flex;overflow:visible}.QAYkWG_rail .QAYkWG_listArea{margin-left:0;margin-right:0;padding-left:0}.QAYkWG_treeBody{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}.QAYkWG_fade{left:0;right:var(--dsh-session-list-edge-inset);background:linear-gradient(to bottom, transparent, var(--dsw-specific-sidebar-fill));pointer-events:none;height:24px;position:absolute;bottom:0}[data-platform=darwin] .QAYkWG_fade{display:none}.QAYkWG_wide{animation:QAYkWG_wide-in .2s var(--ds-ease-in-out)}@keyframes QAYkWG_wide-in{0%{opacity:0}}.QAYkWG_list{min-height:0;margin-left:-4px;margin-right:var(--dsh-session-list-scrollbar-offset);padding-left:4px;padding-right:calc(var(--dsh-session-list-edge-inset) - var(--dsh-session-list-scrollbar-width) - var(--dsh-session-list-scrollbar-offset));scrollbar-gutter:stable;flex:1;padding-bottom:16px;overflow-y:auto}.QAYkWG_flatList>*+*,.QAYkWG_searchTree>[role=treeitem]+[role=treeitem],.QAYkWG_groupSection>*+*{margin-top:2px}.QAYkWG_searchStatus{color:var(--dsw-alias-label-tertiary);padding:10px 12px;font-size:12px;line-height:18px}.QAYkWG_skeletonRow{box-sizing:border-box;align-items:flex-start;gap:8px;min-height:48px;padding:6px 8px 7px;display:flex}.QAYkWG_skeletonDot{corner-shape:round;border-radius:50%;flex:none;width:16px;height:16px}.QAYkWG_skeletonBars{flex-direction:column;flex:1;gap:6px;min-width:0;display:flex}.QAYkWG_skeletonDot,.QAYkWG_skeletonBar{background:var(--dsw-alias-bg-skeleton);animation:2s cubic-bezier(.36,0,.64,1) infinite QAYkWG_search-skeleton}.QAYkWG_skeletonBar{border-radius:var(--dsw-radius-xs);width:65%;height:16px}.QAYkWG_skeletonBarWide{width:90%;height:13px}@keyframes QAYkWG_search-skeleton{0%{opacity:1}40%{opacity:.6}80%,to{opacity:1}}.QAYkWG_groupSection{position:relative}.QAYkWG_groupSection+.QAYkWG_groupSection{margin-top:4px}.QAYkWG_listTopDropIndicator,.QAYkWG_workspaceDropBefore:before,.QAYkWG_workspaceDropAfter:after{content:\"\";z-index:1;background:linear-gradient(55deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 0 / 5px 7px no-repeat, linear-gradient(125deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--dsw-alias-state-business-primary) 0 0) 4px 5px / calc(100% - 4px) 2px no-repeat;pointer-events:none;height:12px;position:absolute;left:0;right:0}.QAYkWG_listTopDropIndicator{top:-8px;left:0;right:var(--dsh-session-list-edge-inset)}.QAYkWG_listTopDropActive>.QAYkWG_workspaceDropBefore:first-child:before{display:none}.QAYkWG_workspaceDropBefore:before{top:-8px}.QAYkWG_workspaceDropAfter:after{bottom:-8px}.QAYkWG_sessionOverflowButton{border-radius:var(--dsw-radius-sm);width:100%;height:28px;padding:0 12px 0 calc(28px + var(--dsh-workspace-indent,0px));cursor:pointer;text-align:left;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;font-size:12px}.QAYkWG_groupSection>.QAYkWG_sessionOverflowButton{margin-top:0}.QAYkWG_sessionOverflowButton:hover{color:var(--dsw-alias-label-secondary);background:0 0}.QAYkWG_empty{color:var(--dsw-alias-label-tertiary);padding:16px 12px;font-size:13px}.QAYkWG_emptyState{color:var(--dsw-alias-label-tertiary);flex-direction:column;align-items:center;gap:8px;margin-top:80px;padding:0 12px;font-size:13px;line-height:20px;display:flex}.QAYkWG_emptyState>svg{color:var(--dsw-alias-label-caption);margin-bottom:4px}.QAYkWG_emptyAction{cursor:pointer;color:var(--dsw-alias-link);background:0 0;border:none;padding:0;font-size:13px;line-height:20px}.QAYkWG_renameInput{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-lg);width:100%;height:44px;color:var(--dsw-alias-label-primary);background:0 0;outline:none;padding:7px 14px;font-size:14px;font-weight:400;line-height:22px}.QAYkWG_renameInput:disabled{color:var(--dsw-alias-label-dimmed)}.QAYkWG_renameError{color:var(--dsw-alias-state-error-primary);margin-top:8px;font-size:12px;line-height:18px}.QAYkWG_deleteAction:not(:disabled){color:var(--dsw-alias-state-error-primary)}.QAYkWG_deleteStatus{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.QAYkWG_archiveActivity{color:var(--dsw-alias-label-primary);margin:0 0 8px;padding-left:18px;font-size:13px;line-height:20px}.QAYkWG_archiveActivity li{overflow-wrap:anywhere}@media (prefers-reduced-motion:reduce){.QAYkWG_wide,.QAYkWG_skeletonDot,.QAYkWG_skeletonBar{animation:none}.QAYkWG_search,.QAYkWG_sectionLabel,.QAYkWG_searchSlot,.QAYkWG_searchInput,.QAYkWG_headerActions{transition:none}}";
|
|
2847
|
+
const tagId$2 = "dsh-group-status/WorkspaceBrowser.module.css";
|
|
2848
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
|
|
2849
|
+
const tag = document.createElement("style");
|
|
2850
|
+
tag.dataset.plugin = "dsh-group-status";
|
|
2851
|
+
tag.dataset.pluginCss = tagId$2;
|
|
2852
|
+
tag.textContent = css$2;
|
|
2853
|
+
document.head.appendChild(tag);
|
|
2854
|
+
}
|
|
2855
|
+
var WorkspaceBrowser_module_css_default = {
|
|
2856
|
+
"archiveActivity": "QAYkWG_archiveActivity",
|
|
2857
|
+
"clearButton": "QAYkWG_clearButton",
|
|
2858
|
+
"deleteAction": "QAYkWG_deleteAction",
|
|
2859
|
+
"deleteStatus": "QAYkWG_deleteStatus",
|
|
2860
|
+
"empty": "QAYkWG_empty",
|
|
2861
|
+
"emptyAction": "QAYkWG_emptyAction",
|
|
2862
|
+
"emptyState": "QAYkWG_emptyState",
|
|
2863
|
+
"fade": "QAYkWG_fade",
|
|
2864
|
+
"flatList": "QAYkWG_flatList",
|
|
2865
|
+
"groupSection": "QAYkWG_groupSection",
|
|
2866
|
+
"headerActions": "QAYkWG_headerActions",
|
|
2867
|
+
"headerActionsHidden": "QAYkWG_headerActionsHidden",
|
|
2868
|
+
"iconButton": "QAYkWG_iconButton",
|
|
2869
|
+
"list": "QAYkWG_list",
|
|
2870
|
+
"listArea": "QAYkWG_listArea",
|
|
2871
|
+
"listTopDropActive": "QAYkWG_listTopDropActive",
|
|
2872
|
+
"listTopDropIndicator": "QAYkWG_listTopDropIndicator",
|
|
2873
|
+
"rail": "QAYkWG_rail",
|
|
2874
|
+
"renameError": "QAYkWG_renameError",
|
|
2875
|
+
"renameInput": "QAYkWG_renameInput",
|
|
2876
|
+
"root": "QAYkWG_root",
|
|
2877
|
+
"search": "QAYkWG_search",
|
|
2878
|
+
"search-skeleton": "QAYkWG_search-skeleton",
|
|
2879
|
+
"searchButton": "QAYkWG_searchButton",
|
|
2880
|
+
"searchExpanded": "QAYkWG_searchExpanded",
|
|
2881
|
+
"searchInput": "QAYkWG_searchInput",
|
|
2882
|
+
"searchSlot": "QAYkWG_searchSlot",
|
|
2883
|
+
"searchSlotExpanded": "QAYkWG_searchSlotExpanded",
|
|
2884
|
+
"searchStatus": "QAYkWG_searchStatus",
|
|
2885
|
+
"searchTree": "QAYkWG_searchTree",
|
|
2886
|
+
"sectionHeader": "QAYkWG_sectionHeader",
|
|
2887
|
+
"sectionLabel": "QAYkWG_sectionLabel",
|
|
2888
|
+
"sectionLabelHidden": "QAYkWG_sectionLabelHidden",
|
|
2889
|
+
"sessionOverflowButton": "QAYkWG_sessionOverflowButton",
|
|
2890
|
+
"skeletonBar": "QAYkWG_skeletonBar",
|
|
2891
|
+
"skeletonBarWide": "QAYkWG_skeletonBarWide",
|
|
2892
|
+
"skeletonBars": "QAYkWG_skeletonBars",
|
|
2893
|
+
"skeletonDot": "QAYkWG_skeletonDot",
|
|
2894
|
+
"skeletonRow": "QAYkWG_skeletonRow",
|
|
2895
|
+
"treeBody": "QAYkWG_treeBody",
|
|
2896
|
+
"viewOptionsMenu": "QAYkWG_viewOptionsMenu",
|
|
2897
|
+
"wide": "QAYkWG_wide",
|
|
2898
|
+
"wide-in": "QAYkWG_wide-in",
|
|
2899
|
+
"workspaceDropAfter": "QAYkWG_workspaceDropAfter",
|
|
2900
|
+
"workspaceDropBefore": "QAYkWG_workspaceDropBefore"
|
|
2901
|
+
};
|
|
2902
|
+
/** Keep controlled input and RPC payload inside the session.search wire contract. */
|
|
2903
|
+
function sanitizeSearchQuery(value) {
|
|
2904
|
+
const withoutNul = value.replaceAll("\0", "");
|
|
2905
|
+
if (withoutNul.length <= 500) return withoutNul;
|
|
2906
|
+
let end = 500;
|
|
2907
|
+
const last = withoutNul.charCodeAt(end - 1);
|
|
2908
|
+
const next = withoutNul.charCodeAt(end);
|
|
2909
|
+
if (last >= 55296 && last <= 56319 && next >= 56320 && next <= 57343) end--;
|
|
2910
|
+
return withoutNul.slice(0, end);
|
|
2840
2911
|
}
|
|
2841
2912
|
/**
|
|
2842
|
-
*
|
|
2843
|
-
*
|
|
2844
|
-
*
|
|
2845
|
-
* keep counting them — so the walk never depends on a group's expanded state.
|
|
2846
|
-
*
|
|
2847
|
-
* @param direct - each group's own-member status, keyed by group key.
|
|
2848
|
-
* @param childrenByParent - the rendered tree: parent key (`undefined` = roots) → children.
|
|
2849
|
-
* @returns aggregated status per group key: own members plus the whole subtree.
|
|
2913
|
+
* Accept the native drag at document level while a row drag is active: row
|
|
2914
|
+
* hover still owns the insertion marker, and releasing outside the list must
|
|
2915
|
+
* not be rendered as a rejected drop before dragend commits that last marker.
|
|
2850
2916
|
*/
|
|
2851
|
-
function
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
return
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2917
|
+
function useNativeDragAcceptance(active) {
|
|
2918
|
+
(0, react.useEffect)(() => {
|
|
2919
|
+
if (!active) return;
|
|
2920
|
+
const acceptDrag = (event) => {
|
|
2921
|
+
event.preventDefault();
|
|
2922
|
+
if (event.dataTransfer !== null) event.dataTransfer.dropEffect = "move";
|
|
2923
|
+
};
|
|
2924
|
+
const acceptDrop = (event) => {
|
|
2925
|
+
event.preventDefault();
|
|
2926
|
+
};
|
|
2927
|
+
document.addEventListener("dragover", acceptDrag);
|
|
2928
|
+
document.addEventListener("drop", acceptDrop);
|
|
2929
|
+
return () => {
|
|
2930
|
+
document.removeEventListener("dragover", acceptDrag);
|
|
2931
|
+
document.removeEventListener("drop", acceptDrop);
|
|
2932
|
+
};
|
|
2933
|
+
}, [active]);
|
|
2934
|
+
}
|
|
2935
|
+
/** Apply a visible drop to the complete account without removing hidden members. */
|
|
2936
|
+
function sessionDragOrder(order, rows, drag, over) {
|
|
2937
|
+
const source = rows.find((row) => row.id === drag.sessionId);
|
|
2938
|
+
const target = rows.find((row) => row.id === over.id);
|
|
2939
|
+
if (source === void 0 || target === void 0 || source.blank || source.pinned !== drag.pinned || target.pinned !== drag.pinned || source.id === target.id || !order.includes(source.id)) return;
|
|
2940
|
+
const section = rows.filter((row) => row.pinned === drag.pinned);
|
|
2941
|
+
const sourceIndex = section.findIndex((row) => row.id === source.id);
|
|
2942
|
+
if (section.filter((row) => row.id !== source.id).findIndex((row) => row.id === target.id) + (over.half === "after" ? 1 : 0) === sourceIndex) return;
|
|
2943
|
+
const next = order.filter((id) => id !== source.id);
|
|
2944
|
+
const targetIndex = next.indexOf(target.id);
|
|
2945
|
+
if (targetIndex === -1) return;
|
|
2946
|
+
next.splice(targetIndex + (over.half === "after" ? 1 : 0), 0, source.id);
|
|
2947
|
+
return pinCurrentBlank(next, rows.find((row) => row.blank)?.id);
|
|
2948
|
+
}
|
|
2949
|
+
/** Resolve an insertion side across the Workspace header, descendants, and Sessions. */
|
|
2950
|
+
function workspaceGroupHalf(e) {
|
|
2951
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
2952
|
+
return e.clientY < rect.top + rect.height / 2 ? "before" : "after";
|
|
2867
2953
|
}
|
|
2868
2954
|
/**
|
|
2869
|
-
*
|
|
2870
|
-
* the sidebar renders from.
|
|
2955
|
+
* Session the main view currently retains, if any.
|
|
2871
2956
|
*
|
|
2872
|
-
*
|
|
2873
|
-
*
|
|
2874
|
-
*
|
|
2875
|
-
*
|
|
2876
|
-
* @
|
|
2877
|
-
* @param direct - each group's own-member status, from {@link directStatusesForGroups}.
|
|
2878
|
-
* @param childrenByParent - the rendered tree: parent key (`undefined` = roots) → children.
|
|
2957
|
+
* All three list bodies and the shell need this same scan, and `Object.values`
|
|
2958
|
+
* allocates a fresh array each time; the call sites memoise it against the list
|
|
2959
|
+
* identity rather than re-scanning on every render.
|
|
2960
|
+
* @param list - always-mounted Session list snapshot.
|
|
2961
|
+
* @returns the retained Session id, or undefined when the main view holds none.
|
|
2879
2962
|
*/
|
|
2880
|
-
function
|
|
2881
|
-
|
|
2882
|
-
return groups.map((group) => ({
|
|
2883
|
-
...group,
|
|
2884
|
-
liveStatus: aggregated.get(group.key) ?? EMPTY_LIVE_STATUS
|
|
2885
|
-
}));
|
|
2886
|
-
}
|
|
2887
|
-
/** The blank row plus the first `watermark` non-blank rows, in list order. */
|
|
2888
|
-
function watermarkRows(sessions, watermark) {
|
|
2889
|
-
let count = 0;
|
|
2890
|
-
const rows = sessions.filter((session) => {
|
|
2891
|
-
if (session.blank) return true;
|
|
2892
|
-
count += 1;
|
|
2893
|
-
return count <= watermark;
|
|
2894
|
-
});
|
|
2895
|
-
return {
|
|
2896
|
-
rows,
|
|
2897
|
-
hiddenCount: sessions.length - rows.length
|
|
2898
|
-
};
|
|
2963
|
+
function mainViewSessionId(list) {
|
|
2964
|
+
return Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
|
|
2899
2965
|
}
|
|
2966
|
+
//#endregion
|
|
2967
|
+
//#region src/client/rows/ViewOptionsMenu.tsx
|
|
2900
2968
|
/**
|
|
2901
|
-
*
|
|
2902
|
-
*
|
|
2903
|
-
*
|
|
2904
|
-
*
|
|
2905
|
-
* @param revealSessionId - Session selected from search and required to be visible.
|
|
2906
|
-
* @returns the group's baseline non-blank row count.
|
|
2907
|
-
*/
|
|
2908
|
-
function requiredWatermark(sessions, current, revealSessionId) {
|
|
2909
|
-
let index = 0;
|
|
2910
|
-
let required = 0;
|
|
2911
|
-
for (const session of sessions) {
|
|
2912
|
-
if (session.blank) continue;
|
|
2913
|
-
index += 1;
|
|
2914
|
-
if (index <= 50 && (session.running || session.runningSubagentCount > 0 || session.pendingInteraction !== void 0 || session.completed || session.id === current) || session.id === revealSessionId) required = index;
|
|
2915
|
-
}
|
|
2916
|
-
return required;
|
|
2917
|
-
}
|
|
2918
|
-
/**
|
|
2919
|
-
* Raise each listed group's watermark to its requirement; returns `marks` itself when nothing rises.
|
|
2920
|
-
* @param marks - watermarks stored by the current browser tree.
|
|
2921
|
-
* @param required - required baselines, in group order.
|
|
2922
|
-
* @returns either the unchanged input or a new object containing only raised groups.
|
|
2969
|
+
* Grouping, ordering, and archived-filter menu for the section header.
|
|
2970
|
+
*
|
|
2971
|
+
* Own open state so it resets with the wide chrome; the trigger button is the
|
|
2972
|
+
* menu's own anchor, so the header does not thread a callback down for it.
|
|
2923
2973
|
*/
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2974
|
+
/** Grouping, ordering, and archived-filter menu; own open state so it resets with the wide chrome. */
|
|
2975
|
+
function ViewOptionsMenu({ groupBy, orderBy, archivedFilter, onGroupPick, onOrderPick, onArchivedFilterPick, t }) {
|
|
2976
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
2977
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
2978
|
+
open,
|
|
2979
|
+
onClose: () => {
|
|
2980
|
+
setOpen(false);
|
|
2981
|
+
},
|
|
2982
|
+
items: [
|
|
2983
|
+
{
|
|
2984
|
+
type: "label",
|
|
2985
|
+
id: "group-by",
|
|
2986
|
+
text: t("groupBy.label")
|
|
2987
|
+
},
|
|
2988
|
+
{
|
|
2989
|
+
id: "workspace",
|
|
2990
|
+
label: t("groupBy.workspace"),
|
|
2991
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderCloseRegular, {})
|
|
2992
|
+
},
|
|
2993
|
+
{
|
|
2994
|
+
id: "workspace-tree",
|
|
2995
|
+
label: t("groupBy.workspaceTree"),
|
|
2996
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWorkspaceTreeOutlineRegular, {})
|
|
2997
|
+
},
|
|
2998
|
+
{
|
|
2999
|
+
id: "flat",
|
|
3000
|
+
label: t("groupBy.flat"),
|
|
3001
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFlatListOutlineRegular, {})
|
|
3002
|
+
},
|
|
3003
|
+
{
|
|
3004
|
+
type: "separator",
|
|
3005
|
+
id: "order-by-separator"
|
|
3006
|
+
},
|
|
3007
|
+
{
|
|
3008
|
+
type: "label",
|
|
3009
|
+
id: "order-by",
|
|
3010
|
+
text: t("orderBy.label")
|
|
3011
|
+
},
|
|
3012
|
+
{
|
|
3013
|
+
id: "manual",
|
|
3014
|
+
label: t("orderBy.manual"),
|
|
3015
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronsUpDownOutlineRegular, {})
|
|
3016
|
+
},
|
|
3017
|
+
{
|
|
3018
|
+
id: "updated",
|
|
3019
|
+
label: t("orderBy.updated"),
|
|
3020
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconClockOutlineRegular, {})
|
|
3021
|
+
},
|
|
3022
|
+
{
|
|
3023
|
+
type: "separator",
|
|
3024
|
+
id: "archived-filter-separator"
|
|
3025
|
+
},
|
|
3026
|
+
{
|
|
3027
|
+
type: "label",
|
|
3028
|
+
id: "filter-by",
|
|
3029
|
+
text: t("filterBy.label")
|
|
3030
|
+
},
|
|
3031
|
+
{
|
|
3032
|
+
id: "hide-archived",
|
|
3033
|
+
label: t("viewOptions.hideArchived"),
|
|
3034
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOffOutlineRegular, {})
|
|
3035
|
+
},
|
|
3036
|
+
{
|
|
3037
|
+
id: "show-archived",
|
|
3038
|
+
label: t("viewOptions.showArchived"),
|
|
3039
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQueueOutlineRegular, {})
|
|
3040
|
+
},
|
|
3041
|
+
{
|
|
3042
|
+
id: "only-archived",
|
|
3043
|
+
label: t("viewOptions.onlyArchived"),
|
|
3044
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveCheckOutlineRegular, {})
|
|
3045
|
+
}
|
|
3046
|
+
],
|
|
3047
|
+
selectedIds: [
|
|
3048
|
+
groupBy,
|
|
3049
|
+
orderBy,
|
|
3050
|
+
{
|
|
3051
|
+
default: "hide-archived",
|
|
3052
|
+
show: "show-archived",
|
|
3053
|
+
only: "only-archived"
|
|
3054
|
+
}[archivedFilter]
|
|
3055
|
+
],
|
|
3056
|
+
onSelect: (id) => {
|
|
3057
|
+
if (id === "workspace" || id === "workspace-tree" || id === "flat") onGroupPick(id);
|
|
3058
|
+
else if (id === "manual" || id === "updated") onOrderPick(id);
|
|
3059
|
+
else if (id === "hide-archived") onArchivedFilterPick("default");
|
|
3060
|
+
else if (id === "show-archived") onArchivedFilterPick("show");
|
|
3061
|
+
else if (id === "only-archived") onArchivedFilterPick("only");
|
|
3062
|
+
setOpen(false);
|
|
3063
|
+
},
|
|
3064
|
+
align: "end",
|
|
3065
|
+
dense: true,
|
|
3066
|
+
listClassName: WorkspaceBrowser_module_css_default.viewOptionsMenu,
|
|
3067
|
+
portal: true,
|
|
3068
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
3069
|
+
label: t("viewOptions.label"),
|
|
3070
|
+
side: "bottom",
|
|
3071
|
+
delayMs: 500,
|
|
3072
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3073
|
+
type: "button",
|
|
3074
|
+
className: clsx(WorkspaceBrowser_module_css_default.iconButton, WorkspaceBrowser_module_css_default.wide),
|
|
3075
|
+
"aria-label": t("viewOptions.label"),
|
|
3076
|
+
onClick: () => {
|
|
3077
|
+
setOpen((v) => !v);
|
|
3078
|
+
},
|
|
3079
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSlidersTwoOutlineRegular, {})
|
|
3080
|
+
})
|
|
3081
|
+
})
|
|
3082
|
+
});
|
|
2931
3083
|
}
|
|
2932
|
-
//#endregion
|
|
2933
|
-
//#region src/client/use-registered-facets.ts
|
|
2934
3084
|
/**
|
|
2935
|
-
*
|
|
2936
|
-
*
|
|
2937
|
-
* ## 为什么单独成文
|
|
2938
|
-
*
|
|
2939
|
-
* 这一步此前直接写在 `rows/WorkspaceBrowser.tsx` 里,两行:
|
|
2940
|
-
*
|
|
2941
|
-
* ```ts
|
|
2942
|
-
* useSyncExternalStore(facetsStore.subscribe, facetsStore.getVersion, …) // 返回值丢掉
|
|
2943
|
-
* const facets = useMemo(() => visibleFacets(facetsStore.list(), settings), [settings])
|
|
2944
|
-
* ```
|
|
2945
|
-
*
|
|
2946
|
-
* 看着没问题——订阅了、也 memo 了。实际是**订阅通知只重渲染、不重算**:
|
|
2947
|
-
* `useSyncExternalStore` 的返回值被丢掉,`useMemo` 的依赖里就没有任何东西随登记面变化,
|
|
2948
|
-
* 于是那次重渲染原样拿回**上一次**的登记面。提供方在自己的 `apply` 里注册,而宿主不保证
|
|
2949
|
-
* `apply` 与本组件首渲染的先后:注册**晚于**首渲染时,facet 明明登记上了,却永远进不了
|
|
2950
|
-
* 徽标——不报错、不红测试(能渲染这条路径的那份上游 spec 在本仓库是跳过的,见
|
|
2951
|
-
* `vitest.config.ts` 的 `MONOREPO_ONLY_SPECS`),只能靠刷新页面碰运气。
|
|
2952
|
-
*
|
|
2953
|
-
* 版本号必须**同时**当两件事用:① 触发重渲染的信号,② memo 的依赖。这里把它收进一个
|
|
2954
|
-
* hook,让「只用①、丢掉②」在结构上写不出来,并且让这条接缝能被 `@testing-library/react`
|
|
2955
|
-
* 直接覆盖(本文件零宿主 import,宿主依赖闭包进不来)。
|
|
2956
|
-
*
|
|
2957
|
-
* ## 为什么现在不再按设置筛
|
|
2958
|
-
*
|
|
2959
|
-
* 旧版本这里会过一道 `visibleFacets(list, settings)`,按 `facet.settingKey` 查本插件的
|
|
2960
|
-
* 布尔开关。那意味着**本插件的配置面里写着别人家的语义**(`showScheduledInBadge` /
|
|
2961
|
-
* `showWaitingReviewInBadge`,连图标形状都替对方写死),与"本插件不认识任何来源"直接
|
|
2962
|
-
* 矛盾。现在外部来源的显隐归**来源自己**:它想被关掉就不注册 facet,登记面即所见。
|
|
2963
|
-
* 本插件的开关只管自己那三档会话状态(见 `badge-settings.ts`)。
|
|
3085
|
+
* Zero-valued sentinel for {@link GroupLiveStatus}.
|
|
2964
3086
|
*
|
|
2965
|
-
*
|
|
3087
|
+
* Used when a group has nothing to report. Kept as a frozen constant so the
|
|
3088
|
+
* reference is stable across renders — `Object.is` comparisons in React selectors
|
|
3089
|
+
* never trigger spurious re-renders on it.
|
|
2966
3090
|
*/
|
|
3091
|
+
const EMPTY_LIVE_STATUS = Object.freeze({
|
|
3092
|
+
running: 0,
|
|
3093
|
+
approval: 0,
|
|
3094
|
+
planReview: 0,
|
|
3095
|
+
question: 0,
|
|
3096
|
+
done: 0,
|
|
3097
|
+
subagents: 0,
|
|
3098
|
+
facets: /* @__PURE__ */ new Map()
|
|
3099
|
+
});
|
|
2967
3100
|
/**
|
|
2968
|
-
*
|
|
3101
|
+
* 一份**没有任何贡献**的折果:六个计数全 0 且没有外部来源。
|
|
2969
3102
|
*
|
|
2970
|
-
*
|
|
2971
|
-
*
|
|
2972
|
-
* @param store - facet 消费侧存储(`facetStore()` 单例)。
|
|
3103
|
+
* 它在合并里是加法单位元,所以「加它」不该产生新对象——见 {@link mergeLiveStatus}。
|
|
2973
3104
|
*/
|
|
2974
|
-
function
|
|
2975
|
-
|
|
2976
|
-
return (0, react.useMemo)(() => [...store.list()], [version]);
|
|
3105
|
+
function isIdleStatus(status) {
|
|
3106
|
+
return status.running === 0 && status.approval === 0 && status.planReview === 0 && status.question === 0 && status.done === 0 && status.subagents === 0 && status.facets.size === 0;
|
|
2977
3107
|
}
|
|
2978
|
-
//#endregion
|
|
2979
|
-
//#region src/client/badge-labels.ts
|
|
2980
3108
|
/**
|
|
2981
|
-
*
|
|
3109
|
+
* 合并两份分组折果:本插件自己的计数相加,外部状态来源**按 facet 各自合并**。
|
|
2982
3110
|
*
|
|
2983
|
-
*
|
|
2984
|
-
*
|
|
2985
|
-
* 抛错),所以外来 facet 传了自己命名空间的键,最坏读出的是那串键名而不是崩渲染。
|
|
2986
|
-
* 要正确本地化,provider 应当闭包自己的翻译函数、忽略这里传入的 `t`。
|
|
2987
|
-
*/
|
|
2988
|
-
function asFacetTranslate(t) {
|
|
2989
|
-
return (key, options) => t(key, options);
|
|
2990
|
-
}
|
|
2991
|
-
/** 该 facet 贡献进徽标数字的条数。 */
|
|
2992
|
-
function facetCountTotal(s) {
|
|
2993
|
-
let total = 0;
|
|
2994
|
-
for (const entry of s.facets.values()) total += entry.fold.count;
|
|
2995
|
-
return total;
|
|
2996
|
-
}
|
|
2997
|
-
/**
|
|
2998
|
-
* 每个外部状态来源的那句话,按登记顺序。
|
|
3111
|
+
* 用于多层聚合:折叠的父文件夹要覆盖整棵子树,而子树里每一层都已各自折过一遍。
|
|
3112
|
+
* `FacetFold.data` 怎么合归该 facet 自己决定,本模块从不解释。
|
|
2999
3113
|
*
|
|
3000
|
-
*
|
|
3001
|
-
*
|
|
3114
|
+
* 加法单位元走**引用原样返回**:值不变时换一个新对象,会让"同一棵空闲子树"在两轮
|
|
3115
|
+
* 计算之间有不同的身份。`EMPTY_LIVE_STATUS` 留在这个模块里的理由就是身份稳定(见其
|
|
3116
|
+
* 注释),合并这一层不该每加一次零就把那个理由作废。返回的是入参本身(不改写、不
|
|
3117
|
+
* 拷贝),因此与逐字段相加的结果等值。
|
|
3002
3118
|
*/
|
|
3003
|
-
function
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3119
|
+
function mergeLiveStatus(a, b) {
|
|
3120
|
+
if (isIdleStatus(b)) return a;
|
|
3121
|
+
if (isIdleStatus(a)) return b;
|
|
3122
|
+
return {
|
|
3123
|
+
running: a.running + b.running,
|
|
3124
|
+
approval: a.approval + b.approval,
|
|
3125
|
+
planReview: a.planReview + b.planReview,
|
|
3126
|
+
question: a.question + b.question,
|
|
3127
|
+
done: a.done + b.done,
|
|
3128
|
+
subagents: a.subagents + b.subagents,
|
|
3129
|
+
facets: mergeFacets(a.facets, b.facets)
|
|
3130
|
+
};
|
|
3131
|
+
}
|
|
3132
|
+
/** 按 facet id 合并两侧折果;只有一侧有的来源原样带过。 */
|
|
3133
|
+
function mergeFacets(a, b) {
|
|
3134
|
+
if (a.size === 0) return b;
|
|
3135
|
+
if (b.size === 0) return a;
|
|
3136
|
+
const merged = new Map(a);
|
|
3137
|
+
for (const [id, entry] of b) {
|
|
3138
|
+
const own = merged.get(id);
|
|
3139
|
+
merged.set(id, own === void 0 ? entry : {
|
|
3140
|
+
facet: own.facet,
|
|
3141
|
+
fold: own.facet.merge?.(own.fold, entry.fold) ?? defaultMergeFold(own.fold, entry.fold)
|
|
3010
3142
|
});
|
|
3011
|
-
lines.push(text === void 0 || text === "" ? `${entry.fold.count} ${t("status.needsAttention")}` : text);
|
|
3012
3143
|
}
|
|
3013
|
-
return
|
|
3144
|
+
return merged;
|
|
3014
3145
|
}
|
|
3015
3146
|
/**
|
|
3016
|
-
*
|
|
3017
|
-
*
|
|
3147
|
+
* Fold every member row of a group into counts so a collapsed folder can hint at
|
|
3148
|
+
* hidden live sessions. Mirrors the session-row status priority (pending
|
|
3149
|
+
* outranks running outranks completed) at the counting level; labels resolve with
|
|
3150
|
+
* locale at render time.
|
|
3018
3151
|
*
|
|
3019
|
-
*
|
|
3020
|
-
*
|
|
3021
|
-
*
|
|
3152
|
+
* @param nodes - member rows of the group.
|
|
3153
|
+
* @param sessionIds - the same members' Session ids (facets address their own data by id).
|
|
3154
|
+
* @param facets - the registered status sources to fold over this group.
|
|
3155
|
+
* @param now - one clock reading for the whole pass, so sibling facets cannot disagree
|
|
3156
|
+
* about which deadlines are still ahead.
|
|
3022
3157
|
*/
|
|
3023
|
-
function
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3158
|
+
function summarizeGroupStatus(nodes, sessionIds, facets = [], now = Date.now()) {
|
|
3159
|
+
let running = 0;
|
|
3160
|
+
let approval = 0;
|
|
3161
|
+
let planReview = 0;
|
|
3162
|
+
let question = 0;
|
|
3163
|
+
let done = 0;
|
|
3164
|
+
let subagents = 0;
|
|
3165
|
+
for (const n of nodes) {
|
|
3166
|
+
if (n.blank || n.archived) continue;
|
|
3167
|
+
const hasSubagents = n.runningSubagentCount > 0;
|
|
3168
|
+
if (n.pendingInteraction === "approval") {
|
|
3169
|
+
approval += 1;
|
|
3170
|
+
subagents += n.runningSubagentCount;
|
|
3171
|
+
continue;
|
|
3172
|
+
}
|
|
3173
|
+
if (n.pendingInteraction === "plan-review") {
|
|
3174
|
+
planReview += 1;
|
|
3175
|
+
subagents += n.runningSubagentCount;
|
|
3176
|
+
continue;
|
|
3177
|
+
}
|
|
3178
|
+
if (n.pendingInteraction === "question") {
|
|
3179
|
+
question += 1;
|
|
3180
|
+
subagents += n.runningSubagentCount;
|
|
3181
|
+
continue;
|
|
3182
|
+
}
|
|
3183
|
+
if (n.running) {
|
|
3184
|
+
running += 1;
|
|
3185
|
+
subagents += n.runningSubagentCount;
|
|
3186
|
+
continue;
|
|
3187
|
+
}
|
|
3188
|
+
if (hasSubagents) {
|
|
3189
|
+
subagents += n.runningSubagentCount;
|
|
3190
|
+
continue;
|
|
3191
|
+
}
|
|
3192
|
+
if (n.completed) done += 1;
|
|
3029
3193
|
}
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3194
|
+
const folded = /* @__PURE__ */ new Map();
|
|
3195
|
+
for (const { facet } of facets) {
|
|
3196
|
+
let snapshot;
|
|
3197
|
+
try {
|
|
3198
|
+
snapshot = facet.getSnapshot();
|
|
3199
|
+
} catch {
|
|
3200
|
+
snapshot = void 0;
|
|
3201
|
+
}
|
|
3202
|
+
try {
|
|
3203
|
+
const result = facet.fold(snapshot, sessionIds, now);
|
|
3204
|
+
if (result !== void 0 && result.count > 0) folded.set(facet.id, {
|
|
3205
|
+
facet,
|
|
3206
|
+
fold: result
|
|
3207
|
+
});
|
|
3208
|
+
} catch {}
|
|
3033
3209
|
}
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3210
|
+
if (running === 0 && approval === 0 && planReview === 0 && question === 0 && done === 0 && subagents === 0 && folded.size === 0) return EMPTY_LIVE_STATUS;
|
|
3211
|
+
return {
|
|
3212
|
+
running,
|
|
3213
|
+
approval,
|
|
3214
|
+
planReview,
|
|
3215
|
+
question,
|
|
3216
|
+
done,
|
|
3217
|
+
subagents,
|
|
3218
|
+
facets: folded
|
|
3219
|
+
};
|
|
3037
3220
|
}
|
|
3038
3221
|
/**
|
|
3039
|
-
*
|
|
3222
|
+
* Measure each group's own members, as the input to {@link aggregateGroupStatuses}.
|
|
3040
3223
|
*
|
|
3041
|
-
*
|
|
3042
|
-
*
|
|
3043
|
-
*
|
|
3044
|
-
*
|
|
3224
|
+
* Every group is measured, expanded ones included. A folder row shows no badge while
|
|
3225
|
+
* it is expanded, but its sessions still need attention and a collapsed ancestor must
|
|
3226
|
+
* keep counting them — so expansion is a rendering fact, never an input to the
|
|
3227
|
+
* aggregation. Short-circuiting expanded groups to `EMPTY_LIVE_STATUS` (the shape this
|
|
3228
|
+
* had before the tree-wide aggregator existed, harmless while nothing aggregated) makes
|
|
3229
|
+
* every collapsed parent undercount by the size of its expanded descendants.
|
|
3045
3230
|
*
|
|
3046
|
-
*
|
|
3231
|
+
* @param groups - derived groups, each with its member rows already populated.
|
|
3232
|
+
* @param facets - registered sources, already filtered by the visibility settings.
|
|
3233
|
+
* @param now - one clock reading shared by every group in the pass.
|
|
3234
|
+
*/
|
|
3235
|
+
function directStatusesForGroups(groups, facets = [], now = Date.now()) {
|
|
3236
|
+
const map = /* @__PURE__ */ new Map();
|
|
3237
|
+
for (const group of groups) map.set(group.key, summarizeGroupStatus(group.sessions, group.sessions.map((s) => s.id), facets, now));
|
|
3238
|
+
return map;
|
|
3239
|
+
}
|
|
3240
|
+
/**
|
|
3241
|
+
* Fold every group's DIRECT status up the workspace tree, so a collapsed folder
|
|
3242
|
+
* counts every descendant session. A group's own row shows no badge while it is
|
|
3243
|
+
* expanded, but its sessions still need attention and its collapsed ancestor must
|
|
3244
|
+
* keep counting them — so the walk never depends on a group's expanded state.
|
|
3047
3245
|
*
|
|
3048
|
-
* @
|
|
3246
|
+
* @param direct - each group's own-member status, keyed by group key.
|
|
3247
|
+
* @param childrenByParent - the rendered tree: parent key (`undefined` = roots) → children.
|
|
3248
|
+
* @returns aggregated status per group key: own members plus the whole subtree.
|
|
3049
3249
|
*/
|
|
3050
|
-
function
|
|
3051
|
-
const
|
|
3052
|
-
const
|
|
3053
|
-
|
|
3054
|
-
const
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
return
|
|
3250
|
+
function aggregateGroupStatuses(direct, childrenByParent) {
|
|
3251
|
+
const aggregated = /* @__PURE__ */ new Map();
|
|
3252
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
3253
|
+
const fold = (key) => {
|
|
3254
|
+
const cached = aggregated.get(key);
|
|
3255
|
+
if (cached !== void 0) return cached;
|
|
3256
|
+
if (visiting.has(key)) return direct.get(key) ?? EMPTY_LIVE_STATUS;
|
|
3257
|
+
visiting.add(key);
|
|
3258
|
+
let total = direct.get(key) ?? EMPTY_LIVE_STATUS;
|
|
3259
|
+
for (const child of childrenByParent.get(key) ?? []) total = mergeLiveStatus(total, fold(child.key));
|
|
3260
|
+
visiting.delete(key);
|
|
3261
|
+
aggregated.set(key, total);
|
|
3262
|
+
return total;
|
|
3263
|
+
};
|
|
3264
|
+
for (const children of childrenByParent.values()) for (const child of children) fold(child.key);
|
|
3265
|
+
return aggregated;
|
|
3066
3266
|
}
|
|
3067
3267
|
/**
|
|
3068
|
-
*
|
|
3268
|
+
* Attach each group's subtree-wide status, in derivation order. This is the producer
|
|
3269
|
+
* the sidebar renders from.
|
|
3069
3270
|
*
|
|
3070
|
-
*
|
|
3071
|
-
*
|
|
3271
|
+
* Expanded groups carry their status too: their own row draws no badge (a decision
|
|
3272
|
+
* inside `groupLiveBadge`), but a collapsed ancestor still has to count them, so
|
|
3273
|
+
* expansion never suppresses the measurement.
|
|
3072
3274
|
*
|
|
3073
|
-
*
|
|
3074
|
-
*
|
|
3075
|
-
*
|
|
3275
|
+
* @param groups - derived rows, in the order they render.
|
|
3276
|
+
* @param direct - each group's own-member status, from {@link directStatusesForGroups}.
|
|
3277
|
+
* @param childrenByParent - the rendered tree: parent key (`undefined` = roots) → children.
|
|
3076
3278
|
*/
|
|
3077
|
-
function
|
|
3078
|
-
const
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
text: t(status.subagents === 1 ? "status.subagentsRunning.one" : "status.subagentsRunning.other", { n: status.subagents })
|
|
3102
|
-
});
|
|
3279
|
+
function withAggregatedStatus(groups, direct, childrenByParent) {
|
|
3280
|
+
const aggregated = aggregateGroupStatuses(direct, childrenByParent);
|
|
3281
|
+
return groups.map((group) => ({
|
|
3282
|
+
...group,
|
|
3283
|
+
liveStatus: aggregated.get(group.key) ?? EMPTY_LIVE_STATUS
|
|
3284
|
+
}));
|
|
3285
|
+
}
|
|
3286
|
+
/**
|
|
3287
|
+
* The blank row plus the first `watermark` non-blank rows, in list order.
|
|
3288
|
+
* @param sessions - one group's rows, whose blank placeholder(s) lead the list
|
|
3289
|
+
* (`sectionMembers` puts every blank session ahead of the pinned/ordinary partitions).
|
|
3290
|
+
* @param watermark - highest non-blank row index that stays visible; `0` keeps only blank rows.
|
|
3291
|
+
*/
|
|
3292
|
+
function watermarkRows(sessions, watermark) {
|
|
3293
|
+
const rows = [];
|
|
3294
|
+
let count = 0;
|
|
3295
|
+
for (const session of sessions) {
|
|
3296
|
+
if (session.blank) {
|
|
3297
|
+
rows.push(session);
|
|
3298
|
+
continue;
|
|
3299
|
+
}
|
|
3300
|
+
if (count >= watermark) break;
|
|
3301
|
+
count += 1;
|
|
3302
|
+
rows.push(session);
|
|
3103
3303
|
}
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
});
|
|
3109
|
-
return lines;
|
|
3304
|
+
return {
|
|
3305
|
+
rows,
|
|
3306
|
+
hiddenCount: sessions.length - rows.length
|
|
3307
|
+
};
|
|
3110
3308
|
}
|
|
3111
3309
|
/**
|
|
3112
|
-
*
|
|
3113
|
-
*
|
|
3114
|
-
*
|
|
3115
|
-
*
|
|
3116
|
-
*
|
|
3117
|
-
*
|
|
3118
|
-
* (见下)。
|
|
3119
|
-
*
|
|
3120
|
-
* 保留这条历史的原因写在所有者那边:dsh-gitea-dispatch 把 `overdue` 画成**静止**(其
|
|
3121
|
-
* `lib/waiting-glyph.js`:「超时后停动画:静止即不再期待」),且它只代表 CI 窗口到期、
|
|
3122
|
-
* 不代表评审失败。同一条等待在行内静止、在父级闪动,就是同一事实的两种说法。
|
|
3310
|
+
* Smallest watermark that covers every non-blank row within the automatic maximum that is running, has a running
|
|
3311
|
+
* child, awaits the user, carries an unread completion, or is current, and the reveal target at any position.
|
|
3312
|
+
* @param sessions - one group's ordered Session rows.
|
|
3313
|
+
* @param current - Session retained by the main view, if the sidebar is the active surface.
|
|
3314
|
+
* @param revealSessionId - Session selected from search and required to be visible.
|
|
3315
|
+
* @returns the group's baseline non-blank row count.
|
|
3123
3316
|
*/
|
|
3124
|
-
function
|
|
3125
|
-
|
|
3317
|
+
function requiredWatermark(sessions, current, revealSessionId) {
|
|
3318
|
+
let index = 0;
|
|
3319
|
+
let required = 0;
|
|
3320
|
+
for (const session of sessions) {
|
|
3321
|
+
if (session.blank) continue;
|
|
3322
|
+
index += 1;
|
|
3323
|
+
if (index <= 50 && (session.running || session.runningSubagentCount > 0 || session.pendingInteraction !== void 0 || session.completed || session.id === current) || session.id === revealSessionId) required = index;
|
|
3324
|
+
}
|
|
3325
|
+
return required;
|
|
3126
3326
|
}
|
|
3127
3327
|
/**
|
|
3128
|
-
*
|
|
3129
|
-
*
|
|
3130
|
-
*
|
|
3131
|
-
*
|
|
3328
|
+
* Raise each listed group's watermark to its requirement; returns `marks` itself when nothing rises.
|
|
3329
|
+
* @param marks - watermarks stored by the current browser tree.
|
|
3330
|
+
* @param required - required baselines, in group order.
|
|
3331
|
+
* @returns either the unchanged input or a new object containing only raised groups.
|
|
3132
3332
|
*/
|
|
3133
|
-
function
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3333
|
+
function raiseWatermarks(marks, required) {
|
|
3334
|
+
let raised;
|
|
3335
|
+
for (const [key, watermark] of required) {
|
|
3336
|
+
if (watermark <= ((raised ?? marks)[key] ?? 5)) continue;
|
|
3337
|
+
raised ??= { ...marks };
|
|
3338
|
+
raised[key] = watermark;
|
|
3339
|
+
}
|
|
3340
|
+
return raised ?? marks;
|
|
3138
3341
|
}
|
|
3139
3342
|
//#endregion
|
|
3140
|
-
//#region
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3343
|
+
//#region src/client/use-registered-facets.ts
|
|
3344
|
+
/**
|
|
3345
|
+
* 把登记面读成「这一轮渲染该折哪些来源」的**唯一**一处接线。
|
|
3346
|
+
*
|
|
3347
|
+
* ## 为什么单独成文
|
|
3348
|
+
*
|
|
3349
|
+
* 这一步此前直接写在 `rows/WorkspaceBrowser.tsx` 里,两行:
|
|
3350
|
+
*
|
|
3351
|
+
* ```ts
|
|
3352
|
+
* useSyncExternalStore(facetsStore.subscribe, facetsStore.getVersion, …) // 返回值丢掉
|
|
3353
|
+
* const facets = useMemo(() => visibleFacets(facetsStore.list(), settings), [settings])
|
|
3354
|
+
* ```
|
|
3355
|
+
*
|
|
3356
|
+
* 看着没问题——订阅了、也 memo 了。实际是**订阅通知只重渲染、不重算**:
|
|
3357
|
+
* `useSyncExternalStore` 的返回值被丢掉,`useMemo` 的依赖里就没有任何东西随登记面变化,
|
|
3358
|
+
* 于是那次重渲染原样拿回**上一次**的登记面。提供方在自己的 `apply` 里注册,而宿主不保证
|
|
3359
|
+
* `apply` 与本组件首渲染的先后:注册**晚于**首渲染时,facet 明明登记上了,却永远进不了
|
|
3360
|
+
* 徽标——不报错、不红测试(能渲染这条路径的那份上游 spec 在本仓库是跳过的,见
|
|
3361
|
+
* `vitest.config.ts` 的 `MONOREPO_ONLY_SPECS`),只能靠刷新页面碰运气。
|
|
3362
|
+
*
|
|
3363
|
+
* 版本号必须**同时**当两件事用:① 触发重渲染的信号,② memo 的依赖。这里把它收进一个
|
|
3364
|
+
* hook,让「只用①、丢掉②」在结构上写不出来,并且让这条接缝能被 `@testing-library/react`
|
|
3365
|
+
* 直接覆盖(本文件零宿主 import,宿主依赖闭包进不来)。
|
|
3366
|
+
*
|
|
3367
|
+
* ## 为什么现在不再按设置筛
|
|
3368
|
+
*
|
|
3369
|
+
* 旧版本这里会过一道 `visibleFacets(list, settings)`,按 `facet.settingKey` 查本插件的
|
|
3370
|
+
* 布尔开关。那意味着**本插件的配置面里写着别人家的语义**(`showScheduledInBadge` /
|
|
3371
|
+
* `showWaitingReviewInBadge`,连图标形状都替对方写死),与"本插件不认识任何来源"直接
|
|
3372
|
+
* 矛盾。现在外部来源的显隐归**来源自己**:它想被关掉就不注册 facet,登记面即所见。
|
|
3373
|
+
* 本插件的开关只管自己那三档会话状态(见 `badge-settings.ts`)。
|
|
3374
|
+
*
|
|
3375
|
+
* @module dsh-group-status/client/use-registered-facets
|
|
3376
|
+
*/
|
|
3377
|
+
/**
|
|
3378
|
+
* 当前登记的来源,按登记面顺序(已按 `order` 排好)。
|
|
3379
|
+
*
|
|
3380
|
+
* 引用在版本号不变时保持稳定:登记面是低频事件,重算成本 = O(facet × 组内会话)。
|
|
3381
|
+
*
|
|
3382
|
+
* @param store - facet 消费侧存储(`facetStore()` 单例)。
|
|
3383
|
+
*/
|
|
3384
|
+
function useRegisteredFacets(store) {
|
|
3385
|
+
const version = (0, react.useSyncExternalStore)(store.subscribe, store.getVersion, store.getServerVersion);
|
|
3386
|
+
return (0, react.useMemo)(() => [...store.list()], [version]);
|
|
3387
|
+
}
|
|
3388
|
+
//#endregion
|
|
3389
|
+
//#region src/client/badge-labels.ts
|
|
3390
|
+
/**
|
|
3391
|
+
* 把侧栏行的翻译位交给 facet。
|
|
3392
|
+
*
|
|
3393
|
+
* 这里有一次**故意的放宽**:协议的 `t` 收任意字符串键,而行位只收本插件 `workspace`
|
|
3394
|
+
* 命名空间的键。放宽是安全的——宿主 locale 服务对未注册的键一律**原样回显键名**(不是
|
|
3395
|
+
* 抛错),所以外来 facet 传了自己命名空间的键,最坏读出的是那串键名而不是崩渲染。
|
|
3396
|
+
* 要正确本地化,provider 应当闭包自己的翻译函数、忽略这里传入的 `t`。
|
|
3397
|
+
*/
|
|
3398
|
+
function asFacetTranslate(t) {
|
|
3399
|
+
return (key, options) => t(key, options);
|
|
3400
|
+
}
|
|
3401
|
+
/** 该 facet 贡献进徽标数字的条数。 */
|
|
3402
|
+
function facetCountTotal(s) {
|
|
3403
|
+
let total = 0;
|
|
3404
|
+
for (const entry of s.facets.values()) total += entry.fold.count;
|
|
3405
|
+
return total;
|
|
3406
|
+
}
|
|
3407
|
+
/**
|
|
3408
|
+
* 每个外部状态来源的那句话,按登记顺序。
|
|
3409
|
+
*
|
|
3410
|
+
* facet 没实现 `label` 时**不能**退化成 `2 dsh-later.schedule` 这种读法,所以给一句
|
|
3411
|
+
* 通用措辞兜底:只说有几个来源在响,具体语义等该来源自己补文案。
|
|
3412
|
+
*/
|
|
3413
|
+
function facetLines(s, t, now) {
|
|
3414
|
+
const translate = asFacetTranslate(t);
|
|
3415
|
+
const lines = [];
|
|
3416
|
+
for (const entry of s.facets.values()) {
|
|
3417
|
+
const text = entry.facet.label?.(entry.fold, {
|
|
3418
|
+
now,
|
|
3419
|
+
t: translate
|
|
3420
|
+
});
|
|
3421
|
+
lines.push(text === void 0 || text === "" ? `${entry.fold.count} ${t("status.needsAttention")}` : text);
|
|
3422
|
+
}
|
|
3423
|
+
return lines;
|
|
3424
|
+
}
|
|
3425
|
+
/**
|
|
3426
|
+
* Accessible label for a folded group's aggregated status, reusing the
|
|
3427
|
+
* session-row locale keys so both surfaces read identically.
|
|
3428
|
+
*
|
|
3429
|
+
* 按设置过滤:本插件那三档会话状态按设置逐项过滤;**外部来源不参与过滤** —— 登记了就
|
|
3430
|
+
* 一定出现在这里,没登记就不在折果里,所以 `aria-label` 与徽标视觉天然一致,不必在这里
|
|
3431
|
+
* 再判一次设置(本插件也没有替别人家存的开关)。
|
|
3432
|
+
*/
|
|
3433
|
+
function groupLiveLabel(s, t, settings, now = Date.now()) {
|
|
3434
|
+
const parts = [];
|
|
3435
|
+
if (settings.showPendingInBadge) {
|
|
3436
|
+
if (s.approval > 0) parts.push(`${s.approval} ${t("status.waitingApproval")}`);
|
|
3437
|
+
if (s.planReview > 0) parts.push(`${s.planReview} ${t("status.planReview")}`);
|
|
3438
|
+
if (s.question > 0) parts.push(`${s.question} ${t("status.waitingAnswer")}`);
|
|
3439
|
+
}
|
|
3440
|
+
if (settings.showRunningInBadge) {
|
|
3441
|
+
if (s.running > 0) parts.push(`${s.running} ${t("status.running")}`);
|
|
3442
|
+
if (s.subagents > 0) parts.push(t(s.subagents === 1 ? "status.subagentsRunning.one" : "status.subagentsRunning.other", { n: s.subagents }));
|
|
3443
|
+
}
|
|
3444
|
+
parts.push(...facetLines(s, t, now));
|
|
3445
|
+
if (settings.showCompletedInBadge && s.done > 0) parts.push(`${s.done} ${t("status.completed")}`);
|
|
3446
|
+
return parts.join(" · ");
|
|
3447
|
+
}
|
|
3448
|
+
/**
|
|
3449
|
+
* 每个外部状态来源的明细行,**带它自己的图标**(#24)。
|
|
3450
|
+
*
|
|
3451
|
+
* 徽标侧画的是 provider 交出的、已含色档的元素(`rows/GroupBadge.tsx` 的 `facetGlyph`),
|
|
3452
|
+
* 明细侧此前只画一个统一的 `StateDot`,于是同一条状态、同一次悬停里,徽标是红色沙漏、
|
|
3453
|
+
* 明细是黄点——同一行的两个视图各说一套。宿主 `StateDot` 只有 warning / ongoing / done
|
|
3454
|
+
* 三档、没有 error 档,所以色档**只能**由来源自己的图标表达,这也是本函数存在的理由。
|
|
3455
|
+
*
|
|
3456
|
+
* provider 没交图标时退回通用点(与徽标侧同一套兜底),陌生来源仍然可见。
|
|
3457
|
+
*
|
|
3458
|
+
* @returns 明细行;`glyph` 为 `undefined` 表示该行用 `StateDot` 渲染。
|
|
3459
|
+
*/
|
|
3460
|
+
function facetDetailLines(s, t, now) {
|
|
3461
|
+
const translate = asFacetTranslate(t);
|
|
3462
|
+
const lines = [];
|
|
3463
|
+
for (const entry of s.facets.values()) {
|
|
3464
|
+
const raw = entry.facet.label?.(entry.fold, {
|
|
3465
|
+
now,
|
|
3466
|
+
t: translate
|
|
3467
|
+
});
|
|
3468
|
+
const text = raw === void 0 || raw === "" ? `${entry.fold.count} ${t("status.needsAttention")}` : raw;
|
|
3469
|
+
lines.push({
|
|
3470
|
+
state: "warning",
|
|
3471
|
+
text,
|
|
3472
|
+
glyph: facetGlyph(entry, t, now)
|
|
3473
|
+
});
|
|
3474
|
+
}
|
|
3475
|
+
return lines;
|
|
3476
|
+
}
|
|
3477
|
+
/**
|
|
3478
|
+
* Build the status detail lines for the workspace hover card.
|
|
3479
|
+
*
|
|
3480
|
+
* Extracted so `WorkspaceHoverContent` in `Rows.tsx` only needs to map the returned
|
|
3481
|
+
* array — the fork-specific status→line logic lives here.
|
|
3482
|
+
*
|
|
3483
|
+
* 按设置过滤:关掉的来源不出现在明细行里;`showHoverDetails=false` 时整段返回空。
|
|
3484
|
+
* 明细行的顺序与徽标**完全一致**(外部来源排在会话状态之后、已完成之前):两处顺序
|
|
3485
|
+
* 不一样会让人怀疑它们是两套事实。
|
|
3486
|
+
*/
|
|
3487
|
+
function hoverStatusLines(status, t, settings, now = Date.now()) {
|
|
3488
|
+
const lines = [];
|
|
3489
|
+
if (status === void 0 || !settings.showHoverDetails) return lines;
|
|
3490
|
+
if (settings.showPendingInBadge) {
|
|
3491
|
+
if (status.approval > 0) lines.push({
|
|
3492
|
+
state: "warning",
|
|
3493
|
+
text: `${status.approval} ${t("status.waitingApproval")}`
|
|
3494
|
+
});
|
|
3495
|
+
if (status.planReview > 0) lines.push({
|
|
3496
|
+
state: "warning",
|
|
3497
|
+
text: `${status.planReview} ${t("status.planReview")}`
|
|
3498
|
+
});
|
|
3499
|
+
if (status.question > 0) lines.push({
|
|
3500
|
+
state: "warning",
|
|
3501
|
+
text: `${status.question} ${t("status.waitingAnswer")}`
|
|
3502
|
+
});
|
|
3503
|
+
}
|
|
3504
|
+
if (settings.showRunningInBadge) {
|
|
3505
|
+
if (status.running > 0) lines.push({
|
|
3506
|
+
state: "ongoing",
|
|
3507
|
+
text: `${status.running} ${t("status.running")}`
|
|
3508
|
+
});
|
|
3509
|
+
if (status.subagents > 0) lines.push({
|
|
3510
|
+
state: "ongoing",
|
|
3511
|
+
text: t(status.subagents === 1 ? "status.subagentsRunning.one" : "status.subagentsRunning.other", { n: status.subagents })
|
|
3512
|
+
});
|
|
3513
|
+
}
|
|
3514
|
+
lines.push(...facetDetailLines(status, t, now));
|
|
3515
|
+
if (settings.showCompletedInBadge && status.done > 0) lines.push({
|
|
3516
|
+
state: "done",
|
|
3517
|
+
text: `${status.done} ${t("status.completed")}`
|
|
3518
|
+
});
|
|
3519
|
+
return lines;
|
|
3520
|
+
}
|
|
3521
|
+
/**
|
|
3522
|
+
* 某一条外部来源的元素该带哪只 class(#22)。
|
|
3523
|
+
*
|
|
3524
|
+
* 脉冲判据仍由各 facet 自己声明(`FacetFold.pulse`),但**作用对象必须下沉到声明它的那个
|
|
3525
|
+
* 元素**。此前 `groupBadgeIsUrgent()` 只返回一个整徽标的布尔值、class 加在容器上,于是同组
|
|
3526
|
+
* 里任何一家声明 `pulse: true` 都会让别家本该静止的图标跟着呼吸:`pulse: false` 是否生效,
|
|
3527
|
+
* 取决于同组里恰好有没有别人开了闪。判断交出去了,作用对象没交出去——这是修过一次的回归
|
|
3528
|
+
* (见下)。
|
|
3529
|
+
*
|
|
3530
|
+
* 保留这条历史的原因写在所有者那边:dsh-gitea-dispatch 把 `overdue` 画成**静止**(其
|
|
3531
|
+
* `lib/waiting-glyph.js`:「超时后停动画:静止即不再期待」),且它只代表 CI 窗口到期、
|
|
3532
|
+
* 不代表评审失败。同一条等待在行内静止、在父级闪动,就是同一事实的两种说法。
|
|
3533
|
+
*/
|
|
3534
|
+
function facetPulseClass(entry, urgentClass) {
|
|
3535
|
+
return entry.fold.pulse ? urgentClass : void 0;
|
|
3536
|
+
}
|
|
3537
|
+
/**
|
|
3538
|
+
* 一个来源的图标。
|
|
3539
|
+
*
|
|
3540
|
+
* 归 provider 画(`facet.renderIcon`):「这个状态长什么样」是它的知识。没实现时本插件
|
|
3541
|
+
* 按色档退回一个通用点(见 `rows/GroupBadge.tsx`),保证陌生来源仍然可见、只是朴素。
|
|
3542
|
+
*/
|
|
3543
|
+
function facetGlyph(entry, t, now) {
|
|
3544
|
+
return entry.facet.renderIcon?.(entry.fold, {
|
|
3545
|
+
now,
|
|
3546
|
+
t: asFacetTranslate(t)
|
|
3547
|
+
});
|
|
3548
|
+
}
|
|
3549
|
+
//#endregion
|
|
3550
|
+
//#region \0dsh-css:src/client/rows/Rows.module.css.mjs
|
|
3551
|
+
const css$1 = "._1Jb0BW_projectRow,._1Jb0BW_sessionRow{border-radius:var(--dsw-radius-md);padding:0 8px;cursor:pointer;user-select:none;color:var(--dsw-alias-label-primary);align-items:center;gap:6px;padding-inline-start:calc(8px + var(--dsh-workspace-indent,0px));display:flex}._1Jb0BW_projectRow:hover,._1Jb0BW_sessionRow:hover,._1Jb0BW_sessionRow._1Jb0BW_selected{background:var(--dsw-alias-interactive-bg-hover)}._1Jb0BW_searchResultRow{box-sizing:border-box;border-radius:var(--dsw-radius-lg);cursor:pointer;text-align:left;width:100%;min-height:48px;color:var(--dsw-alias-label-primary);background:0 0;border:none;flex-direction:column;align-items:stretch;padding:4px 8px;display:flex}._1Jb0BW_searchResultRow:hover,._1Jb0BW_searchResultRow._1Jb0BW_selected{background:var(--dsw-alias-interactive-bg-hover)}._1Jb0BW_searchResultHeading{align-items:center;min-width:0;display:flex}._1Jb0BW_searchResultTitle{text-overflow:ellipsis;white-space:nowrap;flex:0 auto;min-width:0;margin-left:4px;font-size:14px;line-height:20px;overflow:hidden}._1Jb0BW_searchResultMeta{align-items:center;gap:6px;min-width:0;margin-left:20px;display:flex}._1Jb0BW_searchResultWorkspace,._1Jb0BW_searchResultSnippet{text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:17px;overflow:hidden}._1Jb0BW_searchResultWorkspace{max-width:40%;color:var(--dsw-alias-label-tertiary);flex:none}._1Jb0BW_searchResultWorkspace:only-child{max-width:100%}._1Jb0BW_searchResultSnippet{min-width:0;color:var(--dsw-alias-label-secondary);flex:1}._1Jb0BW_projectRow{box-sizing:border-box;align-items:center;height:34px}._1Jb0BW_projectRow ._1Jb0BW_rowActions{height:20px}._1Jb0BW_sessionRow{gap:0;height:32px}._1Jb0BW_sessionRow ._1Jb0BW_title{margin:0 6px 0 4px}._1Jb0BW_slot{width:16px;height:20px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:inline-flex}._1Jb0BW_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}._1Jb0BW_folderActive{color:var(--dsw-alias-state-business-primary)}._1Jb0BW_projectRow ._1Jb0BW_chevron{display:none}._1Jb0BW_projectRow:hover ._1Jb0BW_chevron{display:inline-flex}._1Jb0BW_projectRow:hover ._1Jb0BW_folder{display:none}._1Jb0BW_arrow{transition:transform .15s var(--ds-ease-in-out)}._1Jb0BW_arrowOpen{transform:rotate(90deg)}._1Jb0BW_projectText{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}._1Jb0BW_title{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:14px;line-height:20px;overflow:hidden}._1Jb0BW_renameInput{border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-sm);background:var(--dsw-alias-button-elevated-fill);min-width:0;color:inherit;outline:none;padding:0 2px;font-size:14px;line-height:20px}._1Jb0BW_sessionRow ._1Jb0BW_title{flex:1}._1Jb0BW_sessionRow ._1Jb0BW_title[data-scrolled]{mask-image:linear-gradient(90deg,#0000,#000 12px)}._1Jb0BW_sessionRow ._1Jb0BW_title[data-clipped]{mask-image:linear-gradient(270deg,#0000,#000 12px)}._1Jb0BW_sessionRow ._1Jb0BW_title[data-scrolled][data-clipped]{mask-image:linear-gradient(90deg,#0000,#000 12px calc(100% - 12px),#0000)}._1Jb0BW_meta{text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px;overflow:hidden}._1Jb0BW_time{color:var(--dsw-alias-label-tertiary);flex:none;font-size:10px;line-height:16px}._1Jb0BW_pinIndicator{width:16px;height:20px;color:var(--dsw-alias-label-caption);flex:none;justify-content:center;align-items:center;margin-left:6px;display:inline-flex}._1Jb0BW_sessionRow._1Jb0BW_archived ._1Jb0BW_title,._1Jb0BW_searchResultRow._1Jb0BW_archived ._1Jb0BW_searchResultTitle,._1Jb0BW_searchResultRow._1Jb0BW_archived ._1Jb0BW_searchResultWorkspace,._1Jb0BW_searchResultRow._1Jb0BW_archived ._1Jb0BW_searchResultSnippet{color:var(--dsw-alias-label-caption)}._1Jb0BW_dot{flex:none}._1Jb0BW_rowActions{flex:none;align-items:center;gap:10px;display:none}._1Jb0BW_projectRow:hover ._1Jb0BW_rowActions,._1Jb0BW_sessionRow:hover ._1Jb0BW_rowActions,._1Jb0BW_searchResultRow:hover ._1Jb0BW_rowActions,._1Jb0BW_projectRow._1Jb0BW_menuOpen ._1Jb0BW_rowActions,._1Jb0BW_sessionRow._1Jb0BW_menuOpen ._1Jb0BW_rowActions{display:inline-flex}._1Jb0BW_searchResultHeading ._1Jb0BW_rowActions{margin-left:auto}._1Jb0BW_sessionRow:hover ._1Jb0BW_time,._1Jb0BW_sessionRow._1Jb0BW_menuOpen ._1Jb0BW_time,._1Jb0BW_sessionRow:hover ._1Jb0BW_pinIndicator,._1Jb0BW_sessionRow._1Jb0BW_menuOpen ._1Jb0BW_pinIndicator{display:none}@media (hover:hover){._1Jb0BW_sessionRow:hover ._1Jb0BW_title,._1Jb0BW_sessionRow._1Jb0BW_menuOpen ._1Jb0BW_title{text-overflow:clip}}._1Jb0BW_projectRow._1Jb0BW_menuOpen,._1Jb0BW_sessionRow._1Jb0BW_menuOpen{background:var(--dsw-alias-interactive-bg-hover)}._1Jb0BW_sessionRow._1Jb0BW_dropBefore,._1Jb0BW_sessionRow._1Jb0BW_dropAfter{position:relative}._1Jb0BW_sessionRow._1Jb0BW_dropBefore:before,._1Jb0BW_sessionRow._1Jb0BW_dropAfter:after{content:\"\";z-index:1;background:linear-gradient(55deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 0 / 5px 7px no-repeat, linear-gradient(125deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--dsw-alias-state-business-primary) 0 0) 4px 5px / calc(100% - 4px) 2px no-repeat;pointer-events:none;height:12px;position:absolute;left:0;right:4px}._1Jb0BW_sessionRow._1Jb0BW_dropBefore:before{top:-7px}._1Jb0BW_sessionRow._1Jb0BW_dropAfter:after{bottom:-7px}._1Jb0BW_hoverContent{flex-direction:column;gap:8px;display:flex}._1Jb0BW_hoverTitle{color:var(--dsw-alias-label-primary);overflow-wrap:break-word;font-size:14px;line-height:20px}._1Jb0BW_hoverPath{color:var(--dsw-alias-label-secondary);word-break:break-all;font-size:12px;line-height:16px}._1Jb0BW_hoverTime{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:16px}._1Jb0BW_hoverStatus{color:var(--dsw-alias-label-tertiary);align-items:center;gap:8px;font-size:12px;line-height:20px;display:flex}._1Jb0BW_groupBadge{flex:none;align-items:center;gap:3px;margin-inline-start:4px;display:inline-flex}._1Jb0BW_groupBadgeCount{color:var(--dsw-alias-label-tertiary);border:1px solid;border-radius:6px;padding:0 3px;font-size:9px;line-height:12px}@keyframes _1Jb0BW_groupBadgePulse{0%,to{opacity:1}50%{opacity:.6}}._1Jb0BW_groupBadgeUrgent{animation:2s ease-in-out infinite _1Jb0BW_groupBadgePulse}._1Jb0BW_hoverArchived svg{flex-shrink:0;margin:0 -4px}._1Jb0BW_iconButton{border-radius:var(--dsw-radius-xs);cursor:pointer;width:16px;height:16px;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}._1Jb0BW_iconButton:hover{color:var(--dsw-alias-label-primary)}._1Jb0BW_chevron{color:var(--dsw-alias-label-caption)}@media (prefers-reduced-motion:reduce){._1Jb0BW_arrow{transition:none;animation:none}}";
|
|
3552
|
+
const tagId$1 = "dsh-group-status/Rows.module.css";
|
|
3553
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
3554
|
+
const tag = document.createElement("style");
|
|
3555
|
+
tag.dataset.plugin = "dsh-group-status";
|
|
3556
|
+
tag.dataset.pluginCss = tagId$1;
|
|
3557
|
+
tag.textContent = css$1;
|
|
3558
|
+
document.head.appendChild(tag);
|
|
3559
|
+
}
|
|
3560
|
+
var Rows_module_css_default = {
|
|
3561
|
+
"archived": "_1Jb0BW_archived",
|
|
3562
|
+
"arrow": "_1Jb0BW_arrow",
|
|
3563
|
+
"arrowOpen": "_1Jb0BW_arrowOpen",
|
|
3564
|
+
"chevron": "_1Jb0BW_chevron",
|
|
3565
|
+
"dot": "_1Jb0BW_dot",
|
|
3566
|
+
"dropAfter": "_1Jb0BW_dropAfter",
|
|
3157
3567
|
"dropBefore": "_1Jb0BW_dropBefore",
|
|
3158
3568
|
"folder": "_1Jb0BW_folder",
|
|
3159
3569
|
"folderActive": "_1Jb0BW_folderActive",
|
|
@@ -3792,644 +4202,226 @@ window.__ModuleLoader__.load({
|
|
|
3792
4202
|
},
|
|
3793
4203
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
3794
4204
|
open: menuOpen,
|
|
3795
|
-
onClose: () => {
|
|
3796
|
-
setMenuOpen(false);
|
|
3797
|
-
},
|
|
3798
|
-
portal: true,
|
|
3799
|
-
closeOnPointerLeave: true,
|
|
3800
|
-
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3801
|
-
type: "button",
|
|
3802
|
-
className: Rows_module_css_default.iconButton,
|
|
3803
|
-
"aria-label": t("actions.session.aria", { name: title }),
|
|
3804
|
-
onClick: () => {
|
|
3805
|
-
setMenuOpen((v) => !v);
|
|
3806
|
-
},
|
|
3807
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutlineRegular, {})
|
|
3808
|
-
}),
|
|
3809
|
-
children: renderSlot("sidebar.workspaces.session.menu.item", {
|
|
3810
|
-
sessionId: node.id,
|
|
3811
|
-
displayTitle: row.title
|
|
3812
|
-
}, { hookContext: menuOpenState })
|
|
3813
|
-
}), renderSlot("sidebar.workspaces.session.row.action", {
|
|
3814
|
-
sessionId: node.id,
|
|
3815
|
-
displayTitle: row.title
|
|
3816
|
-
})]
|
|
3817
|
-
})
|
|
3818
|
-
]
|
|
3819
|
-
});
|
|
3820
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.HoverCard, {
|
|
3821
|
-
anchor: ownRow,
|
|
3822
|
-
content: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionHoverContent, {
|
|
3823
|
-
node,
|
|
3824
|
-
now,
|
|
3825
|
-
renderSlot,
|
|
3826
|
-
t
|
|
3827
|
-
}),
|
|
3828
|
-
openDelayMs: 800,
|
|
3829
|
-
grouped: true,
|
|
3830
|
-
disabled: menuOpen || drag?.active === true,
|
|
3831
|
-
copyText: row.blank || row.title === "" ? void 0 : row.title,
|
|
3832
|
-
copyLabel: t("copy"),
|
|
3833
|
-
copiedLabel: t("hover.copied")
|
|
3834
|
-
});
|
|
3835
|
-
}
|
|
3836
|
-
//#endregion
|
|
3837
|
-
//#region \0dsh-css:src/client/rows/AnimatedRows.module.css.mjs
|
|
3838
|
-
const css$2 = "._8TnAsq_exits{contain:strict;pointer-events:none;position:absolute;inset:0;overflow:clip}";
|
|
3839
|
-
const tagId$2 = "dsh-group-status/AnimatedRows.module.css";
|
|
3840
|
-
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
|
|
3841
|
-
const tag = document.createElement("style");
|
|
3842
|
-
tag.dataset.plugin = "dsh-group-status";
|
|
3843
|
-
tag.dataset.pluginCss = tagId$2;
|
|
3844
|
-
tag.textContent = css$2;
|
|
3845
|
-
document.head.appendChild(tag);
|
|
3846
|
-
}
|
|
3847
|
-
var AnimatedRows_module_css_default = { "exits": "_8TnAsq_exits" };
|
|
3848
|
-
//#endregion
|
|
3849
|
-
//#region src/client/rows/AnimatedRows.tsx
|
|
3850
|
-
/** React-commit-driven movement and entry/exit fades for the sidebar's keyed rows. */
|
|
3851
|
-
const ROW_FADE_MS = 100;
|
|
3852
|
-
const ROW_GLIDE_MS = 200;
|
|
3853
|
-
function sameRows(previous, next) {
|
|
3854
|
-
return previous.rowKeys.length === next.rowKeys.length && previous.rowKeys.every((key, index) => key === next.rowKeys[index]);
|
|
3855
|
-
}
|
|
3856
|
-
function intersects(row, viewport) {
|
|
3857
|
-
return row.bottom > viewport.top && row.top < viewport.bottom && row.right > viewport.left && row.left < viewport.right;
|
|
3858
|
-
}
|
|
3859
|
-
/**
|
|
3860
|
-
* Animates keyed sidebar rows only when their rendered membership or order changes.
|
|
3861
|
-
* Motion starts after the first pointer or keyboard input inside the mounted list.
|
|
3862
|
-
* The parent supplies a positioned container for the inert exit overlay.
|
|
3863
|
-
*/
|
|
3864
|
-
var AnimatedRows = class extends react.Component {
|
|
3865
|
-
armed = false;
|
|
3866
|
-
list = (0, react.createRef)();
|
|
3867
|
-
overlay = (0, react.createRef)();
|
|
3868
|
-
movements = /* @__PURE__ */ new Map();
|
|
3869
|
-
exits = /* @__PURE__ */ new Map();
|
|
3870
|
-
getSnapshotBeforeUpdate(previous) {
|
|
3871
|
-
const list = this.list.current;
|
|
3872
|
-
if (!this.armed || sameRows(previous, this.props) || previous.resetKey !== this.props.resetKey || !previous.ready || !this.props.ready || list === null || typeof list.animate !== "function" || window.matchMedia("(prefers-reduced-motion: reduce)").matches) return null;
|
|
3873
|
-
const viewport = list.getBoundingClientRect();
|
|
3874
|
-
const positions = this.readPositions();
|
|
3875
|
-
const nextKeys = new Set(this.props.rowKeys);
|
|
3876
|
-
const removed = /* @__PURE__ */ new Map();
|
|
3877
|
-
for (const [key, row] of positions) {
|
|
3878
|
-
if (nextKeys.has(key) || !intersects(row.rect, viewport)) continue;
|
|
3879
|
-
const clone = row.element.cloneNode(true);
|
|
3880
|
-
clone.removeAttribute("data-row-key");
|
|
3881
|
-
clone.inert = true;
|
|
3882
|
-
clone.style.setProperty("--dsh-workspace-indent", getComputedStyle(row.element).getPropertyValue("--dsh-workspace-indent"));
|
|
3883
|
-
removed.set(key, {
|
|
3884
|
-
...row,
|
|
3885
|
-
element: clone
|
|
3886
|
-
});
|
|
3887
|
-
}
|
|
3888
|
-
return {
|
|
3889
|
-
positions,
|
|
3890
|
-
removed
|
|
3891
|
-
};
|
|
3892
|
-
}
|
|
3893
|
-
componentDidUpdate(previous, _state, snapshot) {
|
|
3894
|
-
if (snapshot === null) {
|
|
3895
|
-
if (!sameRows(previous, this.props) || previous.resetKey !== this.props.resetKey || previous.ready !== this.props.ready) this.clear();
|
|
3896
|
-
return;
|
|
3897
|
-
}
|
|
3898
|
-
this.cancelMovements();
|
|
3899
|
-
const list = this.list.current;
|
|
3900
|
-
const overlay = this.overlay.current;
|
|
3901
|
-
const viewport = list.getBoundingClientRect();
|
|
3902
|
-
const origin = overlay.getBoundingClientRect();
|
|
3903
|
-
const positions = this.readPositions();
|
|
3904
|
-
for (const [key, row] of positions) {
|
|
3905
|
-
this.removeExit(key);
|
|
3906
|
-
const previousRow = snapshot.positions.get(key);
|
|
3907
|
-
if (!intersects(row.rect, viewport) && (previousRow === void 0 || !intersects(previousRow.rect, viewport))) continue;
|
|
3908
|
-
if (previousRow === void 0) {
|
|
3909
|
-
this.move(row.element, [{ opacity: 0 }, { opacity: 1 }], ROW_FADE_MS);
|
|
3910
|
-
continue;
|
|
3911
|
-
}
|
|
3912
|
-
const dx = previousRow.rect.left - row.rect.left;
|
|
3913
|
-
const dy = previousRow.rect.top - row.rect.top;
|
|
3914
|
-
if (dx === 0 && dy === 0 && previousRow.opacity === 1) continue;
|
|
3915
|
-
this.move(row.element, [{
|
|
3916
|
-
transform: `translate(${String(dx)}px, ${String(dy)}px)`,
|
|
3917
|
-
opacity: previousRow.opacity
|
|
3918
|
-
}, {
|
|
3919
|
-
transform: "translate(0, 0)",
|
|
3920
|
-
opacity: 1
|
|
3921
|
-
}], ROW_GLIDE_MS);
|
|
3922
|
-
}
|
|
3923
|
-
for (const [key, row] of snapshot.removed) {
|
|
3924
|
-
const { element } = row;
|
|
3925
|
-
this.removeExit(key);
|
|
3926
|
-
Object.assign(element.style, {
|
|
3927
|
-
position: "absolute",
|
|
3928
|
-
margin: "0",
|
|
3929
|
-
transform: "none",
|
|
3930
|
-
boxSizing: "border-box",
|
|
3931
|
-
left: `${String(row.rect.left - origin.left)}px`,
|
|
3932
|
-
top: `${String(row.rect.top - origin.top)}px`,
|
|
3933
|
-
width: `${String(row.rect.width)}px`,
|
|
3934
|
-
height: `${String(row.rect.height)}px`
|
|
3935
|
-
});
|
|
3936
|
-
overlay.append(element);
|
|
3937
|
-
const animation = element.animate([{ opacity: row.opacity }, { opacity: 0 }], {
|
|
3938
|
-
duration: ROW_FADE_MS,
|
|
3939
|
-
easing: "ease-out",
|
|
3940
|
-
fill: "forwards"
|
|
3941
|
-
});
|
|
3942
|
-
this.exits.set(key, {
|
|
3943
|
-
element,
|
|
3944
|
-
animation
|
|
3945
|
-
});
|
|
3946
|
-
animation.onfinish = () => {
|
|
3947
|
-
this.removeExit(key);
|
|
3948
|
-
};
|
|
3949
|
-
}
|
|
3950
|
-
}
|
|
3951
|
-
componentWillUnmount() {
|
|
3952
|
-
this.clear();
|
|
3953
|
-
}
|
|
3954
|
-
readPositions() {
|
|
3955
|
-
const rows = this.list.current.querySelectorAll("[data-row-key]");
|
|
3956
|
-
return new Map(Array.from(rows, (element) => [element.dataset.rowKey, {
|
|
3957
|
-
element,
|
|
3958
|
-
rect: element.getBoundingClientRect(),
|
|
3959
|
-
opacity: this.movements.has(element) ? Number(getComputedStyle(element).opacity) : 1
|
|
3960
|
-
}]));
|
|
3961
|
-
}
|
|
3962
|
-
move(element, keyframes, duration) {
|
|
3963
|
-
const animation = element.animate(keyframes, {
|
|
3964
|
-
duration,
|
|
3965
|
-
easing: "ease-out"
|
|
3966
|
-
});
|
|
3967
|
-
this.movements.set(element, animation);
|
|
3968
|
-
animation.onfinish = () => {
|
|
3969
|
-
this.movements.delete(element);
|
|
3970
|
-
animation.cancel();
|
|
3971
|
-
};
|
|
3972
|
-
}
|
|
3973
|
-
cancelMovements() {
|
|
3974
|
-
for (const animation of this.movements.values()) {
|
|
3975
|
-
animation.onfinish = null;
|
|
3976
|
-
animation.cancel();
|
|
3977
|
-
}
|
|
3978
|
-
this.movements.clear();
|
|
3979
|
-
}
|
|
3980
|
-
removeExit(key) {
|
|
3981
|
-
const exit = this.exits.get(key);
|
|
3982
|
-
if (exit === void 0) return;
|
|
3983
|
-
exit.animation.onfinish = null;
|
|
3984
|
-
exit.animation.cancel();
|
|
3985
|
-
exit.element.remove();
|
|
3986
|
-
this.exits.delete(key);
|
|
3987
|
-
}
|
|
3988
|
-
clear() {
|
|
3989
|
-
this.cancelMovements();
|
|
3990
|
-
for (const key of this.exits.keys()) this.removeExit(key);
|
|
3991
|
-
}
|
|
3992
|
-
render() {
|
|
3993
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3994
|
-
ref: this.list,
|
|
3995
|
-
className: this.props.className,
|
|
3996
|
-
role: "tree",
|
|
3997
|
-
"aria-label": this.props.label,
|
|
3998
|
-
onPointerDownCapture: () => {
|
|
3999
|
-
this.armed = true;
|
|
4000
|
-
},
|
|
4001
|
-
onKeyDownCapture: () => {
|
|
4002
|
-
this.armed = true;
|
|
4003
|
-
},
|
|
4004
|
-
children: this.props.children
|
|
4005
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4006
|
-
ref: this.overlay,
|
|
4007
|
-
className: AnimatedRows_module_css_default.exits,
|
|
4008
|
-
"aria-hidden": "true"
|
|
4009
|
-
})] });
|
|
4010
|
-
}
|
|
4011
|
-
};
|
|
4012
|
-
//#endregion
|
|
4013
|
-
//#region \0dsh-css:src/client/WorkspacePicker.module.css.mjs
|
|
4014
|
-
const css$1 = ".S_JYgq_modalAction{min-width:72px}.S_JYgq_modalError,.S_JYgq_menuStatus{margin-top:8px;font-size:12px;line-height:18px}.S_JYgq_modalError{color:var(--dsw-alias-state-error-primary)}.S_JYgq_menuStatus{color:var(--dsw-alias-label-secondary)}";
|
|
4015
|
-
const tagId$1 = "dsh-group-status/WorkspacePicker.module.css";
|
|
4016
|
-
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
4017
|
-
const tag = document.createElement("style");
|
|
4018
|
-
tag.dataset.plugin = "dsh-group-status";
|
|
4019
|
-
tag.dataset.pluginCss = tagId$1;
|
|
4020
|
-
tag.textContent = css$1;
|
|
4021
|
-
document.head.appendChild(tag);
|
|
4022
|
-
}
|
|
4023
|
-
var WorkspacePicker_module_css_default = {
|
|
4024
|
-
"menuStatus": "S_JYgq_menuStatus",
|
|
4025
|
-
"modalAction": "S_JYgq_modalAction",
|
|
4026
|
-
"modalError": "S_JYgq_modalError"
|
|
4027
|
-
};
|
|
4028
|
-
//#endregion
|
|
4029
|
-
//#region src/client/WorkspacePicker.tsx
|
|
4030
|
-
const ADD_WORKSPACE = "::add-workspace";
|
|
4031
|
-
/**
|
|
4032
|
-
* Render the pick menu plus the adoption error dialog.
|
|
4033
|
-
* @param props - owner-controlled flow props.
|
|
4034
|
-
* @returns menu + dialog elements.
|
|
4035
|
-
*/
|
|
4036
|
-
function WorkspacePickFlow({ t, open, anchorRef, useWorkspaces, createWorkspace, useDirectoryFlow, renderDirectoryFlow, onPick, onClose, addOnly = false, onBusyChange, side = "bottom", selectedId }) {
|
|
4037
|
-
const workspaceSnapshot = useWorkspaces((state) => state);
|
|
4038
|
-
const workspaces = workspaceSnapshot.items;
|
|
4039
|
-
const getAnchorRect = (0, react.useCallback)(() => anchorRef?.current?.getBoundingClientRect() ?? null, [anchorRef]);
|
|
4040
|
-
const [errorOpen, setErrorOpen] = (0, react.useState)(false);
|
|
4041
|
-
const [modalError, setModalError] = (0, react.useState)(null);
|
|
4042
|
-
const [flowOpen, setFlowOpen] = (0, react.useState)(false);
|
|
4043
|
-
const [pickingFolder, setPickingFolder] = (0, react.useState)(false);
|
|
4044
|
-
const flowBusy = flowOpen || pickingFolder;
|
|
4045
|
-
(0, react.useEffect)(() => {
|
|
4046
|
-
onBusyChange?.(flowBusy);
|
|
4047
|
-
}, [flowBusy, onBusyChange]);
|
|
4048
|
-
const flowAvailable = useDirectoryFlow((occupied) => occupied);
|
|
4049
|
-
(0, react.useEffect)(() => {
|
|
4050
|
-
if (flowOpen && !flowAvailable) setFlowOpen(false);
|
|
4051
|
-
}, [flowOpen, flowAvailable]);
|
|
4052
|
-
const addEntries = flowAvailable ? [{
|
|
4053
|
-
id: ADD_WORKSPACE,
|
|
4054
|
-
label: t("menu.addWorkspace"),
|
|
4055
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutlineRegular, { size: 16 }),
|
|
4056
|
-
disabled: flowBusy
|
|
4057
|
-
}] : [];
|
|
4058
|
-
const pinAdd = !addOnly && workspaces.length > 0;
|
|
4059
|
-
const items = pinAdd ? workspaces.map((workspace) => ({
|
|
4060
|
-
id: workspace.workspaceId,
|
|
4061
|
-
label: workspaceDisplayTitle(workspace.title, t("workspace.defaultName")),
|
|
4062
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderCloseRegular, { size: 16 }),
|
|
4063
|
-
disabled: flowBusy
|
|
4064
|
-
})) : addEntries;
|
|
4065
|
-
const menuIsEmpty = items.length === 0;
|
|
4066
|
-
const closeModal = () => {
|
|
4067
|
-
setErrorOpen(false);
|
|
4068
|
-
setModalError(null);
|
|
4069
|
-
};
|
|
4070
|
-
/** Adopt a picked directory; failures land in the folder-error dialog (Choose again reopens the flow). */
|
|
4071
|
-
const adoptDirectory = (path) => createWorkspace({ path }).then((workspace) => {
|
|
4072
|
-
setFlowOpen(false);
|
|
4073
|
-
onPick(workspace.workspaceId);
|
|
4074
|
-
}).catch((reason) => {
|
|
4075
|
-
setModalError(reason instanceof Error ? reason.message : String(reason));
|
|
4076
|
-
setFlowOpen(false);
|
|
4077
|
-
setErrorOpen(true);
|
|
4078
|
-
});
|
|
4079
|
-
const openDirectoryFlow = (0, react.useCallback)(() => {
|
|
4080
|
-
onClose();
|
|
4081
|
-
setErrorOpen(false);
|
|
4082
|
-
setModalError(null);
|
|
4083
|
-
setFlowOpen(true);
|
|
4084
|
-
}, [onClose]);
|
|
4085
|
-
const listSettled = addOnly || workspaceSnapshot.phase === "ready";
|
|
4086
|
-
const addIsTheOnlyEntry = !pinAdd && listSettled && addEntries.length === 1;
|
|
4087
|
-
(0, react.useEffect)(() => {
|
|
4088
|
-
if (open && addIsTheOnlyEntry && !flowBusy) openDirectoryFlow();
|
|
4089
|
-
}, [
|
|
4090
|
-
open,
|
|
4091
|
-
addIsTheOnlyEntry,
|
|
4092
|
-
flowBusy,
|
|
4093
|
-
openDirectoryFlow
|
|
4094
|
-
]);
|
|
4095
|
-
/** Owner side of the flow conversation: adopt keeps the flow open (busy) until the Host answers. */
|
|
4096
|
-
const flowOwner = {
|
|
4097
|
-
open: flowOpen,
|
|
4098
|
-
busy: pickingFolder,
|
|
4099
|
-
onPicked: (path) => {
|
|
4100
|
-
setPickingFolder(true);
|
|
4101
|
-
adoptDirectory(path).finally(() => {
|
|
4102
|
-
setPickingFolder(false);
|
|
4103
|
-
});
|
|
4104
|
-
},
|
|
4105
|
-
onCancel: () => {
|
|
4106
|
-
setFlowOpen(false);
|
|
4107
|
-
},
|
|
4108
|
-
onError: (message) => {
|
|
4109
|
-
setFlowOpen(false);
|
|
4110
|
-
setModalError(message);
|
|
4111
|
-
setErrorOpen(true);
|
|
4112
|
-
}
|
|
4113
|
-
};
|
|
4114
|
-
const handleSelect = (id) => {
|
|
4115
|
-
if (id === ADD_WORKSPACE) {
|
|
4116
|
-
openDirectoryFlow();
|
|
4117
|
-
return;
|
|
4118
|
-
}
|
|
4119
|
-
onPick(id);
|
|
4120
|
-
};
|
|
4121
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
4122
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
4123
|
-
open: open && !addIsTheOnlyEntry && !menuIsEmpty,
|
|
4124
|
-
anchor: null,
|
|
4125
|
-
items,
|
|
4126
|
-
...pinAdd ? { footer: addEntries } : {},
|
|
4127
|
-
selectedId,
|
|
4128
|
-
onSelect: handleSelect,
|
|
4129
|
-
onClose,
|
|
4130
|
-
side,
|
|
4131
|
-
portal: true,
|
|
4132
|
-
getAnchorRect
|
|
4133
|
-
}),
|
|
4134
|
-
open && !addIsTheOnlyEntry && !menuIsEmpty && workspaceSnapshot.phase === "pending" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4135
|
-
className: WorkspacePicker_module_css_default.menuStatus,
|
|
4136
|
-
role: "status",
|
|
4137
|
-
children: t("picker.loading")
|
|
4138
|
-
}),
|
|
4139
|
-
renderDirectoryFlow(flowOwner),
|
|
4140
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
4141
|
-
open: errorOpen,
|
|
4142
|
-
onClose: closeModal,
|
|
4143
|
-
closeLabel: t("close"),
|
|
4144
|
-
title: t("folderError.title"),
|
|
4145
|
-
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
4146
|
-
variant: "outline",
|
|
4147
|
-
className: WorkspacePicker_module_css_default.modalAction,
|
|
4148
|
-
onClick: closeModal,
|
|
4149
|
-
children: t("cancel")
|
|
4150
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
4151
|
-
variant: "primary",
|
|
4152
|
-
className: WorkspacePicker_module_css_default.modalAction,
|
|
4153
|
-
disabled: !flowAvailable,
|
|
4154
|
-
onClick: openDirectoryFlow,
|
|
4155
|
-
children: t("folderError.retry")
|
|
4156
|
-
})] }),
|
|
4157
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4158
|
-
className: WorkspacePicker_module_css_default.modalError,
|
|
4159
|
-
role: "alert",
|
|
4160
|
-
children: modalError
|
|
4161
|
-
})
|
|
4162
|
-
})
|
|
4163
|
-
] });
|
|
4164
|
-
}
|
|
4165
|
-
/**
|
|
4166
|
-
* The conversation empty-state registration: adapts the owner share to the
|
|
4167
|
-
* core flow (all state and semantics live in the flow / the owner).
|
|
4168
|
-
* @param props - empty-state slot props (owner share + injected creation callback).
|
|
4169
|
-
* @returns the flow element.
|
|
4170
|
-
*/
|
|
4171
|
-
function WorkspacePicker({ open, anchorRef, useWorkspaces, selectedId, onPick, onClose, createWorkspace, useDirectoryFlow, renderSlot, t }) {
|
|
4172
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspacePickFlow, {
|
|
4173
|
-
t,
|
|
4174
|
-
open,
|
|
4175
|
-
anchorRef,
|
|
4176
|
-
useWorkspaces,
|
|
4177
|
-
createWorkspace,
|
|
4178
|
-
useDirectoryFlow,
|
|
4179
|
-
renderDirectoryFlow: (owner) => renderSlot("conversation.hero.workspace.directoryFlow", owner),
|
|
4180
|
-
selectedId,
|
|
4181
|
-
onPick,
|
|
4182
|
-
onClose
|
|
4183
|
-
});
|
|
4184
|
-
}
|
|
4185
|
-
//#endregion
|
|
4186
|
-
//#region \0dsh-css:src/client/rows/WorkspaceBrowser.module.css.mjs
|
|
4187
|
-
const css = ".QAYkWG_root{--dsh-session-list-edge-inset:var(--dsh-sidebar-inline-padding);--dsh-session-list-scrollbar-width:5px;--dsh-session-list-scrollbar-offset:2px;box-sizing:border-box;min-height:0;padding-right:var(--dsh-session-list-edge-inset);flex-direction:column;flex:1;display:flex}.QAYkWG_root.QAYkWG_rail{padding-right:0}.QAYkWG_iconButton{border-radius:var(--dsw-radius-sm);cursor:pointer;width:28px;height:28px;color:var(--dsw-alias-label-secondary);background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.QAYkWG_iconButton:focus-visible,.QAYkWG_searchButton:focus-visible,.QAYkWG_clearButton:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:-2px}.QAYkWG_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.QAYkWG_viewOptionsMenu{min-width:200px}.QAYkWG_sectionHeader{box-sizing:border-box;border-radius:var(--dsw-radius-md);height:36px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:flex-end;align-items:center;gap:4px;margin-bottom:4px;padding-left:4px;display:flex;overflow:hidden}.QAYkWG_root:not(.QAYkWG_rail) .QAYkWG_sectionHeader{margin-top:2px;margin-right:-4px}.QAYkWG_sectionLabel{white-space:nowrap;opacity:1;visibility:visible;min-width:0;max-width:45%;transition:max-width .18s var(--ds-ease-in-out), margin-right .18s var(--ds-ease-in-out), opacity .12s var(--ds-ease-in-out), transform .18s var(--ds-ease-in-out), visibility 0s linear;flex:none;line-height:20px;overflow:hidden}.QAYkWG_sectionLabelHidden{opacity:0;visibility:hidden;max-width:0;margin-right:-4px;transition-delay:0s,0s,0s,0s,.18s;transform:translate(-4px)}.QAYkWG_searchSlot{box-sizing:border-box;min-width:0;max-width:28px;transition:max-width .18s var(--ds-ease-in-out), padding-left .18s var(--ds-ease-in-out);flex:1;align-items:center;margin-left:auto;padding-left:0;display:flex}.QAYkWG_searchSlotExpanded{max-width:100%;padding-left:0}.QAYkWG_headerActions{opacity:1;visibility:visible;max-width:60px;transition:max-width .18s var(--ds-ease-in-out), opacity .12s var(--ds-ease-in-out), transform .18s var(--ds-ease-in-out), visibility 0s linear;flex:none;align-items:center;gap:4px;display:flex;overflow:hidden}.QAYkWG_headerActionsHidden{opacity:0;visibility:hidden;pointer-events:none;max-width:0;transition-delay:0s,0s,0s,.18s;transform:translate(4px)}.QAYkWG_search{box-sizing:border-box;border-radius:var(--dsw-radius-sm);cursor:text;width:100%;height:28px;color:var(--dsw-alias-label-secondary);transition:width .18s var(--ds-ease-in-out), padding .18s var(--ds-ease-in-out), border-color .18s var(--ds-ease-in-out), background-color .18s var(--ds-ease-in-out);background:0 0;border:none;flex:none;align-items:center;gap:0;margin:0;padding:0;display:flex;overflow:hidden}.QAYkWG_searchExpanded{border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);width:calc(100% + 4px);height:30px;color:var(--dsw-alias-label-caption);background:0 0;margin-inline:-2px;padding:0 4px 0 0}.QAYkWG_searchButton{border-radius:var(--dsw-radius-sm);cursor:pointer;width:28px;height:28px;color:inherit;background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.QAYkWG_searchExpanded .QAYkWG_searchButton{width:28px;height:30px}.QAYkWG_searchButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.QAYkWG_searchExpanded .QAYkWG_searchButton:hover{background:0 0}.QAYkWG_searchInput{opacity:0;pointer-events:none;width:0;min-width:0;color:var(--dsw-alias-label-primary);transition:opacity .12s var(--ds-ease-in-out);background:0 0;border:none;outline:none;flex:1;font-size:13px;line-height:18px}.QAYkWG_searchExpanded .QAYkWG_searchInput{opacity:1;pointer-events:auto;margin-left:-2px}.QAYkWG_searchInput::placeholder{color:var(--dsw-alias-label-tertiary)}.QAYkWG_clearButton{border-radius:var(--dsw-radius-sm);cursor:pointer;width:24px;height:24px;color:var(--dsw-alias-label-secondary);background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.QAYkWG_clearButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.QAYkWG_rail .QAYkWG_sectionHeader{justify-content:flex-start;gap:0;margin-bottom:12px;padding-left:0}.QAYkWG_rail .QAYkWG_headerActions{max-width:none}.QAYkWG_rail .QAYkWG_iconButton{border-radius:var(--dsw-radius-md);width:36px;height:36px;color:var(--dsw-alias-label-primary)}.QAYkWG_rail .QAYkWG_search{border-radius:var(--dsw-radius-md);background:0 0;border-color:#0000;gap:0;width:36px;height:36px;margin:0 0 12px;padding:0}.QAYkWG_rail .QAYkWG_searchButton{border-radius:var(--dsw-radius-md);width:36px;height:36px;color:var(--dsw-alias-label-primary)}.QAYkWG_rail .QAYkWG_searchButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.QAYkWG_listArea{min-height:0;margin-left:-4px;margin-right:calc(-1 * var(--dsh-session-list-edge-inset));flex-direction:column;flex:1;padding-left:4px;display:flex;overflow:visible}.QAYkWG_rail .QAYkWG_listArea{margin-left:0;margin-right:0;padding-left:0}.QAYkWG_treeBody{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}.QAYkWG_fade{left:0;right:var(--dsh-session-list-edge-inset);background:linear-gradient(to bottom, transparent, var(--dsw-specific-sidebar-fill));pointer-events:none;height:24px;position:absolute;bottom:0}[data-platform=darwin] .QAYkWG_fade{display:none}.QAYkWG_wide{animation:QAYkWG_wide-in .2s var(--ds-ease-in-out)}@keyframes QAYkWG_wide-in{0%{opacity:0}}.QAYkWG_list{min-height:0;margin-left:-4px;margin-right:var(--dsh-session-list-scrollbar-offset);padding-left:4px;padding-right:calc(var(--dsh-session-list-edge-inset) - var(--dsh-session-list-scrollbar-width) - var(--dsh-session-list-scrollbar-offset));scrollbar-gutter:stable;flex:1;padding-bottom:16px;overflow-y:auto}.QAYkWG_flatList>*+*,.QAYkWG_searchTree>[role=treeitem]+[role=treeitem],.QAYkWG_groupSection>*+*{margin-top:2px}.QAYkWG_searchStatus{color:var(--dsw-alias-label-tertiary);padding:10px 12px;font-size:12px;line-height:18px}.QAYkWG_skeletonRow{box-sizing:border-box;align-items:flex-start;gap:8px;min-height:48px;padding:6px 8px 7px;display:flex}.QAYkWG_skeletonDot{corner-shape:round;border-radius:50%;flex:none;width:16px;height:16px}.QAYkWG_skeletonBars{flex-direction:column;flex:1;gap:6px;min-width:0;display:flex}.QAYkWG_skeletonDot,.QAYkWG_skeletonBar{background:var(--dsw-alias-bg-skeleton);animation:2s cubic-bezier(.36,0,.64,1) infinite QAYkWG_search-skeleton}.QAYkWG_skeletonBar{border-radius:var(--dsw-radius-xs);width:65%;height:16px}.QAYkWG_skeletonBarWide{width:90%;height:13px}@keyframes QAYkWG_search-skeleton{0%{opacity:1}40%{opacity:.6}80%,to{opacity:1}}.QAYkWG_groupSection{position:relative}.QAYkWG_groupSection+.QAYkWG_groupSection{margin-top:4px}.QAYkWG_listTopDropIndicator,.QAYkWG_workspaceDropBefore:before,.QAYkWG_workspaceDropAfter:after{content:\"\";z-index:1;background:linear-gradient(55deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 0 / 5px 7px no-repeat, linear-gradient(125deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--dsw-alias-state-business-primary) 0 0) 4px 5px / calc(100% - 4px) 2px no-repeat;pointer-events:none;height:12px;position:absolute;left:0;right:0}.QAYkWG_listTopDropIndicator{top:-8px;left:0;right:var(--dsh-session-list-edge-inset)}.QAYkWG_listTopDropActive>.QAYkWG_workspaceDropBefore:first-child:before{display:none}.QAYkWG_workspaceDropBefore:before{top:-8px}.QAYkWG_workspaceDropAfter:after{bottom:-8px}.QAYkWG_sessionOverflowButton{border-radius:var(--dsw-radius-sm);width:100%;height:28px;padding:0 12px 0 calc(28px + var(--dsh-workspace-indent,0px));cursor:pointer;text-align:left;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;font-size:12px}.QAYkWG_groupSection>.QAYkWG_sessionOverflowButton{margin-top:0}.QAYkWG_sessionOverflowButton:hover{color:var(--dsw-alias-label-secondary);background:0 0}.QAYkWG_empty{color:var(--dsw-alias-label-tertiary);padding:16px 12px;font-size:13px}.QAYkWG_emptyState{color:var(--dsw-alias-label-tertiary);flex-direction:column;align-items:center;gap:8px;margin-top:80px;padding:0 12px;font-size:13px;line-height:20px;display:flex}.QAYkWG_emptyState>svg{color:var(--dsw-alias-label-caption);margin-bottom:4px}.QAYkWG_emptyAction{cursor:pointer;color:var(--dsw-alias-link);background:0 0;border:none;padding:0;font-size:13px;line-height:20px}.QAYkWG_renameInput{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-lg);width:100%;height:44px;color:var(--dsw-alias-label-primary);background:0 0;outline:none;padding:7px 14px;font-size:14px;font-weight:400;line-height:22px}.QAYkWG_renameInput:disabled{color:var(--dsw-alias-label-dimmed)}.QAYkWG_renameError{color:var(--dsw-alias-state-error-primary);margin-top:8px;font-size:12px;line-height:18px}.QAYkWG_deleteAction:not(:disabled){color:var(--dsw-alias-state-error-primary)}.QAYkWG_deleteStatus{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.QAYkWG_archiveActivity{color:var(--dsw-alias-label-primary);margin:0 0 8px;padding-left:18px;font-size:13px;line-height:20px}.QAYkWG_archiveActivity li{overflow-wrap:anywhere}@media (prefers-reduced-motion:reduce){.QAYkWG_wide,.QAYkWG_skeletonDot,.QAYkWG_skeletonBar{animation:none}.QAYkWG_search,.QAYkWG_sectionLabel,.QAYkWG_searchSlot,.QAYkWG_searchInput,.QAYkWG_headerActions{transition:none}}";
|
|
4188
|
-
const tagId = "dsh-group-status/WorkspaceBrowser.module.css";
|
|
4189
|
-
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
4190
|
-
const tag = document.createElement("style");
|
|
4191
|
-
tag.dataset.plugin = "dsh-group-status";
|
|
4192
|
-
tag.dataset.pluginCss = tagId;
|
|
4193
|
-
tag.textContent = css;
|
|
4194
|
-
document.head.appendChild(tag);
|
|
4195
|
-
}
|
|
4196
|
-
var WorkspaceBrowser_module_css_default = {
|
|
4197
|
-
"archiveActivity": "QAYkWG_archiveActivity",
|
|
4198
|
-
"clearButton": "QAYkWG_clearButton",
|
|
4199
|
-
"deleteAction": "QAYkWG_deleteAction",
|
|
4200
|
-
"deleteStatus": "QAYkWG_deleteStatus",
|
|
4201
|
-
"empty": "QAYkWG_empty",
|
|
4202
|
-
"emptyAction": "QAYkWG_emptyAction",
|
|
4203
|
-
"emptyState": "QAYkWG_emptyState",
|
|
4204
|
-
"fade": "QAYkWG_fade",
|
|
4205
|
-
"flatList": "QAYkWG_flatList",
|
|
4206
|
-
"groupSection": "QAYkWG_groupSection",
|
|
4207
|
-
"headerActions": "QAYkWG_headerActions",
|
|
4208
|
-
"headerActionsHidden": "QAYkWG_headerActionsHidden",
|
|
4209
|
-
"iconButton": "QAYkWG_iconButton",
|
|
4210
|
-
"list": "QAYkWG_list",
|
|
4211
|
-
"listArea": "QAYkWG_listArea",
|
|
4212
|
-
"listTopDropActive": "QAYkWG_listTopDropActive",
|
|
4213
|
-
"listTopDropIndicator": "QAYkWG_listTopDropIndicator",
|
|
4214
|
-
"rail": "QAYkWG_rail",
|
|
4215
|
-
"renameError": "QAYkWG_renameError",
|
|
4216
|
-
"renameInput": "QAYkWG_renameInput",
|
|
4217
|
-
"root": "QAYkWG_root",
|
|
4218
|
-
"search": "QAYkWG_search",
|
|
4219
|
-
"search-skeleton": "QAYkWG_search-skeleton",
|
|
4220
|
-
"searchButton": "QAYkWG_searchButton",
|
|
4221
|
-
"searchExpanded": "QAYkWG_searchExpanded",
|
|
4222
|
-
"searchInput": "QAYkWG_searchInput",
|
|
4223
|
-
"searchSlot": "QAYkWG_searchSlot",
|
|
4224
|
-
"searchSlotExpanded": "QAYkWG_searchSlotExpanded",
|
|
4225
|
-
"searchStatus": "QAYkWG_searchStatus",
|
|
4226
|
-
"searchTree": "QAYkWG_searchTree",
|
|
4227
|
-
"sectionHeader": "QAYkWG_sectionHeader",
|
|
4228
|
-
"sectionLabel": "QAYkWG_sectionLabel",
|
|
4229
|
-
"sectionLabelHidden": "QAYkWG_sectionLabelHidden",
|
|
4230
|
-
"sessionOverflowButton": "QAYkWG_sessionOverflowButton",
|
|
4231
|
-
"skeletonBar": "QAYkWG_skeletonBar",
|
|
4232
|
-
"skeletonBarWide": "QAYkWG_skeletonBarWide",
|
|
4233
|
-
"skeletonBars": "QAYkWG_skeletonBars",
|
|
4234
|
-
"skeletonDot": "QAYkWG_skeletonDot",
|
|
4235
|
-
"skeletonRow": "QAYkWG_skeletonRow",
|
|
4236
|
-
"treeBody": "QAYkWG_treeBody",
|
|
4237
|
-
"viewOptionsMenu": "QAYkWG_viewOptionsMenu",
|
|
4238
|
-
"wide": "QAYkWG_wide",
|
|
4239
|
-
"wide-in": "QAYkWG_wide-in",
|
|
4240
|
-
"workspaceDropAfter": "QAYkWG_workspaceDropAfter",
|
|
4241
|
-
"workspaceDropBefore": "QAYkWG_workspaceDropBefore"
|
|
4242
|
-
};
|
|
4243
|
-
//#endregion
|
|
4244
|
-
//#region src/client/rows/WorkspaceBrowser.tsx
|
|
4245
|
-
/**
|
|
4246
|
-
* The workspace/session browsing region filling the sidebar shell's
|
|
4247
|
-
* `sidebar.workspaces` hole: section header (title + view options + add
|
|
4248
|
-
* workspace), search, the grouped tree or flat list, and the workspace
|
|
4249
|
-
* dialogs. Wide state renders the full browser; rail state renders the two
|
|
4250
|
-
* region icons (search / add workspace) as 36px controls on the shell's shared
|
|
4251
|
-
* rail entry path, each requesting expansion through the owner share. Adding
|
|
4252
|
-
* is the header button's one action, so it raises the directory flow with no
|
|
4253
|
-
* menu in between; the flow and its error dialog live in WorkspacePicker
|
|
4254
|
-
* (same package — direct composition, no slot between them). A Session row's
|
|
4255
|
-
* "..." menu and hover buttons are the `sidebar.workspaces.session.menu.item`
|
|
4256
|
-
* and `sidebar.workspaces.session.row.action` lists rendered through this
|
|
4257
|
-
* entry's `renderSlot`; the actions in them, this package's own included,
|
|
4258
|
-
* are slot entries with their own behavior, so this component threads no
|
|
4259
|
-
* action callbacks and hosts no action surface.
|
|
4260
|
-
*/
|
|
4261
|
-
const facetsStore = facetStore();
|
|
4262
|
-
/**
|
|
4263
|
-
* Column slide length (--ds-transition-duration-slow): rail-search focus waits it out —
|
|
4264
|
-
* focus() forces a synchronous layout and would jank the slide.
|
|
4265
|
-
*/
|
|
4266
|
-
const EXPAND_SLIDE_MS = 300;
|
|
4267
|
-
/** Pause between the latest keystroke and a Host content-search request. */
|
|
4268
|
-
const SEARCH_DEBOUNCE_MS = 250;
|
|
4269
|
-
/** `session.search` wire bound, measured in JavaScript UTF-16 code units. */
|
|
4270
|
-
const SEARCH_QUERY_MAX_CODE_UNITS = 500;
|
|
4271
|
-
/** Keep controlled input and RPC payload inside the session.search wire contract. */
|
|
4272
|
-
function sanitizeSearchQuery(value) {
|
|
4273
|
-
const withoutNul = value.replaceAll("\0", "");
|
|
4274
|
-
if (withoutNul.length <= SEARCH_QUERY_MAX_CODE_UNITS) return withoutNul;
|
|
4275
|
-
let end = SEARCH_QUERY_MAX_CODE_UNITS;
|
|
4276
|
-
const last = withoutNul.charCodeAt(end - 1);
|
|
4277
|
-
const next = withoutNul.charCodeAt(end);
|
|
4278
|
-
if (last >= 55296 && last <= 56319 && next >= 56320 && next <= 57343) end--;
|
|
4279
|
-
return withoutNul.slice(0, end);
|
|
4280
|
-
}
|
|
4281
|
-
/**
|
|
4282
|
-
* Accept the native drag at document level while a row drag is active: row
|
|
4283
|
-
* hover still owns the insertion marker, and releasing outside the list must
|
|
4284
|
-
* not be rendered as a rejected drop before dragend commits that last marker.
|
|
4285
|
-
*/
|
|
4286
|
-
function useNativeDragAcceptance(active) {
|
|
4287
|
-
(0, react.useEffect)(() => {
|
|
4288
|
-
if (!active) return;
|
|
4289
|
-
const acceptDrag = (event) => {
|
|
4290
|
-
event.preventDefault();
|
|
4291
|
-
if (event.dataTransfer !== null) event.dataTransfer.dropEffect = "move";
|
|
4292
|
-
};
|
|
4293
|
-
const acceptDrop = (event) => {
|
|
4294
|
-
event.preventDefault();
|
|
4295
|
-
};
|
|
4296
|
-
document.addEventListener("dragover", acceptDrag);
|
|
4297
|
-
document.addEventListener("drop", acceptDrop);
|
|
4298
|
-
return () => {
|
|
4299
|
-
document.removeEventListener("dragover", acceptDrag);
|
|
4300
|
-
document.removeEventListener("drop", acceptDrop);
|
|
4301
|
-
};
|
|
4302
|
-
}, [active]);
|
|
4303
|
-
}
|
|
4304
|
-
/** Grouping, ordering, and archived-filter menu; own open state so it resets with the wide chrome. */
|
|
4305
|
-
function ViewOptionsMenu({ groupBy, orderBy, archivedFilter, onGroupPick, onOrderPick, onArchivedFilterPick, t }) {
|
|
4306
|
-
const [open, setOpen] = (0, react.useState)(false);
|
|
4307
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
4308
|
-
open,
|
|
4309
|
-
onClose: () => {
|
|
4310
|
-
setOpen(false);
|
|
4311
|
-
},
|
|
4312
|
-
items: [
|
|
4313
|
-
{
|
|
4314
|
-
type: "label",
|
|
4315
|
-
id: "group-by",
|
|
4316
|
-
text: t("groupBy.label")
|
|
4317
|
-
},
|
|
4318
|
-
{
|
|
4319
|
-
id: "workspace",
|
|
4320
|
-
label: t("groupBy.workspace"),
|
|
4321
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderCloseRegular, {})
|
|
4322
|
-
},
|
|
4323
|
-
{
|
|
4324
|
-
id: "workspace-tree",
|
|
4325
|
-
label: t("groupBy.workspaceTree"),
|
|
4326
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWorkspaceTreeOutlineRegular, {})
|
|
4327
|
-
},
|
|
4328
|
-
{
|
|
4329
|
-
id: "flat",
|
|
4330
|
-
label: t("groupBy.flat"),
|
|
4331
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFlatListOutlineRegular, {})
|
|
4332
|
-
},
|
|
4333
|
-
{
|
|
4334
|
-
type: "separator",
|
|
4335
|
-
id: "order-by-separator"
|
|
4336
|
-
},
|
|
4337
|
-
{
|
|
4338
|
-
type: "label",
|
|
4339
|
-
id: "order-by",
|
|
4340
|
-
text: t("orderBy.label")
|
|
4341
|
-
},
|
|
4342
|
-
{
|
|
4343
|
-
id: "manual",
|
|
4344
|
-
label: t("orderBy.manual"),
|
|
4345
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronsUpDownOutlineRegular, {})
|
|
4346
|
-
},
|
|
4347
|
-
{
|
|
4348
|
-
id: "updated",
|
|
4349
|
-
label: t("orderBy.updated"),
|
|
4350
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconClockOutlineRegular, {})
|
|
4351
|
-
},
|
|
4352
|
-
{
|
|
4353
|
-
type: "separator",
|
|
4354
|
-
id: "archived-filter-separator"
|
|
4355
|
-
},
|
|
4356
|
-
{
|
|
4357
|
-
type: "label",
|
|
4358
|
-
id: "filter-by",
|
|
4359
|
-
text: t("filterBy.label")
|
|
4360
|
-
},
|
|
4361
|
-
{
|
|
4362
|
-
id: "hide-archived",
|
|
4363
|
-
label: t("viewOptions.hideArchived"),
|
|
4364
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOffOutlineRegular, {})
|
|
4365
|
-
},
|
|
4366
|
-
{
|
|
4367
|
-
id: "show-archived",
|
|
4368
|
-
label: t("viewOptions.showArchived"),
|
|
4369
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQueueOutlineRegular, {})
|
|
4370
|
-
},
|
|
4371
|
-
{
|
|
4372
|
-
id: "only-archived",
|
|
4373
|
-
label: t("viewOptions.onlyArchived"),
|
|
4374
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveCheckOutlineRegular, {})
|
|
4375
|
-
}
|
|
4376
|
-
],
|
|
4377
|
-
selectedIds: [
|
|
4378
|
-
groupBy,
|
|
4379
|
-
orderBy,
|
|
4380
|
-
{
|
|
4381
|
-
default: "hide-archived",
|
|
4382
|
-
show: "show-archived",
|
|
4383
|
-
only: "only-archived"
|
|
4384
|
-
}[archivedFilter]
|
|
4385
|
-
],
|
|
4386
|
-
onSelect: (id) => {
|
|
4387
|
-
if (id === "workspace" || id === "workspace-tree" || id === "flat") onGroupPick(id);
|
|
4388
|
-
else if (id === "manual" || id === "updated") onOrderPick(id);
|
|
4389
|
-
else if (id === "hide-archived") onArchivedFilterPick("default");
|
|
4390
|
-
else if (id === "show-archived") onArchivedFilterPick("show");
|
|
4391
|
-
else if (id === "only-archived") onArchivedFilterPick("only");
|
|
4392
|
-
setOpen(false);
|
|
4393
|
-
},
|
|
4394
|
-
align: "end",
|
|
4395
|
-
dense: true,
|
|
4396
|
-
listClassName: WorkspaceBrowser_module_css_default.viewOptionsMenu,
|
|
4397
|
-
portal: true,
|
|
4398
|
-
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
4399
|
-
label: t("viewOptions.label"),
|
|
4400
|
-
side: "bottom",
|
|
4401
|
-
delayMs: 500,
|
|
4402
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4403
|
-
type: "button",
|
|
4404
|
-
className: clsx(WorkspaceBrowser_module_css_default.iconButton, WorkspaceBrowser_module_css_default.wide),
|
|
4405
|
-
"aria-label": t("viewOptions.label"),
|
|
4406
|
-
onClick: () => {
|
|
4407
|
-
setOpen((v) => !v);
|
|
4408
|
-
},
|
|
4409
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSlidersTwoOutlineRegular, {})
|
|
4205
|
+
onClose: () => {
|
|
4206
|
+
setMenuOpen(false);
|
|
4207
|
+
},
|
|
4208
|
+
portal: true,
|
|
4209
|
+
closeOnPointerLeave: true,
|
|
4210
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4211
|
+
type: "button",
|
|
4212
|
+
className: Rows_module_css_default.iconButton,
|
|
4213
|
+
"aria-label": t("actions.session.aria", { name: title }),
|
|
4214
|
+
onClick: () => {
|
|
4215
|
+
setMenuOpen((v) => !v);
|
|
4216
|
+
},
|
|
4217
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutlineRegular, {})
|
|
4218
|
+
}),
|
|
4219
|
+
children: renderSlot("sidebar.workspaces.session.menu.item", {
|
|
4220
|
+
sessionId: node.id,
|
|
4221
|
+
displayTitle: row.title
|
|
4222
|
+
}, { hookContext: menuOpenState })
|
|
4223
|
+
}), renderSlot("sidebar.workspaces.session.row.action", {
|
|
4224
|
+
sessionId: node.id,
|
|
4225
|
+
displayTitle: row.title
|
|
4226
|
+
})]
|
|
4410
4227
|
})
|
|
4411
|
-
|
|
4228
|
+
]
|
|
4229
|
+
});
|
|
4230
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.HoverCard, {
|
|
4231
|
+
anchor: ownRow,
|
|
4232
|
+
content: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionHoverContent, {
|
|
4233
|
+
node,
|
|
4234
|
+
now,
|
|
4235
|
+
renderSlot,
|
|
4236
|
+
t
|
|
4237
|
+
}),
|
|
4238
|
+
openDelayMs: 800,
|
|
4239
|
+
grouped: true,
|
|
4240
|
+
disabled: menuOpen || drag?.active === true,
|
|
4241
|
+
copyText: row.blank || row.title === "" ? void 0 : row.title,
|
|
4242
|
+
copyLabel: t("copy"),
|
|
4243
|
+
copiedLabel: t("hover.copied")
|
|
4412
4244
|
});
|
|
4413
4245
|
}
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
const
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
if (targetIndex === -1) return;
|
|
4425
|
-
next.splice(targetIndex + (over.half === "after" ? 1 : 0), 0, source.id);
|
|
4426
|
-
return pinCurrentBlank(next, rows.find((row) => row.blank)?.id);
|
|
4246
|
+
//#endregion
|
|
4247
|
+
//#region \0dsh-css:src/client/rows/AnimatedRows.module.css.mjs
|
|
4248
|
+
const css = "._8TnAsq_exits{contain:strict;pointer-events:none;position:absolute;inset:0;overflow:clip}";
|
|
4249
|
+
const tagId = "dsh-group-status/AnimatedRows.module.css";
|
|
4250
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
4251
|
+
const tag = document.createElement("style");
|
|
4252
|
+
tag.dataset.plugin = "dsh-group-status";
|
|
4253
|
+
tag.dataset.pluginCss = tagId;
|
|
4254
|
+
tag.textContent = css;
|
|
4255
|
+
document.head.appendChild(tag);
|
|
4427
4256
|
}
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4257
|
+
var AnimatedRows_module_css_default = { "exits": "_8TnAsq_exits" };
|
|
4258
|
+
//#endregion
|
|
4259
|
+
//#region src/client/rows/AnimatedRows.tsx
|
|
4260
|
+
/** React-commit-driven movement and entry/exit fades for the sidebar's keyed rows. */
|
|
4261
|
+
const ROW_FADE_MS = 100;
|
|
4262
|
+
const ROW_GLIDE_MS = 200;
|
|
4263
|
+
function sameRows(previous, next) {
|
|
4264
|
+
return previous.rowKeys.length === next.rowKeys.length && previous.rowKeys.every((key, index) => key === next.rowKeys[index]);
|
|
4432
4265
|
}
|
|
4266
|
+
function intersects(row, viewport) {
|
|
4267
|
+
return row.bottom > viewport.top && row.top < viewport.bottom && row.right > viewport.left && row.left < viewport.right;
|
|
4268
|
+
}
|
|
4269
|
+
/**
|
|
4270
|
+
* Animates keyed sidebar rows only when their rendered membership or order changes.
|
|
4271
|
+
* Motion starts after the first pointer or keyboard input inside the mounted list.
|
|
4272
|
+
* The parent supplies a positioned container for the inert exit overlay.
|
|
4273
|
+
*/
|
|
4274
|
+
var AnimatedRows = class extends react.Component {
|
|
4275
|
+
armed = false;
|
|
4276
|
+
list = (0, react.createRef)();
|
|
4277
|
+
overlay = (0, react.createRef)();
|
|
4278
|
+
movements = /* @__PURE__ */ new Map();
|
|
4279
|
+
exits = /* @__PURE__ */ new Map();
|
|
4280
|
+
getSnapshotBeforeUpdate(previous) {
|
|
4281
|
+
const list = this.list.current;
|
|
4282
|
+
if (!this.armed || sameRows(previous, this.props) || previous.resetKey !== this.props.resetKey || !previous.ready || !this.props.ready || list === null || typeof list.animate !== "function" || window.matchMedia("(prefers-reduced-motion: reduce)").matches) return null;
|
|
4283
|
+
const viewport = list.getBoundingClientRect();
|
|
4284
|
+
const positions = this.readPositions();
|
|
4285
|
+
const nextKeys = new Set(this.props.rowKeys);
|
|
4286
|
+
const removed = /* @__PURE__ */ new Map();
|
|
4287
|
+
for (const [key, row] of positions) {
|
|
4288
|
+
if (nextKeys.has(key) || !intersects(row.rect, viewport)) continue;
|
|
4289
|
+
const clone = row.element.cloneNode(true);
|
|
4290
|
+
clone.removeAttribute("data-row-key");
|
|
4291
|
+
clone.inert = true;
|
|
4292
|
+
clone.style.setProperty("--dsh-workspace-indent", getComputedStyle(row.element).getPropertyValue("--dsh-workspace-indent"));
|
|
4293
|
+
removed.set(key, {
|
|
4294
|
+
...row,
|
|
4295
|
+
element: clone
|
|
4296
|
+
});
|
|
4297
|
+
}
|
|
4298
|
+
return {
|
|
4299
|
+
positions,
|
|
4300
|
+
removed
|
|
4301
|
+
};
|
|
4302
|
+
}
|
|
4303
|
+
componentDidUpdate(previous, _state, snapshot) {
|
|
4304
|
+
if (snapshot === null) {
|
|
4305
|
+
if (!sameRows(previous, this.props) || previous.resetKey !== this.props.resetKey || previous.ready !== this.props.ready) this.clear();
|
|
4306
|
+
return;
|
|
4307
|
+
}
|
|
4308
|
+
this.cancelMovements();
|
|
4309
|
+
const list = this.list.current;
|
|
4310
|
+
const overlay = this.overlay.current;
|
|
4311
|
+
const viewport = list.getBoundingClientRect();
|
|
4312
|
+
const origin = overlay.getBoundingClientRect();
|
|
4313
|
+
const positions = this.readPositions();
|
|
4314
|
+
for (const [key, row] of positions) {
|
|
4315
|
+
this.removeExit(key);
|
|
4316
|
+
const previousRow = snapshot.positions.get(key);
|
|
4317
|
+
if (!intersects(row.rect, viewport) && (previousRow === void 0 || !intersects(previousRow.rect, viewport))) continue;
|
|
4318
|
+
if (previousRow === void 0) {
|
|
4319
|
+
this.move(row.element, [{ opacity: 0 }, { opacity: 1 }], ROW_FADE_MS);
|
|
4320
|
+
continue;
|
|
4321
|
+
}
|
|
4322
|
+
const dx = previousRow.rect.left - row.rect.left;
|
|
4323
|
+
const dy = previousRow.rect.top - row.rect.top;
|
|
4324
|
+
if (dx === 0 && dy === 0 && previousRow.opacity === 1) continue;
|
|
4325
|
+
this.move(row.element, [{
|
|
4326
|
+
transform: `translate(${String(dx)}px, ${String(dy)}px)`,
|
|
4327
|
+
opacity: previousRow.opacity
|
|
4328
|
+
}, {
|
|
4329
|
+
transform: "translate(0, 0)",
|
|
4330
|
+
opacity: 1
|
|
4331
|
+
}], ROW_GLIDE_MS);
|
|
4332
|
+
}
|
|
4333
|
+
for (const [key, row] of snapshot.removed) {
|
|
4334
|
+
const { element } = row;
|
|
4335
|
+
this.removeExit(key);
|
|
4336
|
+
Object.assign(element.style, {
|
|
4337
|
+
position: "absolute",
|
|
4338
|
+
margin: "0",
|
|
4339
|
+
transform: "none",
|
|
4340
|
+
boxSizing: "border-box",
|
|
4341
|
+
left: `${String(row.rect.left - origin.left)}px`,
|
|
4342
|
+
top: `${String(row.rect.top - origin.top)}px`,
|
|
4343
|
+
width: `${String(row.rect.width)}px`,
|
|
4344
|
+
height: `${String(row.rect.height)}px`
|
|
4345
|
+
});
|
|
4346
|
+
overlay.append(element);
|
|
4347
|
+
const animation = element.animate([{ opacity: row.opacity }, { opacity: 0 }], {
|
|
4348
|
+
duration: ROW_FADE_MS,
|
|
4349
|
+
easing: "ease-out",
|
|
4350
|
+
fill: "forwards"
|
|
4351
|
+
});
|
|
4352
|
+
this.exits.set(key, {
|
|
4353
|
+
element,
|
|
4354
|
+
animation
|
|
4355
|
+
});
|
|
4356
|
+
animation.onfinish = () => {
|
|
4357
|
+
this.removeExit(key);
|
|
4358
|
+
};
|
|
4359
|
+
}
|
|
4360
|
+
}
|
|
4361
|
+
componentWillUnmount() {
|
|
4362
|
+
this.clear();
|
|
4363
|
+
}
|
|
4364
|
+
readPositions() {
|
|
4365
|
+
const rows = this.list.current.querySelectorAll("[data-row-key]");
|
|
4366
|
+
return new Map(Array.from(rows, (element) => [element.dataset.rowKey, {
|
|
4367
|
+
element,
|
|
4368
|
+
rect: element.getBoundingClientRect(),
|
|
4369
|
+
opacity: this.movements.has(element) ? Number(getComputedStyle(element).opacity) : 1
|
|
4370
|
+
}]));
|
|
4371
|
+
}
|
|
4372
|
+
move(element, keyframes, duration) {
|
|
4373
|
+
const animation = element.animate(keyframes, {
|
|
4374
|
+
duration,
|
|
4375
|
+
easing: "ease-out"
|
|
4376
|
+
});
|
|
4377
|
+
this.movements.set(element, animation);
|
|
4378
|
+
animation.onfinish = () => {
|
|
4379
|
+
this.movements.delete(element);
|
|
4380
|
+
animation.cancel();
|
|
4381
|
+
};
|
|
4382
|
+
}
|
|
4383
|
+
cancelMovements() {
|
|
4384
|
+
for (const animation of this.movements.values()) {
|
|
4385
|
+
animation.onfinish = null;
|
|
4386
|
+
animation.cancel();
|
|
4387
|
+
}
|
|
4388
|
+
this.movements.clear();
|
|
4389
|
+
}
|
|
4390
|
+
removeExit(key) {
|
|
4391
|
+
const exit = this.exits.get(key);
|
|
4392
|
+
if (exit === void 0) return;
|
|
4393
|
+
exit.animation.onfinish = null;
|
|
4394
|
+
exit.animation.cancel();
|
|
4395
|
+
exit.element.remove();
|
|
4396
|
+
this.exits.delete(key);
|
|
4397
|
+
}
|
|
4398
|
+
clear() {
|
|
4399
|
+
this.cancelMovements();
|
|
4400
|
+
for (const key of this.exits.keys()) this.removeExit(key);
|
|
4401
|
+
}
|
|
4402
|
+
render() {
|
|
4403
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4404
|
+
ref: this.list,
|
|
4405
|
+
className: this.props.className,
|
|
4406
|
+
role: "tree",
|
|
4407
|
+
"aria-label": this.props.label,
|
|
4408
|
+
onPointerDownCapture: () => {
|
|
4409
|
+
this.armed = true;
|
|
4410
|
+
},
|
|
4411
|
+
onKeyDownCapture: () => {
|
|
4412
|
+
this.armed = true;
|
|
4413
|
+
},
|
|
4414
|
+
children: this.props.children
|
|
4415
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4416
|
+
ref: this.overlay,
|
|
4417
|
+
className: AnimatedRows_module_css_default.exits,
|
|
4418
|
+
"aria-hidden": "true"
|
|
4419
|
+
})] });
|
|
4420
|
+
}
|
|
4421
|
+
};
|
|
4422
|
+
//#endregion
|
|
4423
|
+
//#region src/client/rows/EmptySessions.tsx
|
|
4424
|
+
/** The list-empty placeholder shared by the tree, flat, and search list bodies. */
|
|
4433
4425
|
/** The list-empty placeholder — a glyph over the text; the archived-only view names its filter and offers the way back. */
|
|
4434
4426
|
function EmptySessions({ rowState, onLeaveArchivedOnly, t }) {
|
|
4435
4427
|
const archivedOnly = rowState.archivedFilter === "only";
|
|
@@ -4448,13 +4440,25 @@ window.__ModuleLoader__.load({
|
|
|
4448
4440
|
]
|
|
4449
4441
|
});
|
|
4450
4442
|
}
|
|
4443
|
+
//#endregion
|
|
4444
|
+
//#region src/client/rows/SessionTree.tsx
|
|
4445
|
+
/**
|
|
4446
|
+
* The scrolling session tree: Workspace groups, aggregated live-status badges,
|
|
4447
|
+
* watermarks, and row drag ordering.
|
|
4448
|
+
*
|
|
4449
|
+
* Split out of `WorkspaceBrowser.tsx` so the shell keeps only the header, search
|
|
4450
|
+
* box, and dialogs; this file owns the tree body and its local row limits, and
|
|
4451
|
+
* unmounting it drops both the sessions subscription and those limits.
|
|
4452
|
+
*/
|
|
4453
|
+
const facetsStore = facetStore();
|
|
4451
4454
|
/** The scrolling session tree; unmounting drops the sessions subscription and local row limits. */
|
|
4452
4455
|
function SessionTree({ list, useSessionStatus, startSession, open, workspaces, ungroupedSessionIds, rowState, onLeaveArchivedOnly, workspaceReady, animationResetKey, usePanelInfo, onRenameRequest, onDeleteRequest, onSessionRenameRequest, renderSlot, insertWorkspaceBefore, nestWorkspaces, groupExpansion, setGroupExpanded, setSessionOrder, home, t, revealSessionId, onSessionRevealed, shortcuts, useBadgeSettings }) {
|
|
4453
4456
|
const panelActive = usePanelInfo((info) => info.activePanelId !== null);
|
|
4454
4457
|
const badgeSettings = useBadgeSettings((settings) => settings);
|
|
4455
4458
|
const statuses = useSessionStatus((s) => s);
|
|
4456
4459
|
const facets = useRegisteredFacets(facetsStore);
|
|
4457
|
-
const
|
|
4460
|
+
const mainViewId = (0, react.useMemo)(() => mainViewSessionId(list), [list]);
|
|
4461
|
+
const current = panelActive ? void 0 : mainViewId;
|
|
4458
4462
|
const revealGroup = revealSessionId === void 0 || !workspaceReady ? void 0 : owningGroupKey(workspaces, revealSessionId);
|
|
4459
4463
|
const [storedWatermarks, setStoredWatermarks] = (0, react.useState)({});
|
|
4460
4464
|
const [watermarkMotionKey, setWatermarkMotionKey] = (0, react.useState)(0);
|
|
@@ -4801,6 +4805,9 @@ window.__ModuleLoader__.load({
|
|
|
4801
4805
|
]
|
|
4802
4806
|
});
|
|
4803
4807
|
}
|
|
4808
|
+
//#endregion
|
|
4809
|
+
//#region src/client/rows/ListView.tsx
|
|
4810
|
+
/** The two wide-only list bodies: the flat single-list view and the search results view. */
|
|
4804
4811
|
/** The flat "In one list" body: every session is one draggable top-level row. */
|
|
4805
4812
|
function FlatList({ list, sessionIds, rowState, onLeaveArchivedOnly, useSessionStatus, open, onSessionRenameRequest, usePanelInfo, setSessionOrder, workspaceReady, animationResetKey, revealSessionId, onSessionRevealed, renderSlot, t }) {
|
|
4806
4813
|
const panelActive = usePanelInfo((info) => info.activePanelId !== null);
|
|
@@ -4814,7 +4821,8 @@ window.__ModuleLoader__.load({
|
|
|
4814
4821
|
const [drag, setDrag] = (0, react.useState)(null);
|
|
4815
4822
|
const dropCommitted = (0, react.useRef)(false);
|
|
4816
4823
|
useNativeDragAcceptance(drag !== null);
|
|
4817
|
-
const
|
|
4824
|
+
const mainViewId = (0, react.useMemo)(() => mainViewSessionId(list), [list]);
|
|
4825
|
+
const currentId = panelActive ? void 0 : mainViewId;
|
|
4818
4826
|
const commitDrag = (activeDrag, over) => {
|
|
4819
4827
|
if (dropCommitted.current) return;
|
|
4820
4828
|
dropCommitted.current = true;
|
|
@@ -4909,7 +4917,8 @@ window.__ModuleLoader__.load({
|
|
|
4909
4917
|
resultLimit
|
|
4910
4918
|
]);
|
|
4911
4919
|
const pending = currentRemote.status === "loading";
|
|
4912
|
-
const
|
|
4920
|
+
const mainViewId = (0, react.useMemo)(() => mainViewSessionId(list), [list]);
|
|
4921
|
+
const currentId = panelActive ? void 0 : mainViewId;
|
|
4913
4922
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4914
4923
|
className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
|
|
4915
4924
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -4919,6 +4928,7 @@ window.__ModuleLoader__.load({
|
|
|
4919
4928
|
className: WorkspaceBrowser_module_css_default.searchTree,
|
|
4920
4929
|
role: "tree",
|
|
4921
4930
|
"aria-label": t("search.results.aria"),
|
|
4931
|
+
"aria-busy": pending,
|
|
4922
4932
|
children: results.items.map((result) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchResultItem, {
|
|
4923
4933
|
result,
|
|
4924
4934
|
currentId,
|
|
@@ -4941,20 +4951,35 @@ window.__ModuleLoader__.load({
|
|
|
4941
4951
|
}),
|
|
4942
4952
|
!pending && results.items.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4943
4953
|
className: WorkspaceBrowser_module_css_default.empty,
|
|
4954
|
+
role: "status",
|
|
4944
4955
|
children: t("search.noMatches")
|
|
4945
4956
|
}),
|
|
4946
4957
|
results.hasMore && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4947
4958
|
className: WorkspaceBrowser_module_css_default.searchStatus,
|
|
4959
|
+
role: "status",
|
|
4948
4960
|
children: t("search.hasMore", { n: resultLimit })
|
|
4949
4961
|
})
|
|
4950
4962
|
]
|
|
4951
4963
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: WorkspaceBrowser_module_css_default.fade })]
|
|
4952
4964
|
});
|
|
4953
4965
|
}
|
|
4966
|
+
//#endregion
|
|
4967
|
+
//#region src/client/rows/WorkspaceBrowser.tsx
|
|
4954
4968
|
/**
|
|
4955
|
-
*
|
|
4956
|
-
*
|
|
4957
|
-
*
|
|
4969
|
+
* The workspace/session browsing region filling the sidebar shell's
|
|
4970
|
+
* `sidebar.workspaces` hole: section header (title + view options + add
|
|
4971
|
+
* workspace), search, the grouped tree or flat list, and the workspace
|
|
4972
|
+
* dialogs. Wide state renders the full browser; rail state renders the two
|
|
4973
|
+
* region icons (search / add workspace) as 36px controls on the shell's shared
|
|
4974
|
+
* rail entry path, each requesting expansion through the owner share. Adding
|
|
4975
|
+
* is the header button's one action, so it raises the directory flow with no
|
|
4976
|
+
* menu in between; the flow and its error dialog live in WorkspacePicker
|
|
4977
|
+
* (same package — direct composition, no slot between them). A Session row's
|
|
4978
|
+
* "..." menu and hover buttons are the `sidebar.workspaces.session.menu.item`
|
|
4979
|
+
* and `sidebar.workspaces.session.row.action` lists rendered through this
|
|
4980
|
+
* entry's `renderSlot`; the actions in them, this package's own included,
|
|
4981
|
+
* are slot entries with their own behavior, so this component threads no
|
|
4982
|
+
* action callbacks and hosts no action surface.
|
|
4958
4983
|
*/
|
|
4959
4984
|
function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionStatus, useWorkspaces, useStore, actions, startSession, open, requestSessionRename, notifyArchivedNotOpenable, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, unarchiveSession, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostInfo, useShortcuts, useWorkspaceShortcuts, useBadgeSettings, requestSearch, requestAddWorkspace, closeAddWorkspace, setDirectoryBusy, renderSlot, t }) {
|
|
4960
4985
|
const home = useHostInfo((info) => info.home);
|
|
@@ -4990,7 +5015,7 @@ window.__ModuleLoader__.load({
|
|
|
4990
5015
|
actions.setArchivedFilter("default");
|
|
4991
5016
|
};
|
|
4992
5017
|
const workspaceReady = workspacePhase === "ready" && workspaceStreamState !== "loading";
|
|
4993
|
-
const mainSessionId =
|
|
5018
|
+
const mainSessionId = (0, react.useMemo)(() => mainViewSessionId(list), [list]);
|
|
4994
5019
|
const currentBlank = mainSessionId !== void 0 && list.byId[mainSessionId]?.blank === true ? mainSessionId : void 0;
|
|
4995
5020
|
const ungroupedMemberIds = (0, react.useMemo)(() => {
|
|
4996
5021
|
const accounted = new Set(workspaces.flatMap((workspace) => workspace.sessionIds));
|
|
@@ -5133,7 +5158,7 @@ window.__ModuleLoader__.load({
|
|
|
5133
5158
|
const timer = window.setTimeout(() => {
|
|
5134
5159
|
searchInput.current?.focus({ preventScroll: true });
|
|
5135
5160
|
setSearchOnExpand(false);
|
|
5136
|
-
},
|
|
5161
|
+
}, 300);
|
|
5137
5162
|
return () => {
|
|
5138
5163
|
window.clearTimeout(timer);
|
|
5139
5164
|
};
|
|
@@ -5209,7 +5234,7 @@ window.__ModuleLoader__.load({
|
|
|
5209
5234
|
hasMore: false
|
|
5210
5235
|
});
|
|
5211
5236
|
});
|
|
5212
|
-
},
|
|
5237
|
+
}, 250);
|
|
5213
5238
|
return () => {
|
|
5214
5239
|
window.clearTimeout(timer);
|
|
5215
5240
|
controller.abort();
|
|
@@ -5316,7 +5341,7 @@ window.__ModuleLoader__.load({
|
|
|
5316
5341
|
className: WorkspaceBrowser_module_css_default.searchInput,
|
|
5317
5342
|
type: "text",
|
|
5318
5343
|
placeholder: t("search.placeholder"),
|
|
5319
|
-
maxLength:
|
|
5344
|
+
maxLength: 500,
|
|
5320
5345
|
value: query,
|
|
5321
5346
|
tabIndex: searchExpanded ? 0 : -1,
|
|
5322
5347
|
onChange: (e) => {
|
|
@@ -5729,6 +5754,15 @@ window.__ModuleLoader__.load({
|
|
|
5729
5754
|
//#endregion
|
|
5730
5755
|
//#region src/client/session-actions/derived.ts
|
|
5731
5756
|
/**
|
|
5757
|
+
* "还没投影过"的标记。
|
|
5758
|
+
*
|
|
5759
|
+
* 不能用 `undefined` 兼任:投影**合法地**可能返回 `undefined`("当前没有待处理的
|
|
5760
|
+
* 请求"就是一次正常的空结果),把它当成"未计算"会让每次读都重跑一次 `project`。
|
|
5761
|
+
* 投影若新建 `Set` / 数组,消费者每次读都拿到新引用,选择器于是永远不收敛——这正是
|
|
5762
|
+
* 本函数要避免的那件事。Symbol 不与任何投影结果相等,且只在本模块内存在。
|
|
5763
|
+
*/
|
|
5764
|
+
const NOT_PROJECTED = Symbol("derive.notProjected");
|
|
5765
|
+
/**
|
|
5732
5766
|
* Project one observable into another, recomputing only when the source
|
|
5733
5767
|
* snapshot changes identity, so consumers that select from the projection
|
|
5734
5768
|
* (a Set lookup per row) never rebuild it per read.
|
|
@@ -5738,11 +5772,11 @@ window.__ModuleLoader__.load({
|
|
|
5738
5772
|
*/
|
|
5739
5773
|
function derive(source, project) {
|
|
5740
5774
|
let seen;
|
|
5741
|
-
let value;
|
|
5775
|
+
let value = NOT_PROJECTED;
|
|
5742
5776
|
return {
|
|
5743
5777
|
getSnapshot: () => {
|
|
5744
5778
|
const snapshot = source.getSnapshot();
|
|
5745
|
-
if (value ===
|
|
5779
|
+
if (value === NOT_PROJECTED || snapshot !== seen) {
|
|
5746
5780
|
seen = snapshot;
|
|
5747
5781
|
value = project(snapshot);
|
|
5748
5782
|
}
|
|
@@ -6130,6 +6164,8 @@ window.__ModuleLoader__.load({
|
|
|
6130
6164
|
"settings.count.section": "数字显示",
|
|
6131
6165
|
"settings.count.threshold": "数字显示阈值",
|
|
6132
6166
|
"settings.count.thresholdHint": "数量 >= 此值时才在徽标上显示数字。默认 2(即 count=1 时省略,因为状态点已表达\"有东西\")。设为 1 则始终显示。",
|
|
6167
|
+
"settings.count.decrease": "减少",
|
|
6168
|
+
"settings.count.increase": "增加",
|
|
6133
6169
|
"settings.save": "保存",
|
|
6134
6170
|
"settings.saving": "保存中…",
|
|
6135
6171
|
"settings.saveFailed": "本部署没有接受这些值,已保留供你修改。",
|
|
@@ -6271,6 +6307,8 @@ window.__ModuleLoader__.load({
|
|
|
6271
6307
|
"settings.count.section": "Count display",
|
|
6272
6308
|
"settings.count.threshold": "Count display threshold",
|
|
6273
6309
|
"settings.count.thresholdHint": "The count is shown on the badge only when the total is >= this value. Default 2 (omits the count when it equals 1 — the dots already convey \"something is happening\"). Set to 1 to always show.",
|
|
6310
|
+
"settings.count.decrease": "Decrease",
|
|
6311
|
+
"settings.count.increase": "Increase",
|
|
6274
6312
|
"settings.save": "Save",
|
|
6275
6313
|
"settings.saving": "Saving…",
|
|
6276
6314
|
"settings.saveFailed": "The deployment did not accept these values; they were left for you to correct.",
|