@william2000/dsh-nova-ui-task-board 0.1.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 (50) hide show
  1. package/README.md +86 -0
  2. package/cordis.patch.yml +13 -0
  3. package/lib/client.js +5641 -0
  4. package/lib/client.js.map +1 -0
  5. package/lib/index.js +4873 -0
  6. package/lib/types/client/NovaTaskBoardSettingsCard.d.ts +17 -0
  7. package/lib/types/client/PlaceholderBoard.d.ts +9 -0
  8. package/lib/types/client/apply-guard.d.ts +25 -0
  9. package/lib/types/client/board/ConfirmDialog.d.ts +12 -0
  10. package/lib/types/client/board/ConfirmTaskDialog.d.ts +8 -0
  11. package/lib/types/client/board/ConvertTaskModal.d.ts +17 -0
  12. package/lib/types/client/board/NewTaskModal.d.ts +6 -0
  13. package/lib/types/client/board/RequirementSplitModal.d.ts +14 -0
  14. package/lib/types/client/board/SplitConfirmDialog.d.ts +9 -0
  15. package/lib/types/client/board/TagEditor.d.ts +6 -0
  16. package/lib/types/client/board/TaskBoard.d.ts +9 -0
  17. package/lib/types/client/board/TaskCard.d.ts +52 -0
  18. package/lib/types/client/board/TaskDetail.d.ts +7 -0
  19. package/lib/types/client/board/status-key.d.ts +8 -0
  20. package/lib/types/client/board-controller.d.ts +274 -0
  21. package/lib/types/client/board-mount.d.ts +10 -0
  22. package/lib/types/client/chat-integration.d.ts +99 -0
  23. package/lib/types/client/drag-utils.d.ts +78 -0
  24. package/lib/types/client/filter.d.ts +42 -0
  25. package/lib/types/client/grouping.d.ts +54 -0
  26. package/lib/types/client/host-api.d.ts +62 -0
  27. package/lib/types/client/index.d.ts +46 -0
  28. package/lib/types/client/legacy-store.d.ts +28 -0
  29. package/lib/types/client/locales.d.ts +202 -0
  30. package/lib/types/client/schedule-presets.d.ts +20 -0
  31. package/lib/types/client/sidebar-entry-core.d.ts +54 -0
  32. package/lib/types/client/sidebar-entry.d.ts +19 -0
  33. package/lib/types/core/context.d.ts +139 -0
  34. package/lib/types/core/cron.d.ts +54 -0
  35. package/lib/types/core/migrate.d.ts +39 -0
  36. package/lib/types/core/model.d.ts +446 -0
  37. package/lib/types/core/recovery.d.ts +25 -0
  38. package/lib/types/core/split.d.ts +81 -0
  39. package/lib/types/core/transitions.d.ts +436 -0
  40. package/lib/types/dsh-home.d.ts +21 -0
  41. package/lib/types/host-automation.d.ts +59 -0
  42. package/lib/types/host-ledger.d.ts +146 -0
  43. package/lib/types/host-routes.d.ts +65 -0
  44. package/lib/types/host-runner.d.ts +177 -0
  45. package/lib/types/host-service.d.ts +291 -0
  46. package/lib/types/index.d.ts +90 -0
  47. package/lib/types/loopback.d.ts +23 -0
  48. package/lib/types/mount-once.d.ts +9 -0
  49. package/lib/types/protocol.d.ts +210 -0
  50. package/package.json +89 -0
@@ -0,0 +1,78 @@
1
+ /**
2
+ * 看板拖拽纯函数(T009 §12.10,D10)。
3
+ *
4
+ * - 合法性(§10.2/§11.2):running 不可作为拖拽来源/目标;跨列拖拽 = move,
5
+ * 目标仅限 backlog/todo(backlog↔todo 互移、done/failed 拖回 backlog/todo 为
6
+ * 重开);同列拖拽 = reorder(各列均可列内排序);拖到 running 等非法目标被
7
+ * 拒绝并提示;
8
+ * - 落位下标语义(与 Host transitions.applyReposition 一致):浏览器提交的
9
+ * `order` = 0-based 插入下标,基准是**剔除拖拽卡片后的目标列**(按 order 稳定
10
+ * 升序)——锚定卡片(before/after)或组尾/列尾(end)换算成该列表的下标;
11
+ * 过滤状态下「列尾」按可见列表最后一张卡在基准列表中的位置定位(先过滤后排序
12
+ * /分组展示,§12.10);
13
+ * - action 构建:同列 → `reorder`(分组视图跨组落位携带归属变更:项目模式
14
+ * project=组名/''(清除);标签模式 tags 使目标标签成为首选或移除首选);
15
+ * 跨列 → `move`(可选 order 落位,done/failed 重开由状态机守卫);
16
+ * - 全部为纯函数,可脱离宿主单测(验收 3)。
17
+ */
18
+ import type { TaskRecord, TaskStatus } from '../core/model.ts';
19
+ import type { TaskBoardAction } from '../protocol.ts';
20
+ import { type GroupMode } from './grouping.ts';
21
+ /** 拖拽提交的 action 判别联合(buildDropAction 只产出 reorder/move 两种)。 */
22
+ export type DropAction = Extract<TaskBoardAction, {
23
+ kind: 'reorder';
24
+ }> | Extract<TaskBoardAction, {
25
+ kind: 'move';
26
+ }>;
27
+ /** 拖拽来源(dragstart 时记录;分组视图记录起始组 key 以判定跨组)。 */
28
+ export interface DragOrigin {
29
+ taskId: string;
30
+ fromStatus: TaskStatus;
31
+ fromTags: readonly string[];
32
+ /** 分组视图下起始组的 key('' = 未分类);手动模式 undefined。 */
33
+ fromGroupKey?: string;
34
+ }
35
+ /** 落位方式:锚定卡片之前/之后,或组尾/列尾。 */
36
+ export type DropPosition = 'before' | 'after' | 'end';
37
+ /** 拖拽目标(dragover/drop 时计算)。 */
38
+ export interface DropTarget {
39
+ /** 目标列。 */
40
+ status: TaskStatus;
41
+ /** 分组视图目标组的 key('' = 未分类);手动模式 undefined。 */
42
+ groupKey?: string;
43
+ /** 锚定卡片 id(落在卡片上时);否则为组尾/列尾。 */
44
+ anchorId?: string;
45
+ /** 落位方式:锚定卡片存在时 before/after;否则 end。 */
46
+ position: DropPosition;
47
+ }
48
+ /**
49
+ * 拖拽合法性(§10.2/§11.2):running 不可作目标;同列 = 列内排序;跨列目标仅限
50
+ * backlog/todo(done/failed 拖回为重开)。proposed/归档不可作为来源由调用方
51
+ * (卡片 draggable=false)保证。
52
+ */
53
+ export declare function isLegalDrop(origin: DragOrigin, targetStatus: TaskStatus): boolean;
54
+ /**
55
+ * 计算落位下标(0-based,基准 = 剔除拖拽卡片后的目标列,按 order 稳定升序;
56
+ * 与 Host applyReposition 的 insert 基准一致):
57
+ * - 锚定卡片:卡片下标(after +1);
58
+ * - 分组视图组尾:目标组最后一张卡下标 +1(组空回退列尾);
59
+ * - 列尾:可见列表最后一张卡下标 +1(过滤定位;无可见卡则列尾)。
60
+ */
61
+ export declare function computeDropIndex(restColumn: readonly TaskRecord[], target: DropTarget, mode: GroupMode, lastVisibleId?: string): number;
62
+ /**
63
+ * 可见列表内的落位下标(拖拽 pending 渲染用;list = 目标列/目标组的可见列表,
64
+ * **已剔除拖拽卡片**):锚定卡片 before/after,否则列表尾。
65
+ */
66
+ export declare function visibleDropIndex(list: readonly TaskRecord[], target: DropTarget): number;
67
+ /**
68
+ * 构建拖拽提交的 action(§11.2/D10):
69
+ * - 同列 → `reorder`(status 与来源一致;分组视图跨组落位携带归属变更——
70
+ * 项目模式 project = 组名 / ''(清除);标签模式 tags 使目标标签成为首选或
71
+ * 移除首选(未分类));
72
+ * - 跨列 → `move`(status 为目标列 + 落位下标,缺省语义由 Host 追加末尾;
73
+ * done/failed 重开不触发执行)。
74
+ * 非法目标/拖到自己返回 undefined(调用方提示拒绝)。
75
+ */
76
+ export declare function buildDropAction(origin: DragOrigin, target: DropTarget, mode: GroupMode, tasks: readonly TaskRecord[], lastVisibleId?: string): DropAction | undefined;
77
+ /** 拖拽目标组的归属变换后的行(pending 渲染预览,与提交的 reorder 补丁一致)。 */
78
+ export declare function pendingTaskView(task: TaskRecord, target: DropTarget, mode: GroupMode): TaskRecord;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * 看板过滤纯函数(T008 §12.8,D9)。
3
+ *
4
+ * - 过滤维度:文本搜索(标题/描述,大小写不敏感)+ 项目(单选)+ 标签(多选);
5
+ * - 语义:多条件(文本/项目/标签)之间 AND;同一条件内标签为 OR(命中任一
6
+ * 标签即通过);项目为单选精确匹配(`none` = 仅无项目任务);
7
+ * - 过滤跨列生效(主看板/归档视图由调用方先筛视图再调本函数);过滤状态仅
8
+ * 会话级记忆(刷新重置,不做持久化,P4 再考虑);
9
+ * - 全部为纯函数(无 fs、无 DOM、无运行时依赖),可脱离宿主单测
10
+ * (验收 3:过滤逻辑纯函数)。
11
+ */
12
+ import type { TaskRecord } from '../core/model.ts';
13
+ /** 项目过滤值:`all` = 不限;`none` = 仅无项目任务;`name` = 精确项目名。 */
14
+ export type ProjectFilterValue = {
15
+ kind: 'all';
16
+ } | {
17
+ kind: 'none';
18
+ } | {
19
+ kind: 'name';
20
+ name: string;
21
+ };
22
+ /** 过滤状态(会话级记忆;刷新重置,不做持久化)。 */
23
+ export interface TaskFilter {
24
+ /** 文本搜索(标题/描述,大小写不敏感;空 = 不限制)。 */
25
+ query: string;
26
+ /** 项目过滤(缺省/`all` = 不限)。 */
27
+ project: ProjectFilterValue;
28
+ /** 标签过滤:空数组 = 不限;非空 = 命中任一标签(同一条件内 OR)。 */
29
+ tags: readonly string[];
30
+ }
31
+ /** 空过滤(看板初始状态)。 */
32
+ export declare const EMPTY_FILTER: TaskFilter;
33
+ /** 单条任务是否命中过滤(多条件 AND、标签内 OR)。 */
34
+ export declare function matchesFilter(task: TaskRecord, filter: TaskFilter): boolean;
35
+ /** 对任务集执行过滤(保持入参顺序与对象引用,memo 边界不受影响)。 */
36
+ export declare function filterTasks(tasks: readonly TaskRecord[], filter: TaskFilter): TaskRecord[];
37
+ /** 过滤是否处于激活状态(任一维度有约束;用于「清除全部」显隐与命中计数)。 */
38
+ export declare function isFilterActive(filter: TaskFilter): boolean;
39
+ /** 从任务集推导去重后的项目名列表(过滤下拉选项),按 zh locale 排序。 */
40
+ export declare function distinctProjects(tasks: readonly TaskRecord[]): string[];
41
+ /** 从任务集推导去重后的标签列表(多选 chips 选项),按 zh locale 排序。 */
42
+ export declare function distinctTags(tasks: readonly TaskRecord[]): string[];
@@ -0,0 +1,54 @@
1
+ /**
2
+ * 看板列内分组/排序纯函数(T009 §12.10,D10)。
3
+ *
4
+ * - 排序视图模式(看板顶部切换,会话级记忆、刷新重置):手动顺序(默认,完全按
5
+ * order)/ 按项目分组 / 按标签分组;
6
+ * - 分组是视图层能力,不改底层 order 与归属数据(归属变更经 reorder/update 落账);
7
+ * - 按项目分组:组头 = 项目名,无 `project` 的任务归「未分类」组,组内按 order
8
+ * 升序;按标签分组:组头 = 标签,任务按全部 `tags` 多归组展示(一个任务可出现
9
+ * 在多个标签组),无标签任务归「未分类」组,组内按 order 升序;
10
+ * - 排序必须与 Host 端 transitions.byOrderStable 完全一致(order 升序,
11
+ * createdAt/id 决胜),否则浏览器计算的落位下标与 Host 重算不一致;
12
+ * - 全部为纯函数(无 fs、无 DOM、无运行时依赖),可脱离宿主单测
13
+ * (验收 3:分组视图过滤/归组逻辑)。
14
+ */
15
+ import type { TaskRecord } from '../core/model.ts';
16
+ /** 排序视图模式(§12.10:看板顶部切换,会话级记忆、刷新重置)。 */
17
+ export type GroupMode = 'manual' | 'project' | 'tag';
18
+ /** 全部模式(顶部切换按钮按此顺序渲染)。 */
19
+ export declare const GROUP_MODES: readonly GroupMode[];
20
+ /**
21
+ * 「未分类」组的哨兵 key。项目名/标签经 trim 后非空(normalizeOptionalString /
22
+ * normalizeTags 丢弃空白项),空串不可能与真实归属冲突,安全用作哨兵。
23
+ */
24
+ export declare const UNGROUPED = "";
25
+ /** 一个分组(组头 = label;组内任务按 order 升序)。 */
26
+ export interface TaskGroup {
27
+ /** 稳定 key:项目名 / 标签名 / UNGROUPED(未分类)。 */
28
+ key: string;
29
+ /** 展示名(未分类由调用方渲染为「未分类」文案)。 */
30
+ label: string;
31
+ tasks: TaskRecord[];
32
+ }
33
+ /** 与 Host transitions.byOrderStable 一致的列内排序(order 升序,createdAt/id 决胜)。 */
34
+ export declare function byOrderStable(a: TaskRecord, b: TaskRecord): number;
35
+ /** 列内任务按 order 升序排序(原数组不改动)。 */
36
+ export declare function sortByOrder(tasks: readonly TaskRecord[]): TaskRecord[];
37
+ /** 任务在给定视图模式下的组 key(手动模式无组概念,返回 undefined)。 */
38
+ export declare function groupKeyOf(task: TaskRecord, mode: GroupMode): string | undefined;
39
+ /** 任务是否属于某个组(按模式解释 key:项目精确匹配 / 标签命中 / 未分类无归属)。 */
40
+ export declare function groupContains(task: TaskRecord, groupKey: string, mode: GroupMode): boolean;
41
+ /**
42
+ * 拖拽目标组的归属变换(视图层预览用,与提交的 reorder 归属补丁一致,§12.10):
43
+ * - 按项目分组:拖到组 G → project = G;拖到「未分类」→ 清除 project;
44
+ * - 按标签分组:拖到组 G → G 成为首选标签(tags[0],G 已含于 tags 时仅调整顺序);
45
+ * 拖到「未分类」→ 移除首选标签(tags.shift(),若 tags 为空则归「未分类」);
46
+ * - 手动模式或非组落位 → 原样返回。
47
+ */
48
+ export declare function applyOwnershipTransform(task: TaskRecord, groupKey: string | undefined, mode: GroupMode): TaskRecord;
49
+ /**
50
+ * 分组(§12.10):项目/标签模式下的归组展示,组内按 order 升序;标签模式多归组
51
+ * (一个任务可出现在其全部标签组),无标签归「未分类」;未分类组恒在最后。
52
+ * 手动模式不分组(调用方直接 sortByOrder)。
53
+ */
54
+ export declare function groupTasks(tasks: readonly TaskRecord[], mode: Exclude<GroupMode, 'manual'>): TaskGroup[];
@@ -0,0 +1,62 @@
1
+ /**
2
+ * 浏览器 ↔ Host 的同源传输层(T004 §11.1/§11.3/§12 实时同步)。
3
+ *
4
+ * 三个端点(与 T003 host-routes 一一对应):
5
+ * - `GET /api/nova-task-board/state` 全量快照(含 tasks),`no-store` 防缓存;
6
+ * - `POST /api/nova-task-board/action` 幂等信封 `{requestId, action}`,返回新快照;
7
+ * - `GET /api/nova-task-board/events` SSE 只推 `{revision, scheduler, power}`
8
+ * (15s 心跳),revision 变化才由控制器重拉 state。
9
+ *
10
+ * 同步契约(§17 一致性):浏览器不先写未确认状态——本层只是传输,动作是否生效
11
+ * 以 Host 返回的快照为准;SSE 连接中断时浏览器 EventSource 自动重连,重连成功
12
+ * (onopen)与页面重新可见(visibilitychange → visible)都会触发一次全量重拉。
13
+ *
14
+ * v1 迁移(§11.2 import / §9.5):首次加载若本地仍有 v1 回退数据且尚未导入
15
+ * 本项目账本(按 ledgerId 记 marker),则用一次性 sourceId/requestId 提交
16
+ * `import` action;marker 只在 Host 确认(返回快照)后写入,原 v1 值永不删除。
17
+ * 迁移键用本项目专属前缀 `dsh.novaTaskBoard.*`,避免与参考实现自己的
18
+ * `dsh.taskBoard.v2.*` 迁移标记互踩(AGENTS.md D2/D3)。
19
+ */
20
+ import type { TaskRecord } from '../core/model.ts';
21
+ import { type TaskBoardAction, type TaskBoardEventPayload, type TaskBoardSnapshot } from '../protocol.ts';
22
+ /** 单次 Host 请求超时(§17 性能边界;超时按传输错误暴露,可重试)。 */
23
+ export declare const REQUEST_TIMEOUT_MS = 15000;
24
+ /** 浏览器存储的读写面(localStorage;测试注入 fake)。 */
25
+ export interface MarkerStorage {
26
+ getItem(key: string): string | null;
27
+ setItem(key: string, value: string): void;
28
+ }
29
+ /** 传输层对外契约:控制器只依赖这四个方法(结构面,测试可注入 fake)。 */
30
+ export interface TaskBoardHostTransport {
31
+ /** 首次同步:先拉全量 state,必要时顺带做一次 v1 一次性迁移(§11.2 import)。 */
32
+ bootstrap(legacy: readonly TaskRecord[]): Promise<TaskBoardSnapshot>;
33
+ /** 全量快照。 */
34
+ state(): Promise<TaskBoardSnapshot>;
35
+ /** 提交幂等动作,返回 Host 确认后的新快照(不先写未确认状态)。 */
36
+ action(action: TaskBoardAction): Promise<TaskBoardSnapshot>;
37
+ /**
38
+ * 订阅实时事件。listener 带载荷 = SSE 帧(revision/scheduler/power);不带
39
+ * 载荷 = 同步信号(重连成功/页面重新可见),控制器据此重拉全量。
40
+ */
41
+ subscribe(listener: (event?: TaskBoardEventPayload) => void): () => void;
42
+ }
43
+ /**
44
+ * 真实 HTTP + SSE 传输(同源 `/api/nova-task-board/*`)。
45
+ * @param markers - 迁移标记存储(缺省用全局 localStorage)。
46
+ */
47
+ export declare class HttpTaskBoardHostTransport implements TaskBoardHostTransport {
48
+ private readonly markers;
49
+ constructor(markers?: MarkerStorage | undefined);
50
+ bootstrap(legacy: readonly TaskRecord[]): Promise<TaskBoardSnapshot>;
51
+ state(): Promise<TaskBoardSnapshot>;
52
+ action(action: TaskBoardAction): Promise<TaskBoardSnapshot>;
53
+ private post;
54
+ /** 同源 fetch,带超时(超时按传输错误抛,控制器转为可重试的错误条)。 */
55
+ private request;
56
+ /**
57
+ * SSE 订阅:消息帧解析为 `{revision, scheduler, power}`(解析失败按同步信号
58
+ * 处理,重拉兜底);`onopen`(含断线重连成功)与页面重新可见 → 同步信号,
59
+ * 控制器据此重拉全量 state。
60
+ */
61
+ subscribe(listener: (event?: TaskBoardEventPayload) => void): () => void;
62
+ }
@@ -0,0 +1,46 @@
1
+ import type { SettingsScope, SettingsScopeSpec } from '@deepseek-ai/dsh-client-runtime/client';
2
+ import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
3
+ import { type NovaTaskBoardKey } from './locales.ts';
4
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
5
+ interface LocaleNamespaceMap {
6
+ /** Nova task-board surface copy. */
7
+ 'nova-task-board': NovaTaskBoardKey;
8
+ }
9
+ interface SlotMap {
10
+ /**
11
+ * The child slot the Web UI plugin group declares; this card registers
12
+ * into the group instead of the top-level `settings.section` list.
13
+ * Spelled here with the same shape so this package can register without
14
+ * depending on the sibling UI package.
15
+ */
16
+ 'web-ui.plugin.item': {
17
+ kind: 'list';
18
+ scope: 'root';
19
+ owner: WebUiPluginItemOwnerProps;
20
+ };
21
+ }
22
+ }
23
+ /** Owner share of a plugin card (the section supplies nothing). */
24
+ export interface WebUiPluginItemOwnerProps {
25
+ /** Marker field: card owner props are intentionally empty. */
26
+ children?: never;
27
+ }
28
+ declare module '@deepseek-ai/cordis' {
29
+ interface Context {
30
+ /**
31
+ * Optional rc.6 compatibility binder provided by dsh-web-ui-settings;
32
+ * absent when that group plugin is not installed, so callers fall back to
33
+ * the official settings scope.
34
+ */
35
+ webUiSettings?: {
36
+ bind<S>(spec: SettingsScopeSpec<S>): SettingsScope<S>;
37
+ };
38
+ }
39
+ }
40
+ /** Required services (fiber inject waiting — the runtime must be up first). */
41
+ export declare const inject: readonly ["slots", "sessions", "workspaces", "connection", "settingsScope", "locale", "remote"];
42
+ /**
43
+ * Mount the nova task board (T004 真实看板).
44
+ * @param ctx - client root context.
45
+ */
46
+ export declare function apply(ctx: ClientContext): void;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * v1 只读回退数据读取(T004 §12 范围项 8:UI 层对 v1 只读回退数据可正常展示)。
3
+ *
4
+ * 参考实现的旧浏览器账本写在 localStorage `dsh.taskBoard.v1`(键名由其
5
+ * `DEFAULT_STORAGE_KEY` 定义,与宿主/参考实现共享同一浏览器源)。本项目与其
6
+ * 共存(AGENTS.md D3):读取该键做一次性迁移,但**绝不删除原值**——迁移后它
7
+ * 仍是只读回退副本;迁移经 Host 的 `import` action 落进本项目自己的账本
8
+ * (`$DSH_HOME/nova-ui/task-board/`,AGENTS.md D2),写入方只有 Host。
9
+ *
10
+ * 行解析走本项目 core/model 的 `normalizeTask`(宽松归一化:未知字段剥离、
11
+ * 状态回退 todo、坏 schedule 修复或丢弃、不丢行),单条坏行被丢弃而不阻断
12
+ * 整批——Host 侧 `import` 是严格逐行校验,任何坏行都会让整批 import 被拒,
13
+ * 所以浏览器侧先做一次宽松过滤,保证一条陈旧坏数据不阻断迁移。
14
+ */
15
+ import type { TaskRecord } from '../core/model.ts';
16
+ /** 参考实现的 v1 浏览器账本键(只读来源;其内部键名 `dsh.taskBoard.v1`)。 */
17
+ export declare const LEGACY_V1_STORAGE_KEY = "dsh.taskBoard.v1";
18
+ /** 浏览器 localStorage 的读取面(测试注入 fake)。 */
19
+ export interface LegacyStorage {
20
+ getItem(key: string): string | null;
21
+ }
22
+ /**
23
+ * 读取并宽松归一化 v1 回退数据。存储缺失/损坏/非数组 → [](降级为空账本,
24
+ * 绝不抛错拖垮看板启动);数组内逐行 normalizeTask,坏行丢弃。
25
+ * @param storage - 存储面(缺省用全局 localStorage;非浏览器环境为 undefined → [])。
26
+ * @param key - 覆盖存储键(测试用)。
27
+ */
28
+ export declare function readLegacyV1Tasks(storage?: LegacyStorage | undefined, key?: string): TaskRecord[];
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Nova task-board copy: zh-first dictionaries with an English fallback,
3
+ * selected by the document language. Kept dependency-free (no dsh locale
4
+ * service) so the DOM-injected entry row and the standalone board tree share
5
+ * one tiny lookup. Key set 以 zh 为准,en 对齐(对齐参考实现的组织方式)。
6
+ *
7
+ * T004 补全看板/详情/新建/状态/执行设置文案;定时编辑器(T006/T007)与
8
+ * 待确认列(T011)的编辑/确认文案随对应任务补充。
9
+ */
10
+ /** zh dictionary (key-set source of truth). */
11
+ export declare const zh: {
12
+ 'entry.label': string;
13
+ 'board.title': string;
14
+ 'board.close': string;
15
+ 'board.new': string;
16
+ 'board.search': string;
17
+ 'board.empty': string;
18
+ 'board.archive': string;
19
+ 'board.archiveView': string;
20
+ 'board.backToBoard': string;
21
+ 'archive.empty': string;
22
+ 'board.status': string;
23
+ 'board.status.proposed': string;
24
+ 'board.status.backlog': string;
25
+ 'board.status.todo': string;
26
+ 'board.status.running': string;
27
+ 'board.status.done': string;
28
+ 'board.status.failed': string;
29
+ 'board.runs': string;
30
+ 'board.pending': string;
31
+ 'board.updated': string;
32
+ 'board.created': string;
33
+ 'board.hostError': string;
34
+ 'board.retryHost': string;
35
+ 'board.hostMeta': string;
36
+ 'new.title': string;
37
+ 'new.titlePlaceholder': string;
38
+ 'new.description': string;
39
+ 'new.descriptionPlaceholder': string;
40
+ 'new.prompt': string;
41
+ 'new.promptPlaceholder': string;
42
+ 'new.submit': string;
43
+ 'new.cancel': string;
44
+ 'new.required': string;
45
+ 'detail.title': string;
46
+ 'detail.close': string;
47
+ 'detail.prompt': string;
48
+ 'detail.description': string;
49
+ 'detail.execution': string;
50
+ 'detail.noExecution': string;
51
+ 'detail.run': string;
52
+ 'detail.rerun': string;
53
+ 'detail.delete': string;
54
+ 'detail.archive': string;
55
+ 'detail.restore': string;
56
+ 'detail.archivedAt': string;
57
+ 'detail.viewSession': string;
58
+ 'detail.executionStarted': string;
59
+ 'detail.executionEnded': string;
60
+ 'detail.result.succeeded': string;
61
+ 'detail.result.failed': string;
62
+ 'detail.result.cancelled': string;
63
+ 'detail.result.running': string;
64
+ 'delete.title': string;
65
+ 'delete.confirm': string;
66
+ 'delete.ok': string;
67
+ 'delete.cancel': string;
68
+ 'status.move.backlog': string;
69
+ 'status.move.todo': string;
70
+ 'time.justNow': string;
71
+ 'card.scheduled': string;
72
+ 'card.oneShot': string;
73
+ 'detail.schedule': string;
74
+ 'detail.schedule.nextRun': string;
75
+ 'detail.schedule.lastTriggered': string;
76
+ 'detail.schedule.notScheduled': string;
77
+ 'detail.schedule.dueSoon': string;
78
+ 'detail.schedule.enable': string;
79
+ 'detail.schedule.cron': string;
80
+ 'detail.schedule.invalid': string;
81
+ 'detail.schedule.presets': string;
82
+ 'detail.schedule.preset.daily9': string;
83
+ 'detail.schedule.preset.hourly': string;
84
+ 'detail.schedule.preset.tenMin': string;
85
+ 'detail.schedule.preset.weeklyMon9': string;
86
+ 'detail.schedule.modeCron': string;
87
+ 'detail.schedule.modeOneShot': string;
88
+ 'detail.oneShot.runAt': string;
89
+ 'detail.oneShot.remaining': string;
90
+ 'detail.oneShot.cancel': string;
91
+ 'detail.oneShot.executed': string;
92
+ 'detail.oneShot.skipped': string;
93
+ 'detail.oneShot.reschedule': string;
94
+ 'detail.oneShot.invalid': string;
95
+ 'detail.oneShot.dueSoon': string;
96
+ 'detail.oneShot.preset.tenMin': string;
97
+ 'detail.oneShot.preset.tonight21': string;
98
+ 'detail.oneShot.preset.tomorrow9': string;
99
+ 'detail.executionSettings': string;
100
+ 'exec.hint': string;
101
+ 'new.workspace': string;
102
+ 'new.mode': string;
103
+ 'new.permission': string;
104
+ 'exec.workspace.recent': string;
105
+ 'exec.mode.default': string;
106
+ 'exec.mode.defaultSuffix': string;
107
+ 'exec.mode.brokenSuffix': string;
108
+ 'exec.mode.removed': string;
109
+ 'exec.permission.default': string;
110
+ 'exec.permission.read-only': string;
111
+ 'exec.permission.workspace-write': string;
112
+ 'exec.permission.danger-full-access': string;
113
+ 'filter.project': string;
114
+ 'filter.projectAll': string;
115
+ 'filter.projectNone': string;
116
+ 'filter.tags': string;
117
+ 'filter.clearAll': string;
118
+ 'filter.hits': string;
119
+ 'filter.removeTag': string;
120
+ 'board.group.label': string;
121
+ 'board.group.manual': string;
122
+ 'board.group.project': string;
123
+ 'board.group.tag': string;
124
+ 'board.group.ungrouped': string;
125
+ 'board.dragRejected': string;
126
+ 'detail.projectTags': string;
127
+ 'detail.projectPlaceholder': string;
128
+ 'new.tagsPlaceholder': string;
129
+ 'confirm.title': string;
130
+ 'confirm.subtitle': string;
131
+ 'confirm.target': string;
132
+ 'confirm.toBacklog': string;
133
+ 'confirm.toTodo': string;
134
+ 'confirm.submit': string;
135
+ 'confirm.dismiss': string;
136
+ 'confirm.dismissTitle': string;
137
+ 'confirm.dismissMessage': string;
138
+ 'card.proposedBadge': string;
139
+ 'card.source': string;
140
+ 'source.conversation': string;
141
+ 'source.requirement': string;
142
+ 'entry.proposedCount': string;
143
+ 'convert.title': string;
144
+ 'convert.fromConversation': string;
145
+ 'convert.submitProposed': string;
146
+ 'convert.submitDirect': string;
147
+ 'convert.directTarget': string;
148
+ 'convert.directBacklog': string;
149
+ 'convert.directTodo': string;
150
+ 'convert.proposedHint': string;
151
+ 'chat.extractedHint': string;
152
+ 'detail.jumpToConversation': string;
153
+ 'detail.sourceConversation': string;
154
+ 'split.entry': string;
155
+ 'split.subtitle': string;
156
+ 'split.recursiveTitle': string;
157
+ 'split.recursiveHint': string;
158
+ 'split.requirementTitle': string;
159
+ 'split.requirementTitlePlaceholder': string;
160
+ 'split.source': string;
161
+ 'split.sourceText': string;
162
+ 'split.sourceFile': string;
163
+ 'split.text': string;
164
+ 'split.textPlaceholder': string;
165
+ 'split.file': string;
166
+ 'split.filePlaceholder': string;
167
+ 'split.required': string;
168
+ 'split.submit': string;
169
+ 'split.asyncHint': string;
170
+ 'split.batchButton': string;
171
+ 'split.batchTitle': string;
172
+ 'split.batchSubtitle': string;
173
+ 'split.addItem': string;
174
+ 'split.added': string;
175
+ 'split.removeItem': string;
176
+ 'split.confirmAll': string;
177
+ 'split.dismissAll': string;
178
+ 'split.dismissAllTitle': string;
179
+ 'split.dismissAllMessage': string;
180
+ 'split.dismissItemTitle': string;
181
+ 'split.dismissItemMessage': string;
182
+ 'split.empty': string;
183
+ 'split.parentOf': string;
184
+ 'split.parentMissing': string;
185
+ 'split.children': string;
186
+ 'settings.title': string;
187
+ 'settings.description': string;
188
+ 'settings.enabled': string;
189
+ 'settings.enabledHint': string;
190
+ 'settings.announceToAgent': string;
191
+ 'settings.announceToAgentHint': string;
192
+ 'settings.notExposed': string;
193
+ 'settings.readOnly': string;
194
+ };
195
+ /** en dictionary, complete against the zh key set. */
196
+ export declare const en: Record<keyof typeof zh, string>;
197
+ /** The dictionary key union. */
198
+ export type NovaTaskBoardKey = keyof typeof zh;
199
+ /** Active dictionary, picked by the document language at call time. */
200
+ export declare function dictionary(): Record<NovaTaskBoardKey, string>;
201
+ /** Translate a key with optional {name} template params. */
202
+ export declare function t(key: NovaTaskBoardKey, params?: Record<string, string>): string;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * 定时编辑器共享预设(T006 §12.3 cron 周期定时「预设」下拉 + T007 §12.3 一次性
3
+ * 计划「快捷预设」):cron 表达式 → locale 文案键;one-shot 快捷预设 → 相对
4
+ * 当前时刻的计算函数(Host 本地时区)。详情页定时编辑器共用。
5
+ */
6
+ import type { NovaTaskBoardKey } from './locales.ts';
7
+ /** 常用 cron 定时预设(cron → locale label)。 */
8
+ export declare const SCHEDULE_PRESETS: ReadonlyArray<{
9
+ cron: string;
10
+ label: NovaTaskBoardKey;
11
+ }>;
12
+ /** 一次性计划快捷预设(T007 §12.3:label → 相对 now 的 runAt 计算)。 */
13
+ export declare const ONE_SHOT_PRESETS: ReadonlyArray<{
14
+ label: NovaTaskBoardKey;
15
+ at: (now: number) => number;
16
+ }>;
17
+ /** 本地毫秒时间戳 → `datetime-local` 输入值(YYYY-MM-DDTHH:mm,Host 本地时区)。 */
18
+ export declare function toLocalInputValue(ms: number): string;
19
+ /** `datetime-local` 输入值 → 本地毫秒时间戳(解析失败返回 undefined)。 */
20
+ export declare function fromLocalInputValue(value: string): number | undefined;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Nova sidebar entry injection core (adapted from the reference dsh-web-ui
3
+ * shared/client/sidebar-entry-core.ts; mechanism identical, attributes
4
+ * namespaced to data-dsh-nova-* for coexistence, AGENTS.md D3).
5
+ *
6
+ * dsh's sidebar shell exposes no slot an external plugin can register into,
7
+ * so the entry row is injected between the shell's New Session button and the
8
+ * workspace browser. The injection self-heals: a MutationObserver watches the
9
+ * sidebar root and re-inserts the row whenever a React re-render displaces it
10
+ * (re-insertion happens in the same frame, before paint, so no flicker).
11
+ *
12
+ * The row is plain DOM (no React tree) so it can never disturb the shell's
13
+ * reconciliation; the view it toggles is a separate root owned by the caller.
14
+ */
15
+ /** Per-package configuration for one sidebar entry row. */
16
+ export interface NovaSidebarEntryOptions {
17
+ /** Full attribute name identifying the injected row (idempotency key). */
18
+ rowAttribute: string;
19
+ /** CSS selector matching the injected row. */
20
+ rowSelector: string;
21
+ /** L2 semantic-attribute plugin id (data-dsh-plugin value). */
22
+ plugin?: string;
23
+ /** Inline icon markup (matches the shell's 16px nav-icon look). */
24
+ icon: string;
25
+ /** CSS module class names for the row and its two spans (entry / entryIcon / entryLabel). */
26
+ css: Record<string, string>;
27
+ /** Localized row label (aria-label + visible text). */
28
+ label(): string;
29
+ /** Optional localized tooltip (title attribute). */
30
+ tooltip?(): string;
31
+ /** Click action (open/toggle the owning panel). */
32
+ onToggle(): void;
33
+ /** Family-block position: 'before' inserts ahead of sibling plugin rows, 'after' behind them. */
34
+ position: 'before' | 'after';
35
+ /** Selectors of the sibling plugin entry rows this package orders against (its own row included). */
36
+ familySelectors: readonly string[];
37
+ /** Optional active-state bridge; highlights the row while the panel is open. */
38
+ active?: {
39
+ subscribe(listener: () => void): () => void;
40
+ isOpen(): boolean;
41
+ };
42
+ /** Optional numeric badge (T011 §12.7 未确认候选数角标); hidden at 0. */
43
+ badge?: {
44
+ subscribe(listener: () => void): () => void;
45
+ count(): number;
46
+ };
47
+ }
48
+ /**
49
+ * Mount the sidebar entry, waiting for the shell to render and self-healing
50
+ * on later React re-renders.
51
+ * @param options - the row's attribute/icon/copy/action/ordering configuration.
52
+ * @returns disposer removing the entry and its observers.
53
+ */
54
+ export declare function mountSidebarEntry(options: NovaSidebarEntryOptions): () => void;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Sidebar entry injection — package-specific wiring over the shared core.
3
+ *
4
+ * The entry row is injected between the shell's New Session button and the
5
+ * workspace browser (data-dsh-nova-taskboard-entry, nova- 前缀与参考实现
6
+ * data-dsh-taskboard-entry 区分,见 AGENTS.md D3)。The row is plain DOM (no
7
+ * React tree); the board view it toggles is a separate React root mounted in
8
+ * the center column (see board-mount.tsx).
9
+ */
10
+ import type { BoardController } from './board-controller.ts';
11
+ /** Stable data attribute identifying the injected entry row. */
12
+ export declare const ENTRY_SELECTOR = "[data-dsh-nova-taskboard-entry]";
13
+ /**
14
+ * Mount the sidebar entry, waiting for the shell to render and self-healing
15
+ * on later React re-renders.
16
+ * @param controller - the board controller the entry toggles.
17
+ * @returns disposer removing the entry and its observers.
18
+ */
19
+ export declare function mountSidebarEntry(controller: BoardController): () => void;