@shendeguize/dsh-agent-sidecar 0.1.1 → 0.2.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.
Files changed (53) hide show
  1. package/README.md +78 -11
  2. package/lib/client.js +8659 -7589
  3. package/lib/client.js.map +1 -1
  4. package/package.json +3 -1
  5. package/src/client/analysis/AnalysisPanel.tsx +19 -27
  6. package/src/client/analysis/analysis.module.css +36 -97
  7. package/src/client/board/Board.tsx +115 -61
  8. package/src/client/board/board.module.css +72 -151
  9. package/src/client/board/project-view-logic.ts +21 -34
  10. package/src/client/board/project-view.module.css +41 -96
  11. package/src/client/board/project-view.tsx +29 -13
  12. package/src/client/board/strings.ts +68 -107
  13. package/src/client/commands.ts +30 -15
  14. package/src/client/detail/SessionDetail.tsx +92 -58
  15. package/src/client/detail/detail.module.css +62 -218
  16. package/src/client/detail/strings.ts +64 -88
  17. package/src/client/detail-view.module.css +30 -55
  18. package/src/client/detail-view.tsx +80 -108
  19. package/src/client/dsh-tools/LineageTree.tsx +22 -13
  20. package/src/client/dsh-tools/SearchPanel.tsx +18 -11
  21. package/src/client/dsh-tools/dsh-tools.module.css +50 -139
  22. package/src/client/dsh-tools/strings.ts +42 -77
  23. package/src/client/index.ts +285 -124
  24. package/src/client/inject/InjectPanel.tsx +31 -64
  25. package/src/client/inject/inject.module.css +97 -198
  26. package/src/client/lifecycle/handoff.ts +83 -0
  27. package/src/client/locales/en.ts +74 -2
  28. package/src/client/locales/host.ts +131 -0
  29. package/src/client/locales/index.ts +196 -8
  30. package/src/client/locales/react.ts +10 -0
  31. package/src/client/locales/view.ts +38 -0
  32. package/src/client/locales/zh.ts +194 -134
  33. package/src/client/mount.tsx +72 -38
  34. package/src/client/navigation/CenterOverlay.tsx +40 -0
  35. package/src/client/navigation/center-overlay.module.css +51 -0
  36. package/src/client/navigation/center.ts +45 -0
  37. package/src/client/navigation/modal-isolation.ts +152 -0
  38. package/src/client/navigation/modal-surface-anchor.ts +72 -0
  39. package/src/client/navigation/sidebar-entry.module.css +73 -0
  40. package/src/client/navigation/sidebar-entry.ts +309 -0
  41. package/src/client/primitives/StaticPill.tsx +21 -0
  42. package/src/client/settings-card.module.css +35 -119
  43. package/src/client/settings-card.tsx +31 -153
  44. package/src/client/settings-fields.tsx +131 -0
  45. package/src/client/sidebar/SidebarTab.tsx +156 -0
  46. package/src/client/sidebar/model.ts +65 -0
  47. package/src/client/sidebar/sidebar-tab.module.css +118 -0
  48. package/src/client/sidebar-tab.tsx +46 -320
  49. package/src/client/theme/agsc.module.css +31 -0
  50. package/src/client/theme/parts.ts +56 -0
  51. package/src/client/ui-integration.ts +86 -0
  52. package/src/client/widget.tsx +9 -8
  53. package/src/client/inject/overlay.module.css +0 -22
@@ -1,9 +1,4 @@
1
- /*
2
- * Project correlation view styles (T5.5): same visual language as
3
- * board.module.css — every color rides a --dsw-alias-* design token with
4
- * a neutral fallback, flex top bar, grouped sections. Sessions render as
5
- * compact rows inside per-agent lanes instead of the board's card grid.
6
- */
1
+ /* Project correlation layout consumes the shared Agent Sidecar theme. */
7
2
 
8
3
  .root {
9
4
  display: flex;
@@ -14,8 +9,8 @@
14
9
  padding: 16px 20px;
15
10
  box-sizing: border-box;
16
11
  overflow-y: auto;
17
- color: var(--dsw-alias-label-primary, #1f2328);
18
- background: var(--dsw-alias-bg-base, transparent);
12
+ color: var(--agsc-fg);
13
+ background: var(--agsc-bg);
19
14
  }
20
15
 
21
16
  /* ------------------------------------------------------------- top bar */
@@ -30,21 +25,12 @@
30
25
  .title {
31
26
  font-size: 15px;
32
27
  font-weight: 650;
33
- color: var(--dsw-alias-label-primary, #1f2328);
28
+ color: var(--agsc-fg);
34
29
  }
35
30
 
36
31
  .summary {
37
32
  font-size: 12px;
38
- color: var(--dsw-alias-label-secondary, #57606a);
39
- }
40
-
41
- .loadingChip {
42
- font-size: 11px;
43
- line-height: 18px;
44
- padding: 0 8px;
45
- border-radius: 999px;
46
- color: var(--dsw-alias-label-secondary, #57606a);
47
- background: var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, 0.04));
33
+ color: var(--agsc-fg-secondary);
48
34
  }
49
35
 
50
36
  /* -------------------------------------------------------------- banner */
@@ -53,14 +39,14 @@
53
39
  font-size: 12px;
54
40
  line-height: 18px;
55
41
  padding: 6px 10px;
56
- border-radius: 8px;
57
- border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.08));
42
+ border-radius: var(--agsc-radius-control);
43
+ border: 1px solid var(--agsc-border);
58
44
  }
59
45
 
60
46
  .banner[data-tone='danger'] {
61
- color: var(--dsw-alias-state-error-primary, #cf222e);
62
- background: var(--dsw-alias-state-error-secondary, rgba(207, 34, 46, 0.08));
63
- border-color: var(--dsw-alias-state-error-secondary, rgba(207, 34, 46, 0.24));
47
+ color: var(--agsc-err);
48
+ background: var(--dsw-alias-state-error-secondary);
49
+ border-color: var(--dsw-alias-state-error-secondary);
64
50
  }
65
51
 
66
52
  /* -------------------------------------------------------- empty states */
@@ -80,14 +66,14 @@
80
66
  .emptyTitle {
81
67
  font-size: 14px;
82
68
  font-weight: 600;
83
- color: var(--dsw-alias-label-primary, #1f2328);
69
+ color: var(--agsc-fg);
84
70
  }
85
71
 
86
72
  .emptyHint {
87
73
  font-size: 12px;
88
74
  max-width: 420px;
89
75
  line-height: 20px;
90
- color: var(--dsw-alias-label-secondary, #57606a);
76
+ color: var(--agsc-fg-secondary);
91
77
  }
92
78
 
93
79
  /* -------------------------------------------------------- group blocks */
@@ -97,9 +83,9 @@
97
83
  flex-direction: column;
98
84
  gap: 8px;
99
85
  padding: 10px 12px;
100
- border-radius: 10px;
101
- border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.08));
102
- background: var(--dsw-alias-bg-layer-1, rgba(0, 0, 0, 0.02));
86
+ border-radius: var(--agsc-radius-card);
87
+ border: 1px solid var(--agsc-border-strong);
88
+ background: var(--agsc-bg-raised);
103
89
  }
104
90
 
105
91
  .groupHead {
@@ -113,7 +99,7 @@
113
99
  .groupName {
114
100
  font-size: 13px;
115
101
  font-weight: 600;
116
- color: var(--dsw-alias-label-primary, #1f2328);
102
+ color: var(--agsc-fg);
117
103
  overflow: hidden;
118
104
  text-overflow: ellipsis;
119
105
  white-space: nowrap;
@@ -134,9 +120,9 @@
134
120
  line-height: 18px;
135
121
  padding: 0 7px;
136
122
  border-radius: 999px;
137
- border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.08));
138
- background: var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, 0.04));
139
- color: var(--dsw-alias-label-secondary, #57606a);
123
+ border: 1px solid var(--agsc-border);
124
+ background: var(--dsw-alias-bg-layer-2);
125
+ color: var(--agsc-fg-secondary);
140
126
  white-space: nowrap;
141
127
  }
142
128
 
@@ -145,8 +131,8 @@
145
131
  line-height: 18px;
146
132
  padding: 0 7px;
147
133
  border-radius: 999px;
148
- color: var(--dsw-alias-static-white, #fff);
149
- background: var(--dsw-alias-brand-primary, #4d6bfe);
134
+ color: var(--dsw-alias-label-primary-foreground);
135
+ background: var(--agsc-accent);
150
136
  white-space: nowrap;
151
137
  }
152
138
 
@@ -157,7 +143,7 @@
157
143
  .groupMeta {
158
144
  font-size: 11px;
159
145
  flex: none;
160
- color: var(--dsw-alias-label-tertiary, #6e7781);
146
+ color: var(--agsc-fg-dimmed);
161
147
  white-space: nowrap;
162
148
  }
163
149
 
@@ -181,12 +167,12 @@
181
167
  gap: 5px;
182
168
  font-size: 12px;
183
169
  font-weight: 600;
184
- color: var(--dsw-alias-label-primary, #1f2328);
170
+ color: var(--agsc-fg);
185
171
  }
186
172
 
187
173
  .glyph {
188
174
  flex: none;
189
- color: var(--dsw-alias-brand-primary, #4d6bfe);
175
+ color: var(--agsc-accent);
190
176
  }
191
177
 
192
178
  .laneSessions {
@@ -198,19 +184,6 @@
198
184
  /* UX-20: lane truncation fold (same look as the board's group fold). */
199
185
  .showMore {
200
186
  align-self: flex-start;
201
- font-size: 12px;
202
- font-family: inherit;
203
- color: var(--dsw-alias-label-secondary, #57606a);
204
- background: transparent;
205
- border: 1px dashed var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12));
206
- border-radius: 6px;
207
- padding: 2px 10px;
208
- cursor: pointer;
209
- }
210
-
211
- .showMore:hover {
212
- color: var(--dsw-alias-label-primary, #1f2328);
213
- background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.04));
214
187
  }
215
188
 
216
189
  /* -------------------------------------------------------- session rows */
@@ -224,36 +197,25 @@
224
197
  font-family: inherit;
225
198
  font-size: 12px;
226
199
  padding: 5px 8px;
227
- border-radius: 7px;
228
- border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.08));
229
- background: var(--dsw-alias-bg-base, transparent);
230
- color: var(--dsw-alias-label-primary, #1f2328);
200
+ border-radius: var(--agsc-radius-control);
201
+ border: 1px solid var(--agsc-border);
202
+ background: var(--dsw-alias-bg-base);
203
+ color: var(--agsc-fg);
231
204
  cursor: pointer;
232
205
  }
233
206
 
234
207
  .session:hover {
235
- border-color: var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12));
236
- background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.04));
208
+ border-color: var(--agsc-border-strong);
209
+ background: var(--dsw-alias-interactive-bg-hover);
237
210
  }
238
211
 
239
212
  .session:focus-visible {
240
- outline: 2px solid var(--dsw-alias-brand-primary, #4d6bfe);
213
+ outline: 2px solid var(--agsc-accent);
241
214
  outline-offset: 1px;
242
215
  }
243
216
 
244
- .badge {
245
- display: inline-flex;
246
- align-items: center;
247
- gap: 5px;
217
+ .statusPill {
248
218
  flex: none;
249
- font-size: 11px;
250
- line-height: 18px;
251
- padding: 0 7px;
252
- border-radius: 999px;
253
- border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.08));
254
- background: var(--dsw-alias-bg-layer-1, rgba(0, 0, 0, 0.03));
255
- color: var(--dsw-alias-label-secondary, #57606a);
256
- white-space: nowrap;
257
219
  }
258
220
 
259
221
  .dot {
@@ -261,36 +223,24 @@
261
223
  height: 7px;
262
224
  border-radius: 50%;
263
225
  flex: none;
264
- background: var(--dsw-alias-label-tertiary, #6e7781);
265
- }
266
-
267
- .dot[data-tone='success'] {
268
- background: var(--dsw-alias-state-success-primary, #1a7f37);
269
- }
270
-
271
- .dot[data-tone='warn'] {
272
- background: var(--dsw-alias-state-warn-primary, #9a6700);
273
- }
274
-
275
- .dot[data-tone='danger'] {
276
- background: var(--dsw-alias-state-error-primary, #cf222e);
226
+ background: var(--dsw-alias-label-tertiary);
277
227
  }
278
228
 
279
229
  .dot[data-tone='neutral'] {
280
- background: var(--dsw-alias-label-tertiary, #6e7781);
230
+ background: var(--dsw-alias-label-tertiary);
281
231
  }
282
232
 
283
233
  .dot[data-tone='muted'] {
284
- background: var(--dsw-alias-label-dimmed, #8c959f);
234
+ background: var(--agsc-fg-dimmed);
285
235
  }
286
236
 
287
237
  .attention {
288
238
  font-size: 11px;
289
- color: var(--dsw-alias-state-warn-primary, #9a6700);
239
+ color: var(--agsc-warn);
290
240
  }
291
241
 
292
242
  .attention[data-kind='gap'] {
293
- color: var(--dsw-alias-state-error-primary, #cf222e);
243
+ color: var(--agsc-err);
294
244
  }
295
245
 
296
246
  .sessionTitle {
@@ -302,24 +252,19 @@
302
252
 
303
253
  .liveChip {
304
254
  flex: none;
305
- font-size: 10px;
306
- line-height: 16px;
307
- padding: 0 6px;
308
- border-radius: 999px;
309
- color: var(--dsw-alias-state-success-primary, #1a7f37);
310
- border: 1px solid var(--dsw-alias-state-success-primary, rgba(26, 127, 55, 0.4));
255
+ color: var(--agsc-ok);
311
256
  }
312
257
 
313
258
  .sessionId {
314
259
  flex: none;
315
260
  font-size: 11px;
316
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
317
- color: var(--dsw-alias-label-tertiary, #6e7781);
261
+ font-family: var(--agsc-font-mono);
262
+ color: var(--agsc-fg-dimmed);
318
263
  }
319
264
 
320
265
  .sessionTime {
321
266
  flex: none;
322
267
  margin-left: auto;
323
268
  font-size: 11px;
324
- color: var(--dsw-alias-label-caption, var(--dsw-alias-label-tertiary, #6e7781));
269
+ color: var(--agsc-fg-dimmed);
325
270
  }
@@ -14,6 +14,7 @@
14
14
  * blanking data the user already has.
15
15
  */
16
16
 
17
+ import { Button, Pill, StateDot, type StateDotState } from '@deepseek-ai/dsh-client-ui-primitives'
17
18
  import { useState } from 'react'
18
19
  import type { ReactElement } from 'react'
19
20
  import {
@@ -26,6 +27,7 @@ import {
26
27
  type ProjectGroupVM,
27
28
  } from './project-view-logic.ts'
28
29
  import { formatTemplate, sliceCardsForDisplay } from './logic.ts'
30
+ import { surfaceProps } from '../theme/parts.ts'
29
31
  import styles from './project-view.module.css'
30
32
 
31
33
  export interface ProjectViewProps {
@@ -39,11 +41,18 @@ export interface ProjectViewProps {
39
41
  nowMs?: number
40
42
  }
41
43
 
44
+ function sessionDotState(status: DerivedProjectSessionVM['badge']['status']): StateDotState | null {
45
+ if (status === 'working') return 'ongoing'
46
+ if (status === 'waiting') return 'warning'
47
+ return null
48
+ }
49
+
42
50
  function SessionRow(props: {
43
51
  session: DerivedProjectSessionVM
44
52
  onSelect: (sessionId: string) => void
45
53
  }): ReactElement {
46
54
  const { session, onSelect } = props
55
+ const dotState = sessionDotState(session.badge.status)
47
56
  return (
48
57
  <button
49
58
  type="button"
@@ -51,19 +60,21 @@ function SessionRow(props: {
51
60
  onClick={() => onSelect(session.sessionId)}
52
61
  data-testid="agent-sidecar-project-session"
53
62
  >
54
- <span className={styles['badge']} data-tone={session.badge.tone}>
55
- <span className={styles['dot']} data-tone={session.badge.tone} />
63
+ <Pill className={styles['statusPill']}>
64
+ {dotState === null
65
+ ? <span className={styles['dot']} data-tone={session.badge.tone} />
66
+ : <StateDot state={dotState} size={8} />}
56
67
  {session.badge.label}
57
68
  {session.badge.attention !== null && (
58
69
  <span className={styles['attention']} data-kind={session.badge.attention}>
59
70
  {session.badge.attentionLabel}
60
71
  </span>
61
72
  )}
62
- </span>
73
+ </Pill>
63
74
  <span className={styles['sessionTitle']} title={session.title}>
64
75
  {session.displayTitle}
65
76
  </span>
66
- {session.live && <span className={styles['liveChip']}>{PROJECT_VIEW_STRINGS.liveChip}</span>}
77
+ {session.live && <Pill className={styles['liveChip']}>{PROJECT_VIEW_STRINGS.liveChip}</Pill>}
67
78
  <span className={styles['sessionId']} title={session.sessionId}>
68
79
  {session.shortId}
69
80
  </span>
@@ -100,22 +111,24 @@ function AgentLane(props: {
100
111
  ))}
101
112
  </div>
102
113
  {hiddenCount > 0 && (
103
- <button
104
- type="button"
114
+ <Button
115
+ size="sm"
116
+ variant="outline"
105
117
  className={styles['showMore']}
106
118
  onClick={() => { setExpanded(true) }}
107
119
  >
108
120
  {formatTemplate(PROJECT_VIEW_STRINGS.showAllSessions, { n: lane.sessions.length })}
109
- </button>
121
+ </Button>
110
122
  )}
111
123
  {expanded && lane.sessions.length > LANE_SESSION_LIMIT && (
112
- <button
113
- type="button"
124
+ <Button
125
+ size="sm"
126
+ variant="outline"
114
127
  className={styles['showMore']}
115
128
  onClick={() => { setExpanded(false) }}
116
129
  >
117
130
  {formatTemplate(PROJECT_VIEW_STRINGS.showLessSessions, { n: LANE_SESSION_LIMIT })}
118
- </button>
131
+ </Button>
119
132
  )}
120
133
  </div>
121
134
  )
@@ -168,13 +181,16 @@ export function ProjectView(props: ProjectViewProps): ReactElement {
168
181
  const hasContent = vm.groups.length > 0
169
182
 
170
183
  return (
171
- <div className={styles['root']} data-testid="agent-sidecar-project-view">
184
+ <div
185
+ {...surfaceProps('project-view', styles['root'])}
186
+ data-testid="agent-sidecar-project-view"
187
+ >
172
188
  <header className={styles['topbar']}>
173
189
  <span className={styles['title']}>{PROJECT_VIEW_STRINGS.title}</span>
174
190
  <span className={styles['summary']}>{vm.summaryLabel}</span>
175
191
  {props.loading && (
176
- <span className={styles['loadingChip']} role="status">
177
- {PROJECT_VIEW_STRINGS.loading}
192
+ <span role="status">
193
+ <Pill>{PROJECT_VIEW_STRINGS.loading}</Pill>
178
194
  </span>
179
195
  )}
180
196
  </header>
@@ -1,136 +1,97 @@
1
- /**
2
- * Centralized user-facing strings for the board view and the footer widget
3
- * (design §5.1 view 1 / view 4, §5.3 honesty wording).
4
- *
5
- * Chinese is the primary UI language for now; every string the components
6
- * render lives in this single table so the i18n skeleton (T2.3,
7
- * `ctx.locale.register`) can take it over without hunting through JSX.
8
- * Templates use `{name}` placeholders resolved by `formatTemplate` in
9
- * `logic.ts` — plain message strings, locale-registry friendly.
10
- *
11
- * Pure data module: no imports, no logic.
12
- *
13
- * @module
14
- */
1
+ import { createLocaleView } from '../locales/view.ts'
15
2
 
16
- export const BOARD_STRINGS = {
17
- /** Session status badge labels (observed values, see hover disclaimer). */
3
+ /** Dynamic board vocabulary; template leaves are interpolated by `formatTemplate`. */
4
+ export const BOARD_STRINGS = createLocaleView({
18
5
  status: {
19
- working: '工作中',
20
- waiting: '等待中',
21
- idle: '空闲',
22
- dead: '已结束',
23
- unknown: '未知',
6
+ working: 'board.status.working',
7
+ waiting: 'board.status.waiting',
8
+ idle: 'board.status.idle',
9
+ dead: 'board.status.dead',
10
+ unknown: 'board.status.unknown',
24
11
  },
25
- /**
26
- * Badge attention markers. Only the per-session gap marker renders at
27
- * card level; the global stream-health notice lives in the top banner
28
- * alone (UX-18: repeating it on every card was pure noise).
29
- */
30
12
  attention: {
31
- gap: '事件缺口',
13
+ gap: 'board.attention.gap',
32
14
  },
33
- /** Daemon supervisor state labels for the top-bar badge. */
34
15
  daemon: {
35
- probe: '探测中',
36
- adopted: '已连接 · 领养',
37
- defer: '等待系统服务',
38
- reprobe: '重新探测中',
39
- hosting: '正在启动',
40
- hosted: '已连接 · 托管',
41
- backoff: '重启退避中',
42
- failed: '离线',
16
+ probe: 'board.daemon.probe',
17
+ adopted: 'board.daemon.adopted',
18
+ defer: 'board.daemon.defer',
19
+ reprobe: 'board.daemon.reprobe',
20
+ hosting: 'board.daemon.hosting',
21
+ hosted: 'board.daemon.hosted',
22
+ backoff: 'board.daemon.backoff',
23
+ failed: 'board.daemon.failed',
43
24
  },
44
- /** Stream health indicator labels. */
45
25
  stream: {
46
- ok: '实时流正常',
47
- degraded: '实时流重连中',
48
- unknown: '实时流未建立',
26
+ ok: 'board.stream.ok',
27
+ degraded: 'board.stream.degraded',
28
+ unknown: 'board.stream.unknown',
49
29
  },
50
- /** Top banner texts (design §5.3: degraded warning / FAILED red banner). */
51
30
  banner: {
52
- daemonFailed: 'sidecar 离线:看板显示最后一次快照,数据不再更新',
53
- streamDegraded: '实时流重连中,数据可能滞后',
31
+ daemonFailed: 'board.banner.daemonFailed',
32
+ streamDegraded: 'board.banner.streamDegraded',
54
33
  },
55
- /** Empty-state guidance blocks. */
56
34
  empty: {
57
- daemonFailedTitle: 'sidecar 已离线',
58
- daemonFailedHint:
59
- 'daemon 连续启动失败已熔断。可在设置卡重试,或手动运行 agent-sidecar daemon start 后等待自动领养。',
60
- daemonDeferTitle: '等待系统服务拉起 daemon',
61
- daemonDeferHint:
62
- '检测到 LaunchAgent 托管,插件不会自行启动 daemon;服务拉起后看板会自动出数。',
63
- filteredTitle: '当前过滤条件下没有会话',
64
- filteredHint: '试试放宽时间窗、取消状态过滤,或打开「显示已结束」。',
65
- noSessionsTitle: '暂无被观测的会话',
66
- noSessionsHint:
67
- '本机 agent(claude / codex / cursor / dsh …)开始工作后会自动出现在这里。',
35
+ daemonFailedTitle: 'board.empty.daemonFailedTitle',
36
+ daemonFailedHint: 'board.empty.daemonFailedHint',
37
+ daemonDeferTitle: 'board.empty.daemonDeferTitle',
38
+ daemonDeferHint: 'board.empty.daemonDeferHint',
39
+ filteredTitle: 'board.empty.filteredTitle',
40
+ filteredHint: 'board.empty.filteredHint',
41
+ noSessionsTitle: 'board.empty.noSessionsTitle',
42
+ noSessionsHint: 'board.empty.noSessionsHint',
68
43
  },
69
- /** Top bar controls. */
70
44
  topbar: {
71
- title: 'Sidecar 多 agent 看板',
72
- refresh: '刷新',
73
- refreshing: '刷新中…',
74
- refreshTitle: '手动拉取最新快照',
75
- refreshFailed: '手动刷新失败,看板仍显示原有快照',
76
- dismiss: '知道了',
77
- showDead: '显示已结束',
78
- timeWindow: '时间窗',
79
- /** Count badges (UX-01); working/waiting are clickable status filters. */
80
- countWorking: '{n} 工作中',
81
- countWaiting: '{n} 等待中',
82
- countTotal: '共 {n} 个会话',
83
- filterByStatusTitle: '只看「{label}」的会话',
84
- clearStatusFilterTitle: '取消状态过滤,显示全部会话',
45
+ title: 'board.topbar.title',
46
+ refresh: 'board.topbar.refresh',
47
+ refreshing: 'board.topbar.refreshing',
48
+ refreshTitle: 'board.topbar.refreshTitle',
49
+ refreshFailed: 'board.topbar.refreshFailed',
50
+ dismiss: 'board.topbar.dismiss',
51
+ showDead: 'board.topbar.showDead',
52
+ timeWindow: 'board.topbar.timeWindow',
53
+ countWorking: 'board.topbar.countWorking',
54
+ countWaiting: 'board.topbar.countWaiting',
55
+ countTotal: 'board.topbar.countTotal',
56
+ filterByStatusTitle: 'board.topbar.filterByStatusTitle',
57
+ clearStatusFilterTitle: 'board.topbar.clearStatusFilterTitle',
85
58
  },
86
- /** Group collapse / truncation controls (UX-02). */
87
59
  group: {
88
- collapseTitle: '收起该分组',
89
- expandTitle: '展开该分组',
90
- showAll: '展开全部 {n} 个会话',
91
- showLess: '只看前 {n} 个',
60
+ collapseTitle: 'board.group.collapseTitle',
61
+ expandTitle: 'board.group.expandTitle',
62
+ showAll: 'board.group.showAll',
63
+ showLess: 'board.group.showLess',
92
64
  },
93
- /** Session card texts. */
94
65
  card: {
95
- noEvent: '暂无事件',
96
- untitled: '(无标题)',
97
- /** Design §5.3 / SKILL.md wording: statuses are inferred observations. */
98
- observedDisclaimer: '状态为从持久化数据推断的观察值,可能滞后',
99
- observedValue: '观察值: {status}',
100
- lastReconcile: '最近对账: {time}',
101
- neverReconciled: '尚未对账',
102
- copyId: '点击复制完整会话 id',
103
- copied: '已复制',
66
+ noEvent: 'board.card.noEvent',
67
+ untitled: 'board.card.untitled',
68
+ observedDisclaimer: 'board.card.observedDisclaimer',
69
+ observedValue: 'board.card.observedValue',
70
+ lastReconcile: 'board.card.lastReconcile',
71
+ neverReconciled: 'board.card.neverReconciled',
72
+ copyId: 'board.card.copyId',
73
+ copied: 'board.card.copied',
104
74
  },
105
- /**
106
- * Relative time templates. Ages ≥ 24h render as an absolute short date
107
- * (`MM-DD HH:mm`, see formatRelativeTime) instead of a `{n} 天前` bucket
108
- * (UX-13: whole columns of "3 天前" carried zero information).
109
- */
110
75
  time: {
111
- justNow: '刚刚',
112
- minutesAgo: '{n} 分钟前',
113
- hoursAgo: '{n} 小时前',
76
+ justNow: 'board.time.justNow',
77
+ minutesAgo: 'board.time.minutesAgo',
78
+ hoursAgo: 'board.time.hoursAgo',
114
79
  },
115
- /** Time-window option templates. */
116
80
  timeWindow: {
117
- hours: '{n} 小时',
118
- days: '{n} 天',
81
+ hours: 'board.timeWindow.hours',
82
+ days: 'board.timeWindow.days',
119
83
  },
120
- /** Group header session count. */
121
- groupCount: '{n} 个会话',
122
- /** Sessions with an empty `project` fall into this group (task spec). */
123
- unknownProject: '未知项目',
124
- /** Footer widget. */
84
+ groupCount: 'board.groupCount',
85
+ unknownProject: 'board.unknownProject',
125
86
  widget: {
126
- label: 'Sidecar',
87
+ label: 'board.widget.label',
127
88
  connection: {
128
- ok: '已连接',
129
- degraded: '连接不稳定',
130
- off: '离线',
89
+ ok: 'board.widget.connection.ok',
90
+ degraded: 'board.widget.connection.degraded',
91
+ off: 'board.widget.connection.off',
131
92
  },
132
- working: '{n} 个会话工作中',
93
+ working: 'board.widget.working',
133
94
  },
134
- } as const
95
+ } as const)
135
96
 
136
97
  export type BoardStrings = typeof BOARD_STRINGS