dsh-taskboard 0.5.5 → 0.6.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 +22 -2
- package/lib/client.js +2604 -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 +272 -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 +131 -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
|
@@ -8,12 +8,14 @@
|
|
|
8
8
|
import { useState } from 'react'
|
|
9
9
|
import type { BoardController } from '../controller.ts'
|
|
10
10
|
import { useAlert } from './AlertModal.tsx'
|
|
11
|
+
import { useT } from '../i18n/runtime.ts'
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* The template manager modal.
|
|
14
15
|
* @param controller - the controller.
|
|
15
16
|
*/
|
|
16
17
|
export function TemplateManager({ controller }: { controller: BoardController }) {
|
|
18
|
+
const t = useT()
|
|
17
19
|
const state = controller.getSnapshot()
|
|
18
20
|
const [edits, setEdits] = useState<Record<string, string>>({})
|
|
19
21
|
const [confirmId, setConfirmId] = useState<string | undefined>(undefined)
|
|
@@ -30,78 +32,79 @@ export function TemplateManager({ controller }: { controller: BoardController })
|
|
|
30
32
|
void controller.upsertTemplate({ id, name, task: template.task }).then(ok => {
|
|
31
33
|
if (ok) {
|
|
32
34
|
setEdits(prev => { const next = { ...prev }; delete next[id]; return next })
|
|
33
|
-
showAlert('
|
|
35
|
+
showAlert(t('tpl.renamed'))
|
|
34
36
|
}
|
|
35
37
|
})
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
return (
|
|
39
41
|
<div className="dsh-atb-modal-backdrop" onClick={e => { if (e.target === e.currentTarget) close() }}>
|
|
40
|
-
<div className="dsh-atb-modal dsh-atb-tplm" role="dialog" aria-modal="true" aria-label=
|
|
42
|
+
<div className="dsh-atb-modal dsh-atb-tplm" role="dialog" aria-modal="true" aria-label={t('tpl.aria')}>
|
|
41
43
|
<div className="dsh-atb-modal-head">
|
|
42
44
|
<span className="dsh-atb-modal-headicon">⌗</span>
|
|
43
45
|
<div className="dsh-atb-modal-headtext">
|
|
44
|
-
<h3
|
|
45
|
-
<p
|
|
46
|
+
<h3>{t('tpl.title')}</h3>
|
|
47
|
+
<p>{t('tpl.subtitle')}</p>
|
|
46
48
|
</div>
|
|
47
|
-
<button type="button" className="dsh-atb-modal-close" aria-label=
|
|
49
|
+
<button type="button" className="dsh-atb-modal-close" aria-label={t('shared.close')} onClick={close}>✕</button>
|
|
48
50
|
</div>
|
|
49
51
|
<div className="dsh-atb-modal-body">
|
|
50
52
|
{state.templates.length === 0
|
|
51
|
-
? <div className="dsh-atb-empty2"
|
|
53
|
+
? <div className="dsh-atb-empty2">{t('tpl.empty')}</div>
|
|
52
54
|
: (
|
|
53
55
|
<div className="dsh-atb-tplm-list">
|
|
54
|
-
{state.templates.map(
|
|
55
|
-
<div key={
|
|
56
|
+
{state.templates.map(tpl => (
|
|
57
|
+
<div key={tpl.id} className="dsh-atb-tplm-row">
|
|
56
58
|
<input
|
|
57
59
|
className="dsh-atb-tplm-name"
|
|
58
|
-
value={nameOf(
|
|
60
|
+
value={nameOf(tpl.id, tpl.name)}
|
|
59
61
|
maxLength={60}
|
|
60
62
|
spellCheck={false}
|
|
61
|
-
aria-label={
|
|
62
|
-
onChange={e => setEdits(prev => ({ ...prev, [
|
|
63
|
+
aria-label={t('tpl.name.aria', { name: tpl.name })}
|
|
64
|
+
onChange={e => setEdits(prev => ({ ...prev, [tpl.id]: e.target.value }))}
|
|
63
65
|
onKeyDown={e => {
|
|
64
|
-
if (e.key === 'Enter') save(
|
|
66
|
+
if (e.key === 'Enter') save(tpl.id, nameOf(tpl.id, tpl.name))
|
|
65
67
|
}}
|
|
66
68
|
/>
|
|
67
69
|
<span
|
|
68
70
|
className="dsh-atb-tplm-meta"
|
|
69
|
-
title={`${
|
|
71
|
+
title={`${tpl.builtin === true ? t('tpl.builtin') : t('tpl.custom')}${tpl.task.checklist !== undefined && tpl.task.checklist.length > 0 ? t('tpl.meta.checklist', { n: tpl.task.checklist.length }) : ''}${tpl.task.urgency !== undefined ? ` · ${tpl.task.urgency}` : ''}${tpl.task.permission !== undefined ? ` · ${t('shared.permission')}: ${tpl.task.permission}` : ''}`}
|
|
70
72
|
>
|
|
71
|
-
{
|
|
72
|
-
{
|
|
73
|
-
{
|
|
73
|
+
{tpl.builtin === true ? t('tpl.builtin') : t('tpl.custom')}
|
|
74
|
+
{tpl.task.checklist !== undefined && tpl.task.checklist.length > 0 ? t('tpl.meta.checklist', { n: tpl.task.checklist.length }) : ''}
|
|
75
|
+
{tpl.task.urgency !== undefined ? ` · ${tpl.task.urgency}` : ''}
|
|
76
|
+
{tpl.task.permission !== undefined && tpl.task.permission !== 'workspace-write' ? ` · ${tpl.task.permission === 'read-only' ? t('tpl.meta.permReadOnly') : t('tpl.meta.permFull')}` : ''}
|
|
74
77
|
</span>
|
|
75
78
|
<span className="dsh-atb-tplm-btns">
|
|
76
79
|
<button
|
|
77
80
|
type="button"
|
|
78
81
|
className="dsh-atb-btn"
|
|
79
|
-
disabled={nameOf(
|
|
80
|
-
title=
|
|
81
|
-
onClick={() => save(
|
|
82
|
+
disabled={nameOf(tpl.id, tpl.name) === tpl.name || nameOf(tpl.id, tpl.name).trim().length === 0}
|
|
83
|
+
title={t('tpl.rename.title')}
|
|
84
|
+
onClick={() => save(tpl.id, nameOf(tpl.id, tpl.name))}
|
|
82
85
|
>
|
|
83
|
-
|
|
86
|
+
{t('tpl.rename.button')}
|
|
84
87
|
</button>
|
|
85
88
|
<button
|
|
86
89
|
type="button"
|
|
87
90
|
className="dsh-atb-btn"
|
|
88
|
-
title=
|
|
91
|
+
title={t('tpl.use.title')}
|
|
89
92
|
onClick={() => {
|
|
90
93
|
close()
|
|
91
|
-
controller.newFromTemplate(
|
|
94
|
+
controller.newFromTemplate(tpl.task)
|
|
92
95
|
}}
|
|
93
96
|
>
|
|
94
|
-
|
|
97
|
+
{t('tpl.use.button')}
|
|
95
98
|
</button>
|
|
96
|
-
{confirmId ===
|
|
99
|
+
{confirmId === tpl.id
|
|
97
100
|
? (
|
|
98
101
|
<>
|
|
99
|
-
<button type="button" className="dsh-atb-btn" data-danger="true" onClick={() => { void controller.deleteTemplate(
|
|
100
|
-
<button type="button" className="dsh-atb-btn" onClick={() => setConfirmId(undefined)}
|
|
102
|
+
<button type="button" className="dsh-atb-btn" data-danger="true" onClick={() => { void controller.deleteTemplate(tpl.id); setConfirmId(undefined) }}>{t('shared.confirmDelete')}</button>
|
|
103
|
+
<button type="button" className="dsh-atb-btn" onClick={() => setConfirmId(undefined)}>{t('shared.cancel')}</button>
|
|
101
104
|
</>
|
|
102
105
|
)
|
|
103
106
|
: (
|
|
104
|
-
<button type="button" className="dsh-atb-btn" data-danger="true" title=
|
|
107
|
+
<button type="button" className="dsh-atb-btn" data-danger="true" title={t('tpl.delete.title')} onClick={() => setConfirmId(tpl.id)}>
|
|
105
108
|
🗑
|
|
106
109
|
</button>
|
|
107
110
|
)}
|
|
@@ -112,9 +115,9 @@ export function TemplateManager({ controller }: { controller: BoardController })
|
|
|
112
115
|
)}
|
|
113
116
|
</div>
|
|
114
117
|
<div className="dsh-atb-modal-foot">
|
|
115
|
-
<span className="dsh-atb-modal-hint"
|
|
118
|
+
<span className="dsh-atb-modal-hint">{t('tpl.foot.hint')}</span>
|
|
116
119
|
<span className="dsh-atb-modal-footbtns">
|
|
117
|
-
<button type="button" className="dsh-atb-btn" onClick={close}
|
|
120
|
+
<button type="button" className="dsh-atb-btn" onClick={close}>{t('shared.close')}</button>
|
|
118
121
|
</span>
|
|
119
122
|
</div>
|
|
120
123
|
</div>
|
|
@@ -1,44 +1,53 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Key maps for the board's enumerated display text (status / urgency /
|
|
3
|
+
* outcome). The values are i18n dictionary keys (see src/client/i18n/zh.ts
|
|
4
|
+
* for the canon and en.ts for the English side); components resolve them
|
|
5
|
+
* with useT()/translate() at render time, so the text follows the GUI
|
|
6
|
+
* language live.
|
|
3
7
|
*
|
|
4
|
-
* Review P2: the same status/urgency/outcome text used to live in
|
|
5
|
-
* components (TaskBoard / TaskCard / TaskDetail) and drifted — adding
|
|
6
|
-
* status meant three edits, missing one leaked the raw English key.
|
|
8
|
+
* Review P2 history: the same status/urgency/outcome text used to live in
|
|
9
|
+
* three components (TaskBoard / TaskCard / TaskDetail) and drifted — adding
|
|
10
|
+
* a status meant three edits, missing one leaked the raw English key. The
|
|
11
|
+
* three-way split below is deliberate and survived the i18n migration:
|
|
12
|
+
*
|
|
13
|
+
* - COLUMN_KEYS — column headers on the five-column main board (+ secondary tab)
|
|
14
|
+
* - STATUS_KEYS — status pill text (detail pane); terminal states read short
|
|
15
|
+
* - MOVE_KEYS — move-button verbs (shorter than the pill text)
|
|
7
16
|
*
|
|
8
17
|
* @module dsh-taskboard/client/board/labels
|
|
9
18
|
*/
|
|
10
19
|
import type { TaskStatus, Urgency } from '../../shared/protocol.ts'
|
|
11
20
|
|
|
12
|
-
/** Column
|
|
13
|
-
export const
|
|
14
|
-
backlog: '
|
|
15
|
-
todo: '
|
|
16
|
-
in_progress: '
|
|
17
|
-
in_review: '
|
|
18
|
-
done: '
|
|
19
|
-
canceled: '
|
|
20
|
-
archived: '
|
|
21
|
+
/** Column header keys on the five-column main board (+ secondary tab). */
|
|
22
|
+
export const COLUMN_KEYS: Readonly<Record<TaskStatus, string>> = {
|
|
23
|
+
backlog: 'status.column.backlog',
|
|
24
|
+
todo: 'status.column.todo',
|
|
25
|
+
in_progress: 'status.column.in_progress',
|
|
26
|
+
in_review: 'status.column.in_review',
|
|
27
|
+
done: 'status.column.done',
|
|
28
|
+
canceled: 'status.column.canceled',
|
|
29
|
+
archived: 'status.column.archived',
|
|
21
30
|
}
|
|
22
31
|
|
|
23
|
-
/** Status pill
|
|
32
|
+
/** Status pill keys (detail pane) — historical wording kept verbatim:
|
|
24
33
|
* terminal states read short here, the column headers carry the full forms. */
|
|
25
|
-
export const
|
|
26
|
-
backlog: '
|
|
27
|
-
done: '
|
|
34
|
+
export const STATUS_KEYS: Readonly<Record<TaskStatus, string>> = {
|
|
35
|
+
backlog: 'status.pill.backlog', todo: 'status.pill.todo', in_progress: 'status.pill.in_progress', in_review: 'status.pill.in_review',
|
|
36
|
+
done: 'status.pill.done', canceled: 'status.pill.canceled', archived: 'status.pill.archived',
|
|
28
37
|
}
|
|
29
38
|
|
|
30
|
-
/** Move-button
|
|
31
|
-
export const
|
|
32
|
-
backlog: '
|
|
33
|
-
done: '
|
|
39
|
+
/** Move-button verb keys (shorter than the pill text). */
|
|
40
|
+
export const MOVE_KEYS: Readonly<Record<TaskStatus, string>> = {
|
|
41
|
+
backlog: 'status.move.backlog', todo: 'status.move.todo', in_progress: 'status.move.in_progress', in_review: 'status.move.in_review',
|
|
42
|
+
done: 'status.move.done', canceled: 'status.move.canceled', archived: 'status.move.archived',
|
|
34
43
|
}
|
|
35
44
|
|
|
36
|
-
/** Urgency chip
|
|
37
|
-
export const
|
|
38
|
-
urgent: '
|
|
45
|
+
/** Urgency chip keys. */
|
|
46
|
+
export const URGENCY_KEYS: Readonly<Record<Urgency, string>> = {
|
|
47
|
+
urgent: 'urgency.urgent', normal: 'urgency.normal', relaxed: 'urgency.relaxed',
|
|
39
48
|
}
|
|
40
49
|
|
|
41
|
-
/** Execution outcome
|
|
42
|
-
export const
|
|
43
|
-
running: '
|
|
50
|
+
/** Execution outcome keys. */
|
|
51
|
+
export const OUTCOME_KEYS: Readonly<Record<string, string>> = {
|
|
52
|
+
running: 'outcome.running', succeeded: 'outcome.succeeded', failed: 'outcome.failed', cancelled: 'outcome.cancelled',
|
|
44
53
|
}
|
package/src/client/controller.ts
CHANGED
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module dsh-taskboard/client/controller
|
|
9
9
|
*/
|
|
10
|
-
import type { ChangeEvent, DiagnosticsResponse, DiffResponse, ImportCommitResponse, ImportPreviewResponse, TaskTemplate, TaskTemplateSpec, UpdateTaskBody, WorkspaceView } from '../shared/api.ts'
|
|
10
|
+
import type { ChangeEvent, DiagnosticsResponse, DiffResponse, ImportCommitResponse, ImportPreviewResponse, PromptCompletionsResponse, TaskTemplate, TaskTemplateSpec, UpdateTaskBody, WorkspaceView } from '../shared/api.ts'
|
|
11
11
|
import type { ChecklistItem, TaskLedger, TaskRecord, Urgency } from '../shared/protocol.ts'
|
|
12
12
|
import { emptyLedger } from '../shared/protocol.ts'
|
|
13
13
|
import type { TaskboardClient } from './api.ts'
|
|
14
14
|
import type { SessionJumpResult } from './session-jump.ts'
|
|
15
|
+
import { translate } from './i18n/runtime.ts'
|
|
15
16
|
|
|
16
17
|
/** View filters over the ledger. */
|
|
17
18
|
export interface BoardFilters {
|
|
@@ -321,6 +322,54 @@ export class BoardController {
|
|
|
321
322
|
return this.catalogFaces.presets
|
|
322
323
|
}
|
|
323
324
|
|
|
325
|
+
/**
|
|
326
|
+
* Fetch model catalog: prefers installed runtime face, falls back to Taskboard client API (0.5.5).
|
|
327
|
+
*/
|
|
328
|
+
async fetchModelCatalog(): Promise<Array<{
|
|
329
|
+
provider: string
|
|
330
|
+
model: string
|
|
331
|
+
name?: string
|
|
332
|
+
description?: string
|
|
333
|
+
reasoning?: {
|
|
334
|
+
efforts: Array<{ id: string; name: string; description?: string }>
|
|
335
|
+
defaultEffort?: string
|
|
336
|
+
}
|
|
337
|
+
}>> {
|
|
338
|
+
if (this.catalogFaces.models !== undefined) {
|
|
339
|
+
try {
|
|
340
|
+
const list = await this.catalogFaces.models()
|
|
341
|
+
if (list !== undefined && list.length > 0) return list
|
|
342
|
+
} catch { /* fallback to client */ }
|
|
343
|
+
}
|
|
344
|
+
try {
|
|
345
|
+
const res = await this.client.modelCatalog()
|
|
346
|
+
return res.models ?? []
|
|
347
|
+
} catch {
|
|
348
|
+
return []
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Fetch preset roster: prefers installed runtime face, falls back to Taskboard client API (0.5.5).
|
|
354
|
+
*/
|
|
355
|
+
async fetchPresetCatalog(): Promise<{ presets: Array<{ id: string; name?: string }>; defaultId?: string }> {
|
|
356
|
+
if (this.catalogFaces.presets !== undefined) {
|
|
357
|
+
try {
|
|
358
|
+
const roster = await this.catalogFaces.presets()
|
|
359
|
+
if (roster !== undefined && roster.presets !== undefined && roster.presets.length > 0) return roster
|
|
360
|
+
} catch { /* fallback to client */ }
|
|
361
|
+
}
|
|
362
|
+
try {
|
|
363
|
+
const res = await this.client.modelCatalog()
|
|
364
|
+
return {
|
|
365
|
+
presets: res.presets ?? [],
|
|
366
|
+
...(res.defaultPresetId !== undefined ? { defaultId: res.defaultPresetId } : {}),
|
|
367
|
+
}
|
|
368
|
+
} catch {
|
|
369
|
+
return { presets: [] }
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
324
373
|
/**
|
|
325
374
|
* Jump to an execution's session (open it in the GUI). On success the board
|
|
326
375
|
* closes so the conversation shows; a deleted-or-archived session reports
|
|
@@ -557,7 +606,7 @@ export class BoardController {
|
|
|
557
606
|
try {
|
|
558
607
|
await this.client.create({
|
|
559
608
|
// Keep the suffix under the host's 200-char title cap (review P1).
|
|
560
|
-
title:
|
|
609
|
+
title: task.title.slice(0, 196) + translate('shared.duplicate.suffix'),
|
|
561
610
|
workspaceId: task.workspaceId,
|
|
562
611
|
urgency: task.urgency,
|
|
563
612
|
description: task.description.length > 0 ? task.description : undefined,
|
|
@@ -568,6 +617,7 @@ export class BoardController {
|
|
|
568
617
|
model: task.model,
|
|
569
618
|
isolation: task.isolation,
|
|
570
619
|
...(task.presetId !== undefined ? { presetId: task.presetId } : {}),
|
|
620
|
+
...(task.permission !== undefined ? { permission: task.permission } : {}),
|
|
571
621
|
...(task.checklist !== undefined && task.checklist.length > 0 ? { checklist: task.checklist.map(i => i.text) } : {}),
|
|
572
622
|
})
|
|
573
623
|
await this.refresh()
|
|
@@ -640,11 +690,21 @@ export class BoardController {
|
|
|
640
690
|
model: task.model,
|
|
641
691
|
isolation: task.isolation,
|
|
642
692
|
...(task.presetId !== undefined ? { presetId: task.presetId } : {}),
|
|
693
|
+
...(task.permission !== undefined ? { permission: task.permission } : {}),
|
|
643
694
|
...(task.checklist !== undefined && task.checklist.length > 0 ? { checklist: task.checklist.map(i => i.text) } : {}),
|
|
644
695
|
},
|
|
645
696
|
})
|
|
646
697
|
}
|
|
647
698
|
|
|
699
|
+
/** Load prompt completions (skills + slash commands) from host (0.5.5). */
|
|
700
|
+
async fetchPromptCompletions(): Promise<PromptCompletionsResponse | undefined> {
|
|
701
|
+
try {
|
|
702
|
+
return await this.client.promptCompletions()
|
|
703
|
+
} catch {
|
|
704
|
+
return undefined
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
648
708
|
// ------------------------------------------------ import (0.4.0)
|
|
649
709
|
/** Open the import modal. */
|
|
650
710
|
openImport(): void { this.setState({ importOpen: true }) }
|