@qzsy/dsh-command-palette 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/client.js +118 -17
- package/package.json +1 -1
- package/scripts/verify-status.mjs +13 -0
package/README.md
CHANGED
|
@@ -77,6 +77,16 @@ initials, or any mixed form.
|
|
|
77
77
|
movement inside the query. IME composition is never intercepted, so pinyin
|
|
78
78
|
candidate selection keeps working. Focus returns to whatever had it before
|
|
79
79
|
opening.
|
|
80
|
+
- **Mobile**: at `(max-width: 720px), (pointer: coarse) and (max-width: 1024px)`
|
|
81
|
+
the palette becomes a bottom sheet at 90% of the layer height. It opens
|
|
82
|
+
without focusing the search input, so the soft keyboard stays closed until
|
|
83
|
+
the user taps the field. All keyboard hints (Esc key, row shortcuts, footer
|
|
84
|
+
key legend) are hidden; the footer only remains for the subagent toggle.
|
|
85
|
+
- **Unread count**: the sidebar row shows the number of Sessions that finished
|
|
86
|
+
successfully but have not been viewed (the green 已完成 badge; failed,
|
|
87
|
+
archived, blank and subagent Sessions are excluded). The collapsed rail shows
|
|
88
|
+
a dot instead. The always-mounted overlay computes the count and mirrors it
|
|
89
|
+
into a plugin-local store read by the sidebar glyph.
|
|
80
90
|
- **Sidebar entry**: the *命令面板 / Command palette* row joins the global panel
|
|
81
91
|
stack directly below 自动化任务 / Automations (glyph-only in the collapsed
|
|
82
92
|
rail). Choosing it opens the overlay without switching the main panel: the row
|
package/dist/client.js
CHANGED
|
@@ -89,6 +89,7 @@ window.__ModuleLoader__.load({
|
|
|
89
89
|
'footer.filter': '切换分类',
|
|
90
90
|
'footer.close': '关闭',
|
|
91
91
|
'trigger.label': '命令面板',
|
|
92
|
+
'trigger.unread': '{n} 个会话已完成,尚未查看',
|
|
92
93
|
'trigger.title': '打开命令面板',
|
|
93
94
|
'time.now': '刚刚',
|
|
94
95
|
'time.short.m': '{n}m',
|
|
@@ -155,6 +156,7 @@ window.__ModuleLoader__.load({
|
|
|
155
156
|
'footer.filter': 'Change filter',
|
|
156
157
|
'footer.close': 'Close',
|
|
157
158
|
'trigger.label': 'Command palette',
|
|
159
|
+
'trigger.unread': '{n} sessions finished, not yet viewed',
|
|
158
160
|
'trigger.title': 'Open command palette',
|
|
159
161
|
'time.now': 'now',
|
|
160
162
|
'time.short.m': '{n}m',
|
|
@@ -818,7 +820,16 @@ window.__ModuleLoader__.load({
|
|
|
818
820
|
'.dshcp-projectBadge{display:inline-flex;align-items:center;max-width:100%;height:19px;padding:0 7px;border-radius:999px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);font-size:11px;line-height:16px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
819
821
|
'[data-dshcp-reveal]{animation:dshcp-reveal 1.2s var(--ds-ease-out,ease-out)}',
|
|
820
822
|
'@keyframes dshcp-reveal{0%{box-shadow:inset 0 0 0 2px var(--dsw-alias-state-business-primary)}100%{box-shadow:inset 0 0 0 2px transparent}}',
|
|
821
|
-
'
|
|
823
|
+
'.dshcp-layer[data-mobile=true]{align-items:flex-end;padding:0}',
|
|
824
|
+
'.dshcp-layer[data-mobile=true] .dshcp-panel{width:100%;height:90%;max-height:90%;border-bottom:none;border-radius:16px 16px 0 0;padding-bottom:env(safe-area-inset-bottom,0px);animation:dshcp-sheet .2s var(--ds-ease-out,ease-out)}',
|
|
825
|
+
'@keyframes dshcp-sheet{from{transform:translateY(100%)}to{transform:none}}',
|
|
826
|
+
'.dshcp-layer[data-mobile=true] .dshcp-input{font-size:16px}',
|
|
827
|
+
'.dshcp-layer[data-mobile=true] .dshcp-tabs{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none}',
|
|
828
|
+
'.dshcp-layer[data-mobile=true] .dshcp-tab{flex:none}',
|
|
829
|
+
'.dshcp-glyph{position:relative;display:inline-flex}',
|
|
830
|
+
'.dshcp-glyphDot{position:absolute;top:-2px;right:-3px;width:7px;height:7px;border-radius:50%;background:var(--dsw-alias-state-success-primary)}',
|
|
831
|
+
'.dshcp-unreadCount{position:absolute;right:10px;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;min-width:20px;height:18px;padding:0 6px;border-radius:999px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-state-success-primary);font-size:11px;line-height:16px;font-weight:600;font-variant-numeric:tabular-nums;pointer-events:none}',
|
|
832
|
+
'@media (prefers-reduced-motion:reduce){.dshcp-backdrop,.dshcp-panel,.dshcp-layer[data-mobile=true] .dshcp-panel{animation:none}.dshcp-badge[data-tone=running] .dshcp-badgeDot{animation:none}[data-dshcp-reveal]{animation:none;box-shadow:inset 0 0 0 2px var(--dsw-alias-state-business-primary)}}'
|
|
822
833
|
].join('');
|
|
823
834
|
const STYLE_ATTR = 'data-dsh-command-palette';
|
|
824
835
|
function installStyles() {
|
|
@@ -840,8 +851,63 @@ window.__ModuleLoader__.load({
|
|
|
840
851
|
* Glyph rendered inside the sidebar's global panel row. The shell owns the
|
|
841
852
|
* row, label, tooltip and rail sizing; this component only supplies artwork.
|
|
842
853
|
*/
|
|
843
|
-
function PalettePanelGlyph({ size }) {
|
|
844
|
-
|
|
854
|
+
function PalettePanelGlyph({ size, t }) {
|
|
855
|
+
const count = useUnreadCount();
|
|
856
|
+
const wide = size !== 18;
|
|
857
|
+
const label = count > 0 ? t('trigger.unread', { n: count }) : undefined;
|
|
858
|
+
return h(React.Fragment, null,
|
|
859
|
+
h('span', { className: 'dshcp-glyph', title: label },
|
|
860
|
+
h(IconSearch, { size }),
|
|
861
|
+
!wide && count > 0 ? h('span', { className: 'dshcp-glyphDot', 'aria-hidden': 'true' }) : null),
|
|
862
|
+
wide && count > 0 ? h('span', { className: 'dshcp-unreadCount', 'aria-label': label }, count > 99 ? '99+' : String(count)) : null);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Unread completions shown on the sidebar row. The panel-list slot is not
|
|
867
|
+
* documented to receive the session status hook, so the always-mounted
|
|
868
|
+
* overlay computes the count and mirrors it into this plugin-local store.
|
|
869
|
+
*/
|
|
870
|
+
const unreadStore = createStore({ count: 0 });
|
|
871
|
+
function useUnreadCount() {
|
|
872
|
+
const read = () => unreadStore.getSnapshot().count;
|
|
873
|
+
if (typeof React.useSyncExternalStore === 'function') return React.useSyncExternalStore(unreadStore.subscribe, read, read);
|
|
874
|
+
const [count, setCount] = useState(read);
|
|
875
|
+
useEffect(() => unreadStore.subscribe(() => setCount(read())), []);
|
|
876
|
+
return count;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Sessions that finished successfully and the user has not opened yet: the
|
|
881
|
+
* same rows that carry the green 已完成 badge in the default palette view
|
|
882
|
+
* (archived, blank and subagent Sessions excluded; failures count as 出错).
|
|
883
|
+
*/
|
|
884
|
+
function countUnreadCompletions(sessions, workspaces, statuses, failures) {
|
|
885
|
+
const archived = new Set(workspaces?.archivedSessionIds ?? []);
|
|
886
|
+
let count = 0;
|
|
887
|
+
for (const row of Object.values(sessions?.byId ?? {})) {
|
|
888
|
+
if (row === undefined || row === null || row.blank === true || archived.has(row.id) || row.origin === 'subagent') continue;
|
|
889
|
+
const state = sessionState(row, statuses?.get?.(row.id), failures?.get?.(row.id));
|
|
890
|
+
if (state !== null && state.tone === 'done') count += 1;
|
|
891
|
+
}
|
|
892
|
+
return count;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/** Breakpoint shared with the quick-commands plugin. */
|
|
896
|
+
const MOBILE_QUERY = '(max-width: 720px), (pointer: coarse) and (max-width: 1024px)';
|
|
897
|
+
function isMobileLayout() {
|
|
898
|
+
return typeof window !== 'undefined' && typeof window.matchMedia === 'function' && window.matchMedia(MOBILE_QUERY).matches;
|
|
899
|
+
}
|
|
900
|
+
function useMobileLayout() {
|
|
901
|
+
const [mobile, setMobile] = useState(isMobileLayout);
|
|
902
|
+
useEffect(() => {
|
|
903
|
+
if (typeof window.matchMedia !== 'function') return undefined;
|
|
904
|
+
const query = window.matchMedia(MOBILE_QUERY);
|
|
905
|
+
const update = () => setMobile(query.matches);
|
|
906
|
+
update();
|
|
907
|
+
query.addEventListener?.('change', update);
|
|
908
|
+
return () => query.removeEventListener?.('change', update);
|
|
909
|
+
}, []);
|
|
910
|
+
return mobile;
|
|
845
911
|
}
|
|
846
912
|
//#endregion
|
|
847
913
|
|
|
@@ -1046,8 +1112,22 @@ window.__ModuleLoader__.load({
|
|
|
1046
1112
|
function Palette(props) {
|
|
1047
1113
|
const { usePalette } = props;
|
|
1048
1114
|
const open = usePalette((state) => state.open);
|
|
1049
|
-
|
|
1050
|
-
|
|
1115
|
+
return h(React.Fragment, null,
|
|
1116
|
+
h(UnreadMirror, props),
|
|
1117
|
+
open ? h(PaletteDialog, props) : null);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function UnreadMirror({ useSessions, useWorkspaces, useSessionStatus, useSessionFailures }) {
|
|
1121
|
+
const sessions = useSessions((value) => value);
|
|
1122
|
+
const workspaces = useWorkspaces((value) => value);
|
|
1123
|
+
const statuses = useSessionStatus((value) => value);
|
|
1124
|
+
const failures = useSessionFailures((value) => value) ?? EMPTY_FAILURES;
|
|
1125
|
+
const count = useMemo(() => countUnreadCompletions(sessions, workspaces, statuses, failures), [sessions, workspaces, statuses, failures]);
|
|
1126
|
+
useEffect(() => {
|
|
1127
|
+
if (unreadStore.getSnapshot().count !== count) unreadStore.set({ count });
|
|
1128
|
+
}, [count]);
|
|
1129
|
+
useEffect(() => () => unreadStore.set({ count: 0 }), []);
|
|
1130
|
+
return null;
|
|
1051
1131
|
}
|
|
1052
1132
|
|
|
1053
1133
|
function PaletteDialog({ usePalette, useShortcutCatalog, useSessions, useWorkspaces, useSessionStatus, useSessionFailures, actions, t }) {
|
|
@@ -1060,6 +1140,7 @@ window.__ModuleLoader__.load({
|
|
|
1060
1140
|
const [includeSubagents, setIncludeSubagents] = useState(false);
|
|
1061
1141
|
const [gitEntries, setGitEntries] = useState(() => gitStatusCache.entries);
|
|
1062
1142
|
const inputRef = useRef(null);
|
|
1143
|
+
const mobile = useMobileLayout();
|
|
1063
1144
|
const panelRef = useRef(null);
|
|
1064
1145
|
const activeRowRef = useRef(null);
|
|
1065
1146
|
const sessions = useSessions((value) => value);
|
|
@@ -1118,7 +1199,9 @@ window.__ModuleLoader__.load({
|
|
|
1118
1199
|
|
|
1119
1200
|
useEffect(() => {
|
|
1120
1201
|
const frame = window.requestAnimationFrame(() => {
|
|
1121
|
-
|
|
1202
|
+
// Phones open without the soft keyboard: focus the sheet, not the input.
|
|
1203
|
+
const target = isMobileLayout() ? panelRef.current : inputRef.current;
|
|
1204
|
+
target?.focus({ preventScroll: true });
|
|
1122
1205
|
});
|
|
1123
1206
|
return () => window.cancelAnimationFrame(frame);
|
|
1124
1207
|
}, []);
|
|
@@ -1322,7 +1405,7 @@ window.__ModuleLoader__.load({
|
|
|
1322
1405
|
item.badge !== null && item.badge !== undefined
|
|
1323
1406
|
? h('span', { className: 'dshcp-badge', 'data-tone': item.badge.tone, title: item.badge.title }, h('span', { className: 'dshcp-badgeDot' }), item.badge.label)
|
|
1324
1407
|
: null,
|
|
1325
|
-
item.keys !== undefined && item.keys.length > 0
|
|
1408
|
+
!mobile && item.keys !== undefined && item.keys.length > 0
|
|
1326
1409
|
? h('span', { className: 'dshcp-keys' }, item.keys.map((key, keyIndex) => h('kbd', { key: keyIndex, className: 'dshcp-key' }, key)))
|
|
1327
1410
|
: item.meta !== undefined && item.meta !== '' ? h('span', { className: 'dshcp-meta' }, item.meta) : null));
|
|
1328
1411
|
}
|
|
@@ -1334,11 +1417,28 @@ window.__ModuleLoader__.load({
|
|
|
1334
1417
|
rows.push(h('div', { key: 'files-error', className: 'dshcp-status' }, t('files.error')));
|
|
1335
1418
|
}
|
|
1336
1419
|
|
|
1420
|
+
const subagentToggle = subagentCount > 0 && (filter === 'all' || filter === 'sessions')
|
|
1421
|
+
? h('button', {
|
|
1422
|
+
className: 'dshcp-toggle',
|
|
1423
|
+
type: 'button',
|
|
1424
|
+
title: includeSubagents ? t('subagents.hide', { n: subagentCount }) : t('subagents.show', { n: subagentCount }),
|
|
1425
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
1426
|
+
onClick: () => setIncludeSubagents((value) => !value)
|
|
1427
|
+
}, includeSubagents ? t('subagents.hide', { n: subagentCount }) : t('subagents.show', { n: subagentCount }))
|
|
1428
|
+
: null;
|
|
1429
|
+
// Touch layouts have no keyboard to hint at.
|
|
1430
|
+
const footerHints = mobile ? null : [
|
|
1431
|
+
h('span', { key: 'select', className: 'dshcp-hint' }, h('kbd', { className: 'dshcp-hintKey' }, '↑'), h('kbd', { className: 'dshcp-hintKey' }, '↓'), t('footer.select')),
|
|
1432
|
+
h('span', { key: 'open', className: 'dshcp-hint' }, h('kbd', { className: 'dshcp-hintKey' }, '↵'), t('footer.open')),
|
|
1433
|
+
h('span', { key: 'filter', className: 'dshcp-hint' }, h('kbd', { className: 'dshcp-hintKey' }, '←'), h('kbd', { className: 'dshcp-hintKey' }, '→'), t('footer.filter')),
|
|
1434
|
+
h('span', { key: 'close', className: 'dshcp-hint' }, h('kbd', { className: 'dshcp-hintKey' }, 'Esc'), t('footer.close'))
|
|
1435
|
+
];
|
|
1436
|
+
const showToggle = subagentCount > 0 && (filter === 'all' || filter === 'sessions');
|
|
1337
1437
|
const placeholder = filter === 'files'
|
|
1338
1438
|
? currentSessionId === undefined ? t('empty.files.noSession') : t('placeholder.files', { name: currentProject ?? t('item.noWorkspace') })
|
|
1339
1439
|
: t('placeholder');
|
|
1340
1440
|
return h(React.Fragment, null,
|
|
1341
|
-
h('div', { className: 'dshcp-layer' },
|
|
1441
|
+
h('div', { className: 'dshcp-layer', 'data-mobile': mobile },
|
|
1342
1442
|
h('div', { className: 'dshcp-backdrop', onMouseDown: close }),
|
|
1343
1443
|
h('div', {
|
|
1344
1444
|
className: 'dshcp-panel',
|
|
@@ -1367,7 +1467,7 @@ window.__ModuleLoader__.load({
|
|
|
1367
1467
|
spellCheck: false,
|
|
1368
1468
|
onChange: (event) => setQuery(event.target.value)
|
|
1369
1469
|
}),
|
|
1370
|
-
h('span', { className: 'dshcp-escKey' }, 'Esc')),
|
|
1470
|
+
mobile ? null : h('span', { className: 'dshcp-escKey' }, 'Esc')),
|
|
1371
1471
|
h('div', { className: 'dshcp-tabs', role: 'tablist' },
|
|
1372
1472
|
filterButton('all', t('tab.all')),
|
|
1373
1473
|
filterButton('sessions', t('tab.sessions')),
|
|
@@ -1375,12 +1475,12 @@ window.__ModuleLoader__.load({
|
|
|
1375
1475
|
filterButton('files', t('tab.files')),
|
|
1376
1476
|
filterButton('actions', t('tab.actions'))),
|
|
1377
1477
|
h('div', { className: 'dshcp-list', id: 'dshcp-listbox', role: 'listbox', 'aria-label': t('command.open') }, rows),
|
|
1378
|
-
h('div', { className: 'dshcp-footer' },
|
|
1379
|
-
h('span', { className: 'dshcp-hint' }, h('kbd', { className: 'dshcp-hintKey' }, '↑'), h('kbd', { className: 'dshcp-hintKey' }, '↓'), t('footer.select')),
|
|
1380
|
-
h('span', { className: 'dshcp-hint' }, h('kbd', { className: 'dshcp-hintKey' }, '↵'), t('footer.open')),
|
|
1381
|
-
h('span', { className: 'dshcp-hint' }, h('kbd', { className: 'dshcp-hintKey' }, '←'), h('kbd', { className: 'dshcp-hintKey' }, '→'), t('footer.filter')),
|
|
1382
|
-
h('span', { className: 'dshcp-hint' }, h('kbd', { className: 'dshcp-hintKey' }, 'Esc'), t('footer.close')),
|
|
1383
|
-
|
|
1478
|
+
mobile && !showToggle ? null : h('div', { className: 'dshcp-footer' },
|
|
1479
|
+
mobile ? null : h('span', { className: 'dshcp-hint' }, h('kbd', { className: 'dshcp-hintKey' }, '↑'), h('kbd', { className: 'dshcp-hintKey' }, '↓'), t('footer.select')),
|
|
1480
|
+
mobile ? null : h('span', { className: 'dshcp-hint' }, h('kbd', { className: 'dshcp-hintKey' }, '↵'), t('footer.open')),
|
|
1481
|
+
mobile ? null : h('span', { className: 'dshcp-hint' }, h('kbd', { className: 'dshcp-hintKey' }, '←'), h('kbd', { className: 'dshcp-hintKey' }, '→'), t('footer.filter')),
|
|
1482
|
+
mobile ? null : h('span', { className: 'dshcp-hint' }, h('kbd', { className: 'dshcp-hintKey' }, 'Esc'), t('footer.close')),
|
|
1483
|
+
showToggle
|
|
1384
1484
|
? h('button', {
|
|
1385
1485
|
className: 'dshcp-toggle',
|
|
1386
1486
|
type: 'button',
|
|
@@ -1637,7 +1737,7 @@ window.__ModuleLoader__.load({
|
|
|
1637
1737
|
order: 20,
|
|
1638
1738
|
label: () => t('trigger.label'),
|
|
1639
1739
|
locale: NS
|
|
1640
|
-
}, PalettePanelGlyph));
|
|
1740
|
+
}, (props) => h(PalettePanelGlyph, { ...props, t })));
|
|
1641
1741
|
interceptSidebarSelection(ctx, actions);
|
|
1642
1742
|
adoptShortcut(ctx);
|
|
1643
1743
|
}
|
|
@@ -1656,7 +1756,8 @@ window.__ModuleLoader__.load({
|
|
|
1656
1756
|
sessionBadge,
|
|
1657
1757
|
summarizeSessionRows,
|
|
1658
1758
|
sessionItems,
|
|
1659
|
-
workspaceItems
|
|
1759
|
+
workspaceItems,
|
|
1760
|
+
countUnreadCompletions
|
|
1660
1761
|
};
|
|
1661
1762
|
|
|
1662
1763
|
return { inject, apply, __test };
|
package/package.json
CHANGED
|
@@ -245,6 +245,7 @@ function runChecks({ plugin, registration, source }) {
|
|
|
245
245
|
|
|
246
246
|
check('surface: __test exposes the documented helpers', () => {
|
|
247
247
|
const expected = [
|
|
248
|
+
'countUnreadCompletions',
|
|
248
249
|
'createFailureStore',
|
|
249
250
|
'installFailureFeed',
|
|
250
251
|
'normalizeFailureMessage',
|
|
@@ -756,6 +757,18 @@ function runChecks({ plugin, registration, source }) {
|
|
|
756
757
|
equal(row.badge, { tone: 'running', label: '工作中' }, 'badge');
|
|
757
758
|
equal(row.status, { running: true }, 'status payload');
|
|
758
759
|
});
|
|
760
|
+
|
|
761
|
+
check('unread: counts unread successes only, skipping failed, archived, blank and subagent rows', () => {
|
|
762
|
+
const sessions = { byId: {
|
|
763
|
+
a: { id: 'a' }, b: { id: 'b' }, c: { id: 'c' }, d: { id: 'd', blank: true },
|
|
764
|
+
e: { id: 'e', origin: 'subagent' }, f: { id: 'f' }, g: { id: 'g' }
|
|
765
|
+
} };
|
|
766
|
+
const unread = { completionUnread: true };
|
|
767
|
+
const statuses = new Map([['a', unread], ['b', unread], ['c', unread], ['d', unread], ['e', unread], ['f', { completionUnread: true, running: true }], ['g', {}]]);
|
|
768
|
+
const failures = new Map([['b', { message: 'x', at: NOW }]]);
|
|
769
|
+
equal(api.countUnreadCompletions(sessions, { archivedSessionIds: ['c'] }, statuses, failures), 1, 'unread count');
|
|
770
|
+
equal(api.countUnreadCompletions(undefined, undefined, undefined, undefined), 0, 'empty snapshot');
|
|
771
|
+
});
|
|
759
772
|
}
|
|
760
773
|
|
|
761
774
|
// ---------------------------------------------------------------------------
|