dsh-brief-sidebar 0.3.0 → 0.4.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 +188 -178
- package/assets/.gitkeep +0 -0
- package/assets/brief.png +0 -0
- package/cordis.patch.yml +18 -18
- package/dsh.plugin.json +16 -16
- package/lib/client.js.map +1 -1
- package/lib/index.mjs.map +1 -1
- package/package.json +106 -94
- package/screenshots.json +5 -0
- package/src/client/BriefIcon.tsx +56 -56
- package/src/client/SummaryTab.tsx +73 -73
- package/src/client/TodoBoardTab.tsx +188 -188
- package/src/client/brief/board.ts +104 -104
- package/src/client/brief/dock-shadow.tsx +74 -74
- package/src/client/brief/use-todos.ts +27 -27
- package/src/client/file-open.ts +108 -108
- package/src/client/index.tsx +144 -144
- package/src/client/locales.ts +78 -78
- package/src/client/summary/DeliverablesSection.tsx +192 -192
- package/src/client/summary/deliverables.ts +93 -93
- package/src/client/use-projection.ts +117 -117
- package/src/index.ts +32 -32
- package/src/projection/deliverables-fold.ts +206 -206
- package/src/projection/keys.ts +27 -27
- package/src/projection/register.ts +99 -99
package/src/client/index.tsx
CHANGED
|
@@ -1,144 +1,144 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser half: the task board tab, plus the shadow that hides the official
|
|
3
|
-
* composer-band todo panel.
|
|
4
|
-
*
|
|
5
|
-
* Registration order matters and is deliberate:
|
|
6
|
-
*
|
|
7
|
-
* 1. **Dictionaries first.** Every later registration can render translated
|
|
8
|
-
* copy, and a dictionary that lands after the first paint would show raw
|
|
9
|
-
* keys for a frame.
|
|
10
|
-
* 2. **Soft-dependency check.** `inject` already waits for `betterSidebar`, so
|
|
11
|
-
* the guard only fires in a composition that relaxes the inject list. It is
|
|
12
|
-
* kept because it is what stops the plugin from hiding the official panel
|
|
13
|
-
* when it has no board to replace it with.
|
|
14
|
-
* 3. **Shadow the official dock.** Deliberately BEFORE the tab registration:
|
|
15
|
-
* the panel is the only other carrier of the list, so the replacement has to
|
|
16
|
-
* exist in the same activation.
|
|
17
|
-
* 4. **Register the tab.**
|
|
18
|
-
*
|
|
19
|
-
* Every registration rides `ctx.effect(fn, label)` so its disposer is revoked on
|
|
20
|
-
* fiber teardown (HMR / disable), which is also what makes the shadow
|
|
21
|
-
* reversible: unload the plugin and the official dock renders again.
|
|
22
|
-
*
|
|
23
|
-
* Contract notes for the two surfaces touched here:
|
|
24
|
-
* - better-sidebar tabs are hosted by DSH's native right Sidebar, so the tab
|
|
25
|
-
* body receives `TabComponentProps` (`ctx` + `scope` + `visible`) and NOT the
|
|
26
|
-
* framework's session-scoped slot props.
|
|
27
|
-
* - `conversation.input.dock` is the DSH slot the official todo panel occupies;
|
|
28
|
-
* see `brief/dock-shadow.tsx` for why a lower priority wins the cell.
|
|
29
|
-
*/
|
|
30
|
-
import { createElement } from 'react'
|
|
31
|
-
import type { Context } from '@deepseek-ai/cordis'
|
|
32
|
-
import type { BetterSidebarService, TabComponentProps } from 'dsh-better-sidebar/client/service'
|
|
33
|
-
import { SummaryTab } from './SummaryTab'
|
|
34
|
-
import { BriefIcon } from './BriefIcon'
|
|
35
|
-
import { NS, dictionaries } from './locales'
|
|
36
|
-
import { readTodos, unfinishedCount } from './brief/board'
|
|
37
|
-
import { registerTodoDockShadow } from './brief/dock-shadow'
|
|
38
|
-
import { TODOS_KEY } from './brief/use-todos'
|
|
39
|
-
import { projectionSnapshot } from './use-projection'
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Tab type id. Package-prefixed so it cannot collide with a built-in type.
|
|
43
|
-
* Unchanged since the 0.1.0 board tab: the summary tab REPLACES the board tab
|
|
44
|
-
* in place, so an opened or pinned tab survives the upgrade.
|
|
45
|
-
*/
|
|
46
|
-
export const TAB_ID = 'dsh-brief-sidebar:board'
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Position in the host's new-tab guide.
|
|
50
|
-
*
|
|
51
|
-
* Built-ins on this host generation sit at editor 10 / git 20 / subagent 30 /
|
|
52
|
-
* sidechat 35 / terminal 40 / browser 50. 15 places a per-session task board
|
|
53
|
-
* right after the editor, which is where a reader looks for session state.
|
|
54
|
-
*/
|
|
55
|
-
export const TAB_ORDER = 15
|
|
56
|
-
|
|
57
|
-
/** Services required before `apply` runs. */
|
|
58
|
-
export const inject = ['betterSidebar', 'locale', 'slots']
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Structural view of the DSH locale service — only the two members this plugin
|
|
62
|
-
* calls. Declared locally on purpose: importing the real type would pull the
|
|
63
|
-
* whole client type graph into a browser-only plugin for two signatures.
|
|
64
|
-
*/
|
|
65
|
-
interface LocaleLike {
|
|
66
|
-
register(ns: string, dicts: Record<string, Record<string, string>>): () => void
|
|
67
|
-
bind(ns: string): (key: string) => string
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* An effect body must return a disposer (cordis rejects `undefined` with a
|
|
72
|
-
* `TypeError`), so a path that could not register anything returns this no-op
|
|
73
|
-
* instead of nothing.
|
|
74
|
-
*/
|
|
75
|
-
const NOOP = (): void => {}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Browser-face apply.
|
|
79
|
-
*
|
|
80
|
-
* @param ctx - the client root context.
|
|
81
|
-
*/
|
|
82
|
-
export function apply(ctx: Context): void {
|
|
83
|
-
// 1. Plugin-owned dictionaries, in one registration.
|
|
84
|
-
const locale: LocaleLike | undefined = ctx.get('locale')
|
|
85
|
-
if (locale !== undefined) {
|
|
86
|
-
ctx.effect(() => locale.register(NS, dictionaries), 'dsh-brief-sidebar: dictionaries')
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Translate one of our keys. `bind` returns a cached function that reads the
|
|
91
|
-
* ACTIVE locale at call time, so a language switch needs no re-apply; an
|
|
92
|
-
* unknown namespace or key falls back to the key itself rather than throwing
|
|
93
|
-
* inside a render.
|
|
94
|
-
*/
|
|
95
|
-
const t = (key: string): string => {
|
|
96
|
-
if (locale === undefined) return key
|
|
97
|
-
try {
|
|
98
|
-
return locale.bind(NS)(key) || key
|
|
99
|
-
} catch {
|
|
100
|
-
return key
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// 2. Soft dependency: without better-sidebar there is no place to show the
|
|
105
|
-
// list, so the plugin stays inert and the official panel keeps rendering.
|
|
106
|
-
const bar: BetterSidebarService | undefined = ctx.get('betterSidebar')
|
|
107
|
-
if (bar === undefined) return
|
|
108
|
-
|
|
109
|
-
// 3. Hide the official composer-band panel (reversible on unload).
|
|
110
|
-
ctx.effect(() => registerTodoDockShadow(ctx) ?? NOOP, 'dsh-brief-sidebar: dock shadow')
|
|
111
|
-
|
|
112
|
-
// 4. The board.
|
|
113
|
-
ctx.effect(
|
|
114
|
-
() =>
|
|
115
|
-
bar.registerTab({
|
|
116
|
-
id: TAB_ID,
|
|
117
|
-
title: () => t('tab.title'),
|
|
118
|
-
description: () => t('tab.desc'),
|
|
119
|
-
icon: (size: number) => BriefIcon(size),
|
|
120
|
-
order: TAB_ORDER,
|
|
121
|
-
// Shorthand for `dedupeKey: () => id`: reopening focuses the existing tab.
|
|
122
|
-
single: true,
|
|
123
|
-
/**
|
|
124
|
-
* Unfinished count on the tab strip. Called on every tab-bar render, so
|
|
125
|
-
* it reads one snapshot and allocates no subscription; a completed list
|
|
126
|
-
* shows no badge at all.
|
|
127
|
-
*/
|
|
128
|
-
badge: (badgeCtx, badgeScope) => {
|
|
129
|
-
const todos = readTodos(projectionSnapshot(badgeCtx, badgeScope.sessionId, TODOS_KEY))
|
|
130
|
-
if (todos === undefined) return null
|
|
131
|
-
const pending = unfinishedCount(todos)
|
|
132
|
-
return pending > 0 ? pending : null
|
|
133
|
-
},
|
|
134
|
-
component: (props: TabComponentProps) =>
|
|
135
|
-
createElement(SummaryTab, {
|
|
136
|
-
t,
|
|
137
|
-
ctx: props.ctx,
|
|
138
|
-
scope: props.scope,
|
|
139
|
-
visible: props.visible,
|
|
140
|
-
}),
|
|
141
|
-
}),
|
|
142
|
-
'dsh-brief-sidebar: tab',
|
|
143
|
-
)
|
|
144
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Browser half: the task board tab, plus the shadow that hides the official
|
|
3
|
+
* composer-band todo panel.
|
|
4
|
+
*
|
|
5
|
+
* Registration order matters and is deliberate:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Dictionaries first.** Every later registration can render translated
|
|
8
|
+
* copy, and a dictionary that lands after the first paint would show raw
|
|
9
|
+
* keys for a frame.
|
|
10
|
+
* 2. **Soft-dependency check.** `inject` already waits for `betterSidebar`, so
|
|
11
|
+
* the guard only fires in a composition that relaxes the inject list. It is
|
|
12
|
+
* kept because it is what stops the plugin from hiding the official panel
|
|
13
|
+
* when it has no board to replace it with.
|
|
14
|
+
* 3. **Shadow the official dock.** Deliberately BEFORE the tab registration:
|
|
15
|
+
* the panel is the only other carrier of the list, so the replacement has to
|
|
16
|
+
* exist in the same activation.
|
|
17
|
+
* 4. **Register the tab.**
|
|
18
|
+
*
|
|
19
|
+
* Every registration rides `ctx.effect(fn, label)` so its disposer is revoked on
|
|
20
|
+
* fiber teardown (HMR / disable), which is also what makes the shadow
|
|
21
|
+
* reversible: unload the plugin and the official dock renders again.
|
|
22
|
+
*
|
|
23
|
+
* Contract notes for the two surfaces touched here:
|
|
24
|
+
* - better-sidebar tabs are hosted by DSH's native right Sidebar, so the tab
|
|
25
|
+
* body receives `TabComponentProps` (`ctx` + `scope` + `visible`) and NOT the
|
|
26
|
+
* framework's session-scoped slot props.
|
|
27
|
+
* - `conversation.input.dock` is the DSH slot the official todo panel occupies;
|
|
28
|
+
* see `brief/dock-shadow.tsx` for why a lower priority wins the cell.
|
|
29
|
+
*/
|
|
30
|
+
import { createElement } from 'react'
|
|
31
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
32
|
+
import type { BetterSidebarService, TabComponentProps } from 'dsh-better-sidebar/client/service'
|
|
33
|
+
import { SummaryTab } from './SummaryTab'
|
|
34
|
+
import { BriefIcon } from './BriefIcon'
|
|
35
|
+
import { NS, dictionaries } from './locales'
|
|
36
|
+
import { readTodos, unfinishedCount } from './brief/board'
|
|
37
|
+
import { registerTodoDockShadow } from './brief/dock-shadow'
|
|
38
|
+
import { TODOS_KEY } from './brief/use-todos'
|
|
39
|
+
import { projectionSnapshot } from './use-projection'
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Tab type id. Package-prefixed so it cannot collide with a built-in type.
|
|
43
|
+
* Unchanged since the 0.1.0 board tab: the summary tab REPLACES the board tab
|
|
44
|
+
* in place, so an opened or pinned tab survives the upgrade.
|
|
45
|
+
*/
|
|
46
|
+
export const TAB_ID = 'dsh-brief-sidebar:board'
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Position in the host's new-tab guide.
|
|
50
|
+
*
|
|
51
|
+
* Built-ins on this host generation sit at editor 10 / git 20 / subagent 30 /
|
|
52
|
+
* sidechat 35 / terminal 40 / browser 50. 15 places a per-session task board
|
|
53
|
+
* right after the editor, which is where a reader looks for session state.
|
|
54
|
+
*/
|
|
55
|
+
export const TAB_ORDER = 15
|
|
56
|
+
|
|
57
|
+
/** Services required before `apply` runs. */
|
|
58
|
+
export const inject = ['betterSidebar', 'locale', 'slots']
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Structural view of the DSH locale service — only the two members this plugin
|
|
62
|
+
* calls. Declared locally on purpose: importing the real type would pull the
|
|
63
|
+
* whole client type graph into a browser-only plugin for two signatures.
|
|
64
|
+
*/
|
|
65
|
+
interface LocaleLike {
|
|
66
|
+
register(ns: string, dicts: Record<string, Record<string, string>>): () => void
|
|
67
|
+
bind(ns: string): (key: string) => string
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* An effect body must return a disposer (cordis rejects `undefined` with a
|
|
72
|
+
* `TypeError`), so a path that could not register anything returns this no-op
|
|
73
|
+
* instead of nothing.
|
|
74
|
+
*/
|
|
75
|
+
const NOOP = (): void => {}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Browser-face apply.
|
|
79
|
+
*
|
|
80
|
+
* @param ctx - the client root context.
|
|
81
|
+
*/
|
|
82
|
+
export function apply(ctx: Context): void {
|
|
83
|
+
// 1. Plugin-owned dictionaries, in one registration.
|
|
84
|
+
const locale: LocaleLike | undefined = ctx.get('locale')
|
|
85
|
+
if (locale !== undefined) {
|
|
86
|
+
ctx.effect(() => locale.register(NS, dictionaries), 'dsh-brief-sidebar: dictionaries')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Translate one of our keys. `bind` returns a cached function that reads the
|
|
91
|
+
* ACTIVE locale at call time, so a language switch needs no re-apply; an
|
|
92
|
+
* unknown namespace or key falls back to the key itself rather than throwing
|
|
93
|
+
* inside a render.
|
|
94
|
+
*/
|
|
95
|
+
const t = (key: string): string => {
|
|
96
|
+
if (locale === undefined) return key
|
|
97
|
+
try {
|
|
98
|
+
return locale.bind(NS)(key) || key
|
|
99
|
+
} catch {
|
|
100
|
+
return key
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 2. Soft dependency: without better-sidebar there is no place to show the
|
|
105
|
+
// list, so the plugin stays inert and the official panel keeps rendering.
|
|
106
|
+
const bar: BetterSidebarService | undefined = ctx.get('betterSidebar')
|
|
107
|
+
if (bar === undefined) return
|
|
108
|
+
|
|
109
|
+
// 3. Hide the official composer-band panel (reversible on unload).
|
|
110
|
+
ctx.effect(() => registerTodoDockShadow(ctx) ?? NOOP, 'dsh-brief-sidebar: dock shadow')
|
|
111
|
+
|
|
112
|
+
// 4. The board.
|
|
113
|
+
ctx.effect(
|
|
114
|
+
() =>
|
|
115
|
+
bar.registerTab({
|
|
116
|
+
id: TAB_ID,
|
|
117
|
+
title: () => t('tab.title'),
|
|
118
|
+
description: () => t('tab.desc'),
|
|
119
|
+
icon: (size: number) => BriefIcon(size),
|
|
120
|
+
order: TAB_ORDER,
|
|
121
|
+
// Shorthand for `dedupeKey: () => id`: reopening focuses the existing tab.
|
|
122
|
+
single: true,
|
|
123
|
+
/**
|
|
124
|
+
* Unfinished count on the tab strip. Called on every tab-bar render, so
|
|
125
|
+
* it reads one snapshot and allocates no subscription; a completed list
|
|
126
|
+
* shows no badge at all.
|
|
127
|
+
*/
|
|
128
|
+
badge: (badgeCtx, badgeScope) => {
|
|
129
|
+
const todos = readTodos(projectionSnapshot(badgeCtx, badgeScope.sessionId, TODOS_KEY))
|
|
130
|
+
if (todos === undefined) return null
|
|
131
|
+
const pending = unfinishedCount(todos)
|
|
132
|
+
return pending > 0 ? pending : null
|
|
133
|
+
},
|
|
134
|
+
component: (props: TabComponentProps) =>
|
|
135
|
+
createElement(SummaryTab, {
|
|
136
|
+
t,
|
|
137
|
+
ctx: props.ctx,
|
|
138
|
+
scope: props.scope,
|
|
139
|
+
visible: props.visible,
|
|
140
|
+
}),
|
|
141
|
+
}),
|
|
142
|
+
'dsh-brief-sidebar: tab',
|
|
143
|
+
)
|
|
144
|
+
}
|
package/src/client/locales.ts
CHANGED
|
@@ -1,78 +1,78 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plugin-owned i18n dictionaries.
|
|
3
|
-
*
|
|
4
|
-
* Consumer plugins must NOT reach into better-sidebar's internal `t()` or its
|
|
5
|
-
* `betterSidebar` dictionary namespace, so this plugin registers its own
|
|
6
|
-
* namespace through the DSH `locale` service. `dsh-brief-sidebar` is outside the
|
|
7
|
-
* `LocaleNamespaceMap` merge table, which is exactly what the untyped
|
|
8
|
-
* `register(ns, dicts)` overload is for — but the dictionaries themselves stay
|
|
9
|
-
* strictly paired: `zh` is the key-set source of truth and `en` is typed as
|
|
10
|
-
* `Record<keyof typeof zh, string>`, so a missing or extra English key is a
|
|
11
|
-
* COMPILE error rather than a runtime fallback to the raw key.
|
|
12
|
-
*
|
|
13
|
-
* Every key referenced anywhere in `src/**` must exist in both dictionaries;
|
|
14
|
-
* `tests/purity.spec.ts` asserts that, so a typo in a `t('…')` call cannot
|
|
15
|
-
* ship as an untranslated literal.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/** The namespace owned by this plugin (own vocabulary, own lifecycle). */
|
|
19
|
-
export const NS = 'dsh-brief-sidebar'
|
|
20
|
-
|
|
21
|
-
/** Simplified Chinese dictionary — the key-set source of truth. */
|
|
22
|
-
export const zh = {
|
|
23
|
-
'tab.title': '概要',
|
|
24
|
-
'tab.desc': '当前会话的任务、产物与规划文件(只读)',
|
|
25
|
-
'section.progress': '进展',
|
|
26
|
-
'section.deliverables': '产物',
|
|
27
|
-
'board.empty': '当前没有任务',
|
|
28
|
-
'board.emptyHint': '模型调用 todo_write 之后,任务会出现在这里。',
|
|
29
|
-
'board.unavailable': '任务暂不可用',
|
|
30
|
-
'board.unavailableHint': '这个会话还没有收到 todo 数据;宿主推来第一帧后会自动显示。',
|
|
31
|
-
'status.pending': '待处理',
|
|
32
|
-
'status.inProgress': '进行中',
|
|
33
|
-
'status.completed': '已完成',
|
|
34
|
-
'deliverables.empty': '本回合暂无改动文件',
|
|
35
|
-
'deliverables.emptyHint': '会话里成功写入或修改文件后,它们会实时出现在这里。',
|
|
36
|
-
'deliverables.sessionTotal': '本会话共 {count} 个文件',
|
|
37
|
-
'deliverable.codeplanTag': '规划',
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** The key union: what a `t('…')` call may name inside this namespace. */
|
|
41
|
-
export type BriefKey = keyof typeof zh
|
|
42
|
-
|
|
43
|
-
/** English dictionary, checked complete against the zh key set. */
|
|
44
|
-
export const en: Record<BriefKey, string> = {
|
|
45
|
-
'tab.title': 'Summary',
|
|
46
|
-
'tab.desc': 'Tasks, produced files and plan artifacts of this session (read-only)',
|
|
47
|
-
'section.progress': 'Progress',
|
|
48
|
-
'section.deliverables': 'Produced files',
|
|
49
|
-
'board.empty': 'No tasks yet',
|
|
50
|
-
'board.emptyHint': 'Tasks show up here once the model calls todo_write.',
|
|
51
|
-
'board.unavailable': 'Tasks unavailable',
|
|
52
|
-
'board.unavailableHint':
|
|
53
|
-
'This session has not received any todo data yet; the board fills in on the first frame.',
|
|
54
|
-
'status.pending': 'Pending',
|
|
55
|
-
'status.inProgress': 'In progress',
|
|
56
|
-
'status.completed': 'Done',
|
|
57
|
-
'deliverables.empty': 'No files changed in this turn',
|
|
58
|
-
'deliverables.emptyHint': 'Files written or edited successfully in the session appear here live.',
|
|
59
|
-
'deliverables.sessionTotal': '{count} files in this session',
|
|
60
|
-
'deliverable.codeplanTag': 'Plan',
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Both locales in the shape the locale service consumes. Registered in ONE
|
|
65
|
-
* call: the registry rejects a duplicate `(namespace, locale)` pair and the
|
|
66
|
-
* per-locale form would leave a half-registered namespace if the second call
|
|
67
|
-
* threw.
|
|
68
|
-
*/
|
|
69
|
-
export const dictionaries: Record<string, Record<string, string>> = { zh, en }
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Expand the one `{count}` placeholder the deliverables totals line uses. The
|
|
73
|
-
* locale binder is a plain key → string lookup (no interpolation machinery),
|
|
74
|
-
* mirroring how `progressLine` composes its summary from parts.
|
|
75
|
-
*/
|
|
76
|
-
export function expandCount(template: string, count: number): string {
|
|
77
|
-
return template.replace('{count}', String(count))
|
|
78
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Plugin-owned i18n dictionaries.
|
|
3
|
+
*
|
|
4
|
+
* Consumer plugins must NOT reach into better-sidebar's internal `t()` or its
|
|
5
|
+
* `betterSidebar` dictionary namespace, so this plugin registers its own
|
|
6
|
+
* namespace through the DSH `locale` service. `dsh-brief-sidebar` is outside the
|
|
7
|
+
* `LocaleNamespaceMap` merge table, which is exactly what the untyped
|
|
8
|
+
* `register(ns, dicts)` overload is for — but the dictionaries themselves stay
|
|
9
|
+
* strictly paired: `zh` is the key-set source of truth and `en` is typed as
|
|
10
|
+
* `Record<keyof typeof zh, string>`, so a missing or extra English key is a
|
|
11
|
+
* COMPILE error rather than a runtime fallback to the raw key.
|
|
12
|
+
*
|
|
13
|
+
* Every key referenced anywhere in `src/**` must exist in both dictionaries;
|
|
14
|
+
* `tests/purity.spec.ts` asserts that, so a typo in a `t('…')` call cannot
|
|
15
|
+
* ship as an untranslated literal.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** The namespace owned by this plugin (own vocabulary, own lifecycle). */
|
|
19
|
+
export const NS = 'dsh-brief-sidebar'
|
|
20
|
+
|
|
21
|
+
/** Simplified Chinese dictionary — the key-set source of truth. */
|
|
22
|
+
export const zh = {
|
|
23
|
+
'tab.title': '概要',
|
|
24
|
+
'tab.desc': '当前会话的任务、产物与规划文件(只读)',
|
|
25
|
+
'section.progress': '进展',
|
|
26
|
+
'section.deliverables': '产物',
|
|
27
|
+
'board.empty': '当前没有任务',
|
|
28
|
+
'board.emptyHint': '模型调用 todo_write 之后,任务会出现在这里。',
|
|
29
|
+
'board.unavailable': '任务暂不可用',
|
|
30
|
+
'board.unavailableHint': '这个会话还没有收到 todo 数据;宿主推来第一帧后会自动显示。',
|
|
31
|
+
'status.pending': '待处理',
|
|
32
|
+
'status.inProgress': '进行中',
|
|
33
|
+
'status.completed': '已完成',
|
|
34
|
+
'deliverables.empty': '本回合暂无改动文件',
|
|
35
|
+
'deliverables.emptyHint': '会话里成功写入或修改文件后,它们会实时出现在这里。',
|
|
36
|
+
'deliverables.sessionTotal': '本会话共 {count} 个文件',
|
|
37
|
+
'deliverable.codeplanTag': '规划',
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The key union: what a `t('…')` call may name inside this namespace. */
|
|
41
|
+
export type BriefKey = keyof typeof zh
|
|
42
|
+
|
|
43
|
+
/** English dictionary, checked complete against the zh key set. */
|
|
44
|
+
export const en: Record<BriefKey, string> = {
|
|
45
|
+
'tab.title': 'Summary',
|
|
46
|
+
'tab.desc': 'Tasks, produced files and plan artifacts of this session (read-only)',
|
|
47
|
+
'section.progress': 'Progress',
|
|
48
|
+
'section.deliverables': 'Produced files',
|
|
49
|
+
'board.empty': 'No tasks yet',
|
|
50
|
+
'board.emptyHint': 'Tasks show up here once the model calls todo_write.',
|
|
51
|
+
'board.unavailable': 'Tasks unavailable',
|
|
52
|
+
'board.unavailableHint':
|
|
53
|
+
'This session has not received any todo data yet; the board fills in on the first frame.',
|
|
54
|
+
'status.pending': 'Pending',
|
|
55
|
+
'status.inProgress': 'In progress',
|
|
56
|
+
'status.completed': 'Done',
|
|
57
|
+
'deliverables.empty': 'No files changed in this turn',
|
|
58
|
+
'deliverables.emptyHint': 'Files written or edited successfully in the session appear here live.',
|
|
59
|
+
'deliverables.sessionTotal': '{count} files in this session',
|
|
60
|
+
'deliverable.codeplanTag': 'Plan',
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Both locales in the shape the locale service consumes. Registered in ONE
|
|
65
|
+
* call: the registry rejects a duplicate `(namespace, locale)` pair and the
|
|
66
|
+
* per-locale form would leave a half-registered namespace if the second call
|
|
67
|
+
* threw.
|
|
68
|
+
*/
|
|
69
|
+
export const dictionaries: Record<string, Record<string, string>> = { zh, en }
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Expand the one `{count}` placeholder the deliverables totals line uses. The
|
|
73
|
+
* locale binder is a plain key → string lookup (no interpolation machinery),
|
|
74
|
+
* mirroring how `progressLine` composes its summary from parts.
|
|
75
|
+
*/
|
|
76
|
+
export function expandCount(template: string, count: number): string {
|
|
77
|
+
return template.replace('{count}', String(count))
|
|
78
|
+
}
|