dsh-global-todo 0.6.0 → 0.6.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 +28 -0
- package/docs/screenshots/detail-dark.png +0 -0
- package/docs/screenshots/panel-dark.png +0 -0
- package/docs/screenshots/panel-detail.png +0 -0
- package/docs/screenshots/panel-select.png +0 -0
- package/docs/screenshots/panel.png +0 -0
- package/lib/client.js +69 -23
- package/lib/index.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
## [0.6.2] — 2026-10-20
|
|
2
|
+
|
|
3
|
+
The narrow-width details the spec called for after the two-column work.
|
|
4
|
+
|
|
5
|
+
- **Grouping becomes a dropdown when the column is narrow.** The three segmented buttons collapse
|
|
6
|
+
into one `select` (Conversation / Time / Level) below 960px, so the toolbar stops taking a row
|
|
7
|
+
for grouping at the width where rows are already tight.
|
|
8
|
+
- **The inbox explains itself.** Opening the inbox view now leads with one line: items were written
|
|
9
|
+
by external sources or the agent and only join the list after confirmation.
|
|
10
|
+
- **Toasts are anchored to the panel.** `position: absolute` on the panel root instead of `fixed`
|
|
11
|
+
against the viewport, so a notice appears above the list rather than at the bottom of the window.
|
|
12
|
+
|
|
13
|
+
## [0.6.1] — 2026-10-20
|
|
14
|
+
|
|
15
|
+
The follow-up pass from the design spec: the breakpoint now follows the panel, not the window,
|
|
16
|
+
and the form stops competing with itself.
|
|
17
|
+
|
|
18
|
+
- **Panel-measured breakpoint.** A `ResizeObserver` on the column sets `data-wide`; the detail card
|
|
19
|
+
becomes a right-hand card only when the column itself is at least 960px. At 1100 the detail now
|
|
20
|
+
expands inline under its row instead of squeezing the list into a 416px strip.
|
|
21
|
+
- **Inline detail stops repeating itself.** The card title is hidden in inline mode (the row title
|
|
22
|
+
is directly above it) and keeps only the close affordance.
|
|
23
|
+
- **Add form.** The header action turns into an icon-only close while the form is open, so there is
|
|
24
|
+
one `+ Add` on screen; the form orders 更多字段 before 添加, matching the spec.
|
|
25
|
+
- **Rows are 72px tall** at minimum, matching the spec's rhythm, and the detail/card radius is 10px.
|
|
26
|
+
- Dark theme verified against the real host: layer-2 rows, overlay inspector and ghost-active
|
|
27
|
+
selected states all resolve correctly.
|
|
28
|
+
|
|
1
29
|
## [0.6.0] — 2026-10-20
|
|
2
30
|
|
|
3
31
|
A design pass over the whole panel: one list, fewer competing controls, one type scale.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/client.js
CHANGED
|
@@ -50,7 +50,7 @@ window.__ModuleLoader__.load({
|
|
|
50
50
|
* different mechanisms (restart vs. hot reload), so "which one am I looking
|
|
51
51
|
* at" needs an answer that the bundle itself carries.
|
|
52
52
|
*/
|
|
53
|
-
const UI_REV = '
|
|
53
|
+
const UI_REV = 'ui38'
|
|
54
54
|
/** Last language resolved for the shell; module-level so a fetch failure can be worded too. */
|
|
55
55
|
let activeLang = 'zh'
|
|
56
56
|
|
|
@@ -106,6 +106,7 @@ window.__ModuleLoader__.load({
|
|
|
106
106
|
'settings.cancel': '取消',
|
|
107
107
|
'settings.saved': '设置已保存',
|
|
108
108
|
'settings.about': '关于',
|
|
109
|
+
'inbox.explain': '以下由外部写入或 Agent 提交,确认后才进入清单。',
|
|
109
110
|
'notice.exported': '已导出 {n} 条待办',
|
|
110
111
|
'notice.imported': '已导入 {n} 条(合并,现有共 {total} 条)',
|
|
111
112
|
'import.badJson': '这个文件不是合法的 JSON',
|
|
@@ -347,6 +348,7 @@ window.__ModuleLoader__.load({
|
|
|
347
348
|
'settings.cancel': 'Cancel',
|
|
348
349
|
'settings.saved': 'Settings saved',
|
|
349
350
|
'settings.about': 'About',
|
|
351
|
+
'inbox.explain': 'Written by external sources or the agent. Confirm it before it joins the list.',
|
|
350
352
|
'notice.exported': 'Exported {n} todos',
|
|
351
353
|
'notice.imported': 'Imported {n} (merged; {total} in the list now)',
|
|
352
354
|
'import.badJson': 'That file is not valid JSON',
|
|
@@ -908,7 +910,7 @@ window.__ModuleLoader__.load({
|
|
|
908
910
|
// ------------------------------------------------------------------ 样式
|
|
909
911
|
|
|
910
912
|
const CSS = `
|
|
911
|
-
.gt-root { display: flex; flex-direction: column; height: 100%; min-height: 0; min-width: 0;
|
|
913
|
+
.gt-root { position: relative; display: flex; flex-direction: column; height: 100%; min-height: 0; min-width: 0;
|
|
912
914
|
color: var(--dsw-alias-label-primary); font-size: 13px; line-height: 1.5; }
|
|
913
915
|
.gt-inner { display: flex; flex-direction: column; gap: 14px; width: 100%; max-width: 1040px; min-width: 0;
|
|
914
916
|
box-sizing: border-box; margin: 0 auto; padding: 20px 24px 32px; flex: 1; }
|
|
@@ -1051,7 +1053,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
1051
1053
|
.gt-list { display: flex; flex-direction: column; gap: 8px; }
|
|
1052
1054
|
.gt-item { position: relative; display: flex; flex-direction: column; gap: 8px;
|
|
1053
1055
|
background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1);
|
|
1054
|
-
border-radius:
|
|
1056
|
+
border-radius: 10px; padding: 12px 14px; min-height: 72px; box-sizing: border-box; }
|
|
1055
1057
|
.gt-item:hover { border-color: var(--dsw-alias-border-l2); }
|
|
1056
1058
|
.gt-item[data-status="doing"] { border-left: 2px solid var(--dsw-alias-brand-primary); }
|
|
1057
1059
|
.gt-item[data-overdue="1"] { border-left: 2px solid var(--dsw-alias-state-error-primary); }
|
|
@@ -1091,6 +1093,13 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
1091
1093
|
.gt-settings { display: flex; flex-direction: column; gap: 12px; padding: 16px; width: 340px;
|
|
1092
1094
|
box-sizing: border-box; border: 1px solid var(--dsw-alias-border-l2); border-radius: 12px;
|
|
1093
1095
|
background: var(--dsw-alias-bg-overlay); font-size: 13px; line-height: 20px; }
|
|
1096
|
+
.gt-group-select { display: none; min-height: 28px; max-width: 150px; padding: 0 8px; font: inherit;
|
|
1097
|
+
font-size: 12.5px; color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-layer-1);
|
|
1098
|
+
border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; }
|
|
1099
|
+
.gt-root[data-wide="0"] .gt-group-tabs { display: none; }
|
|
1100
|
+
.gt-root[data-wide="0"] .gt-group-select { display: inline-flex; align-items: center; }
|
|
1101
|
+
.gt-inbox-note { padding: 0 2px 2px; color: var(--dsw-alias-label-secondary); font-size: 13px;
|
|
1102
|
+
line-height: 20px; }
|
|
1094
1103
|
.gt-settings-path { font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1095
1104
|
.gt-settings .gt-input { flex: 0 0 auto; width: 100%; min-height: 32px; }
|
|
1096
1105
|
.gt-settings .gt-check-line .gt-input { width: 64px; }
|
|
@@ -1105,6 +1114,9 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
1105
1114
|
background: var(--dsw-alias-bg-layer-1); }
|
|
1106
1115
|
.gt-context-details[data-open="1"] { display: flex; }
|
|
1107
1116
|
.gt-detail-head { display: flex; align-items: flex-start; gap: 8px; justify-content: space-between; }
|
|
1117
|
+
/* Inline detail sits directly under its row: repeating the title would read as a second item. */
|
|
1118
|
+
.gt-root[data-wide="0"][data-wide-detail="1"] .gt-detail-title { display: none; }
|
|
1119
|
+
.gt-root[data-wide="0"][data-wide-detail="1"] .gt-detail-head { justify-content: flex-end; }
|
|
1108
1120
|
.gt-detail-title { font-size: 14px; line-height: 22px; font-weight: 600; min-width: 0;
|
|
1109
1121
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1110
1122
|
.gt-truncated { color: var(--dsw-alias-state-warn-primary); font-size: 12px; }
|
|
@@ -1139,12 +1151,12 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
1139
1151
|
/* Wide screens: the open row's detail becomes a card on the right instead of pushing the list
|
|
1140
1152
|
down. Deliberately CSS-only over unchanged markup: one detail implementation, and it only
|
|
1141
1153
|
applies where there is room for a second column. */
|
|
1142
|
-
|
|
1154
|
+
/* Wide layout follows the panel's measured width (data-wide), not the viewport. */
|
|
1143
1155
|
/* Only the list gives way: the title row and the toolbar keep their full width, so the
|
|
1144
1156
|
controls never wrap or hide behind the card. */
|
|
1145
|
-
.gt-root[data-wide-detail="1"] .gt-groups { padding-right: 360px; }
|
|
1146
|
-
.gt-root[data-wide-detail="1"] .gt-inner { position: relative; }
|
|
1147
|
-
.gt-root[data-wide-detail="1"] .gt-item[data-open="1"] .gt-detail {
|
|
1157
|
+
.gt-root[data-wide="1"][data-wide-detail="1"] .gt-groups { padding-right: 360px; }
|
|
1158
|
+
.gt-root[data-wide="1"][data-wide-detail="1"] .gt-inner { position: relative; }
|
|
1159
|
+
.gt-root[data-wide="1"][data-wide-detail="1"] .gt-item[data-open="1"] .gt-detail {
|
|
1148
1160
|
/* Anchored to the panel's own column (.gt-inner), not to the viewport: the slot does not
|
|
1149
1161
|
always fill the window, and a viewport-centred offset put the card over the list. */
|
|
1150
1162
|
/* The var fallback matters for the static previews (server-rendered, so effects never
|
|
@@ -1158,7 +1170,6 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
1158
1170
|
overflow: auto; padding: 16px; margin: 0; border: 1px solid var(--dsw-alias-border-l2);
|
|
1159
1171
|
border-radius: 12px; background: var(--dsw-alias-bg-overlay); z-index: 30;
|
|
1160
1172
|
}
|
|
1161
|
-
}
|
|
1162
1173
|
.gt-section { display: flex; flex-direction: column; gap: 5px; }
|
|
1163
1174
|
.gt-completion { white-space: pre-wrap; word-break: break-word; line-height: 1.5; }
|
|
1164
1175
|
.gt-section-title { color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 18px; font-weight: 600; }
|
|
@@ -1185,7 +1196,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
1185
1196
|
.gt-empty-title { font-size: 14px; color: var(--dsw-alias-label-primary); }
|
|
1186
1197
|
.gt-foot { color: var(--dsw-alias-label-secondary); font-size: 11px; display: flex; gap: 10px; flex-wrap: wrap;
|
|
1187
1198
|
align-items: center; }
|
|
1188
|
-
.gt-toast { position:
|
|
1199
|
+
.gt-toast { position: absolute; left: 50%; bottom: 16px; transform: translateX(-50%); z-index: 50;
|
|
1189
1200
|
display: flex; align-items: center; gap: 12px; background: var(--dsw-alias-bg-overlay);
|
|
1190
1201
|
border: 1px solid var(--dsw-alias-border-l2); border-radius: 10px; padding: 9px 13px;
|
|
1191
1202
|
color: var(--dsw-alias-label-primary); }
|
|
@@ -2166,6 +2177,10 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
2166
2177
|
// reach without curl.
|
|
2167
2178
|
const [settingsOpen, setSettingsOpen] = useState(false)
|
|
2168
2179
|
const [settingsBox, setSettingsBox] = useState(null)
|
|
2180
|
+
// The detail card's breakpoint follows the panel's own width, not the viewport: the
|
|
2181
|
+
// slot is ~265px narrower than the window, so a viewport query opens the two-column
|
|
2182
|
+
// layout while the panel is still too narrow for it.
|
|
2183
|
+
const [panelWidth, setPanelWidth] = useState(null)
|
|
2169
2184
|
const [contextOpen, setContextOpen] = useState(false)
|
|
2170
2185
|
const [tokenDraft, setTokenDraft] = useState('')
|
|
2171
2186
|
const [ingestDraft, setIngestDraft] = useState(false)
|
|
@@ -2406,6 +2421,20 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
2406
2421
|
}
|
|
2407
2422
|
}, [lang])
|
|
2408
2423
|
|
|
2424
|
+
useEffect(() => {
|
|
2425
|
+
const element = innerRef.current
|
|
2426
|
+
if (element === null || typeof window === 'undefined') return undefined
|
|
2427
|
+
const measure = () => setPanelWidth(element.getBoundingClientRect().width)
|
|
2428
|
+
measure()
|
|
2429
|
+
window.addEventListener('resize', measure)
|
|
2430
|
+
const observer = typeof window.ResizeObserver === 'function' ? new window.ResizeObserver(measure) : null
|
|
2431
|
+
if (observer !== null) observer.observe(element)
|
|
2432
|
+
return () => {
|
|
2433
|
+
window.removeEventListener('resize', measure)
|
|
2434
|
+
if (observer !== null) observer.disconnect()
|
|
2435
|
+
}
|
|
2436
|
+
}, [])
|
|
2437
|
+
|
|
2409
2438
|
useEffect(() => {
|
|
2410
2439
|
if (!settingsOpen || typeof window === 'undefined') {
|
|
2411
2440
|
setSettingsBox(null)
|
|
@@ -3157,6 +3186,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
3157
3186
|
{
|
|
3158
3187
|
className: 'gt-root',
|
|
3159
3188
|
'data-wide-detail': expandedId === '' ? '0' : '1',
|
|
3189
|
+
'data-wide': panelWidth === null || panelWidth >= 960 ? '1' : '0',
|
|
3160
3190
|
// The slot does not always fill the window, so the card's viewport offsets are
|
|
3161
3191
|
// measured from the panel itself instead of assumed from 50vw.
|
|
3162
3192
|
style:
|
|
@@ -3198,15 +3228,16 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
3198
3228
|
'button',
|
|
3199
3229
|
{
|
|
3200
3230
|
type: 'button',
|
|
3201
|
-
className: 'gt-btn gt-btn-primary',
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3231
|
+
className: adding ? 'gt-btn gt-btn-quiet' : 'gt-btn gt-btn-primary',
|
|
3232
|
+
'aria-expanded': adding,
|
|
3233
|
+
title: adding ? t('action.less') : t('action.add'),
|
|
3234
|
+
onClick: () => {
|
|
3235
|
+
setAdding((value) => !value)
|
|
3236
|
+
setMore(false)
|
|
3237
|
+
},
|
|
3206
3238
|
},
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
t('action.add'),
|
|
3239
|
+
h(Icon, { name: adding ? 'close' : 'plus', size: 13 }),
|
|
3240
|
+
adding ? '' : t('action.add'),
|
|
3210
3241
|
),
|
|
3211
3242
|
// One overflow menu instead of four competing buttons: the primary action is
|
|
3212
3243
|
// adding a todo; everything else is occasional.
|
|
@@ -3351,14 +3382,14 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
3351
3382
|
}),
|
|
3352
3383
|
h(
|
|
3353
3384
|
'button',
|
|
3354
|
-
{ type: 'button', className: 'gt-btn gt-btn-
|
|
3355
|
-
|
|
3356
|
-
t('action.add'),
|
|
3385
|
+
{ type: 'button', className: 'gt-btn gt-btn-quiet', 'aria-expanded': more, onClick: () => setMore((value) => !value) },
|
|
3386
|
+
more ? t('action.less') : t('action.more'),
|
|
3357
3387
|
),
|
|
3358
3388
|
h(
|
|
3359
3389
|
'button',
|
|
3360
|
-
{ type: 'button', className: 'gt-btn gt-btn-
|
|
3361
|
-
|
|
3390
|
+
{ type: 'button', className: 'gt-btn gt-btn-primary', disabled: busyId === '__new__' || draft.title.trim() === '', onClick: () => void submit() },
|
|
3391
|
+
h(Icon, { name: 'plus', size: 13 }),
|
|
3392
|
+
t('action.add'),
|
|
3362
3393
|
),
|
|
3363
3394
|
),
|
|
3364
3395
|
more
|
|
@@ -3455,7 +3486,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
3455
3486
|
h('div', { className: 'gt-spacer' }),
|
|
3456
3487
|
h(
|
|
3457
3488
|
'span',
|
|
3458
|
-
{ className: 'gt-tabs', role: 'group', 'aria-label': t('group.by'), title: t('group.by') },
|
|
3489
|
+
{ className: 'gt-tabs gt-group-tabs', role: 'group', 'aria-label': t('group.by'), title: t('group.by') },
|
|
3459
3490
|
GROUP_MODES.map((mode) =>
|
|
3460
3491
|
h(
|
|
3461
3492
|
'button',
|
|
@@ -3473,6 +3504,20 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
3473
3504
|
),
|
|
3474
3505
|
),
|
|
3475
3506
|
),
|
|
3507
|
+
h(
|
|
3508
|
+
'select',
|
|
3509
|
+
{
|
|
3510
|
+
className: 'gt-group-select',
|
|
3511
|
+
value: groupMode,
|
|
3512
|
+
'aria-label': t('group.by'),
|
|
3513
|
+
title: t('group.by'),
|
|
3514
|
+
onChange: (event) => {
|
|
3515
|
+
setGroupMode(event.target.value)
|
|
3516
|
+
storeStoredGroupMode(event.target.value)
|
|
3517
|
+
},
|
|
3518
|
+
},
|
|
3519
|
+
GROUP_MODES.map((mode) => h('option', { key: mode, value: mode }, t(`group.${mode}`))),
|
|
3520
|
+
),
|
|
3476
3521
|
),
|
|
3477
3522
|
h('div', { className: 'gt-spacer' }),
|
|
3478
3523
|
h(
|
|
@@ -3888,6 +3933,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
3888
3933
|
: h(
|
|
3889
3934
|
'div',
|
|
3890
3935
|
{ className: 'gt-groups', role: 'list' },
|
|
3936
|
+
filter === 'inbox' ? h('div', { className: 'gt-inbox-note', role: 'note' }, t('inbox.explain')) : null,
|
|
3891
3937
|
groups.map((group) => {
|
|
3892
3938
|
const isCollapsed = collapsed[group.key] ?? group.collapsed === true
|
|
3893
3939
|
const exemptGroup = typeof group.dir === 'string' && group.dir !== '' && exemptWorkspaces.includes(group.dir)
|
package/lib/index.js
CHANGED
|
@@ -36,7 +36,7 @@ import { createStore, resolveStorePath } from './store.js'
|
|
|
36
36
|
import { createTools } from './tools.js'
|
|
37
37
|
|
|
38
38
|
/** Must equal `package.json`'s version; `npm run check` enforces that. */
|
|
39
|
-
export const VERSION = '0.6.
|
|
39
|
+
export const VERSION = '0.6.2'
|
|
40
40
|
|
|
41
41
|
/** Plugin id. Also the Loader row id in `cordis.patch.yml`. */
|
|
42
42
|
export const name = 'dsh-global-todo'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-global-todo",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"icon": "./docs/icon.svg",
|
|
5
5
|
"description": "Global, cross-session todos for DeepSeek Harness: one list owned by the human and the agent together, with creation time, attached documents and links, and one-click hand-off to an agent session. 全局待办:跨会话一份清单,记录加入时间,可挂文档与地址,可一键交给 Agent 继续干。",
|
|
6
6
|
"type": "module",
|