@linxin666/dsh-client-ui-task-board 0.3.3 → 0.3.5
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/lib/client.js +399 -201
- package/lib/client.js.map +1 -1
- package/lib/index.js +42 -6
- package/lib/types/client/PluginSettingsCard.d.ts +2 -0
- package/lib/types/client/board/EditTaskModal.d.ts +8 -0
- package/lib/types/client/board/TaskCard.d.ts +0 -1
- package/lib/types/client/locales.d.ts +3 -0
- package/lib/types/core/controller.d.ts +21 -5
- package/lib/types/core/use-cases/task-update.d.ts +19 -3
- package/package.json +1 -1
- package/src/client/PluginSettingsCard.tsx +4 -2
- package/src/client/board/EditTaskModal.tsx +94 -0
- package/src/client/board/TaskBoard.tsx +21 -2
- package/src/client/board/TaskCard.tsx +7 -1
- package/src/client/board/TaskDetail.tsx +19 -0
- package/src/client/locales.ts +6 -0
- package/src/client/settings-card.module.css +5 -0
- package/src/core/controller.ts +29 -21
- package/src/core/use-cases/task-update.ts +33 -3
- package/src/dsh-home.ts +9 -3
- package/src/host-ledger.ts +9 -1
|
@@ -4,15 +4,38 @@
|
|
|
4
4
|
* fresh updatedAt. Pure ledger transition (no persistence or notify — the
|
|
5
5
|
* controller orchestrates those).
|
|
6
6
|
*
|
|
7
|
-
* An explicit `undefined` in the patch clears
|
|
8
|
-
* back to the runtime default);
|
|
9
|
-
*
|
|
7
|
+
* An explicit `undefined` in the patch clears an execution-target field
|
|
8
|
+
* (the task falls back to the runtime default); content fields are trimmed
|
|
9
|
+
* and never cleared. An unknown permission string is ignored so stale UI
|
|
10
|
+
* can never persist a value the execution service rejects.
|
|
10
11
|
*/
|
|
11
12
|
import { isTaskPermission, normalizeTargetId, type TaskRecord, type TaskPermission } from '../tasks.ts'
|
|
12
13
|
|
|
13
14
|
/** Editable fields on a task (the update patch surface). */
|
|
14
15
|
export type TaskUpdatePatch = Partial<Pick<TaskRecord, 'title' | 'description' | 'prompt' | 'workspaceId' | 'mode' | 'permission'>>
|
|
15
16
|
|
|
17
|
+
/**
|
|
18
|
+
* The fields that edit the task's content (what the user reads and what the
|
|
19
|
+
* next execution sends). Unlike the execution targets they stay editable only
|
|
20
|
+
* while the task has never started executing — after the first run the
|
|
21
|
+
* recorded prompt is the record of what actually ran, so it becomes read-only.
|
|
22
|
+
*/
|
|
23
|
+
export const TASK_CONTENT_FIELDS = ['title', 'description', 'prompt'] as const
|
|
24
|
+
|
|
25
|
+
/** Whether an update patch touches any task-content field. */
|
|
26
|
+
export function hasContentPatch(patch: TaskUpdatePatch): boolean {
|
|
27
|
+
return (TASK_CONTENT_FIELDS as readonly string[]).some(field => field in patch)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Whether a task's content may still be edited: the task must be on-board
|
|
32
|
+
* (not archived) and must never have started executing. Fail-closed: a
|
|
33
|
+
* running, settled, or cancelled-before-launch task keeps its content fixed.
|
|
34
|
+
*/
|
|
35
|
+
export function canEditTaskContent(task: TaskRecord): boolean {
|
|
36
|
+
return task.archivedAt === undefined && task.status !== 'running' && task.executions.length === 0
|
|
37
|
+
}
|
|
38
|
+
|
|
16
39
|
/** Keep an unknown permission string from entering the ledger. */
|
|
17
40
|
function normalizePermission(
|
|
18
41
|
current: TaskPermission | undefined,
|
|
@@ -42,6 +65,13 @@ export function applyUpdateTask(
|
|
|
42
65
|
const mode = 'mode' in patch ? normalizeTargetId(patch.mode) : undefined
|
|
43
66
|
const permission = 'permission' in patch ? normalizePermission(task.permission, patch.permission) : undefined
|
|
44
67
|
const next: TaskRecord = { ...task, ...patch, updatedAt: now }
|
|
68
|
+
// Content fields normalize like creation does (trimmed); an explicit
|
|
69
|
+
// undefined keeps the current value — content cannot be cleared.
|
|
70
|
+
for (const field of TASK_CONTENT_FIELDS) {
|
|
71
|
+
if (!(field in patch)) continue
|
|
72
|
+
const value = patch[field]
|
|
73
|
+
next[field] = value === undefined ? task[field] : value.trim()
|
|
74
|
+
}
|
|
45
75
|
if (workspaceId !== undefined || 'workspaceId' in patch) next.workspaceId = workspaceId
|
|
46
76
|
if (mode !== undefined || 'mode' in patch) next.mode = mode
|
|
47
77
|
if (permission !== undefined || 'permission' in patch) next.permission = permission
|
package/src/dsh-home.ts
CHANGED
|
@@ -7,11 +7,14 @@
|
|
|
7
7
|
|
|
8
8
|
import { homedir } from 'node:os'
|
|
9
9
|
import { isAbsolute, join } from 'node:path'
|
|
10
|
+
import { isAbsolute as posixIsAbsolute, join as posixJoin } from 'node:path/posix'
|
|
10
11
|
|
|
11
12
|
/** Expand a leading ~ (or ~user) in a path, platform-style. */
|
|
12
13
|
export function expandHome(path: string, home: string = homedir()): string {
|
|
14
|
+
const isPosix = home.startsWith('/')
|
|
15
|
+
const j = isPosix ? posixJoin : join
|
|
13
16
|
if (path === '~') return home
|
|
14
|
-
if (path.startsWith('~/') || path.startsWith('~\\')) return
|
|
17
|
+
if (path.startsWith('~/') || path.startsWith('~\\')) return j(home, path.slice(2))
|
|
15
18
|
return path
|
|
16
19
|
}
|
|
17
20
|
|
|
@@ -22,12 +25,15 @@ export function expandHome(path: string, home: string = homedir()): string {
|
|
|
22
25
|
* @returns the absolute DSH home path.
|
|
23
26
|
*/
|
|
24
27
|
export function resolveDshHome(env: NodeJS.ProcessEnv = process.env, home: string = homedir()): string {
|
|
28
|
+
const isPosix = home.startsWith('/')
|
|
29
|
+
const j = isPosix ? posixJoin : join
|
|
30
|
+
const isAbs = isPosix ? posixIsAbsolute : isAbsolute
|
|
25
31
|
const raw = env.DSH_HOME
|
|
26
32
|
if (raw !== undefined && raw.trim() !== '') {
|
|
27
33
|
const expanded = expandHome(raw.trim(), home)
|
|
28
|
-
return
|
|
34
|
+
return isAbs(expanded) ? expanded : j(process.cwd(), expanded)
|
|
29
35
|
}
|
|
30
|
-
return
|
|
36
|
+
return j(home, '.dsh')
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
/** Resolve the DSH home directory from the live environment. */
|
package/src/host-ledger.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { applyArchiveTask, applyRestoreTask } from './core/use-cases/task-archiv
|
|
|
10
10
|
import { applyCreateTask } from './core/use-cases/task-create.ts'
|
|
11
11
|
import { applyDeleteTask } from './core/use-cases/task-delete.ts'
|
|
12
12
|
import { applySetSchedule, applyScheduleNextRun } from './core/use-cases/task-schedule.ts'
|
|
13
|
-
import { applyUpdateTask } from './core/use-cases/task-update.ts'
|
|
13
|
+
import { applyUpdateTask, canEditTaskContent, hasContentPatch } from './core/use-cases/task-update.ts'
|
|
14
14
|
import { TASK_BOARD_SCHEMA_VERSION, type TaskBoardAction, type TaskBoardSchedulerSnapshot } from './protocol.ts'
|
|
15
15
|
|
|
16
16
|
interface PersistedScheduler extends TaskBoardSchedulerSnapshot {
|
|
@@ -482,6 +482,14 @@ export class HostTaskLedger {
|
|
|
482
482
|
const task = this.document.tasks.find(task => task.id === action.taskId)
|
|
483
483
|
if (task === undefined) throw new Error('task not found')
|
|
484
484
|
if (task.archivedAt !== undefined) throw new Error('archived task is read-only')
|
|
485
|
+
// The task content (title/description/prompt) is the record of what
|
|
486
|
+
// was planned; once an execution started it must not change under a
|
|
487
|
+
// running session or an executed history. Execution targets stay
|
|
488
|
+
// editable (they only affect future runs).
|
|
489
|
+
if (hasContentPatch(action.patch) && !canEditTaskContent(task)) {
|
|
490
|
+
throw new Error('task has already been executed')
|
|
491
|
+
}
|
|
492
|
+
if ('title' in action.patch && action.patch.title?.trim() === '') throw new Error('title is required')
|
|
485
493
|
this.document.tasks = [...applyUpdateTask(this.document.tasks, action.taskId, action.patch, now)]
|
|
486
494
|
break
|
|
487
495
|
}
|