dsh-taskboard 0.5.5 → 0.6.1
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 +26 -2
- package/lib/client.js +2663 -767
- package/lib/host/execution.js +3 -0
- package/lib/host/execution.js.map +1 -1
- package/lib/host/routes.js +31 -1
- package/lib/host/routes.js.map +1 -1
- package/lib/host/session-sync.js +210 -10
- package/lib/host/session-sync.js.map +1 -1
- package/lib/host/store.js +9 -2
- package/lib/host/store.js.map +1 -1
- package/lib/index.js +100 -1
- package/lib/index.js.map +1 -1
- package/lib/shared/api.js.map +1 -1
- package/lib/shared/protocol.js +19 -1
- package/lib/shared/protocol.js.map +1 -1
- package/package.json +75 -75
- package/src/client/api.ts +8 -0
- package/src/client/board/AlertModal.tsx +3 -1
- package/src/client/board/ImportModal.tsx +26 -24
- package/src/client/board/SettingsModal.tsx +66 -26
- package/src/client/board/SlashPromptInput.tsx +340 -0
- package/src/client/board/TaskBoard.tsx +53 -49
- package/src/client/board/TaskCard.tsx +33 -21
- package/src/client/board/TaskDetail.tsx +169 -104
- package/src/client/board/TaskFormModal.tsx +254 -202
- package/src/client/board/TemplateManager.tsx +32 -29
- package/src/client/board/labels.ts +36 -27
- package/src/client/controller.ts +62 -2
- package/src/client/i18n/en.ts +455 -0
- package/src/client/i18n/runtime.ts +155 -0
- package/src/client/i18n/zh.ts +460 -0
- package/src/client/index.ts +182 -42
- package/src/client/sidebar-entry.ts +13 -3
- package/src/client/styles.ts +133 -0
- package/src/host/execution.ts +13 -0
- package/src/host/routes.ts +49 -1
- package/src/host/session-sync.ts +334 -14
- package/src/host/store.ts +15 -1
- package/src/index.ts +115 -1
- package/src/shared/api.ts +47 -0
- package/src/shared/protocol.ts +41 -0
- package/src/shared/version.ts +1 -1
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* English dictionary — typed against zh.ts's key set, so a missing or
|
|
3
|
+
* extra key is a compile error (bilingual balance enforced where the
|
|
4
|
+
* dictionaries are written, not at render time).
|
|
5
|
+
*
|
|
6
|
+
* @module dsh-taskboard/client/i18n/en
|
|
7
|
+
*/
|
|
8
|
+
import type { TaskboardDict } from './zh.ts'
|
|
9
|
+
|
|
10
|
+
export const en: TaskboardDict = {
|
|
11
|
+
// ── shared vocabulary ──────────────────────────────────────────────
|
|
12
|
+
'shared.close': 'Close',
|
|
13
|
+
'shared.cancel': 'Cancel',
|
|
14
|
+
'shared.loading': 'Loading…',
|
|
15
|
+
'shared.blocked': 'Blocked',
|
|
16
|
+
'shared.permission': 'Permission',
|
|
17
|
+
'shared.confirmDelete': 'Delete',
|
|
18
|
+
'shared.current': ' (current: {name})',
|
|
19
|
+
'shared.entry.aria': 'Agent task board',
|
|
20
|
+
'shared.entry.label': 'Task board',
|
|
21
|
+
'shared.stats.title': 'To do {todo} | In progress {doing} | In review {review} (todo | in progress | in review)',
|
|
22
|
+
'shared.duplicate.suffix': ' (copy)',
|
|
23
|
+
|
|
24
|
+
// ── status / urgency / outcome enums (labels.ts) ──────────────────
|
|
25
|
+
'status.column.backlog': 'Backlog',
|
|
26
|
+
'status.column.todo': 'To do',
|
|
27
|
+
'status.column.in_progress': 'In progress',
|
|
28
|
+
'status.column.in_review': 'In review',
|
|
29
|
+
'status.column.done': 'Done',
|
|
30
|
+
'status.column.canceled': 'Canceled',
|
|
31
|
+
'status.column.archived': 'Archived',
|
|
32
|
+
'status.pill.backlog': 'Planned',
|
|
33
|
+
'status.pill.todo': 'To do',
|
|
34
|
+
'status.pill.in_progress': 'In progress',
|
|
35
|
+
'status.pill.in_review': 'In review',
|
|
36
|
+
'status.pill.done': 'Done',
|
|
37
|
+
'status.pill.canceled': 'Canceled',
|
|
38
|
+
'status.pill.archived': 'Archived',
|
|
39
|
+
'status.move.backlog': 'Backlog',
|
|
40
|
+
'status.move.todo': 'To do',
|
|
41
|
+
'status.move.in_progress': 'In progress',
|
|
42
|
+
'status.move.in_review': 'In review',
|
|
43
|
+
'status.move.done': 'Done',
|
|
44
|
+
'status.move.canceled': 'Canceled',
|
|
45
|
+
'status.move.archived': 'Archived',
|
|
46
|
+
'urgency.urgent': 'Urgent',
|
|
47
|
+
'urgency.normal': 'Normal',
|
|
48
|
+
'urgency.relaxed': 'Relaxed',
|
|
49
|
+
'outcome.running': 'Running',
|
|
50
|
+
'outcome.succeeded': 'Succeeded',
|
|
51
|
+
'outcome.failed': 'Failed',
|
|
52
|
+
'outcome.cancelled': 'Canceled',
|
|
53
|
+
|
|
54
|
+
// ── board toolbar & columns (TaskBoard) ───────────────────────────
|
|
55
|
+
'board.title': 'Agent Task Board',
|
|
56
|
+
'board.count.tasks': '{n} tasks · rev {rev}',
|
|
57
|
+
'board.action.newTask': '+ New Task ▼',
|
|
58
|
+
'board.action.blankTask': 'Blank task',
|
|
59
|
+
'board.action.manageTemplates': '⌗ Manage templates…',
|
|
60
|
+
'board.search.placeholder': 'Search title / ID…',
|
|
61
|
+
'board.filter.allProjects': 'All projects',
|
|
62
|
+
'board.sort.title': 'Sort within columns',
|
|
63
|
+
'board.sort.default': 'Default order',
|
|
64
|
+
'board.sort.updated': 'Recently updated',
|
|
65
|
+
'board.sort.urgency': 'By urgency',
|
|
66
|
+
'board.sort.created': 'Creation time',
|
|
67
|
+
'board.sort.byTitle': 'By title',
|
|
68
|
+
'board.action.backToBoard': 'Back to board',
|
|
69
|
+
'board.action.otherTasks': 'Other tasks',
|
|
70
|
+
'board.action.settingsTitle': 'Board settings: default execution isolation for new tasks, etc.',
|
|
71
|
+
'board.action.settings': '🛠 Settings',
|
|
72
|
+
'board.action.diagTitle': 'Health diagnostics: orphan worktrees, ledger basics',
|
|
73
|
+
'board.action.diag': '⚙ Diagnostics',
|
|
74
|
+
'board.action.importTitle': 'Import the ledger from a JSON backup (preview, then merge or replace)',
|
|
75
|
+
'board.action.import': '⬆ Import',
|
|
76
|
+
'board.action.exportTitle': 'Export the ledger: full JSON backup or task-list CSV',
|
|
77
|
+
'board.action.export': '⬇ Export ▼',
|
|
78
|
+
'board.export.jsonTitle': 'Full ledger backup (execution history and board settings included); import it to restore',
|
|
79
|
+
'board.export.json': 'Full ledger (JSON)',
|
|
80
|
+
'board.export.csvTitle': 'Task-list spreadsheet (opens directly in Excel; BOM added for CJK text)',
|
|
81
|
+
'board.export.csv': 'Task list (CSV)',
|
|
82
|
+
'board.drag.forbidden': 'Cannot drag from "{from}" to "{to}"',
|
|
83
|
+
'board.empty': 'No tasks',
|
|
84
|
+
'board.secondary.empty': 'No canceled / archived / deleted tasks',
|
|
85
|
+
'board.group.trashed': 'Deleted',
|
|
86
|
+
|
|
87
|
+
// ── diagnostics panel (TaskBoard) ─────────────────────────────────
|
|
88
|
+
'diag.title': 'Health diagnostics',
|
|
89
|
+
'diag.subtitle': 'Ledger basics and orphan-worktree cleanup',
|
|
90
|
+
'diag.revision': 'Ledger revision',
|
|
91
|
+
'diag.tasks': 'Total tasks',
|
|
92
|
+
'diag.running': 'Running',
|
|
93
|
+
'diag.orphans': 'Orphan worktrees',
|
|
94
|
+
'diag.orphans.heading': 'Orphan worktrees (no owning task, directory still present)',
|
|
95
|
+
'diag.orphans.none': 'None — every project\u2019s .dsh-worktrees directory is clean',
|
|
96
|
+
'diag.orphans.cleanup': 'Clean up',
|
|
97
|
+
'diag.orphans.hint': 'Note: orphan directories with uncommitted changes are refused; handle their contents manually first. Remove a live task\u2019s worktree from its task detail pane.',
|
|
98
|
+
'diag.gitignore.heading': 'gitignore suggestions',
|
|
99
|
+
'diag.gitignore.none': 'Nothing to do — every git project already ignores .dsh-worktrees',
|
|
100
|
+
'diag.gitignore.suggestA': 'suggests adding one line to .gitignore:',
|
|
101
|
+
'diag.gitignore.suggestB': '(no automatic edits)',
|
|
102
|
+
|
|
103
|
+
// ── task card (TaskCard) ──────────────────────────────────────────
|
|
104
|
+
'card.drag.running': 'This task is being executed by a session ({title}) and cannot be dragged',
|
|
105
|
+
'card.badge.stale': '⏱ Claim stale',
|
|
106
|
+
'card.badge.modelTitle': 'Pinned model: {model}',
|
|
107
|
+
'card.badge.modelEffort': ' · reasoning effort: {effort}',
|
|
108
|
+
'card.badge.checklistReview': 'In review: checklist has unchecked items',
|
|
109
|
+
'card.badge.checklist': 'Acceptance checklist progress',
|
|
110
|
+
'card.badge.pendingPurge': 'Pending purge',
|
|
111
|
+
'card.session.jumpTitle': 'Click to jump to the session: {id}',
|
|
112
|
+
'card.session.missing': 'The session has been deleted ({id}) and cannot be opened',
|
|
113
|
+
'card.session.archived': 'The session is archived ({id}) and hidden from the session list',
|
|
114
|
+
'card.session.unavailable': 'Session navigation unavailable; session id: {id}',
|
|
115
|
+
'card.reject.placeholder': 'Reason for sending back (optional; the agent reads it before starting)…',
|
|
116
|
+
'card.reject.confirm': 'Send back to todo',
|
|
117
|
+
'card.action.doneTitle': 'Accept and complete: move to Done',
|
|
118
|
+
'card.action.done': '✓ Done',
|
|
119
|
+
'card.action.rejectTitle': 'Send back to todo, optionally with a reason',
|
|
120
|
+
'card.action.reject': '✗ Send back',
|
|
121
|
+
|
|
122
|
+
// ── alert modal ───────────────────────────────────────────────────
|
|
123
|
+
'alert.ok': 'Got it',
|
|
124
|
+
|
|
125
|
+
// ── diff view (TaskDetail) ────────────────────────────────────────
|
|
126
|
+
'diff.commit': 'Commit {hash}',
|
|
127
|
+
'diff.file': 'File {path}',
|
|
128
|
+
'diff.truncated': '⚠ Content truncated (too long)',
|
|
129
|
+
'diff.failed': 'Failed to fetch (see the error bar at the top of the board; the object may be gone with a removed worktree)',
|
|
130
|
+
|
|
131
|
+
// ── checklist block (TaskDetail) ──────────────────────────────────
|
|
132
|
+
'checklist.title': 'Acceptance checklist (DoD)',
|
|
133
|
+
'checklist.unchecked': ' · {n} unchecked',
|
|
134
|
+
'checklist.allDone': ' · all done',
|
|
135
|
+
'checklist.byUser': '👤 User',
|
|
136
|
+
'checklist.uncheckedItem': 'Unchecked',
|
|
137
|
+
'checklist.evidence': 'Evidence: {note}',
|
|
138
|
+
|
|
139
|
+
// ── execution report block (TaskDetail) ───────────────────────────
|
|
140
|
+
'report.title': 'Execution report',
|
|
141
|
+
'report.submitted': 'Submitted by the execution session · {time}',
|
|
142
|
+
'report.changedFiles': 'Changed files',
|
|
143
|
+
'report.checks': 'Self-verification',
|
|
144
|
+
'report.artifacts': 'Artifacts',
|
|
145
|
+
'report.risk': 'Remaining risks',
|
|
146
|
+
|
|
147
|
+
// ── isolation block (TaskDetail) ──────────────────────────────────
|
|
148
|
+
'iso.title': 'Execution isolation',
|
|
149
|
+
'iso.none': '📁 Run in the original directory',
|
|
150
|
+
'iso.worktreeTitle': 'Execution isolation · Worktree',
|
|
151
|
+
'iso.branch': '🌿 Branch',
|
|
152
|
+
'iso.baseline': 'Baseline {base} → {head}',
|
|
153
|
+
'iso.changed': '{n} files changed',
|
|
154
|
+
'iso.commit.openTitle': 'Click to expand this commit\u2019s diff',
|
|
155
|
+
'iso.commits.more': '… {n} commits in total',
|
|
156
|
+
'iso.nocommit': 'This execution produced no commits (changes may be uncommitted — see the warning below)',
|
|
157
|
+
'iso.dirty.toggle': '⚠ {n} uncommitted changes (have the agent commit before merging, or handle them manually)',
|
|
158
|
+
'iso.dirty.expand': ' ▼ view files',
|
|
159
|
+
'iso.dirty.collapse': ' ▲',
|
|
160
|
+
'iso.dirty.openTitle': 'Click to view the uncommitted diff of this file',
|
|
161
|
+
'iso.dirty.more': '… {n} in total (full list in the task ledger)',
|
|
162
|
+
'iso.hint.running': 'Running — merge or clean up after it ends',
|
|
163
|
+
'iso.merge.confirm': 'Merge the branch into the main worktree with --no-ff?',
|
|
164
|
+
'iso.merge.go': 'Merge',
|
|
165
|
+
'iso.merge.title': 'git merge --no-ff the task branch into the main worktree (requires a clean main worktree; conflicts are reported as-is)',
|
|
166
|
+
'iso.merge.button': '⇥ Merge into main worktree',
|
|
167
|
+
'iso.merge.failed': 'Merge failed: {error}',
|
|
168
|
+
'iso.merge.noop': 'The branch has no commits ahead of the main worktree — nothing to merge (send it back to continue running, or clean it up)',
|
|
169
|
+
'iso.remove.wt': '🗑 Remove worktree',
|
|
170
|
+
'iso.remove.wtTitle': 'git worktree remove (refused when uncommitted changes exist)',
|
|
171
|
+
'iso.remove.wtb': '🗑 Remove worktree + branch',
|
|
172
|
+
'iso.remove.wtbTitle': 'Remove the worktree and delete the task branch (refused when uncommitted changes exist)',
|
|
173
|
+
'iso.remove.confirmWt': 'Remove the worktree directory?',
|
|
174
|
+
'iso.remove.confirmWtb': 'Remove the worktree and delete the branch?',
|
|
175
|
+
'iso.remove.failed': 'Removal failed: {error}',
|
|
176
|
+
'iso.remove.branchFailed': 'Worktree removed, but branch deletion failed: {error}',
|
|
177
|
+
'iso.hint.keep': 'Branch and worktree kept — send back to continue editing',
|
|
178
|
+
|
|
179
|
+
// ── detail pane (TaskDetail) ──────────────────────────────────────
|
|
180
|
+
'detail.session.jumpTitle': 'Jump to the corresponding session: {id}',
|
|
181
|
+
'detail.session.jump': '🤖 Jump to session ↗',
|
|
182
|
+
'detail.action.edit': '✎ Edit',
|
|
183
|
+
'detail.action.duplicateTitle': 'Duplicate this task\u2019s full configuration as a new card (todo column)',
|
|
184
|
+
'detail.action.duplicate': '⧉ Duplicate',
|
|
185
|
+
'detail.action.saveTplTitle': 'Save this task\u2019s configuration (checklist included) as a template for new tasks',
|
|
186
|
+
'detail.action.saveTplDone': 'Saved as a template (available in the New Task ▼ menu; rename it in template management)',
|
|
187
|
+
'detail.action.saveTpl': '⌗ Save as template',
|
|
188
|
+
'detail.action.reuseTitle': 'Reuse-run: keep the existing worktree and branch (last run\u2019s changes and commits stay in place) and continue on top of them; the default "Run" resets to a fresh baseline',
|
|
189
|
+
'detail.action.reuse': '↻ Reuse-run',
|
|
190
|
+
'detail.action.runTitleModel': 'Run in a new session ({model})',
|
|
191
|
+
'detail.action.runTitleDefault': 'Run in a new session (default model)',
|
|
192
|
+
'detail.action.run': '▶ Run',
|
|
193
|
+
'detail.action.stopConfirm': 'Stop this execution session?',
|
|
194
|
+
'detail.action.stop': 'Stop',
|
|
195
|
+
'detail.action.stopTitle': 'Stop execution session {id} (the task returns to todo)',
|
|
196
|
+
'detail.action.stopExec': '■ Stop execution',
|
|
197
|
+
'detail.chip.nextRun': '{cron} · next {time}',
|
|
198
|
+
'detail.chip.checklist': 'Checklist {done}/{total}',
|
|
199
|
+
'detail.chip.isolated': 'Worktree isolation',
|
|
200
|
+
'detail.chip.holderTitle': 'Click to jump to the session: {id}',
|
|
201
|
+
'detail.chip.holderStale': 'Claim stale · ',
|
|
202
|
+
'detail.chip.holderBy': 'Held by ',
|
|
203
|
+
'detail.chip.holderSuffix': ' ↗',
|
|
204
|
+
'detail.chip.trashed': 'Deleted, pending purge',
|
|
205
|
+
'detail.sub.line': 'Updated {time} · last change by {who}',
|
|
206
|
+
'detail.updatedBy.system': '⚙️ System',
|
|
207
|
+
'detail.updatedBy.user': '👤 User',
|
|
208
|
+
'detail.field.description': 'Description',
|
|
209
|
+
'detail.field.prompt': 'Execution prompt',
|
|
210
|
+
'detail.move.to': 'Move to → {status}',
|
|
211
|
+
'detail.move.confirmDoneUnchecked': '{n} checklist items are still unchecked — confirm done?',
|
|
212
|
+
'detail.move.confirmDone': 'Confirm done?',
|
|
213
|
+
'detail.move.confirm': 'Confirm',
|
|
214
|
+
'detail.blocked.unmark': '✓ Unblock',
|
|
215
|
+
'detail.blocked.mark': '⛔ Mark blocked',
|
|
216
|
+
'detail.release.title': 'Release {id}\u2019s claim: the task returns to todo (the holding session may still be working — make sure it has stopped first)',
|
|
217
|
+
'detail.release.button': '🔓 Release claim',
|
|
218
|
+
'detail.comments.title': 'Comments',
|
|
219
|
+
'detail.comments.empty': 'No comments yet — the agent reports changes and verification here at handoff',
|
|
220
|
+
'detail.comments.user': 'User',
|
|
221
|
+
'detail.composer.placeholder': 'Leave a comment as the user (the agent reads it before starting)…',
|
|
222
|
+
'detail.composer.send': 'Post',
|
|
223
|
+
'detail.exec.title': 'Executions',
|
|
224
|
+
'detail.exec.prunedTitle': '{n} older execution records were pruned at the retention cap',
|
|
225
|
+
'detail.exec.pruned': '+{n} pruned',
|
|
226
|
+
'detail.exec.trigger.manual': 'Manual',
|
|
227
|
+
'detail.exec.trigger.scheduled': 'Scheduled',
|
|
228
|
+
'detail.exec.openTitle': 'Click to open the execution session: {id}',
|
|
229
|
+
'detail.danger.delete': '🗑 Delete (mark for purge)',
|
|
230
|
+
'detail.danger.purgeConfirm': 'Physical purge is irreversible',
|
|
231
|
+
'detail.danger.purgeGo': 'Purge',
|
|
232
|
+
'detail.danger.purge': '🔥 Purge physically (confirm required)',
|
|
233
|
+
|
|
234
|
+
// ── task form modal (TaskFormModal) ───────────────────────────────
|
|
235
|
+
'form.title.create': 'New task',
|
|
236
|
+
'form.title.edit': 'Edit task',
|
|
237
|
+
'form.subtitle.create': 'Push onto the board; sessions in the project can claim and run it',
|
|
238
|
+
'form.subtitle.edit': 'Adjust the task content and execution configuration',
|
|
239
|
+
'form.field.title': 'Title',
|
|
240
|
+
'form.field.titlePlaceholder': 'One line: what should be done',
|
|
241
|
+
'form.field.project': 'Project',
|
|
242
|
+
'form.field.model': 'Model (default = session default model)',
|
|
243
|
+
'form.field.modelDefault': 'Default model',
|
|
244
|
+
'form.model.option': '{name} ({provider})',
|
|
245
|
+
'form.field.effort': 'Reasoning effort',
|
|
246
|
+
'form.field.effortTitle': 'Set the model\u2019s reasoning effort (e.g. low/medium/high); default = follow the model/provider default',
|
|
247
|
+
'form.effort.follow': 'Follow model default',
|
|
248
|
+
'form.effort.low': 'Low (low)',
|
|
249
|
+
'form.effort.medium': 'Medium (medium)',
|
|
250
|
+
'form.effort.high': 'High (high)',
|
|
251
|
+
'form.effort.none': 'Off (none)',
|
|
252
|
+
'form.field.preset': 'Execution preset',
|
|
253
|
+
'form.field.presetTitle': 'The execution session is composed from this preset (tool set and persona); default = the deployment default preset',
|
|
254
|
+
'form.preset.follow': 'Follow deployment default',
|
|
255
|
+
'form.preset.defaultTag': ' (deployment default)',
|
|
256
|
+
'form.field.urgency': 'Urgency',
|
|
257
|
+
'form.urgency.urgent': 'Urgent',
|
|
258
|
+
'form.urgency.urgentHint': 'Handle first',
|
|
259
|
+
'form.urgency.normal': 'Normal',
|
|
260
|
+
'form.urgency.normalHint': 'Normal scheduling',
|
|
261
|
+
'form.urgency.relaxed': 'Relaxed',
|
|
262
|
+
'form.urgency.relaxedHint': 'When there is time',
|
|
263
|
+
'form.field.description': 'Description',
|
|
264
|
+
'form.field.descriptionOptional': 'Description (optional)',
|
|
265
|
+
'form.desc.placeholder': 'Requirement details, background, acceptance criteria…',
|
|
266
|
+
'form.field.prompt': 'Execution prompt (the actual prompt = title + description + prompt)',
|
|
267
|
+
'form.field.promptOptional': 'Execution prompt (optional; the actual prompt = title + description + prompt)',
|
|
268
|
+
'form.prompt.placeholder': 'Extra instructions appended after "title + task description" and sent to the execution session. Template variables: {{lastExecution}} (last execution result), {{lastComments}} (latest 3 comments)',
|
|
269
|
+
'form.field.mode': 'Execution mode',
|
|
270
|
+
'form.mode.claim': '🤝 Claim-based',
|
|
271
|
+
'form.mode.claimHint': 'Sessions in the project claim it',
|
|
272
|
+
'form.mode.scheduled': '⏰ Scheduled',
|
|
273
|
+
'form.mode.scheduledHint': 'Starts automatically on schedule',
|
|
274
|
+
'form.field.cron': 'Cron expression',
|
|
275
|
+
'form.cron.placeholder': 'min hour day month weekday',
|
|
276
|
+
'form.cron.daily': 'Daily 09:00',
|
|
277
|
+
'form.cron.hourly': 'Hourly',
|
|
278
|
+
'form.cron.every10min': 'Every 10 minutes',
|
|
279
|
+
'form.cron.weekly': 'Mondays 09:00',
|
|
280
|
+
'form.cron.next': 'Next {time}',
|
|
281
|
+
'form.field.isolation': 'Execution isolation',
|
|
282
|
+
'form.iso.locked': 'The task has execution history; isolation is locked',
|
|
283
|
+
'form.iso.lockedShort': 'Locked (cannot change once execution has started)',
|
|
284
|
+
'form.iso.nonGit': 'This project is not a git repository',
|
|
285
|
+
'form.iso.worktree': '🌿 Worktree isolation',
|
|
286
|
+
'form.iso.worktreeTitle': 'Each execution runs on its own worktree branch',
|
|
287
|
+
'form.iso.worktreeHint': 'Isolated branch task/title+ID, no cross-contamination',
|
|
288
|
+
'form.iso.none': '📁 Run in the project directory',
|
|
289
|
+
'form.iso.noneTitle': 'Run directly in the project directory (no git)',
|
|
290
|
+
'form.iso.noneHintNonGit': 'Not a git repository; will run in the project directory',
|
|
291
|
+
'form.iso.noneHint': 'No git; works directly in the project directory',
|
|
292
|
+
'form.iso.nonGitNote': 'This project is not a git repository; the task will run in its directory (still created with the default configuration; the runtime degrades automatically)',
|
|
293
|
+
'form.field.checklist': 'Acceptance checklist (DoD)',
|
|
294
|
+
'form.field.checklistOptional': 'Acceptance checklist (DoD, optional)',
|
|
295
|
+
'form.check.itemPlaceholder': 'Checklist item {n} (definition of done)',
|
|
296
|
+
'form.check.removeTitle': 'Remove this checklist item',
|
|
297
|
+
'form.check.add': '+ Add checklist item',
|
|
298
|
+
'form.check.checkedTitle': 'Checked state is preserved on save (currently checked by: {who})',
|
|
299
|
+
'form.check.notCheckedYet': 'not checked yet',
|
|
300
|
+
'form.check.hintCreate': '{n} items; the execution session works through them and checks each off; unfinished items are highlighted at review',
|
|
301
|
+
'form.check.hintEdit': '{checked}/{total} checked (saving replaces the whole list; checked state is preserved)',
|
|
302
|
+
'form.hint.needTitle': 'Enter a title',
|
|
303
|
+
'form.hint.needProject': 'Select a project',
|
|
304
|
+
'form.hint.cronBad': 'Invalid cron expression (min hour day month weekday)',
|
|
305
|
+
'form.hint.nextRun': 'Next run {time}',
|
|
306
|
+
'form.hint.saveVersion': 'On save: v{v} → v{next}',
|
|
307
|
+
'form.hint.createClaim': 'After creation, sessions in the project can claim and run it',
|
|
308
|
+
'form.action.runBlockedTitle': 'The task is running; cannot start another',
|
|
309
|
+
'form.action.runBusyTitle': 'Submitting…',
|
|
310
|
+
'form.action.runTitle': 'Save, then immediately start an execution (new session)',
|
|
311
|
+
'form.action.run': '⚡ Run now',
|
|
312
|
+
'form.action.save': 'Save changes',
|
|
313
|
+
'form.action.create': 'Create task',
|
|
314
|
+
|
|
315
|
+
// ── template manager (TemplateManager) ────────────────────────────
|
|
316
|
+
'tpl.aria': 'Manage templates',
|
|
317
|
+
'tpl.title': 'Task templates',
|
|
318
|
+
'tpl.subtitle': 'Templates from the New Task ▼ menu: rename / delete / use directly; "Save as template" in the task detail pane adds new ones',
|
|
319
|
+
'tpl.empty': 'No templates yet — click "Save as template" in a task detail pane to capture a common configuration',
|
|
320
|
+
'tpl.name.aria': 'Template name {name}',
|
|
321
|
+
'tpl.builtin': 'Built-in',
|
|
322
|
+
'tpl.custom': 'Custom',
|
|
323
|
+
'tpl.meta.checklist': ' · {n} checklist items',
|
|
324
|
+
'tpl.rename.title': 'Save rename',
|
|
325
|
+
'tpl.rename.button': 'Rename',
|
|
326
|
+
'tpl.use.title': 'Open the new-task form with this template',
|
|
327
|
+
'tpl.use.button': 'Use',
|
|
328
|
+
'tpl.delete.title': 'Delete this template',
|
|
329
|
+
'tpl.renamed': 'Template renamed',
|
|
330
|
+
'tpl.foot.hint': 'Templates are stored with the ledger in the DSH home directory and survive upgrades',
|
|
331
|
+
|
|
332
|
+
// ── import modal (ImportModal) ────────────────────────────────────
|
|
333
|
+
'imp.aria': 'Import ledger',
|
|
334
|
+
'imp.title': 'Import ledger',
|
|
335
|
+
'imp.subtitle': 'Pick an exported JSON backup: preview first, then merge or replace everything',
|
|
336
|
+
'imp.parseError': 'The file is not valid JSON',
|
|
337
|
+
'imp.note': 'The ⬇ JSON export is a same-format backup and can be imported to restore; the file\u2019s schemaVersion must match the current version.',
|
|
338
|
+
'imp.previewing': 'Previewing…',
|
|
339
|
+
'imp.stat.create': 'New',
|
|
340
|
+
'imp.stat.overwrite': 'Overwrite (same id)',
|
|
341
|
+
'imp.stat.invalid': 'Invalid (skipped)',
|
|
342
|
+
'imp.sec.create': 'New tasks',
|
|
343
|
+
'imp.sec.overwrite': 'Overwritten tasks (whole-card replacement, execution history and comments included)',
|
|
344
|
+
'imp.sec.invalid': 'Invalid entries (not imported)',
|
|
345
|
+
'imp.noId': '(no id)',
|
|
346
|
+
'imp.mode.merge': '⊕ Merge',
|
|
347
|
+
'imp.mode.mergeHint': 'Adds new + overwrites by id; everything else untouched',
|
|
348
|
+
'imp.mode.replace': '💣 Replace all',
|
|
349
|
+
'imp.mode.replaceHint': 'Clears the current ledger and adopts the imported file (automatic backup first)',
|
|
350
|
+
'imp.result.replace': 'Replace complete: imported {n} tasks ({total} former tasks backed up)',
|
|
351
|
+
'imp.result.merge': 'Merge complete: {n} added, {m} overwritten',
|
|
352
|
+
'imp.foot.replaceConfirm': '⚠ Click again to confirm the full replacement (irreversible; a backup has been taken)',
|
|
353
|
+
'imp.foot.replaceNeedConfirm': 'Full replacement requires a second confirmation',
|
|
354
|
+
'imp.foot.mergeHint': 'Merge writes only the tasks listed in the preview',
|
|
355
|
+
'imp.action.run': 'Import',
|
|
356
|
+
'imp.action.confirmReplace': 'Confirm replace',
|
|
357
|
+
|
|
358
|
+
// ── board settings modal (SettingsModal) ──────────────────────────
|
|
359
|
+
'set.aria': 'Board settings',
|
|
360
|
+
'set.title': 'Board settings',
|
|
361
|
+
'set.subtitle': 'Global defaults for new tasks and session sync',
|
|
362
|
+
'set.iso.heading': 'Default execution isolation',
|
|
363
|
+
'set.iso.noneHint': 'No git; works directly in the project directory (factory default)',
|
|
364
|
+
'set.iso.worktreeHint': 'Each execution runs on its own worktree branch (task/title+ID), isolated from the others',
|
|
365
|
+
'set.iso.current': 'Currently saved default: {current}. Affects only tasks created hereafter; existing tasks keep their creation-time choice, and non-git projects still degrade to the project directory at run time.',
|
|
366
|
+
'set.sync.heading': 'Auto-sync workspace sessions',
|
|
367
|
+
'set.sync.off.name': '🚫 Sync off',
|
|
368
|
+
'set.sync.off.title': 'Manage only tasks created and triggered in the task board',
|
|
369
|
+
'set.sync.off.hint': 'Board tasks only (factory default)',
|
|
370
|
+
'set.sync.on.name': '🔄 Auto-include sessions',
|
|
371
|
+
'set.sync.on.title': 'Sessions created directly in workspaces also appear on the board: In progress while running, In review automatically when done',
|
|
372
|
+
'set.sync.on.hint': 'Tracks runs and enters review on completion',
|
|
373
|
+
'set.sync.stateOn': 'On: sessions created and run directly in workspaces automatically become board cards and flow to the "In review" column when done.',
|
|
374
|
+
'set.sync.stateOff': 'Off: only tasks created and triggered inside the board appear on the board.',
|
|
375
|
+
'set.foot.dirty': 'Unsaved changes',
|
|
376
|
+
'set.foot.clean': 'Matches the current board settings',
|
|
377
|
+
'set.action.save': 'Save settings',
|
|
378
|
+
|
|
379
|
+
// ── execution permission (PR #14) ─────────────────────────────────
|
|
380
|
+
'form.field.permission': 'Execution permission',
|
|
381
|
+
'form.perm.write': 'Workspace write',
|
|
382
|
+
'form.perm.writeHint': 'Read-write access to the workspace and temp directories (recommended default)',
|
|
383
|
+
'form.perm.readOnly': 'Read-only',
|
|
384
|
+
'form.perm.readOnlyHint': 'View and search only; no file changes or external commands',
|
|
385
|
+
'form.perm.fullAccess': 'Full access',
|
|
386
|
+
'form.perm.fullAccessHint': 'Unrestricted: whole-disk access and external commands',
|
|
387
|
+
'form.perm.defaultTag': ' (default)',
|
|
388
|
+
'set.perm.heading': 'Default execution permission',
|
|
389
|
+
'set.perm.writeName': '📁 Workspace write (factory default)',
|
|
390
|
+
'set.perm.writeHint': 'Read-write access to the workspace and temp directories; writes need no extra confirmation',
|
|
391
|
+
'set.perm.readOnlyName': '🔒 Read-only',
|
|
392
|
+
'set.perm.readOnlyHint': 'Read-only viewing and search only; no file changes or destructive commands',
|
|
393
|
+
'set.perm.fullName': '⚡ Full access',
|
|
394
|
+
'set.perm.fullHint': 'Unrestricted: whole-disk access and external system commands',
|
|
395
|
+
'set.perm.current': 'Currently saved default: {current}. The execution permission preset applied to new tasks.',
|
|
396
|
+
'card.badge.permReadOnly': '🔒 Read-only',
|
|
397
|
+
'card.badge.permReadOnlyTitle': 'Permission: read-only',
|
|
398
|
+
'card.badge.permFull': '⚡ Full access',
|
|
399
|
+
'card.badge.permFullTitle': 'Permission: full access',
|
|
400
|
+
'detail.chip.permReadOnly': 'Read-only',
|
|
401
|
+
'detail.chip.permFull': 'Full access',
|
|
402
|
+
'detail.chip.permWrite': 'Workspace write',
|
|
403
|
+
'tpl.meta.permReadOnly': 'read-only',
|
|
404
|
+
'tpl.meta.permFull': 'full access',
|
|
405
|
+
|
|
406
|
+
// ── markdown lightbox (PR #14) ────────────────────────────────────
|
|
407
|
+
'md.imageAlt': 'Image {n}',
|
|
408
|
+
'md.imageTitle': 'Click to view full size ({alt})',
|
|
409
|
+
'md.lightboxAlt': 'Full-size preview',
|
|
410
|
+
'md.closePreview': 'Close preview',
|
|
411
|
+
|
|
412
|
+
// ── slash completion popup (PR #14) ───────────────────────────────
|
|
413
|
+
'slash.aria': 'Quick commands and skills',
|
|
414
|
+
'slash.title': 'Quick command and skill completion',
|
|
415
|
+
'slash.hint': '↑↓ navigate · Enter / Tab pick · Esc close',
|
|
416
|
+
'slash.badge.command': '⚡ command',
|
|
417
|
+
'slash.badge.skill': '🧩 skill',
|
|
418
|
+
'slash.tipA': '💡 Type',
|
|
419
|
+
'slash.tipB': 'to autocomplete slash commands and agent skills',
|
|
420
|
+
'slash.cmd.goal.desc': 'Autonomously drive long-horizon goals to completion',
|
|
421
|
+
'slash.cmd.goal.hint': '<goal description>',
|
|
422
|
+
'slash.cmd.schedule.desc': 'Set one-off or recurring cron schedules',
|
|
423
|
+
'slash.cmd.schedule.hint': '<time or expression>',
|
|
424
|
+
'slash.cmd.plan.desc': 'Draft a step-by-step plan and get user confirmation before acting',
|
|
425
|
+
'slash.cmd.browser.desc': 'Launch a browser for page interaction and live search',
|
|
426
|
+
'slash.cmd.grill-me.desc': 'Align requirements and design intent through one-question-at-a-time interviews',
|
|
427
|
+
'slash.cmd.teamwork-preview.desc': 'Multi-agent collaboration and team workflow preview',
|
|
428
|
+
'slash.cmd.learn.desc': 'Capture solutions and new rules into the knowledge base',
|
|
429
|
+
'slash.cmd.review.desc': 'Multi-axis code review (correctness, architecture, security)',
|
|
430
|
+
'slash.cmd.security.desc': 'Security hardening and vulnerability scanning',
|
|
431
|
+
'slash.cmd.permission.desc': 'Switch the session permission level (read-only / workspace-write / full-access)',
|
|
432
|
+
'slash.cmd.permission.hint': '<preset>',
|
|
433
|
+
'slash.skill.frontend-ui-engineering': 'Build production-grade, accessible frontend interfaces and components',
|
|
434
|
+
'slash.skill.api-and-interface-design': 'Design stable, well-bounded REST / RPC interfaces',
|
|
435
|
+
'slash.skill.test-driven-development': 'Test-driven development (TDD): unit and integration tests',
|
|
436
|
+
'slash.skill.debugging-and-error-recovery': 'Systematically locate bug root causes and recover',
|
|
437
|
+
'slash.skill.performance-optimization': 'Frontend/backend performance tuning and query optimization',
|
|
438
|
+
'slash.skill.ci-cd-and-automation': 'Build automation, CI/CD pipelines and quality gates',
|
|
439
|
+
'slash.skill.code-review-and-quality': 'Multi-axis code review and refactoring guidance',
|
|
440
|
+
'slash.skill.code-simplification': 'Simplify complex logic for readability and maintainability',
|
|
441
|
+
'slash.skill.context-engineering': 'Optimize context structure and prompt engineering',
|
|
442
|
+
'slash.skill.doubt-driven-development': 'Adversarial, doubt-driven review of core logic',
|
|
443
|
+
'slash.skill.git-workflow-and-versioning': 'Git workflow, branching, semantic versioning and changelogs',
|
|
444
|
+
'slash.skill.idea-refine': 'Refine ideas and test hypotheses via divergent-convergent thinking',
|
|
445
|
+
'slash.skill.incremental-implementation': 'Deliver multi-file changes in small, incremental steps',
|
|
446
|
+
'slash.skill.interview-me': 'Deep interviews to surface true intent',
|
|
447
|
+
'slash.skill.memory-leak-debugging': 'Diagnose JavaScript/Node.js memory leaks',
|
|
448
|
+
'slash.skill.observability-and-instrumentation': 'Add logging, metrics and distributed tracing',
|
|
449
|
+
'slash.skill.planning-and-task-breakdown': 'Break complex requirements into ordered, executable tasks',
|
|
450
|
+
'slash.skill.security-and-hardening': 'Harden against vulnerabilities; input filtering and auth hardening',
|
|
451
|
+
'slash.skill.shipping-and-launch': 'Pre-launch checklists and rollback strategies',
|
|
452
|
+
'slash.skill.source-driven-development': 'Design from authoritative docs and source code',
|
|
453
|
+
'slash.skill.spec-driven-development': 'Write clear technical specs before coding',
|
|
454
|
+
'slash.skill.using-agent-skills': 'Discover and invoke agent skills dynamically',
|
|
455
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Taskboard i18n runtime: the zh/en dictionary lookup plus the locale
|
|
3
|
+
* SOURCE adapter.
|
|
4
|
+
*
|
|
5
|
+
* Locale source, in priority order:
|
|
6
|
+
* 1. The DSH locale service (ctx.get('locale'), the same plugin that backs
|
|
7
|
+
* 设置 → 通用设置 → 语言). Consumed through a NARROW structural face and
|
|
8
|
+
* attached SOFTLY — the plugin must keep working on compositions where
|
|
9
|
+
* the service is absent, so 'locale' is deliberately NOT in the client
|
|
10
|
+
* inject list (a hard inject would wait forever there).
|
|
11
|
+
* 2. A local fallback: <html lang> when the DSH locale plugin maintains it
|
|
12
|
+
* (it points <html lang> at the active locale), else navigator.language,
|
|
13
|
+
* else 'en' (matches DSH: zh only when something asked for Chinese).
|
|
14
|
+
*
|
|
15
|
+
* Preference WRITES are never made here: switching languages is the DSH
|
|
16
|
+
* settings page's job; this module only reads.
|
|
17
|
+
*
|
|
18
|
+
* React re-render: useT() subscribes via useSyncExternalStore; the snapshot
|
|
19
|
+
* object is replaced only on locale change, so renders are stable.
|
|
20
|
+
*
|
|
21
|
+
* @module dsh-taskboard/client/i18n/runtime
|
|
22
|
+
*/
|
|
23
|
+
import { useSyncExternalStore } from 'react'
|
|
24
|
+
import { zh, type TaskboardDict } from './zh.ts'
|
|
25
|
+
import { en } from './en.ts'
|
|
26
|
+
|
|
27
|
+
/** Shipped locales (mirrors the DSH locale plugin's LOCALE_IDS). */
|
|
28
|
+
export type LocaleId = 'zh' | 'en'
|
|
29
|
+
|
|
30
|
+
/** Immutable locale state published on every change. */
|
|
31
|
+
export interface TaskboardLocaleSnapshot {
|
|
32
|
+
active: LocaleId
|
|
33
|
+
/** Monotonic change counter (source snapshot revision or local bumps). */
|
|
34
|
+
revision: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Translate function: flat key lookup + {name} substitution. */
|
|
38
|
+
export type Translate = (key: string, params?: Record<string, string | number>) => string
|
|
39
|
+
|
|
40
|
+
const DICTS: Record<LocaleId, TaskboardDict> = { zh, en }
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Narrow structural face of the DSH LocaleRuntime this module consumes.
|
|
44
|
+
* Only the source side (snapshot/subscribe) — translation and preference
|
|
45
|
+
* writes stay local, which keeps us decoupled from the DSH Translate
|
|
46
|
+
* signature and the ns/common lookup chain.
|
|
47
|
+
*/
|
|
48
|
+
interface LocaleServiceFace {
|
|
49
|
+
getSnapshot(): { active: string; revision: number }
|
|
50
|
+
subscribe(fn: () => void): () => void
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let service: LocaleServiceFace | undefined
|
|
54
|
+
let unsubscribeService: (() => void) | undefined
|
|
55
|
+
let snapshot: TaskboardLocaleSnapshot = { active: detectFallbackLocale(), revision: 0 }
|
|
56
|
+
const listeners = new Set<() => void>()
|
|
57
|
+
|
|
58
|
+
function isLocaleId(value: string): value is LocaleId {
|
|
59
|
+
return value === 'zh' || value === 'en'
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Derive the fallback locale without the DSH service (zh only when asked). */
|
|
63
|
+
function detectFallbackLocale(): LocaleId {
|
|
64
|
+
try {
|
|
65
|
+
if (typeof document !== 'undefined') {
|
|
66
|
+
const lang = document.documentElement.lang
|
|
67
|
+
if (typeof lang === 'string' && lang.length > 0) {
|
|
68
|
+
const lower = lang.toLowerCase()
|
|
69
|
+
if (lower.startsWith('zh')) return 'zh'
|
|
70
|
+
if (lower.startsWith('en')) return 'en'
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch { /* no DOM — fall through */ }
|
|
74
|
+
try {
|
|
75
|
+
if (typeof navigator !== 'undefined') {
|
|
76
|
+
const lang = navigator.language ?? 'en'
|
|
77
|
+
return lang.toLowerCase().startsWith('zh') ? 'zh' : 'en'
|
|
78
|
+
}
|
|
79
|
+
} catch { /* no navigator — fall through */ }
|
|
80
|
+
return 'en'
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function publish(next: TaskboardLocaleSnapshot): void {
|
|
84
|
+
if (next.active === snapshot.active && next.revision === snapshot.revision) return
|
|
85
|
+
snapshot = next
|
|
86
|
+
for (const fn of listeners) fn()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Attach the DSH locale service (call from the client entry's apply with
|
|
91
|
+
* ctx.get('locale')). Absent/malformed services are ignored — the fallback
|
|
92
|
+
* detection stays in charge.
|
|
93
|
+
* @param localeService - the ctx 'locale' service, when provided.
|
|
94
|
+
*/
|
|
95
|
+
export function initI18n(localeService: unknown): void {
|
|
96
|
+
const face = localeService as LocaleServiceFace | null | undefined
|
|
97
|
+
if (face === null || face === undefined || typeof face.getSnapshot !== 'function' || typeof face.subscribe !== 'function') {
|
|
98
|
+
// No usable service: resolve the fallback NOW so a caller that inits
|
|
99
|
+
// after the DOM is up gets the current detection, never a stale
|
|
100
|
+
// module-load snapshot.
|
|
101
|
+
publish({ active: detectFallbackLocale(), revision: 0 })
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
service = face
|
|
105
|
+
const sync = (): void => {
|
|
106
|
+
try {
|
|
107
|
+
const s = face.getSnapshot()
|
|
108
|
+
const active = isLocaleId(s.active) ? s.active : detectFallbackLocale()
|
|
109
|
+
publish({ active, revision: s.revision })
|
|
110
|
+
} catch { /* a throwing source must not break the board */ }
|
|
111
|
+
}
|
|
112
|
+
sync()
|
|
113
|
+
unsubscribeService = face.subscribe(sync)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Detach the service and return to fallback detection (tests, dispose). */
|
|
117
|
+
export function disposeI18n(): void {
|
|
118
|
+
try { unsubscribeService?.() } catch { /* source already gone */ }
|
|
119
|
+
unsubscribeService = undefined
|
|
120
|
+
service = undefined
|
|
121
|
+
publish({ active: detectFallbackLocale(), revision: 0 })
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Subscribe-side store face for useSyncExternalStore. */
|
|
125
|
+
export const localeStore = {
|
|
126
|
+
getSnapshot(): TaskboardLocaleSnapshot {
|
|
127
|
+
return snapshot
|
|
128
|
+
},
|
|
129
|
+
subscribe(fn: () => void): () => void {
|
|
130
|
+
listeners.add(fn)
|
|
131
|
+
return () => { listeners.delete(fn) }
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const PLACEHOLDER = /\{(\w+)\}/g
|
|
136
|
+
|
|
137
|
+
/** Translate through the active dictionary, {name} placeholders substituted. */
|
|
138
|
+
export const translate: Translate = (key, params) => {
|
|
139
|
+
const template = DICTS[snapshot.active][key as keyof TaskboardDict] ?? DICTS.en[key as keyof TaskboardDict] ?? key
|
|
140
|
+
if (params === undefined) return template
|
|
141
|
+
return template.replace(PLACEHOLDER, (match, name: string) => {
|
|
142
|
+
const value = params[name]
|
|
143
|
+
return value === undefined ? match : String(value)
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* React hook: subscribes the component to locale changes and returns the
|
|
149
|
+
* translate function (stable identity — it reads the active locale at call
|
|
150
|
+
* time, so any re-render triggered by the subscription renders fresh text).
|
|
151
|
+
*/
|
|
152
|
+
export function useT(): Translate {
|
|
153
|
+
useSyncExternalStore(localeStore.subscribe, localeStore.getSnapshot)
|
|
154
|
+
return translate
|
|
155
|
+
}
|