dsh-archify-projects 0.1.0 → 0.1.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 CHANGED
@@ -27,6 +27,31 @@
27
27
 
28
28
  ---
29
29
 
30
+ ## 效果
31
+
32
+ > 截图来自真实运行——被测仓库就是这个插件自己,数据都是跑出来的,不是设计稿。
33
+
34
+ **总览**:每个仓库一张卡片,直接看出分支、工作区改动、概念数、哪些图已经就绪、有没有人在跑。
35
+
36
+ ![总览:仓库卡片、概念数、图状态](https://raw.githubusercontent.com/guicaiyue/dsh-archify-projects/main/docs/verify/shot-atlas.png)
37
+
38
+ **工作台**:图名 tab 在图片上方(只有文字,不占高度),左边是图,右边是检查器——
39
+ prompt、概念范围、分支、进度、token 用量、运行记录都在这一栏。
40
+
41
+ ![工作台:图名 tab、成品图、右侧检查器](https://raw.githubusercontent.com/guicaiyue/dsh-archify-projects/main/docs/verify/shot-workbench.png)
42
+
43
+ **提问**:对着某张图问细节,agent 在**只读会话**里读代码回答,回答可以直接「补进图里」
44
+ 或变成一张子图。
45
+
46
+ ![提问:只读会话基于图和代码回答](https://raw.githubusercontent.com/guicaiyue/dsh-archify-projects/main/docs/verify/shot-qa.png)
47
+
48
+ **新建图**:图型 tag 直接对应 archify 的 5 种渲染器,点一下告诉你「适合看什么 / 会展示
49
+ 代码库的哪些信息 / 能让你掌握什么」;不选也行,由 agent 判断。
50
+
51
+ ![新建图:图型 tag 与说明](https://raw.githubusercontent.com/guicaiyue/dsh-archify-projects/main/docs/verify/shot-dialog.png)
52
+
53
+ ---
54
+
30
55
  ## 安装
31
56
 
32
57
  装的是**已发布的包**,一条命令,装在哪个 profile 就写哪个名字(默认 `web`):
@@ -37,10 +62,12 @@ dsh plugin --profile web add dsh-archify-projects
37
62
 
38
63
  然后重启 dsh(宿主侧代码,必须重启)。重启后左栏出现「架构建模」。
39
64
 
40
- **还没有 npm 包时**,用 GitHub Release 上的预构建 tarball 装(和 npm 包内容一致):
65
+ 也在 npm 上(`dsh-archify-projects@0.1.1`)。
66
+
67
+ **备用路径**:GitHub Release 上的预构建 tarball(和 npm 包内容一致):
41
68
 
42
69
  ```bash
43
- dsh plugin --profile web add https://github.com/guicaiyue/dsh-archify-projects/releases/download/v0.1.0/dsh-archify-projects-0.1.0.tgz
70
+ dsh plugin --profile web add https://github.com/guicaiyue/dsh-archify-projects/releases/download/v0.1.1/dsh-archify-projects-0.1.1.tgz
44
71
  ```
45
72
 
46
73
  > ⚠️ **不要用 `add github:guicaiyue/dsh-archify-projects` 从源码装**:
package/dist/client.js CHANGED
@@ -1126,7 +1126,8 @@ function Workbench({ t, project, diagram, tab, busy, question, pricing, ago, onT
1126
1126
  className: "dap-iframe",
1127
1127
  title: diagram.title,
1128
1128
  src: ROUTES.artifact(diagram.htmlPath),
1129
- sandbox: "allow-scripts allow-same-origin"
1129
+ sandbox: "allow-scripts allow-same-origin",
1130
+ onLoad: (event) => hideArtifactHeader(event.currentTarget)
1130
1131
  })]
1131
1132
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1132
1133
  className: "dap-frame",
@@ -1416,6 +1417,25 @@ function ThumbStrip({ t, project, selectedId, onSelect, onNewDiagram }) {
1416
1417
  });
1417
1418
  }
1418
1419
  /**
1420
+ * 内嵌查看器里去掉成品自带的页头(大标题 + 状态圆点)。
1421
+ *
1422
+ * 成品 HTML 顶部有一行「<项目名> · 图名」的大标题和一个 pulse-dot,而插件自己的
1423
+ * 查看器标题栏已经写了图名、帧头也写了分支与时间——重复一遍只是白吃画布高度。
1424
+ * 只在 iframe 里注入,点「新窗口」单独打开时仍是成品原样(那里没有外层标题)。
1425
+ * 同源才改得动;被沙箱或跨域挡住就静默跳过,不影响看图。
1426
+ */
1427
+ function hideArtifactHeader(frame) {
1428
+ try {
1429
+ const doc = frame.contentDocument;
1430
+ if (doc === null) return;
1431
+ if (doc.head.querySelector("style[data-embedded=\"compact\"]") !== null) return;
1432
+ const style = doc.createElement("style");
1433
+ style.dataset.embedded = "compact";
1434
+ style.textContent = ".header{display:none!important}";
1435
+ doc.head.appendChild(style);
1436
+ } catch {}
1437
+ }
1438
+ /**
1419
1439
  * 一个图名 tab:只有文字,没有缩略图。
1420
1440
  *
1421
1441
  * 之前用 0.25 倍 iframe 做真预览,一条占 91px,把图片本身压下去;改成纯文字后
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["useState","useMemo","useState","useRef","useCallback","useMemo","Fragment"],"sources":["../src/shared/protocol.ts","../src/shared/types.ts","../src/shared/format.ts","../src/client/api.ts","../src/client/components.tsx","../src/client/page.tsx","../src/client/panel.tsx","../src/client/locale.ts","../src/client/styles.ts","../src/client/entry.tsx"],"sourcesContent":["/**\n * HTTP 协议:路由、请求/响应类型、错误码。\n *\n * host 与 client 共用;改这里等于改接口契约,两侧同时失配会立刻被类型检查抓到。\n */\n\nimport type { Pricing } from './format.js'\nimport type {\n ArchifyType,\n Concept,\n DiagramKind,\n Persistence,\n Project,\n QaEntry,\n RunKind,\n} from './types.js'\n\n/** 所有路由的前缀。 */\nexport const ROUTE_PREFIX = '/dsh-archify-projects'\n\n/** 路由表。host 注册与 client 请求都从这里取,避免字符串各写一份。 */\nexport const ROUTES = {\n health: `${ROUTE_PREFIX}/health`,\n projects: `${ROUTE_PREFIX}/projects`,\n project: (id: string) => `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}`,\n check: (id: string) => `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}/check`,\n update: (id: string) => `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}/update`,\n /** 兼容旧接口:按 kind 重画默认图。 */\n diagram: (id: string, kind: DiagramKind) =>\n `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}/diagram/${kind}`,\n diagrams: (id: string) => `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}/diagrams`,\n customDiagram: (id: string, diagramId: string) =>\n `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}/diagrams/${encodeURIComponent(diagramId)}`,\n regenerate: (id: string, diagramId: string) =>\n `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}/diagrams/${encodeURIComponent(diagramId)}/regenerate`,\n ask: (id: string, diagramId: string) =>\n `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}/diagrams/${encodeURIComponent(diagramId)}/ask`,\n mergeQa: (id: string, qaId: string) =>\n `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}/qa/${encodeURIComponent(qaId)}/merge`,\n childQa: (id: string, qaId: string) =>\n `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}/qa/${encodeURIComponent(qaId)}/child`,\n runs: (id: string) => `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}/runs`,\n artifact: (relativePath: string) =>\n `${ROUTE_PREFIX}/artifact/${relativePath\n .split(/[\\\\/]+/)\n .map((part) => encodeURIComponent(part))\n .join('/')}`,\n} as const\n\n/** 错误码。页面按码决定文案与重试策略,不解析 message。 */\nexport type ApiErrorCode =\n | 'bad-request'\n | 'forbidden'\n | 'not-found'\n | 'conflict'\n | 'path-invalid'\n | 'git-failed'\n | 'agent-unavailable'\n | 'internal'\n\n/** 统一响应外壳。 */\nexport type ApiResult<T> =\n | { readonly ok: true; readonly value: T }\n | {\n readonly ok: false\n readonly error: { readonly code: ApiErrorCode; readonly message: string }\n }\n\n/** GET /health */\nexport interface HealthValue {\n readonly name: string\n readonly version: string\n readonly workspacePath: string\n readonly hasGit: boolean\n readonly hasSessionService: boolean\n readonly presetId: string\n readonly archifySkillPath: string | null\n}\n\n/** POST /projects */\nexport interface CreateProjectRequest {\n readonly name?: string\n readonly repoPath: string\n}\n\n/** POST /projects/:id 的响应:建项目即触发首次运行,所以回整条记录。 */\nexport interface ProjectValue {\n readonly project: Project\n}\n\n/** GET /projects */\nexport interface ProjectsValue {\n readonly projects: readonly Project[]\n readonly workspacePath: string\n /** token 单价;null 表示页面只显示 token,不估算费用。 */\n readonly pricing: Pricing | null\n}\n\n/** PATCH /projects/:id */\nexport interface PatchProjectRequest {\n readonly name?: string\n readonly repoPath?: string\n}\n\n/** POST /projects/:id/update */\nexport interface UpdateValue {\n readonly project: Project\n readonly git: {\n readonly skipped: boolean\n readonly reason: string | null\n readonly from: string | null\n readonly to: string | null\n }\n}\n\n/** POST /projects/:id/check */\nexport interface CheckValue {\n readonly project: Project\n readonly drift: GitDrift\n}\n\n/** Git 漂移:页面用它决定要不要提示「更新」。分支 + 工作区,不看 commit。 */\nexport interface GitDrift {\n readonly branchChanged: boolean\n readonly treeChanged: boolean\n readonly dirty: boolean\n readonly behind: number\n readonly summary: string | null\n}\n\n/** POST /projects/:id/diagram/:kind(旧的按 kind 重画) */\nexport interface RegenerateRequest {\n readonly kind: DiagramKind\n}\n\n/** POST /projects/:id/diagrams:新建一张自定义图。 */\nexport interface CreateDiagramRequest {\n readonly title: string\n readonly prompt: string\n readonly persistence?: Persistence\n readonly parentId?: string | null\n readonly setId?: string | null\n readonly conceptIds?: readonly string[]\n /** archify 渲染器;不传 = 由 agent 判断。 */\n readonly renderer?: ArchifyType | null\n /** 限定取证的目录(相对仓库根);不传 = 全仓库。 */\n readonly pathScope?: readonly string[]\n /** 建完立刻跑;默认 true。 */\n readonly run?: boolean\n}\n\n/** PATCH /projects/:id/diagrams/:diagramId */\nexport interface PatchDiagramRequest {\n readonly title?: string\n readonly prompt?: string\n readonly persistence?: Persistence\n readonly conceptIds?: readonly string[]\n readonly renderer?: ArchifyType | null\n readonly pathScope?: readonly string[]\n}\n\n/** POST .../diagrams/:diagramId/ask */\nexport interface AskRequest {\n readonly question: string\n}\n\n/** 提问回执。 */\nexport interface AskValue {\n readonly sessionId: string | null\n readonly entry: QaEntry\n readonly project: Project\n}\n\n/** 「补进图里」/「创建子图」的回执。 */\nexport interface QaActionValue {\n readonly project: Project\n readonly qa: QaEntry\n /** 「创建子图」时返回新图的 id。 */\n readonly diagramId: string | null\n}\n\n/** 运行发起的统一回执。 */\nexport interface RunValue {\n readonly project: Project\n readonly run: { readonly id: string; readonly kind: RunKind; readonly sessionId: string | null }\n}\n\n/** 概念表回执(GET /projects/:id 里已带 concepts,这里留给未来单独刷新)。 */\nexport interface ConceptsValue {\n readonly concepts: readonly Concept[]\n}\n\n/** 生成产物在 res 里的机器标记(prompt 要求 agent 输出,runner 解析)。 */\nexport const ARTIFACT_MARKER = 'ARCHIFY_ARTIFACT'\n","/**\n * 台账数据模型(纯类型 + 纯构造函数,零 IO)。\n *\n * 这里是 host 与 client 唯一的「事实形状」来源:宿主写盘用它,页面渲染用它,\n * 序列化契约(protocol.ts 的请求/响应)也复用它。改这里的字段等于改协议版本。\n *\n * v2(图册):版本口径从 commit 级降到分支级;图谱从「两张固定图」变成\n * 「默认两张 + 任意自定义图」;概念(Concept)成为项目级一等公民;\n * 图可以临时(只跑这一次)或永久(下次更新沿用同一 prompt)。\n */\n\n/** 台账文件格式版本。字段形状不兼容时 +1,并在 store 里做迁移。 */\nexport const LEDGER_VERSION = 2\n\n/** 两张默认图谱(自定义图统一用 `custom`)。 */\nexport const DIAGRAM_KINDS = ['structure', 'domain'] as const\nexport type DiagramKind = (typeof DIAGRAM_KINDS)[number]\n\n/** 图种类:默认两张 + 自定义。 */\nexport type DiagramMode = DiagramKind | 'custom'\n\n/** Archify 的真实渲染器(见 skills/archify 的 Type router)。 */\nexport const ARCHIFY_TYPES = [\n 'architecture',\n 'workflow',\n 'sequence',\n 'dataflow',\n 'lifecycle',\n] as const\nexport type ArchifyType = (typeof ARCHIFY_TYPES)[number]\n\n/** 图的时效。永久图会在「更新图册」时用同一 prompt 重画;临时图只跑这一次。 */\nexport type Persistence = 'permanent' | 'temporary'\n\n/** 一次运行因何而起。 */\nexport type RunKind = 'create' | 'update' | 'regenerate'\n\n/** 运行状态。 */\nexport type RunStatus = 'running' | 'succeeded' | 'failed'\n\n/** 单张图谱的产物状态机:idle → queued → running → ready | failed。 */\nexport type DiagramStatus = 'idle' | 'queued' | 'running' | 'ready' | 'failed'\n\n/** 工作区状态(暂存 / 未暂存 / 未跟踪)。 */\nexport interface TreeState {\n readonly staged: number\n readonly unstaged: number\n readonly untracked: number\n}\n\n/**\n * 代码库的 Git 快照。\n *\n * 无 Git 目录时 `isRepo=false`,其余字段一律 `null` ——「不知道」和「空」必须能区分,\n * 否则页面会把 `null` 渲染成 0 或空串,用户会以为仓库干净。\n *\n * `commit` 仍保留(同步、排障用),但**不再是版本键**:图的「过期」看分支 + 工作区。\n */\nexport interface GitSnapshot {\n readonly isRepo: boolean\n readonly root: string | null\n readonly branch: string | null\n readonly detached: boolean\n readonly commit: string | null\n readonly commitFull: string | null\n readonly commitTime: string | null\n readonly commitSubject: string | null\n readonly upstream: string | null\n readonly ahead: number | null\n readonly behind: number | null\n readonly dirty: boolean | null\n /** 工作区明细;无 Git 或探测失败时为 null。 */\n readonly tree: TreeState | null\n readonly remoteUrl: string | null\n readonly checkedAt: string\n}\n\n/** 一张图谱的产物与元信息。`htmlPath` / `specPath` 都是相对插件工作区的路径。 */\nexport interface DiagramState {\n /** 稳定 id:默认两张是 `structure` / `domain`,自定义图是 `dgm_xxx`。 */\n readonly id: string\n readonly kind: DiagramMode\n readonly title: string\n /** 自定义图必填;默认图可为 null(用内置口径)。 */\n readonly prompt: string | null\n readonly persistence: Persistence\n /** 父子图:挂在哪张图下。 */\n readonly parentId: string | null\n /** 套图:同一 setId 的图在页面上归为一组(大图 + 细节小图)。 */\n readonly setId: string | null\n /** 这张图引用的概念 id。 */\n readonly conceptIds: readonly string[]\n /** 用户选的 archify 渲染器;null = 由 agent 按 prompt 判断。 */\n readonly renderer: ArchifyType | null\n /** 限定取证的目录(相对仓库根);空数组 = 全仓库。 */\n readonly pathScope: readonly string[]\n readonly status: DiagramStatus\n readonly htmlPath: string | null\n readonly specPath: string | null\n readonly generatedAt: string | null\n /** 生成时的分支(版本键的一半)。 */\n readonly branch: string | null\n /** 生成时的工作区状态(版本键的另一半)。 */\n readonly tree: TreeState | null\n readonly error: string | null\n}\n\n/** 从代码里提取出来的架构概念;项目级,可被多张图引用。 */\nexport interface Concept {\n readonly id: string\n readonly name: string\n readonly summary: string\n /** 支撑这个概念的代码路径(相对仓库根),追问时用来定位。 */\n readonly evidence: readonly string[]\n readonly tags: readonly string[]\n readonly updatedAt: string\n}\n\n/** 一条 agent 进度(`todo_write` 的快照)。 */\nexport interface TodoItem {\n readonly content: string\n readonly status: 'pending' | 'in_progress' | 'completed'\n}\n\n/** 一次运行消耗的 token(来自宿主的 tokenUsage projection)。 */\nexport interface TokenUsage {\n readonly uncachedInput: number\n readonly output: number\n readonly cacheRead: number\n readonly cacheWrite: number\n}\n\n/** 一次运行/一次提问的 token 合计。 */\nexport function usageTotalOf(usage: TokenUsage | null): number {\n if (usage === null) return 0\n return usage.uncachedInput + usage.output + usage.cacheRead + usage.cacheWrite\n}\n\n/** 对一张图的提问与回答。 */\nexport interface QaEntry {\n readonly id: string\n readonly diagramId: string\n readonly question: string\n readonly answer: string\n readonly at: string\n /** 回答是否已经并进图里。 */\n readonly applied: boolean\n /** 问答会话的运行状态;运行中页面据此显示进度。 */\n readonly status: 'running' | 'done' | 'failed'\n /** 问答会话的 todo 快照。 */\n readonly todos: readonly TodoItem[]\n readonly usage: TokenUsage | null\n}\n\n/** 一次 Agent 运行。会话 id 留着是为了能从页面跳回去看日志。 */\nexport interface RunRecord {\n readonly id: string\n readonly kind: RunKind\n readonly status: RunStatus\n readonly sessionId: string | null\n readonly startedAt: string\n readonly finishedAt: string | null\n readonly error: string | null\n readonly note: string | null\n /** 这次运行涉及哪些图(默认图用 kind,自定义图用 id)。 */\n readonly diagramIds: readonly string[]\n /** agent 的 todo 快照(最后写入的一份,用于运行结束后回看)。 */\n readonly todos: readonly TodoItem[]\n readonly usage: TokenUsage | null\n}\n\n/** 一个项目。 */\nexport interface Project {\n readonly id: string\n readonly name: string\n readonly repoPath: string\n readonly createdAt: string\n readonly updatedAt: string\n readonly git: GitSnapshot\n /** 两张默认图(结构图 / 领域图)。 */\n readonly diagrams: Readonly<Record<DiagramKind, DiagramState>>\n /** 用户自定义的图(任意数量,可父子、可套图)。 */\n readonly custom: readonly DiagramState[]\n /** 项目级概念表。 */\n readonly concepts: readonly Concept[]\n /** 提问记录(按图归属)。 */\n readonly qa: readonly QaEntry[]\n /** 最近一次运行(`runs` 的末条,单独冗余一份方便渲染)。 */\n readonly lastRun: RunRecord | null\n /** 运行历史,旧→新,上限由 config.keepRuns 控制。 */\n readonly runs: readonly RunRecord[]\n /** 历史累计 token(不随 runs 裁剪丢失)。 */\n readonly usageTotal: number\n}\n\n/** 台账文件根。 */\nexport interface ProjectLedger {\n readonly version: number\n readonly updatedAt: string\n readonly projects: readonly Project[]\n}\n\n/** 空 Git 快照(无 Git 或尚未探测)。 */\nexport function emptyGitSnapshot(checkedAt: string): GitSnapshot {\n return {\n isRepo: false,\n root: null,\n branch: null,\n detached: false,\n commit: null,\n commitFull: null,\n commitTime: null,\n commitSubject: null,\n upstream: null,\n ahead: null,\n behind: null,\n dirty: null,\n tree: null,\n remoteUrl: null,\n checkedAt,\n }\n}\n\n/** 默认两张图的标题。 */\nconst DEFAULT_TITLES: Record<DiagramKind, string> = {\n structure: '项目结构图',\n domain: '领域图',\n}\n\n/** 空图谱状态。 */\nexport function emptyDiagramState(kind: DiagramKind = 'structure'): DiagramState {\n return {\n id: kind,\n kind,\n title: DEFAULT_TITLES[kind],\n prompt: null,\n persistence: 'permanent',\n parentId: null,\n setId: kind === 'structure' ? 'structure' : null,\n conceptIds: [],\n renderer: 'architecture',\n pathScope: [],\n status: 'idle',\n htmlPath: null,\n specPath: null,\n generatedAt: null,\n branch: null,\n tree: null,\n error: null,\n }\n}\n\n/** 空台账。 */\nexport function emptyLedger(now: string): ProjectLedger {\n return { version: LEDGER_VERSION, updatedAt: now, projects: [] }\n}\n\n/** 默认两张 + 自定义图,统一成一条列表(默认在前)。 */\nexport function allDiagrams(project: Project): readonly DiagramState[] {\n return [...DIAGRAM_KINDS.map((kind) => project.diagrams[kind]), ...project.custom]\n}\n\n/** 按 id 找图;`structure` / `domain` 也能命中默认图。 */\nexport function findDiagram(project: Project, id: string): DiagramState | undefined {\n if ((DIAGRAM_KINDS as readonly string[]).includes(id)) {\n return project.diagrams[id as DiagramKind]\n }\n return project.custom.find((diagram) => diagram.id === id)\n}\n\n/** 不可变地替换一张图(默认图走 record,自定义图走数组)。 */\nexport function withDiagram(project: Project, next: DiagramState): Project {\n if ((DIAGRAM_KINDS as readonly string[]).includes(next.kind)) {\n return {\n ...project,\n diagrams: { ...project.diagrams, [next.kind as DiagramKind]: next },\n }\n }\n return { ...project, custom: project.custom.map((d) => (d.id === next.id ? next : d)) }\n}\n\n/** 需要「更新图册」的图:默认两张 + 所有永久自定义图。 */\nexport function runnableDiagrams(project: Project): readonly DiagramState[] {\n return allDiagrams(project).filter(\n (diagram) =>\n diagram.persistence === 'permanent' || DIAGRAM_KINDS.includes(diagram.kind as DiagramKind),\n )\n}\n\n/** 工作区状态签名,用于判断图是否过期。 */\nexport function treeSignature(tree: TreeState | null): string {\n if (tree === null) return 'none'\n return `${tree.staged}:${tree.unstaged}:${tree.untracked}`\n}\n\n/** 由 Git 快照派生「图谱是否落后于代码」——按分支 + 工作区,不看 commit。 */\nexport function isStale(project: Project): boolean {\n if (!project.git.isRepo) return false\n const ready = allDiagrams(project).filter((d) => d.status === 'ready')\n if (ready.length === 0) return false\n const currentTree = treeSignature(project.git.tree)\n return ready.some((d) => d.branch !== project.git.branch || treeSignature(d.tree) !== currentTree)\n}\n\n/** 运行记录超过这个时长还没结束,就当它已经死了。\n *\n * 宿主被 kill / 断电 / 重启时,`running` 会被原样留在台账里。页面如果照字面\n * 相信它,就会永远显示「生成中」并把按钮全部禁用——用户只能去手改 JSON。 */\nexport const RUN_STALE_MS = 25 * 60 * 1000\n\n/** 这条运行记录还算不算「活着」。 */\nexport function isRunAlive(run: RunRecord, now: number = Date.now()): boolean {\n if (run.status !== 'running') return false\n const started = Date.parse(run.startedAt)\n if (Number.isNaN(started)) return false\n return now - started < RUN_STALE_MS\n}\n\n/** 项目是否有正在进行的运行。 */\nexport function isRunning(project: Project, now: number = Date.now()): boolean {\n return (\n project.runs.some((run) => isRunAlive(run, now)) ||\n allDiagrams(project).some((d) => d.status === 'queued' || d.status === 'running')\n )\n}\n\n/** 有运行记录卡在 running、但其实早就过期了的项目。 */\nexport function hasStaleRun(project: Project, now: number = Date.now()): boolean {\n return project.runs.some((run) => run.status === 'running' && !isRunAlive(run, now))\n}\n\n/** 项目名兜底:名称空时用仓库目录名。 */\nexport function defaultProjectName(repoPath: string): string {\n const parts = repoPath.split(/[\\\\/]+/).filter((part) => part.length > 0)\n return parts.length > 0 ? (parts[parts.length - 1] as string) : repoPath\n}\n\n/** 构造项目时要给的输入(全部显式,便于测试确定性)。 */\nexport interface CreateProjectInput {\n readonly id: string\n readonly name: string\n readonly repoPath: string\n readonly now: string\n readonly git: GitSnapshot\n}\n\n/** 纯构造:不读盘、不探测,把已知事实拼成一条台账记录。 */\nexport function createProject(input: CreateProjectInput): Project {\n return {\n id: input.id,\n name: input.name,\n repoPath: input.repoPath,\n createdAt: input.now,\n updatedAt: input.now,\n git: input.git,\n diagrams: { structure: emptyDiagramState('structure'), domain: emptyDiagramState('domain') },\n custom: [],\n concepts: [],\n qa: [],\n lastRun: null,\n runs: [],\n usageTotal: 0,\n }\n}\n\n/** 当前正在进行的 todo(没有就是 null)。 */\nexport function activeTodo(run: RunRecord | null): TodoItem | null {\n if (run === null) return null\n return run.todos.find((todo) => todo.status === 'in_progress') ?? null\n}\n","/**\n * 展示与文件名用的纯函数(零 IO)。\n *\n * 这些函数被 host(拼目录名、写日志)和 client(渲染 Git 条、时间)同时使用,\n * 所以不能碰 Date.now() —— 当前时间一律由调用方传入,测试才能确定性。\n */\n\nimport { allDiagrams, type DiagramKind, type GitSnapshot, type Project } from './types.js'\n\n/** 空值占位符,页面统一用它,避免各处出现 '' / '-' / '—' 三种。 */\nexport const PLACEHOLDER = '—'\n\n/** commit 短写(7 位);空值给占位符。 */\nexport function shortCommit(commit: string | null | undefined): string {\n if (commit === null || commit === undefined || commit.length === 0) return PLACEHOLDER\n return commit.slice(0, 7)\n}\n\n/** 本地时间戳 `YYYYMMDD-HHMMSS`,用作 archify 目录名后缀。 */\nexport function formatStamp(date: Date): string {\n const pad = (n: number): string => String(n).padStart(2, '0')\n return (\n `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}` +\n `-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`\n )\n}\n\n/** 相对时间的结构化形态:单位与数值分开,交给调用方决定用哪种语言说。 */\nexport interface RelativeSpan {\n readonly kind: 'now' | 'minutes' | 'hours' | 'days' | 'date'\n readonly value: number\n readonly date?: string\n}\n\n/**\n * 相对时间(结构化)。\n *\n * 页面要中英双语,日文/德文同理,所以这里**不拼句子**,只回答\n * 「多久以前、以什么粒度」—— 拼句子是渲染层的事。\n */\nexport function relativeSpan(iso: string | null | undefined, now: Date): RelativeSpan | null {\n if (iso === null || iso === undefined || iso.length === 0) return null\n const then = new Date(iso).getTime()\n if (Number.isNaN(then)) return null\n const deltaSec = Math.round((now.getTime() - then) / 1000)\n if (deltaSec < 60) return { kind: 'now', value: 0 }\n if (deltaSec < 3600) return { kind: 'minutes', value: Math.floor(deltaSec / 60) }\n if (deltaSec < 86400) return { kind: 'hours', value: Math.floor(deltaSec / 3600) }\n if (deltaSec < 86400 * 30) return { kind: 'days', value: Math.floor(deltaSec / 86400) }\n return { kind: 'date', value: 0, date: new Date(then).toISOString().slice(0, 10) }\n}\n\n/** 相对时间文案(中文)。宿主日志与兜底用;页面走 `relativeSpan`。 */\nexport function formatRelative(iso: string | null | undefined, now: Date): string {\n if (iso === null || iso === undefined || iso.length === 0) return PLACEHOLDER\n const then = new Date(iso).getTime()\n if (Number.isNaN(then)) return PLACEHOLDER\n const deltaSec = Math.round((now.getTime() - then) / 1000)\n if (deltaSec < 0) return '刚刚'\n if (deltaSec < 60) return '刚刚'\n if (deltaSec < 3600) return `${Math.floor(deltaSec / 60)} 分钟前`\n if (deltaSec < 86400) return `${Math.floor(deltaSec / 3600)} 小时前`\n if (deltaSec < 86400 * 30) return `${Math.floor(deltaSec / 86400)} 天前`\n return new Date(then).toISOString().slice(0, 10)\n}\n\n/** ahead / behind 文案。两者都为 null(无上游)时返回 null,页面据此隐藏整块。 */\nexport function aheadBehindText(ahead: number | null, behind: number | null): string | null {\n if (ahead === null && behind === null) return null\n const parts: string[] = []\n if (ahead !== null && ahead > 0) parts.push(`↑${ahead}`)\n if (behind !== null && behind > 0) parts.push(`↓${behind}`)\n return parts.length > 0 ? parts.join(' ') : '同步'\n}\n\n/** Git 单行摘要:`branch @ shortcommit`,无 Git 时说明来源是当前目录。 */\nexport function gitSummary(git: GitSnapshot): string {\n if (!git.isRepo) return '非 Git 目录 · 基于当前目录分析'\n const where = git.detached ? `detached ${shortCommit(git.commit)}` : (git.branch ?? PLACEHOLDER)\n return `${where} @ ${shortCommit(git.commit)}`\n}\n\n/** 目录名安全化:只留 ASCII 字母数字与短横,其它折叠成单个短横。 */\nexport function slugify(input: string): string {\n const ascii = input\n .normalize('NFKD')\n .replace(/[^\\x20-\\x7E]/g, '')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n if (ascii.length === 0) return 'project'\n return ascii.slice(0, 40).replace(/-+$/g, '')\n}\n\n/** archify 目录名:`<type>-<slug>-<stamp>`,与 Skill 的约定一致。 */\nexport function archifyDirName(kind: DiagramKind, slug: string, stamp: string): string {\n return `${kind}-${slugify(slug)}-${stamp}`\n}\n\n/** 图谱类型的中文标题。 */\nexport function diagramTitle(kind: DiagramKind): string {\n return kind === 'structure' ? '项目结构图' : '领域图'\n}\n\n/** 两张图里最新的生成时间(用于「上次分析」)。 */\nexport function latestGeneratedAt(project: Project): string | null {\n const stamps = allDiagrams(project)\n .map((diagram) => diagram.generatedAt)\n .filter((v): v is string => v !== null)\n .sort()\n return stamps.length > 0 ? (stamps[stamps.length - 1] as string) : null\n}\n\n/** token 数的紧凑写法:1234 → 1.2k,12_345_678 → 12.3M。 */\nexport function formatTokens(n: number): string {\n if (!Number.isFinite(n) || n <= 0) return '0'\n if (n < 1000) return String(Math.round(n))\n if (n < 1_000_000) return `${(n / 1000).toFixed(n < 10_000 ? 1 : 0)}k`\n return `${(n / 1_000_000).toFixed(1)}M`\n}\n\n/** 费用估算:按每百万 token 的输入/输出单价(0 表示不显示)。 */\nexport interface Pricing {\n readonly inputPerMTok: number\n readonly outputPerMTok: number\n readonly cacheReadPerMTok: number\n}\n\n/** 估算费用(单位:货币,由配置决定是人民币还是美元)。 */\nexport function estimateCost(\n usage: { uncachedInput: number; output: number; cacheRead: number; cacheWrite: number } | null,\n pricing: Pricing | null,\n): number | null {\n if (usage === null || pricing === null) return null\n const perM = 1_000_000\n const cost =\n (usage.uncachedInput / perM) * pricing.inputPerMTok +\n (usage.output / perM) * pricing.outputPerMTok +\n (usage.cacheRead / perM) * pricing.cacheReadPerMTok\n return cost\n}\n\n/** 把绝对路径缩成「末两段」,页面里放不下全路径时用。 */\nexport function tailPath(fullPath: string, segments = 2): string {\n const parts = fullPath.split(/[\\\\/]+/).filter((p) => p.length > 0)\n if (parts.length <= segments) return fullPath\n return `…/${parts.slice(-segments).join('/')}`\n}\n","/**\n * 客户端 HTTP 封装。\n *\n * 只用同源 fetch(`credentials: 'same-origin'`),不引入任何请求库 ——\n * 插件产物必须能在外置 11 个 baseline 模块之外零依赖运行。\n */\n\nimport type { ApiResult } from '../shared/protocol.js'\n\n/** 带错误码的失败。 */\nexport class ApiError extends Error {\n readonly code: string\n\n constructor(code: string, message: string) {\n super(message)\n this.name = 'ApiError'\n this.code = code\n }\n}\n\n/** 发一次请求并解包 `{ok, value}`。 */\nexport async function api<T>(path: string, init?: RequestInit): Promise<T> {\n const response = await fetch(path, {\n credentials: 'same-origin',\n headers: init?.body === undefined ? undefined : { 'Content-Type': 'application/json' },\n ...init,\n })\n let payload: unknown\n try {\n payload = await response.json()\n } catch {\n throw new ApiError('bad-response', `服务返回了非 JSON 内容(HTTP ${response.status})`)\n }\n const result = payload as ApiResult<T>\n if (result.ok === true) return result.value\n const code = result.error?.code ?? 'unknown'\n const message = result.error?.message ?? '未知错误'\n throw new ApiError(code, message)\n}\n\n/** POST/PATCH 的便捷封装。 */\nexport function send<T>(\n path: string,\n method: 'POST' | 'PATCH' | 'DELETE',\n body?: unknown,\n): Promise<T> {\n return api<T>(path, {\n method,\n body: body === undefined ? undefined : JSON.stringify(body),\n })\n}\n","/**\n * 页面里可复用的小组件:标签、状态点、两个对话框。\n *\n * 只做展示与受控输入;请求与状态全部留在 page.tsx —— 组件不碰 fetch,\n * 测试才能只渲染、不 mock 网络。\n */\n\nimport { useMemo, useState } from 'react'\nimport type { ReactElement, ReactNode } from 'react'\nimport {\n ARCHIFY_TYPES,\n type ArchifyType,\n type Concept,\n type Persistence,\n type Project,\n} from '../shared/types.js'\nimport type { Translate } from './panel.js'\n\n/** 运行记录里的时钟:按本地时区显示 HH:mm:ss。 */\nexport function clockText(iso: string): string {\n const at = new Date(iso)\n if (Number.isNaN(at.getTime())) return iso.slice(11, 19)\n const pad = (n: number): string => String(n).padStart(2, '0')\n return `${pad(at.getHours())}:${pad(at.getMinutes())}:${pad(at.getSeconds())}`\n}\n\n/** 小标签。 */\nexport function Chip({\n tone = 'default',\n dot = false,\n children,\n}: {\n tone?: 'default' | 'ok' | 'warn' | 'accent'\n dot?: boolean\n children: ReactNode\n}): ReactElement {\n const cls = tone === 'default' ? 'dap-chip' : `dap-chip dap-chip--${tone}`\n return (\n <span className={cls}>\n {dot ? <span className=\"dap-chip__dot\" aria-hidden=\"true\" /> : null}\n {children}\n </span>\n )\n}\n\n/** 状态点。 */\nexport function Dot({ tone = 'ok' }: { tone?: 'ok' | 'warn' | 'err' | 'run' }): ReactElement {\n return (\n <span className={tone === 'ok' ? 'dap-dot' : `dap-dot dap-dot--${tone}`} aria-hidden=\"true\" />\n )\n}\n\n/** 新增项目对话框。 */\nexport function NewProjectDialog({\n t,\n busy,\n onClose,\n onSubmit,\n}: {\n t: Translate\n busy: boolean\n onClose: () => void\n onSubmit: (name: string, repoPath: string) => void\n}): ReactElement {\n const [name, setName] = useState('')\n const [repoPath, setRepoPath] = useState('')\n const [invalid, setInvalid] = useState(false)\n\n const submit = (): void => {\n if (repoPath.trim().length === 0) {\n setInvalid(true)\n return\n }\n onSubmit(name.trim(), repoPath.trim())\n }\n\n return (\n <div\n className=\"dap-mask\"\n role=\"presentation\"\n onClick={(e) => e.target === e.currentTarget && onClose()}\n >\n <div className=\"dap-dialog\" role=\"dialog\" aria-modal=\"true\" aria-label={t('dialog.addTitle')}>\n <h2 className=\"dap-dialog__title\">{t('dialog.addTitle')}</h2>\n <p className=\"dap-dialog__sub\">{t('empty.body')}</p>\n <label className=\"dap-field\">\n <span className=\"dap-field__label\">{t('field.name')}</span>\n <input\n className=\"dap-input\"\n value={name}\n placeholder={t('field.namePlaceholder')}\n onChange={(e) => setName(e.target.value)}\n />\n </label>\n <label className=\"dap-field\" style={{ marginTop: 8 }}>\n <span className=\"dap-field__label\">{t('field.path')}</span>\n <input\n className=\"dap-input\"\n value={repoPath}\n placeholder={t('field.pathPlaceholder')}\n aria-invalid={invalid}\n onChange={(e) => {\n setRepoPath(e.target.value)\n setInvalid(false)\n }}\n />\n </label>\n {invalid ? <p className=\"dap-notice dap-notice--error\">{t('field.pathRequired')}</p> : null}\n <div className=\"dap-dialog__foot\">\n <span className=\"dap-hint\">{t('git.noRepoHint')}</span>\n <div style={{ display: 'flex', gap: 6 }}>\n <button className=\"dap-btn dap-btn--sm\" onClick={onClose} disabled={busy}>\n {t('action.cancel')}\n </button>\n <button\n className=\"dap-btn dap-btn--sm dap-btn--primary\"\n onClick={submit}\n disabled={busy}\n >\n {t('action.submit')}\n </button>\n </div>\n </div>\n </div>\n </div>\n )\n}\n\nexport interface DiagramDraft {\n readonly title: string\n readonly prompt: string\n readonly persistence: Persistence\n readonly parentId: string | null\n readonly conceptIds: readonly string[]\n readonly renderer: ArchifyType | null\n readonly pathScope: readonly string[]\n}\n\n/** 从概念证据里推几个目录候选,够弹窗做「目录范围」。 */\nexport function scopeOptions(project: Project): readonly string[] {\n const dirs = new Set<string>()\n for (const concept of project.concepts) {\n for (const evidence of concept.evidence) {\n const parts = evidence.split('/').filter((part) => part.length > 0)\n if (parts.length === 0) continue\n const top = parts[0] as string\n dirs.add(\n parts.length >= 2 && (top === 'src' || top === 'packages' || top === 'apps')\n ? `${top}/${parts[1] as string}`\n : top,\n )\n }\n }\n const out = [...dirs].slice(0, 8)\n return out.length > 0 ? out : ['src']\n}\n\n/** 新建图对话框。 */\nexport function NewDiagramDialog({\n t,\n project,\n selectedId,\n busy,\n onClose,\n onSubmit,\n}: {\n t: Translate\n project: Project\n selectedId: string | null\n busy: boolean\n onClose: () => void\n onSubmit: (draft: DiagramDraft) => void\n}): ReactElement {\n const [prompt, setPrompt] = useState('')\n const [persistence, setPersistence] = useState<Persistence>('permanent')\n const [underCurrent, setUnderCurrent] = useState(selectedId !== null)\n const [chosen, setChosen] = useState<readonly string[]>([])\n const [renderer, setRenderer] = useState<ArchifyType | null>(null)\n const [pathScope, setPathScope] = useState<readonly string[]>([])\n const concepts = useMemo(() => project.concepts.slice(0, 24), [project.concepts])\n const scopes = useMemo(() => scopeOptions(project), [project])\n\n const toggleConcept = (id: string): void => {\n setChosen((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]))\n }\n const toggleScope = (dir: string): void => {\n setPathScope((prev) => (prev.includes(dir) ? prev.filter((x) => x !== dir) : [...prev, dir]))\n }\n\n const submit = (): void => {\n onSubmit({\n title: prompt.trim().slice(0, 24),\n prompt: prompt.trim(),\n persistence,\n parentId: underCurrent && selectedId !== null ? selectedId : null,\n conceptIds: chosen,\n renderer,\n pathScope,\n })\n }\n\n return (\n <div\n className=\"dap-mask\"\n role=\"presentation\"\n onClick={(e) => e.target === e.currentTarget && onClose()}\n >\n <div\n className=\"dap-dialog dap-dialog--wide\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={t('dialog.newDiagram')}\n >\n <h2 className=\"dap-dialog__title\">{t('dialog.newDiagram')}</h2>\n <p className=\"dap-dialog__sub\">{t('dialog.newDiagramSub')}</p>\n\n <div className=\"dap-dialog__row\" style={{ marginBottom: 4 }}>\n <span className=\"dap-field__label\">{t('field.renderer')}</span>\n <span className=\"dap-hint\">{t('field.rendererHint')}</span>\n </div>\n <div className=\"dap-typebar\">\n <button\n className={renderer === null ? 'dap-type-tag dap-type-tag--on' : 'dap-type-tag'}\n onClick={() => setRenderer(null)}\n >\n {t('renderer.auto')} <span className=\"dap-type-tag__k\">agent</span>\n </button>\n {ARCHIFY_TYPES.map((kind) => (\n <button\n key={kind}\n className={renderer === kind ? 'dap-type-tag dap-type-tag--on' : 'dap-type-tag'}\n onClick={() => setRenderer(kind)}\n >\n {t(`renderer.${kind}`)} <span className=\"dap-type-tag__k\">{kind}</span>\n </button>\n ))}\n </div>\n <div className=\"dap-type-detail\">\n {renderer === null ? (\n <div className=\"dap-type-detail__empty\">{t('renderer.autoHint')}</div>\n ) : (\n <>\n <div className=\"dap-type-detail__head\">\n <b>{t(`renderer.${renderer}`)}</b>\n <code>{renderer}</code>\n </div>\n <div className=\"dap-type-detail__row\">\n <span>{t('renderer.fit')}</span>\n <p>{t(`renderer.${renderer}.fit`)}</p>\n </div>\n <div className=\"dap-type-detail__row\">\n <span>{t('renderer.show')}</span>\n <p>{t(`renderer.${renderer}.show`)}</p>\n </div>\n <div className=\"dap-type-detail__row\">\n <span>{t('renderer.grasp')}</span>\n <p>{t(`renderer.${renderer}.grasp`)}</p>\n </div>\n </>\n )}\n </div>\n\n <div className=\"dap-field\" style={{ marginTop: 10 }}>\n <span className=\"dap-field__label\">{t('field.prompt')}</span>\n <textarea\n className=\"dap-textarea\"\n value={prompt}\n placeholder={\n renderer === null\n ? t('field.promptPlaceholder')\n : `${t(`renderer.${renderer}`)}:${t('field.promptPlaceholder')}`\n }\n onChange={(e) => setPrompt(e.target.value)}\n />\n </div>\n\n <div className=\"dap-dialog__row\" style={{ alignItems: 'flex-start' }}>\n <span className=\"dap-field__label\" style={{ flex: 'none' }}>\n {t('field.concepts')}\n </span>\n <div>\n <div className=\"dap-concepts\">\n {concepts.map((concept: Concept) => (\n <button\n key={concept.id}\n className={\n chosen.includes(concept.id) ? 'dap-concept dap-concept--on' : 'dap-concept'\n }\n onClick={() => toggleConcept(concept.id)}\n title={concept.summary}\n >\n {concept.name}\n </button>\n ))}\n </div>\n <p className=\"dap-hint\">{t('scope.conceptHint')}</p>\n </div>\n </div>\n\n <div className=\"dap-dialog__row\" style={{ alignItems: 'flex-start' }}>\n <span className=\"dap-field__label\" style={{ flex: 'none' }}>\n {t('field.pathScope')}\n </span>\n <div>\n <div className=\"dap-concepts\">\n {scopes.map((dir) => (\n <button\n key={dir}\n className={\n pathScope.includes(dir) ? 'dap-concept dap-concept--on' : 'dap-concept'\n }\n onClick={() => toggleScope(dir)}\n >\n {dir}\n </button>\n ))}\n </div>\n <p className=\"dap-hint\">{t('scope.pathHint')}</p>\n </div>\n </div>\n\n <div className=\"dap-dialog__row\">\n <span className=\"dap-field__label\">{t('field.persistence')}</span>\n <div className=\"dap-seg\">\n <button\n className={\n persistence === 'permanent' ? 'dap-seg__btn dap-seg__btn--on' : 'dap-seg__btn'\n }\n onClick={() => setPersistence('permanent')}\n >\n {t('persistence.permanent')}\n </button>\n <button\n className={\n persistence === 'temporary' ? 'dap-seg__btn dap-seg__btn--on' : 'dap-seg__btn'\n }\n onClick={() => setPersistence('temporary')}\n >\n {t('persistence.temporary')}\n </button>\n </div>\n </div>\n <p className=\"dap-hint\">\n {persistence === 'permanent'\n ? t('persistence.permanentHint')\n : t('persistence.temporaryHint')}\n </p>\n\n <div className=\"dap-dialog__row\">\n <span className=\"dap-field__label\">{t('field.parent')}</span>\n <div className=\"dap-seg\">\n <button\n className={!underCurrent ? 'dap-seg__btn dap-seg__btn--on' : 'dap-seg__btn'}\n onClick={() => setUnderCurrent(false)}\n >\n {t('parent.none')}\n </button>\n <button\n className={underCurrent ? 'dap-seg__btn dap-seg__btn--on' : 'dap-seg__btn'}\n onClick={() => setUnderCurrent(true)}\n disabled={selectedId === null}\n >\n {t('parent.current')}\n </button>\n </div>\n </div>\n\n <div className=\"dap-dialog__foot\">\n <span className=\"dap-hint\">{t('qa.mergeHint')}</span>\n <div style={{ display: 'flex', gap: 6 }}>\n <button className=\"dap-btn dap-btn--sm\" onClick={onClose} disabled={busy}>\n {t('action.cancel')}\n </button>\n <button\n className=\"dap-btn dap-btn--sm dap-btn--primary\"\n onClick={submit}\n disabled={busy || prompt.trim().length === 0}\n >\n {t('action.submit')}\n </button>\n </div>\n </div>\n </div>\n </div>\n )\n}\n","/**\n * 插件主页面:图册(总览) + 工作台(三栏)。\n *\n * 数据全部走插件自己的 HTTP 路由;有任务在跑时 2.5s 轮询,跑完自动停 ——\n * 不新增 WebSocket、不新增宿主契约。所有动作都有「进行中」状态与错误回执,\n * 失败不吞。\n */\n\nimport { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport type { ReactElement } from 'react'\nimport { ROUTES } from '../shared/protocol.js'\nimport type {\n AskValue,\n ProjectValue,\n ProjectsValue,\n QaActionValue,\n RunValue,\n UpdateValue,\n} from '../shared/protocol.js'\nimport {\n allDiagrams,\n findDiagram,\n isRunning,\n isStale,\n usageTotalOf,\n type DiagramState,\n type Project,\n type TodoItem,\n type TokenUsage,\n} from '../shared/types.js'\nimport { estimateCost, formatTokens, relativeSpan, type Pricing } from '../shared/format.js'\nimport { api, ApiError, send } from './api.js'\nimport {\n Chip,\n Dot,\n NewDiagramDialog,\n NewProjectDialog,\n clockText,\n type DiagramDraft,\n} from './components.js'\nimport type { Translate } from './panel.js'\n\n/** 页面状态。 */\ninterface PageState {\n readonly loading: boolean\n readonly error: string | null\n readonly projects: readonly Project[]\n readonly workspacePath: string | null\n readonly pricing: Pricing | null\n}\n\nconst INITIAL: PageState = {\n loading: true,\n error: null,\n projects: [],\n workspacePath: null,\n pricing: null,\n}\n\n/** 有任务在跑时的轮询间隔。 */\nconst POLL_MS = 2500\n\n/** 图册 / 工作台。 */\ntype Screen = 'atlas' | 'wb'\n\n/** 右栏页签。 */\ntype Tab = 'info' | 'qa'\n\n/** 页面组件。 */\nexport function StudioPage({ t }: { t: Translate }): ReactElement {\n const [state, setState] = useState<PageState>(INITIAL)\n const [screen, setScreen] = useState<Screen>('atlas')\n const [projectId, setProjectId] = useState<string | null>(null)\n const [diagramId, setDiagramId] = useState<string | null>(null)\n const [tab, setTab] = useState<Tab>('info')\n const [dialog, setDialog] = useState<'project' | 'diagram' | null>(null)\n const [busy, setBusy] = useState<string | null>(null)\n const [notice, setNotice] = useState<{ text: string; error: boolean } | null>(null)\n const [question, setQuestion] = useState('')\n const mounted = useRef(true)\n\n useEffect(() => {\n mounted.current = true\n return () => {\n mounted.current = false\n }\n }, [])\n\n const load = useCallback(async (): Promise<void> => {\n try {\n const value = await api<ProjectsValue>(ROUTES.projects)\n if (!mounted.current) return\n setState({\n loading: false,\n error: null,\n projects: value.projects,\n workspacePath: value.workspacePath,\n pricing: value.pricing ?? null,\n })\n } catch (err) {\n if (!mounted.current) return\n setState((prev) => ({ ...prev, loading: false, error: messageOf(err) }))\n }\n }, [])\n\n useEffect(() => {\n void load()\n }, [load])\n\n const running = state.projects.some((project) => isRunning(project))\n useEffect(() => {\n if (!running) return undefined\n const timer = setInterval(() => void load(), POLL_MS)\n return () => clearInterval(timer)\n }, [running, load])\n\n const project = useMemo(\n () => state.projects.find((item) => item.id === projectId) ?? null,\n [state.projects, projectId],\n )\n const diagram = useMemo(\n () => (project !== null && diagramId !== null ? findDiagram(project, diagramId) : undefined),\n [project, diagramId],\n )\n\n // 数据刷新后选中的图可能被删:回落到第一张默认图\n useEffect(() => {\n if (project === null) return\n if (diagramId !== null && findDiagram(project, diagramId) !== undefined) return\n setDiagramId(project.diagrams.structure.id)\n }, [project, diagramId])\n\n const applyProject = useCallback((next: Project): void => {\n if (!mounted.current) return\n setState((prev) => ({\n ...prev,\n projects: prev.projects.map((item) => (item.id === next.id ? next : item)),\n }))\n }, [])\n\n const fail = useCallback(\n (err: unknown): void => {\n setNotice({ text: t('error.generic', { message: messageOf(err) }), error: true })\n },\n [t],\n )\n\n const openProject = useCallback((id: string): void => {\n setProjectId(id)\n setScreen('wb')\n setTab('info')\n setDiagramId('structure')\n }, [])\n\n const runUpdate = async (target: Project): Promise<void> => {\n setBusy(`update:${target.id}`)\n setNotice(null)\n try {\n const value = await send<UpdateValue>(ROUTES.update(target.id), 'POST')\n applyProject(value.project)\n } catch (err) {\n fail(err)\n } finally {\n setBusy(null)\n }\n }\n\n const updateAll = async (): Promise<void> => {\n for (const item of state.projects) {\n if (isRunning(item)) continue\n await runUpdate(item)\n }\n }\n\n const regenerate = async (): Promise<void> => {\n if (project === null || diagram === undefined) return\n setBusy(`regen:${diagram.id}`)\n setNotice(null)\n try {\n const value = await send<RunValue>(ROUTES.regenerate(project.id, diagram.id), 'POST')\n applyProject(value.project)\n } catch (err) {\n fail(err)\n } finally {\n setBusy(null)\n }\n }\n\n const createProject = async (name: string, repoPath: string): Promise<void> => {\n setBusy('create:project')\n setNotice(null)\n try {\n const value = await send<ProjectValue>(ROUTES.projects, 'POST', { name, repoPath })\n setDialog(null)\n if (!mounted.current) return\n setState((prev) => ({\n ...prev,\n projects: [value.project, ...prev.projects.filter((p) => p.id !== value.project.id)],\n }))\n setProjectId(value.project.id)\n setDiagramId('structure')\n setScreen('wb')\n } catch (err) {\n fail(err)\n } finally {\n setBusy(null)\n }\n }\n\n const createDiagram = async (draft: DiagramDraft): Promise<void> => {\n if (project === null) return\n setBusy('create:diagram')\n setNotice(null)\n try {\n const value = await send<ProjectValue>(ROUTES.diagrams(project.id), 'POST', draft)\n setDialog(null)\n applyProject(value.project)\n const newest = value.project.custom[value.project.custom.length - 1]\n if (newest !== undefined) setDiagramId(newest.id)\n setNotice({ text: t('notice.running'), error: false })\n } catch (err) {\n fail(err)\n } finally {\n setBusy(null)\n }\n }\n\n const ask = async (): Promise<void> => {\n if (project === null || diagram === undefined || question.trim().length === 0) return\n setBusy('ask')\n setNotice(null)\n try {\n const value = await send<AskValue>(ROUTES.ask(project.id, diagram.id), 'POST', {\n question: question.trim(),\n })\n setQuestion('')\n applyProject(value.project)\n } catch (err) {\n fail(err)\n } finally {\n setBusy(null)\n }\n }\n\n const applyQa = async (qaId: string, action: 'merge' | 'child'): Promise<void> => {\n if (project === null) return\n setBusy(`${action}:${qaId}`)\n setNotice(null)\n try {\n const path =\n action === 'merge' ? ROUTES.mergeQa(project.id, qaId) : ROUTES.childQa(project.id, qaId)\n const value = await send<QaActionValue>(path, 'POST')\n applyProject(value.project)\n setNotice({ text: t(action === 'merge' ? 'notice.merged' : 'notice.child'), error: false })\n if (action === 'child' && value.diagramId !== null) setDiagramId(value.diagramId)\n } catch (err) {\n fail(err)\n } finally {\n setBusy(null)\n }\n }\n\n const setPersistence = async (persistence: 'permanent' | 'temporary'): Promise<void> => {\n if (project === null || diagram === undefined) return\n setBusy('persistence')\n try {\n const value = await send<ProjectValue>(\n ROUTES.customDiagram(project.id, diagram.id),\n 'PATCH',\n {\n persistence,\n },\n )\n applyProject(value.project)\n } catch (err) {\n fail(err)\n } finally {\n setBusy(null)\n }\n }\n\n const removeDiagram = async (): Promise<void> => {\n if (project === null || diagram === undefined) return\n setBusy('delete:diagram')\n try {\n const value = await send<ProjectValue>(ROUTES.customDiagram(project.id, diagram.id), 'DELETE')\n applyProject(value.project)\n setDiagramId('structure')\n } catch (err) {\n fail(err)\n } finally {\n setBusy(null)\n }\n }\n\n const ago = (iso: string | null): string => {\n const span = relativeSpan(iso, new Date())\n if (span === null) return t('status.none')\n if (span.kind === 'now') return t('time.now')\n if (span.kind === 'minutes') return t('time.minutes', { n: span.value })\n if (span.kind === 'hours') return t('time.hours', { n: span.value })\n if (span.kind === 'days') return t('time.days', { n: span.value })\n return span.date ?? t('status.none')\n }\n\n if (screen === 'wb' && project !== null) {\n return (\n <div className=\"dap-page\">\n <header className=\"dap-head\">\n <div>\n <button\n className=\"dap-btn dap-btn--ghost dap-btn--sm\"\n onClick={() => setScreen('atlas')}\n >\n ← {t('action.back')}\n </button>\n <h1 className=\"dap-head__title\" style={{ marginTop: 6 }}>\n {project.name}\n </h1>\n <p className=\"dap-head__sub\">{project.repoPath}</p>\n </div>\n <div className=\"dap-head__actions\">\n <button\n className=\"dap-btn dap-btn--sm dap-btn--primary\"\n onClick={() => void runUpdate(project)}\n disabled={busy !== null || isRunning(project)}\n >\n {t('action.update')}\n </button>\n </div>\n </header>\n <div className=\"dap-scroll dap-scroll--wb\">\n {notice !== null ? (\n <p className={notice.error ? 'dap-notice dap-notice--error' : 'dap-notice'}>\n {notice.text}\n </p>\n ) : null}\n <Workbench\n t={t}\n project={project}\n diagram={diagram}\n tab={tab}\n busy={busy}\n question={question}\n pricing={state.pricing}\n ago={ago}\n onTab={setTab}\n onQuestion={setQuestion}\n onAsk={() => void ask()}\n onSelect={setDiagramId}\n onRegenerate={() => void regenerate()}\n onPersistence={(p) => void setPersistence(p)}\n onDelete={() => void removeDiagram()}\n onQa={(id, action) => void applyQa(id, action)}\n onNewDiagram={() => setDialog('diagram')}\n />\n </div>\n {dialog === 'diagram' ? (\n <NewDiagramDialog\n t={t}\n project={project}\n selectedId={diagram?.id ?? null}\n busy={busy !== null}\n onClose={() => setDialog(null)}\n onSubmit={(draft) => void createDiagram(draft)}\n />\n ) : null}\n </div>\n )\n }\n\n return (\n <div className=\"dap-page\">\n <header className=\"dap-head\">\n <div>\n <h1 className=\"dap-head__title\">{t('page.title')}</h1>\n <p className=\"dap-head__sub\">{t('page.subtitle')}</p>\n </div>\n <div className=\"dap-head__actions\">\n <button\n className=\"dap-btn dap-btn--sm\"\n onClick={() => void updateAll()}\n disabled={busy !== null || state.projects.length === 0}\n >\n {t('action.updateAll')}\n </button>\n <button\n className=\"dap-btn dap-btn--sm dap-btn--primary\"\n onClick={() => setDialog('project')}\n >\n + {t('action.add')}\n </button>\n </div>\n </header>\n <div className=\"dap-scroll\">\n {notice !== null ? (\n <p className={notice.error ? 'dap-notice dap-notice--error' : 'dap-notice'}>\n {notice.text}\n </p>\n ) : null}\n {state.error !== null ? (\n <p className=\"dap-notice dap-notice--error\">{state.error}</p>\n ) : null}\n {state.projects.length === 0 && !state.loading ? (\n <div className=\"dap-empty\">\n <div className=\"dap-empty__title\">{t('empty.title')}</div>\n <div className=\"dap-empty__body\">{t('empty.body')}</div>\n <button\n className=\"dap-btn dap-btn--sm dap-btn--primary\"\n onClick={() => setDialog('project')}\n >\n {t('empty.action')}\n </button>\n </div>\n ) : (\n <>\n <div className=\"dap-stats\">\n <div className=\"dap-stat\">\n <div className=\"dap-stat__k\">{t('stat.repos')}</div>\n <div className=\"dap-stat__v\">{state.projects.length}</div>\n </div>\n <div className=\"dap-stat\">\n <div className=\"dap-stat__k\">{t('stat.concepts')}</div>\n <div className=\"dap-stat__v\">\n {state.projects.reduce((sum, item) => sum + item.concepts.length, 0)}\n </div>\n </div>\n <div className=\"dap-stat\">\n <div className=\"dap-stat__k\">{t('stat.diagrams')}</div>\n <div className=\"dap-stat__v\">\n {state.projects.reduce((sum, item) => sum + allDiagrams(item).length, 0)}\n </div>\n </div>\n <div className=\"dap-stat\">\n <div className=\"dap-stat__k\">{t('stat.running')}</div>\n <div className=\"dap-stat__v\">\n {state.projects.filter((item) => isRunning(item)).length}\n </div>\n </div>\n </div>\n <div className=\"dap-atlas\">\n {state.projects.map((item) => (\n <AtlasCard\n key={item.id}\n t={t}\n project={item}\n busy={busy}\n ago={ago}\n onOpen={() => openProject(item.id)}\n onUpdate={() => void runUpdate(item)}\n />\n ))}\n </div>\n </>\n )}\n </div>\n {dialog === 'project' ? (\n <NewProjectDialog\n t={t}\n busy={busy !== null}\n onClose={() => setDialog(null)}\n onSubmit={(name, repoPath) => void createProject(name, repoPath)}\n />\n ) : null}\n </div>\n )\n}\n\n// ------------------------------------------------------------------ 总览卡片\n\nfunction AtlasCard({\n t,\n project,\n busy,\n ago,\n onOpen,\n onUpdate,\n}: {\n t: Translate\n project: Project\n busy: string | null\n ago: (iso: string | null) => string\n onOpen: () => void\n onUpdate: () => void\n}): ReactElement {\n const diagrams = allDiagrams(project)\n const stale = isStale(project)\n const running = isRunning(project)\n const tree = project.git.tree\n return (\n <article\n className=\"dap-card\"\n role=\"button\"\n tabIndex={0}\n onClick={onOpen}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') onOpen()\n }}\n >\n <div className=\"dap-card__top\">\n <div className=\"dap-repo\">\n <Dot tone={running ? 'run' : stale ? 'warn' : 'ok'} />\n <div style={{ minWidth: 0 }}>\n <div className=\"dap-repo__name\">{project.name}</div>\n <div className=\"dap-repo__path\">{project.repoPath}</div>\n </div>\n </div>\n </div>\n <div className=\"dap-chips\">\n <Chip dot>\n {project.git.isRepo ? (project.git.branch ?? t('git.detached')) : t('git.noRepo')}\n </Chip>\n {tree !== null && tree.staged + tree.unstaged + tree.untracked > 0 ? (\n <Chip tone=\"warn\">\n {t('git.treeShort', { staged: tree.staged, unstaged: tree.unstaged })}\n </Chip>\n ) : (\n <Chip tone=\"ok\">{t('git.clean')}</Chip>\n )}\n <Chip tone=\"accent\">\n {project.concepts.length} {t('concept.title')}\n </Chip>\n {stale ? <Chip tone=\"warn\">{t('status.stale')}</Chip> : null}\n </div>\n <div className=\"dap-thumbs\">\n {diagrams.slice(0, 3).map((item) => (\n <div\n key={item.id}\n className={item.kind === 'structure' ? 'dap-thumb dap-thumb--wide' : 'dap-thumb'}\n >\n <div className=\"dap-thumb__title\">\n <b>{item.title}</b>\n <span className=\"dap-thumb__count\">\n {item.persistence === 'temporary' ? t('persistence.temporary') : t('status.ready')}\n </span>\n </div>\n </div>\n ))}\n </div>\n <div className=\"dap-card__foot\">\n <span>\n {running\n ? t('runbar.step', { step: activeStep(project) ?? t('runbar.unknown') })\n : `${project.git.branch ?? t('git.noRepo')} · ${ago(\n diagrams.find((d) => d.generatedAt)?.generatedAt ?? null,\n )}`}\n {project.usageTotal > 0 ? ` · ${formatTokens(project.usageTotal)} tokens` : ''}\n </span>\n <button\n className=\"dap-btn dap-btn--sm\"\n onClick={(event) => {\n event.stopPropagation()\n onUpdate()\n }}\n >\n {t('action.update')}\n </button>\n </div>\n {busy === `update:${project.id}` ? (\n <span className=\"dap-hint\">{t('notice.running')}</span>\n ) : null}\n </article>\n )\n}\n\n// ------------------------------------------------------------------ 工作台\n\nfunction Workbench({\n t,\n project,\n diagram,\n tab,\n busy,\n question,\n pricing,\n ago,\n onTab,\n onQuestion,\n onAsk,\n onSelect,\n onRegenerate,\n onPersistence,\n onDelete,\n onQa,\n onNewDiagram,\n}: {\n t: Translate\n project: Project\n diagram: DiagramState | undefined\n tab: Tab\n busy: string | null\n question: string\n pricing: Pricing | null\n ago: (iso: string | null) => string\n onTab: (tab: Tab) => void\n onQuestion: (value: string) => void\n onAsk: () => void\n onSelect: (id: string) => void\n onRegenerate: () => void\n onPersistence: (persistence: 'permanent' | 'temporary') => void\n onDelete: () => void\n onQa: (qaId: string, action: 'merge' | 'child') => void\n onNewDiagram: () => void\n}): ReactElement {\n const custom = project.custom\n const children =\n diagram === undefined ? [] : custom.filter((item) => item.parentId === diagram.id)\n const parent = diagram?.parentId != null ? findDiagram(project, diagram.parentId) : undefined\n const thread = project.qa.filter((entry) => entry.diagramId === diagram?.id)\n\n const tree = project.git.tree\n return (\n <>\n <div className=\"dap-chips\" style={{ marginBottom: 10 }}>\n <Chip dot>\n {project.git.isRepo ? (project.git.branch ?? t('git.detached')) : t('git.noRepo')}\n </Chip>\n {tree !== null ? (\n <Chip tone=\"warn\">\n {t('git.tree', {\n staged: tree.staged,\n unstaged: tree.unstaged,\n untracked: tree.untracked,\n })}\n </Chip>\n ) : null}\n <span className=\"dap-hint\">\n {t('meta.branchLevel', { branch: project.git.branch ?? '—' })}\n </span>\n </div>\n\n <RunBar t={t} project={project} />\n\n <div className=\"dap-wb\">\n <div className=\"dap-panel dap-viewer\">\n <ThumbStrip\n t={t}\n project={project}\n selectedId={diagram?.id ?? null}\n onSelect={onSelect}\n onNewDiagram={onNewDiagram}\n />\n <div className=\"dap-viewer__head\">\n <div className=\"dap-viewer__title\">\n {diagram?.title ?? t('status.none')}\n {parent !== undefined ? <span>/ {parent.title}</span> : null}\n </div>\n <div className=\"dap-viewer__tools\">\n {diagram?.htmlPath != null ? (\n <>\n <a\n className=\"dap-btn dap-btn--ghost dap-btn--sm\"\n href={ROUTES.artifact(diagram.htmlPath)}\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n {t('action.openTab')}\n </a>\n <a\n className=\"dap-btn dap-btn--ghost dap-btn--sm\"\n href={ROUTES.artifact(diagram.htmlPath)}\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n {t('action.fullscreen')}\n </a>\n </>\n ) : null}\n <button\n className=\"dap-btn dap-btn--ghost dap-btn--sm\"\n onClick={onRegenerate}\n disabled={busy !== null}\n >\n {t('action.regenerate')}\n </button>\n </div>\n </div>\n <div className=\"dap-stage\">\n {diagram?.status === 'ready' && diagram.htmlPath !== null ? (\n <div className=\"dap-frame\">\n <div className=\"dap-frame__cap\">\n <span>\n {project.git.branch ?? t('git.noRepo')} · {ago(diagram.generatedAt)}\n </span>\n <span>{diagram.title}</span>\n </div>\n <iframe\n className=\"dap-iframe\"\n title={diagram.title}\n src={ROUTES.artifact(diagram.htmlPath)}\n sandbox=\"allow-scripts allow-same-origin\"\n />\n </div>\n ) : (\n <div className=\"dap-frame\">\n <div className=\"dap-empty\">\n <div className=\"dap-empty__title\">\n {diagram === undefined\n ? t('status.none')\n : t(`status.${diagram.status}` as never)}\n </div>\n {diagram?.error != null ? (\n <div className=\"dap-empty__body\">{diagram.error}</div>\n ) : (\n <div className=\"dap-empty__body\">{t('notice.running')}</div>\n )}\n <button\n className=\"dap-btn dap-btn--sm dap-btn--primary\"\n onClick={onRegenerate}\n disabled={busy !== null}\n >\n {t('action.regenerate')}\n </button>\n </div>\n </div>\n )}\n {children.length > 0 ? (\n <div className=\"dap-insets\">\n {children.map((child) => (\n <button\n key={child.id}\n className={child.id === diagram?.id ? 'dap-inset dap-inset--on' : 'dap-inset'}\n onClick={() => onSelect(child.id)}\n >\n <Dot\n tone={\n child.status === 'ready' ? 'ok' : child.status === 'failed' ? 'err' : 'run'\n }\n />\n <span className=\"dap-inset__name\">{child.title}</span>\n </button>\n ))}\n </div>\n ) : null}\n </div>\n </div>\n\n <div className=\"dap-panel\">\n <div className=\"dap-tabs\">\n <button\n className={tab === 'info' ? 'dap-tab dap-tab--on' : 'dap-tab'}\n onClick={() => onTab('info')}\n >\n {t('field.status')}\n </button>\n <button\n className={tab === 'qa' ? 'dap-tab dap-tab--on' : 'dap-tab'}\n onClick={() => onTab('qa')}\n >\n {t('qa.title')}\n {thread.length > 0 ? ` ${thread.length}` : ''}\n </button>\n </div>\n {tab === 'info' ? (\n <div className=\"dap-insp\">\n {diagram === undefined ? (\n <p className=\"dap-hint\">{t('status.none')}</p>\n ) : (\n <>\n <div className=\"dap-field\">\n <span className=\"dap-field__label\">{t('field.status')}</span>\n <div className=\"dap-chips\">\n <Chip\n tone={\n diagram.status === 'ready'\n ? 'ok'\n : diagram.status === 'failed'\n ? 'warn'\n : 'default'\n }\n >\n {t(`status.${diagram.status}` as never)}\n </Chip>\n <Chip tone={diagram.persistence === 'permanent' ? 'accent' : 'default'}>\n {diagram.persistence === 'permanent'\n ? t('persistence.permanent')\n : t('persistence.temporary')}\n </Chip>\n <Chip>{ago(diagram.generatedAt)}</Chip>\n </div>\n </div>\n {diagram.kind === 'custom' ? (\n <div className=\"dap-field\">\n <span className=\"dap-field__label\">{t('field.prompt')}</span>\n <div className=\"dap-box\">\n {diagram.prompt ?? t('status.none')}\n <div className=\"dap-box__meta\">\n <button\n className=\"dap-btn dap-btn--ghost dap-btn--sm\"\n onClick={() =>\n onPersistence(\n diagram.persistence === 'permanent' ? 'temporary' : 'permanent',\n )\n }\n disabled={busy !== null}\n >\n {diagram.persistence === 'permanent'\n ? t('persistence.temporary')\n : t('persistence.permanent')}\n </button>\n <button\n className=\"dap-btn dap-btn--ghost dap-btn--sm\"\n onClick={onDelete}\n disabled={busy !== null}\n >\n {t('action.delete')}\n </button>\n </div>\n </div>\n </div>\n ) : null}\n {diagram.error !== null ? (\n <p className=\"dap-notice dap-notice--error\">{diagram.error}</p>\n ) : null}\n <div className=\"dap-field\">\n <span className=\"dap-field__label\">{t('field.branch')}</span>\n <div className=\"dap-chips\">\n <Chip dot>{diagram.branch ?? project.git.branch ?? t('git.noRepo')}</Chip>\n {diagram.tree !== null ? (\n <Chip>\n {t('git.treeShort', {\n staged: diagram.tree.staged,\n unstaged: diagram.tree.unstaged,\n })}\n </Chip>\n ) : null}\n </div>\n </div>\n <div className=\"dap-field\">\n <span className=\"dap-field__label\">{t('field.concepts')}</span>\n <div className=\"dap-chips\">\n {diagram.conceptIds.length === 0\n ? project.concepts\n .slice(0, 6)\n .map((concept) => <Chip key={concept.id}>{concept.name}</Chip>)\n : diagram.conceptIds.map((id) => {\n const concept = project.concepts.find((item) => item.id === id)\n return <Chip key={id}>{concept?.name ?? id}</Chip>\n })}\n </div>\n </div>\n <div className=\"dap-field\">\n <span className=\"dap-field__label\">{t('progress.title')}</span>\n <TodoList t={t} todos={project.lastRun?.todos ?? []} />\n </div>\n <div className=\"dap-field\">\n <span className=\"dap-field__label\">{t('usage.title')}</span>\n <UsageBlock t={t} project={project} pricing={pricing} />\n </div>\n <div className=\"dap-field\">\n <span className=\"dap-field__label\">{t('run.history')}</span>\n <div className=\"dap-hint\">\n {project.runs\n .slice(-3)\n .reverse()\n .map((run) => (\n <div key={run.id}>\n {clockText(run.startedAt)} · {t(`run.kind.${run.kind}` as never)} ·{' '}\n {t(`run.status.${run.status}` as never)}\n </div>\n ))}\n </div>\n </div>\n </>\n )}\n </div>\n ) : (\n <>\n <div className=\"dap-insp\">\n {thread.length === 0 ? <p className=\"dap-hint\">{t('qa.empty')}</p> : null}\n {thread.map((entry) => (\n <div key={entry.id} className=\"dap-qa\">\n <div className=\"dap-msg dap-msg--me\">\n <span className=\"dap-msg__who\">{t('qa.me')}</span>\n <div className=\"dap-msg__bubble\">{entry.question}</div>\n </div>\n <div className=\"dap-msg\">\n <span className=\"dap-msg__who\">{t('qa.answer')}</span>\n <div className=\"dap-msg__bubble\">{entry.answer}</div>\n <div className=\"dap-msg__acts\">\n {entry.applied ? (\n <Chip tone=\"ok\">{t('qa.applied')}</Chip>\n ) : (\n <>\n <button\n className=\"dap-btn dap-btn--sm\"\n onClick={() => onQa(entry.id, 'merge')}\n disabled={busy !== null}\n >\n {t('action.merge')}\n </button>\n <button\n className=\"dap-btn dap-btn--sm\"\n onClick={() => onQa(entry.id, 'child')}\n disabled={busy !== null}\n >\n {t('action.child')}\n </button>\n </>\n )}\n </div>\n </div>\n </div>\n ))}\n </div>\n <div className=\"dap-ask\">\n <textarea\n value={question}\n placeholder={t('qa.placeholder')}\n onChange={(event) => onQuestion(event.target.value)}\n disabled={busy === 'ask'}\n />\n <button\n className=\"dap-btn dap-btn--sm dap-btn--primary\"\n onClick={onAsk}\n disabled={busy !== null || question.trim().length === 0}\n >\n {busy === 'ask' ? t('qa.working') : t('action.send')}\n </button>\n </div>\n <p className=\"dap-hint\" style={{ padding: '0 10px 10px' }}>\n {t('qa.mergeHint')}\n </p>\n </>\n )}\n </div>\n </div>\n </>\n )\n}\n\n/** 图片上方的图名 tab 条:默认两张 + 自定义图(子图挂在父图后面)。 */\nexport function ThumbStrip({\n t,\n project,\n selectedId,\n onSelect,\n onNewDiagram,\n}: {\n t: Translate\n project: Project\n selectedId: string | null\n onSelect: (id: string) => void\n onNewDiagram: () => void\n}): ReactElement {\n const diagrams = allDiagrams(project)\n const defaults = diagrams.filter((item) => item.kind !== 'custom')\n const custom = diagrams.filter((item) => item.kind === 'custom')\n const roots = custom.filter((item) => item.parentId === null)\n const childrenOf = (id: string): readonly DiagramState[] =>\n custom.filter((item) => item.parentId === id)\n\n return (\n <div className=\"dap-strip\" role=\"tablist\" aria-label={t('page.title')}>\n {defaults.map((item) => (\n <Thumb\n key={item.id}\n t={t}\n diagram={item}\n active={item.id === selectedId}\n onSelect={onSelect}\n />\n ))}\n {roots.length > 0 ? <span className=\"dap-strip__sep\" aria-hidden=\"true\" /> : null}\n {roots.map((item) => (\n <Fragment key={item.id}>\n <Thumb t={t} diagram={item} active={item.id === selectedId} onSelect={onSelect} />\n {childrenOf(item.id).map((child) => (\n <Thumb\n key={child.id}\n t={t}\n diagram={child}\n active={child.id === selectedId}\n child\n onSelect={onSelect}\n />\n ))}\n </Fragment>\n ))}\n <button className=\"dap-strip__new\" onClick={onNewDiagram} title={t('action.newDiagram')}>\n + {t('action.newDiagram')}\n </button>\n </div>\n )\n}\n\n/**\n * 一个图名 tab:只有文字,没有缩略图。\n *\n * 之前用 0.25 倍 iframe 做真预览,一条占 91px,把图片本身压下去;改成纯文字后\n * 整条 32px,状态用一个 5px 圆点表达,临时图挂一个小标签。\n */\nfunction Thumb({\n t,\n diagram,\n active,\n child = false,\n onSelect,\n}: {\n t: Translate\n diagram: DiagramState\n active: boolean\n child?: boolean\n onSelect: (id: string) => void\n}): ReactElement {\n const tone = diagram.status === 'ready' ? 'ok' : diagram.status === 'failed' ? 'err' : 'warn'\n return (\n <button\n className={['dap-tabitem', active ? 'dap-tabitem--on' : '', child ? 'dap-tabitem--child' : '']\n .filter((part) => part.length > 0)\n .join(' ')}\n role=\"tab\"\n aria-selected={active}\n onClick={() => onSelect(diagram.id)}\n title={`${diagram.title} · ${t(`status.${diagram.status}` as never)}`}\n >\n <Dot tone={tone} />\n <span className=\"dap-tabitem__name\">{diagram.title}</span>\n {diagram.persistence === 'temporary' ? (\n <span className=\"dap-tabitem__tag\">{t('persistence.temporaryShort')}</span>\n ) : null}\n </button>\n )\n}\n\nfunction messageOf(err: unknown): string {\n if (err instanceof ApiError) return err.message\n return err instanceof Error ? err.message : String(err)\n}\n\n// ------------------------------------------------------------------ 进度与用量\n\n/** 运行中当前步骤;没有 todo 时返回 null。 */\nfunction activeStep(project: Project): string | null {\n const todo = project.lastRun?.todos.find((item) => item.status === 'in_progress')\n return todo?.content ?? null\n}\n\n/** 顶部运行进度条:只在有 agent 在跑时出现。 */\nfunction RunBar({ t, project }: { t: Translate; project: Project }): ReactElement | null {\n const run = project.lastRun\n const running = isRunning(project)\n const qaRunning = project.qa.find((entry) => entry.status === 'running')\n if (!running && qaRunning === undefined) return null\n\n const todos = running ? (run?.todos ?? []) : (qaRunning?.todos ?? [])\n const current = todos.find((todo) => todo.status === 'in_progress')\n const done = todos.filter((todo) => todo.status === 'completed').length\n const percent = todos.length === 0 ? 0 : Math.round((done / todos.length) * 100)\n const usage = running ? (run?.usage ?? null) : (qaRunning?.usage ?? null)\n\n return (\n <div className=\"dap-runbar\">\n <span className=\"dap-spinner\" aria-hidden=\"true\" />\n <b>{t('runbar.running')}</b>\n <span className=\"dap-runbar__step\">\n {t('runbar.step', { step: current?.content ?? todos[0]?.content ?? t('runbar.unknown') })}\n {todos.length > 0 ? `(${done}/${todos.length})` : ''}\n </span>\n {todos.length > 0 ? (\n <>\n <span className=\"dap-runbar__bar\">\n <i style={{ width: `${percent}%` }} />\n </span>\n <span className=\"dap-runbar__pct\">{percent}%</span>\n </>\n ) : null}\n {usage !== null ? (\n <span className=\"dap-runbar__tok\">\n {t('runbar.tokens', { n: formatTokens(usageTotalOf(usage)) })}\n </span>\n ) : null}\n </div>\n )\n}\n\n/** todo 列表:✓ / spinner / ○。 */\nexport function TodoList({ t, todos }: { t: Translate; todos: readonly TodoItem[] }): ReactElement {\n if (todos.length === 0) return <p className=\"dap-hint\">{t('progress.none')}</p>\n return (\n <div className=\"dap-todos\">\n {todos.map((todo) => (\n <div\n key={todo.content}\n className={\n todo.status === 'completed'\n ? 'dap-todo dap-todo--done'\n : todo.status === 'in_progress'\n ? 'dap-todo dap-todo--now'\n : 'dap-todo'\n }\n >\n <span className=\"dap-todo__mark\" aria-hidden=\"true\">\n {todo.status === 'completed' ? (\n '✓'\n ) : todo.status === 'in_progress' ? (\n <span className=\"dap-spinner\" />\n ) : (\n '○'\n )}\n </span>\n {todo.content}\n </div>\n ))}\n </div>\n )\n}\n\n/** 用量块:本次 / 累计 tokens,配了单价才算钱。 */\nexport function UsageBlock({\n t,\n project,\n pricing,\n}: {\n t: Translate\n project: Project\n pricing: Pricing | null\n}): ReactElement {\n const current: TokenUsage | null = project.lastRun?.usage ?? null\n const cost = estimateCost(current, pricing)\n return (\n <div className=\"dap-usage\">\n <div>\n <span>{t('usage.current')}</span>\n <b>{formatTokens(usageTotalOf(current))}</b>\n {cost !== null ? <em>{t('usage.estimated', { cost: cost.toFixed(3) })}</em> : null}\n </div>\n <div>\n <span>{t('usage.total')}</span>\n <b>{formatTokens(project.usageTotal)}</b>\n </div>\n {current !== null ? (\n <div className=\"dap-usage__detail\">\n <span>\n {t('usage.detail', {\n input: formatTokens(current.uncachedInput),\n cacheRead: formatTokens(current.cacheRead),\n output: formatTokens(current.output),\n })}\n </span>\n </div>\n ) : null}\n {pricing === null ? <span className=\"dap-hint\">{t('usage.estimateNote')}</span> : null}\n </div>\n )\n}\n","/**\n * 侧栏入口图标。\n *\n * 契约:`sidebar.panellist` 只给 `{ size, active }`,组件**只画图标** ——\n * 外层容器、点击、aria、选中态全部由 sidebar 自己负责,插件不要抢。\n */\n\nimport type { CSSProperties, ReactElement } from 'react'\n\n/** 文案取值函数。 */\nexport type Translate = (key: string, vars?: Record<string, string | number>) => string\n\nexport interface PanelIconProps {\n readonly size?: number\n readonly active?: boolean\n}\n\n/** 三点连接的结构图小图标,用 currentColor 跟随侧栏配色。 */\nexport function PanelIcon({ size = 16 }: PanelIconProps): ReactElement {\n const style: CSSProperties = { display: 'block' }\n return (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.6}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n style={style}\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path d=\"M3.6 6.3V5.2c0-.9.7-1.6 1.6-1.6h3\" opacity=\"0.55\" />\n <path d=\"M20.4 17.7v1.1c0 .9-.7 1.6-1.6 1.6h-3\" opacity=\"0.55\" />\n <circle cx=\"7.6\" cy=\"9.4\" r=\"2.1\" />\n <circle cx=\"16.4\" cy=\"7.1\" r=\"1.7\" />\n <circle cx=\"12.6\" cy=\"16.6\" r=\"1.7\" />\n <path d=\"M9.5 8.4l5.1-1\" />\n <path d=\"M8.6 11.2l2.9 3.6\" />\n <path d=\"M15.9 8.8l-2.2 5.9\" opacity=\"0.7\" />\n </svg>\n )\n}\n","/**\n * 页面文案。中英双语,键集合必须完全一致(`tests/contract/locale.test.ts` 守着)。\n *\n * 不放在 `locale/*.json` 里:那两个文件是**包级元数据**(宿主用来显示插件名与描述),\n * 页面内的即时文案走 `ctx.locale.register(ns, DICT)`。\n */\n\nexport const LOCALE_NS = 'dsh-archify-projects'\n\n/** 中文(默认)。 */\nconst zh: Record<string, string> = {\n 'panel.title': '架构建模',\n 'panel.desc': '代码库概念与图册',\n\n 'page.title': '架构建模',\n 'page.subtitle': '代码库 → 概念 → 图册 · 按分支维护,工作区改动一起纳入',\n\n 'stat.repos': '在册仓库',\n 'stat.concepts': '已提取概念',\n 'stat.diagrams': '图与套图',\n 'stat.running': '正在生成',\n 'tab.info': '图信息',\n 'tab.qa': '提问',\n\n 'action.add': '新增项目',\n 'action.refresh': '刷新',\n 'action.update': '更新图册',\n 'action.updateAll': '全部更新',\n 'action.check': '重探 Git',\n 'action.openTab': '新窗口',\n 'action.fullscreen': '全屏',\n 'action.regenerate': '重画',\n 'action.delete': '删除',\n 'action.openSession': '查看会话',\n 'action.cancel': '取消',\n 'action.submit': '创建并分析',\n 'action.close': '关闭',\n 'action.back': '返回图册',\n 'action.newDiagram': '新建图',\n 'action.ask': '提问',\n 'action.send': '发送',\n 'action.merge': '补进图里',\n 'action.child': '创建子图',\n 'action.save': '保存',\n\n 'empty.title': '还没有项目',\n 'empty.body': '新增一个项目:填名称和本地代码库路径,插件会先提取概念,再生成默认两张图。',\n 'empty.action': '新增第一个项目',\n\n 'dialog.addTitle': '新增项目',\n 'field.name': '项目名称',\n 'field.namePlaceholder': '留空则用目录名',\n 'field.path': '代码库路径',\n 'field.pathPlaceholder': '/home/me/projects/your-repo',\n 'field.pathRequired': '请填写代码库路径',\n\n 'dialog.newDiagram': '新建图',\n 'dialog.newDiagramSub': '用自然语言描述要什么图,agent 读代码后按这个 prompt 画。',\n 'field.title': '标题',\n 'field.titlePlaceholder': '例如:一次更新的调用链',\n 'field.prompt': '出图要求',\n 'field.promptPlaceholder':\n '例如:画出一次「更新」从按钮到产物回收的完整调用链,标出每一步失败会怎样。',\n 'field.persistence': '时效',\n 'field.parent': '归属',\n 'field.concepts': '概念范围',\n 'field.branch': '分支',\n 'field.status': '状态',\n 'field.generatedAt': '生成时间',\n 'field.owner': '所属',\n 'field.tree': '工作区',\n\n 'field.renderer': '图型',\n 'field.pathScope': '目录范围',\n 'field.rendererHint': '点一下看它展示什么、能让你掌握什么',\n 'renderer.auto': '不限',\n 'renderer.autoHint':\n '不选也行:Archify 会按你的 prompt 自行判断图型;选了只是让 agent 少猜一步,图会更稳。',\n 'renderer.fit': '适合看',\n 'renderer.show': '会展示',\n 'renderer.grasp': '能掌握',\n 'renderer.architecture': '架构图',\n 'renderer.architecture.fit': '系统由什么组成、谁依赖谁',\n 'renderer.architecture.show': '组件与服务、进程/网络/信任边界、外部依赖',\n 'renderer.architecture.grasp': '分层与职责、关键依赖、边界外的风险',\n 'renderer.workflow': '流程图',\n 'renderer.workflow.fit': '一个流程怎么走、有哪些判断与审批',\n 'renderer.workflow.show': '步骤、分支条件、失败与回退',\n 'renderer.workflow.grasp': '主流程与异常路径、谁在什么条件下做什么',\n 'renderer.sequence': '时序图',\n 'renderer.sequence.fit': '一次请求在模块之间怎么往返',\n 'renderer.sequence.show': '参与者、消息顺序、返回与异步',\n 'renderer.sequence.grasp': '调用链、时序依赖、失败重试点',\n 'renderer.dataflow': '数据流',\n 'renderer.dataflow.fit': '数据从哪来、经过谁、到哪去',\n 'renderer.dataflow.show': '数据源、转换、存储、消费者',\n 'renderer.dataflow.grasp': '数据血缘与口径、敏感数据流向',\n 'renderer.lifecycle': '状态机',\n 'renderer.lifecycle.fit': '一个对象有哪些状态、怎么流转',\n 'renderer.lifecycle.show': '状态、转移条件、终态',\n 'renderer.lifecycle.grasp': '状态机全貌、等待与重试、非法流转',\n 'scope.conceptHint':\n '选中概念会限制取证范围:agent 只围绕这几个概念读代码,图更聚焦。不选则自行取舍。',\n 'scope.pathHint': '只让 agent 在这些目录里取证;不选则扫全仓库。',\n\n 'persistence.permanent': '永久 · 下次更新沿用',\n 'persistence.temporary': '临时 · 仅这一次',\n 'persistence.temporaryShort': '临时',\n 'persistence.permanentHint': '存住 prompt,下次「更新图册」自动用同一 prompt 重画。',\n 'persistence.temporaryHint': '只跑这一次,不占下次更新的时间;产物仍可查看。',\n 'parent.none': '独立套图',\n 'parent.current': '挂在当前图下',\n\n 'git.noRepo': '非 Git 目录',\n 'git.noRepoHint': '不做分支切换,直接基于当前目录分析',\n 'git.branch': '分支',\n 'git.dirty': '工作区有未提交改动',\n 'git.clean': '工作区干净',\n 'git.detached': '分离头指针',\n 'git.checkedAt': '探测于 {time}',\n 'git.behind': '落后 {n}',\n 'git.ahead': '领先 {n}',\n 'git.inSync': '与上游同步',\n 'git.tree': '暂存 {staged} · 未暂存 {unstaged} · 未跟踪 {untracked}',\n 'git.treeShort': '{staged} 暂存 · {unstaged} 未暂存',\n\n 'status.idle': '未生成',\n 'status.queued': '排队中',\n 'status.running': '生成中',\n 'status.ready': '已就绪',\n 'status.failed': '失败',\n 'status.stale': '工作区有变化',\n 'status.none': '—',\n\n 'runbar.running': '生成中',\n 'runbar.step': '正在:{step}',\n 'runbar.tokens': '本次 {n} tokens',\n 'runbar.unknown': '正在准备…',\n 'progress.title': '进度(agent 的 todo)',\n 'progress.none': 'agent 还没写 todo',\n 'usage.title': '用量',\n 'usage.current': '本次',\n 'usage.total': '累计',\n 'usage.detail': '输入 {input} · 缓存读 {cacheRead} · 输出 {output}',\n 'usage.estimated': '≈ {cost}',\n 'usage.estimateNote': '按配置单价估算,不是账单',\n 'usage.tokens': '{n} tokens',\n 'qa.running': 'agent 正在读代码回答…',\n\n 'qa.title': '提问',\n 'qa.empty': '对这张图提问;agent 只读代码与这张图来回答。',\n 'qa.placeholder': '例如:路由注册失败时插件会怎样?',\n 'qa.answer': 'agent',\n 'qa.me': '我',\n 'qa.applied': '已补进图里',\n 'qa.working': 'agent 正在读代码回答…',\n 'qa.mergeHint': '「补进图里」会把这次回答并进这张图并重画;「创建子图」会挂一张新图。',\n\n 'concept.title': '概念',\n\n 'run.history': '运行记录',\n 'run.none': '还没有运行记录',\n 'run.kind.create': '首次生成',\n 'run.kind.update': '更新',\n 'run.kind.regenerate': '重生成',\n 'run.status.running': '运行中',\n 'run.status.succeeded': '成功',\n 'run.status.failed': '失败',\n\n 'meta.repoPath': '代码库',\n 'meta.createdAt': '创建于 {time}',\n 'meta.generatedAt': '生成于 {time}',\n 'meta.branchLevel': '按分支维护 {branch}',\n\n 'confirm.update': '更新会先快进到上游最新提交,然后用同一 prompt 重画所有永久图。继续?',\n 'confirm.delete': '删除只移除台账和产物目录,不会动你的代码库。继续?',\n 'confirm.updateOk': '更新',\n 'error.generic': '出错了:{message}',\n 'notice.dirty': '工作区有未提交改动,已跳过自动同步,本次基于当前内容分析。',\n 'notice.noGit': '这个目录不是 Git 仓库,跳过分支切换。',\n 'notice.running': 'Agent 正在生成图谱,完成后会自动刷新。',\n 'notice.merged': '已把回答并进图里,正在重画。',\n 'notice.child': '已创建子图,正在生成。',\n 'notice.temporary': '这是临时图:只跑这一次,下次更新不会重画。',\n\n 'time.now': '刚刚',\n 'time.minutes': '{n} 分钟前',\n 'time.hours': '{n} 小时前',\n 'time.days': '{n} 天前',\n}\n\n/** English. */\nconst en: Record<string, string> = {\n 'panel.title': 'Archify',\n 'panel.desc': 'Concepts and diagram atlas',\n\n 'page.title': 'Architecture Studio',\n 'page.subtitle': 'Codebase → concepts → atlas · per branch, working tree included',\n\n 'stat.repos': 'Repositories',\n 'stat.concepts': 'Concepts',\n 'stat.diagrams': 'Diagrams & sets',\n 'stat.running': 'Generating',\n 'tab.info': 'Info',\n 'tab.qa': 'Ask',\n\n 'action.add': 'New project',\n 'action.refresh': 'Refresh',\n 'action.update': 'Update atlas',\n 'action.updateAll': 'Update all',\n 'action.check': 'Re-check Git',\n 'action.openTab': 'Open',\n 'action.fullscreen': 'Fullscreen',\n 'action.regenerate': 'Redraw',\n 'action.delete': 'Delete',\n 'action.openSession': 'Open session',\n 'action.cancel': 'Cancel',\n 'action.submit': 'Create and analyse',\n 'action.close': 'Close',\n 'action.back': 'Back to atlas',\n 'action.newDiagram': 'New diagram',\n 'action.ask': 'Ask',\n 'action.send': 'Send',\n 'action.merge': 'Merge into diagram',\n 'action.child': 'Create child diagram',\n 'action.save': 'Save',\n\n 'empty.title': 'No projects yet',\n 'empty.body':\n 'Add a project with a local repository path. Concepts are extracted first, then the two default diagrams are generated.',\n 'empty.action': 'Add your first project',\n\n 'dialog.addTitle': 'New project',\n 'field.name': 'Project name',\n 'field.namePlaceholder': 'Defaults to the folder name',\n 'field.path': 'Repository path',\n 'field.pathPlaceholder': '/home/me/projects/your-repo',\n 'field.pathRequired': 'Repository path is required',\n\n 'dialog.newDiagram': 'New diagram',\n 'dialog.newDiagramSub':\n 'Describe the diagram you want. The agent reads the code and draws from this prompt.',\n 'field.title': 'Title',\n 'field.titlePlaceholder': 'e.g. Update call chain',\n 'field.prompt': 'Prompt',\n 'field.promptPlaceholder':\n 'e.g. Draw the full call chain of an update, from the button to artifact collection, including failure modes.',\n 'field.persistence': 'Lifetime',\n 'field.parent': 'Placement',\n 'field.concepts': 'Concept scope',\n 'field.branch': 'Branch',\n 'field.status': 'Status',\n 'field.generatedAt': 'Generated',\n 'field.owner': 'Owner',\n 'field.tree': 'Working tree',\n\n 'field.renderer': 'Diagram type',\n 'field.pathScope': 'Directories',\n 'field.rendererHint': 'Click one to see what it shows and what you get',\n 'renderer.auto': 'Any',\n 'renderer.autoHint':\n 'Optional: Archify picks a type from your prompt on its own. Choosing one just saves the agent a guess.',\n 'renderer.fit': 'Best for',\n 'renderer.show': 'Shows',\n 'renderer.grasp': 'You learn',\n 'renderer.architecture': 'Architecture',\n 'renderer.architecture.fit': 'What the system is made of and who depends on whom',\n 'renderer.architecture.show':\n 'Components and services, process/network/trust boundaries, external deps',\n 'renderer.architecture.grasp':\n 'Layers and responsibilities, key dependencies, outside-boundary risks',\n 'renderer.workflow': 'Workflow',\n 'renderer.workflow.fit': 'How a process runs, which branches and approvals exist',\n 'renderer.workflow.show': 'Steps, branch conditions, failures and fallbacks',\n 'renderer.workflow.grasp': 'Main and exception paths, who does what under which condition',\n 'renderer.sequence': 'Sequence',\n 'renderer.sequence.fit': 'How one request round-trips between modules',\n 'renderer.sequence.show': 'Participants, message order, returns and async',\n 'renderer.sequence.grasp': 'Call chains, ordering dependencies, retry points',\n 'renderer.dataflow': 'Dataflow',\n 'renderer.dataflow.fit': 'Where data comes from, passes through, and ends up',\n 'renderer.dataflow.show': 'Sources, transforms, storage, consumers',\n 'renderer.dataflow.grasp': 'Lineage and definitions, sensitive-data movement',\n 'renderer.lifecycle': 'Lifecycle',\n 'renderer.lifecycle.fit': 'Which states an object has and how it transitions',\n 'renderer.lifecycle.show': 'States, transition conditions, terminal states',\n 'renderer.lifecycle.grasp': 'The state machine, waits and retries, illegal transitions',\n 'scope.conceptHint':\n 'Selected concepts narrow the evidence: the agent reads only around them, so the diagram stays focused. Optional.',\n 'scope.pathHint':\n 'Only let the agent read these directories; leave empty to scan the whole repository.',\n\n 'persistence.permanent': 'Permanent · reuse next update',\n 'persistence.temporary': 'Temporary · this run only',\n 'persistence.temporaryShort': 'TEMP',\n 'persistence.permanentHint':\n 'The prompt is kept; the next atlas update redraws it with the same prompt.',\n 'persistence.temporaryHint': 'Runs once, skipped on future updates; artifacts stay available.',\n 'parent.none': 'Own set',\n 'parent.current': 'Under selected diagram',\n\n 'git.noRepo': 'Not a Git repository',\n 'git.noRepoHint': 'No branch switching; analysis runs on the current directory',\n 'git.branch': 'Branch',\n 'git.dirty': 'Uncommitted changes',\n 'git.clean': 'Clean',\n 'git.detached': 'Detached HEAD',\n 'git.checkedAt': 'Checked {time}',\n 'git.behind': '{n} behind',\n 'git.ahead': '{n} ahead',\n 'git.inSync': 'Up to date',\n 'git.tree': '{staged} staged · {unstaged} unstaged · {untracked} untracked',\n 'git.treeShort': '{staged} staged · {unstaged} unstaged',\n\n 'status.idle': 'Not generated',\n 'status.queued': 'Queued',\n 'status.running': 'Generating',\n 'status.ready': 'Ready',\n 'status.failed': 'Failed',\n 'status.stale': 'Working tree changed',\n 'status.none': '—',\n\n 'runbar.running': 'Generating',\n 'runbar.step': 'Now: {step}',\n 'runbar.tokens': '{n} tokens this run',\n 'runbar.unknown': 'Preparing…',\n 'progress.title': 'Progress (agent todos)',\n 'progress.none': 'The agent has not written todos yet',\n 'usage.title': 'Usage',\n 'usage.current': 'This run',\n 'usage.total': 'Total',\n 'usage.detail': '{input} input · {cacheRead} cache read · {output} output',\n 'usage.estimated': '≈ {cost}',\n 'usage.estimateNote': 'Estimated from configured unit prices, not a bill',\n 'usage.tokens': '{n} tokens',\n 'qa.running': 'The agent is reading code…',\n\n 'qa.title': 'Ask',\n 'qa.empty': 'Ask about this diagram; the agent answers from the code and this diagram only.',\n 'qa.placeholder': 'e.g. What happens when route registration fails?',\n 'qa.answer': 'agent',\n 'qa.me': 'me',\n 'qa.applied': 'Merged into diagram',\n 'qa.working': 'The agent is reading code…',\n 'qa.mergeHint':\n 'Merge folds the answer into this diagram and redraws it; Create child attaches a new diagram.',\n\n 'concept.title': 'Concepts',\n\n 'run.history': 'Runs',\n 'run.none': 'No runs yet',\n 'run.kind.create': 'Initial',\n 'run.kind.update': 'Update',\n 'run.kind.regenerate': 'Regenerate',\n 'run.status.running': 'Running',\n 'run.status.succeeded': 'Succeeded',\n 'run.status.failed': 'Failed',\n\n 'meta.repoPath': 'Repository',\n 'meta.createdAt': 'Created {time}',\n 'meta.generatedAt': 'Generated {time}',\n 'meta.branchLevel': 'Maintained per branch {branch}',\n\n 'confirm.update':\n 'Update fast-forwards to the latest upstream commit, then redraws every permanent diagram with its own prompt. Continue?',\n 'confirm.delete':\n 'Deleting removes the ledger entry and artifact folder only; your repository is untouched. Continue?',\n 'confirm.updateOk': 'Update',\n 'error.generic': 'Error: {message}',\n 'notice.dirty':\n 'Working tree has uncommitted changes; automatic sync was skipped and this run uses the current content.',\n 'notice.noGit': 'This directory is not a Git repository; branch switching is skipped.',\n 'notice.running': 'The agent is generating diagrams. This refreshes automatically.',\n 'notice.merged': 'Answer merged into the diagram; redrawing.',\n 'notice.child': 'Child diagram created; generating.',\n 'notice.temporary': 'Temporary diagram: runs once and is skipped on future updates.',\n\n 'time.now': 'just now',\n 'time.minutes': '{n} min ago',\n 'time.hours': '{n} h ago',\n 'time.days': '{n} d ago',\n}\n\n/** 注册给 `ctx.locale.register` 的字典。 */\nexport const DICT: Record<'zh' | 'en', Record<string, string>> = { zh, en }\n\n/** 文案键(供类型与测试使用)。 */\nexport type MessageKey = keyof typeof zh\n","/**\n * 页面样式。\n *\n * 两条硬规则:\n * 1. **只用 DSH alias 令牌**(`--dsw-alias-*` / `--dsw-radius-*`),不写死任何色值 ——\n * 宿主切深浅色时 alias 自动翻转,页面不需要第二条样式分支;\n * 2. 所有类名带 `dap-` 前缀,样式以 `<style data-plugin>` 注入并在卸载时摘掉,\n * 不污染宿主。\n */\n\nexport const PAGE_CSS = `\n.dap-page {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n background: var(--dsw-alias-bg-base);\n color: var(--dsw-alias-label-primary);\n font-family: var(--dsw-font-family, inherit);\n font-size: 13px;\n line-height: 1.5;\n}\n.dap-scroll { flex: 1; min-height: 0; overflow: auto; padding: 18px 22px 26px; }\n/* 工作台整屏:三栏各自滚动,右栏再长也不撑高页面 */\n.dap-scroll--wb { overflow: hidden; display: flex; flex-direction: column; padding-bottom: 12px; }\n\n/* ---------------------------------------------------------------- 顶栏 */\n.dap-head {\n display: flex; align-items: flex-start; justify-content: space-between; gap: 20px;\n padding: 18px 22px 12px; border-bottom: 1px solid var(--dsw-alias-border-l1); flex: none;\n}\n.dap-head__title { margin: 0; font-size: 17px; font-weight: 600; letter-spacing: -0.01em; }\n.dap-head__sub { margin: 3px 0 0; font-size: 12px; color: var(--dsw-alias-label-tertiary); }\n.dap-head__actions { display: flex; gap: 8px; flex: none; }\n\n/* ---------------------------------------------------------------- 通用 */\n.dap-btn {\n display: inline-flex; align-items: center; gap: 5px; height: 30px; padding: 0 11px;\n border-radius: var(--dsw-radius-md); border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary);\n font-size: 12.5px; font-weight: 500; white-space: nowrap; cursor: pointer;\n}\n.dap-btn:hover { background: var(--dsw-alias-interactive-bg-hover); }\n.dap-btn:disabled { opacity: .45; cursor: not-allowed; }\n.dap-btn--primary { background: var(--dsw-alias-button-primary-fill); color: var(--dsw-alias-label-primary-inverted); border-color: transparent; }\n.dap-btn--primary:hover { background: var(--dsw-alias-button-primary-hover); }\n.dap-btn--ghost { border-color: transparent; background: transparent; color: var(--dsw-alias-label-secondary); }\n.dap-btn--ghost:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }\n.dap-btn--sm { height: 26px; padding: 0 8px; font-size: 11.5px; border-radius: var(--dsw-radius-md); }\n.dap-btn__icon { width: 13px; height: 13px; display: block; }\n\n.dap-chip {\n display: inline-flex; align-items: center; gap: 4px; height: 20px; padding: 0 7px;\n border-radius: 999px; border: 1px solid var(--dsw-alias-border-l1);\n background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-secondary);\n font-size: 11px; white-space: nowrap; font-variant-numeric: tabular-nums;\n}\n.dap-chip--ok { color: var(--dsw-alias-state-success-primary); border-color: transparent; }\n.dap-chip--warn { color: var(--dsw-alias-state-warn-primary); border-color: transparent; }\n.dap-chip--accent { color: var(--dsw-alias-state-business-primary); border-color: transparent; }\n.dap-chip__dot { width: 5px; height: 5px; border-radius: 50%; background: currentColor; }\n\n.dap-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--dsw-alias-state-success-primary); flex: none; }\n.dap-dot--warn { background: var(--dsw-alias-state-warn-primary); }\n.dap-dot--err { background: var(--dsw-alias-state-error-primary); }\n.dap-dot--run { background: var(--dsw-alias-state-warn-primary); animation: dap-pulse 1.4s ease-in-out infinite; }\n@keyframes dap-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }\n\n/* ---------------------------------------------------------------- 总览 */\n.dap-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin: 0 0 14px; }\n.dap-stat { border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-lg); background: var(--dsw-alias-bg-layer-1); padding: 11px 13px; }\n.dap-stat__k { font-size: 11.5px; color: var(--dsw-alias-label-tertiary); }\n.dap-stat__v { margin-top: 3px; font-size: 20px; font-weight: 600; letter-spacing: -0.02em; }\n.dap-stat__v small { font-size: 11.5px; font-weight: 500; color: var(--dsw-alias-label-tertiary); margin-left: 6px; letter-spacing: 0; }\n\n.dap-atlas { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }\n.dap-card {\n border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-lg);\n background: var(--dsw-alias-bg-layer-1); padding: 14px; display: flex; flex-direction: column; gap: 10px;\n cursor: pointer; text-align: left; width: 100%; transition: border-color .15s, box-shadow .15s;\n}\n.dap-card:hover { border-color: var(--dsw-alias-border-l3); box-shadow: var(--dsw-shadow-lv2); }\n.dap-card__top { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }\n.dap-repo { display: flex; align-items: center; gap: 7px; min-width: 0; }\n.dap-repo__name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.dap-repo__path { font-size: 11px; color: var(--dsw-alias-label-tertiary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-family: ui-monospace, monospace; }\n.dap-chips { display: flex; flex-wrap: wrap; gap: 5px; }\n.dap-thumbs { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }\n.dap-thumb {\n border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-md);\n background: var(--dsw-alias-bg-layer-2); padding: 6px 8px; min-width: 0;\n}\n.dap-thumb--wide { grid-column: 1 / -1; }\n.dap-thumb__title { display: flex; align-items: center; justify-content: space-between; gap: 6px; font-size: 11px; color: var(--dsw-alias-label-secondary); }\n.dap-thumb__title b { font-weight: 550; color: var(--dsw-alias-label-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.dap-thumb__count { font-size: 10.5px; color: var(--dsw-alias-label-tertiary); }\n.dap-card__foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 11.5px; color: var(--dsw-alias-label-tertiary); }\n\n/* ---------------------------------------------------------------- 工作台 */\n.dap-wb { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 12px; align-items: stretch; flex: 1; min-height: 0; }\n.dap-panel { border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-lg); background: var(--dsw-alias-bg-layer-1); overflow: hidden; display: flex; flex-direction: column; min-height: 0; }\n.dap-panel__body { overflow: auto; flex: 1; min-height: 0; }\n.dap-panel__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 9px 11px; border-bottom: 1px solid var(--dsw-alias-border-l1); font-size: 11.5px; color: var(--dsw-alias-label-tertiary); }\n.dap-panel__body { padding: 7px; }\n\n.dap-strip {\n display: flex; gap: 2px; align-items: center; padding: 4px 8px;\n overflow-x: auto; border-bottom: 1px solid var(--dsw-alias-border-l1);\n background: var(--dsw-alias-bg-layer-1); flex: none;\n}\n.dap-strip__sep { flex: none; width: 1px; height: 16px; background: var(--dsw-alias-border-l1); margin: 0 4px; }\n.dap-tabitem {\n display: inline-flex; align-items: center; gap: 6px; flex: none; max-width: 220px;\n height: 26px; padding: 0 9px; border-radius: var(--dsw-radius-md);\n border: 1px solid transparent; background: transparent;\n color: var(--dsw-alias-label-secondary); font-size: 12px; cursor: pointer; white-space: nowrap;\n}\n.dap-tabitem:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }\n.dap-tabitem--on {\n background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary);\n font-weight: 550; border-color: var(--dsw-alias-border-l1);\n}\n.dap-tabitem--child { margin-left: 10px; }\n.dap-tabitem--child::before { content: \"└\"; color: var(--dsw-alias-label-tertiary); font-size: 10px; }\n.dap-tabitem__name { overflow: hidden; text-overflow: ellipsis; }\n.dap-tabitem__tag {\n font-size: 9.5px; padding: 0 4px; border-radius: 4px;\n background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-tertiary); font-weight: 400;\n}\n.dap-strip__new {\n flex: none; margin-left: auto; height: 26px; padding: 0 9px; border-radius: var(--dsw-radius-md);\n border: 1px dashed var(--dsw-alias-border-l2); background: transparent;\n color: var(--dsw-alias-label-tertiary); font-size: 11.5px; cursor: pointer; white-space: nowrap;\n}\n.dap-strip__new:hover { color: var(--dsw-alias-label-primary); border-color: var(--dsw-alias-border-l3); }\n\n.dap-viewer { display: flex; flex-direction: column; min-width: 0; }\n.dap-viewer__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px 11px; border-bottom: 1px solid var(--dsw-alias-border-l1); }\n.dap-viewer__title { display: flex; align-items: center; gap: 6px; font-size: 12.5px; font-weight: 600; min-width: 0; }\n.dap-viewer__title span { color: var(--dsw-alias-label-tertiary); font-weight: 400; font-size: 11.5px; }\n.dap-viewer__tools { display: flex; gap: 2px; }\n.dap-stage { background: var(--dsw-alias-bg-layer-2); padding: 11px; flex: 1; min-height: 0; overflow: auto; }\n.dap-frame { background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-md); overflow: hidden; }\n.dap-frame__cap { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 7px 10px; font-size: 11px; color: var(--dsw-alias-label-tertiary); border-bottom: 1px solid var(--dsw-alias-border-l1); }\n.dap-iframe { display: block; width: 100%; height: clamp(420px, 56vh, 720px); border: 0; background: var(--dsw-alias-bg-base); }\n.dap-insets { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin-top: 8px; }\n.dap-inset {\n display: flex; align-items: center; gap: 6px; padding: 6px 8px; border-radius: var(--dsw-radius-md);\n border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-1); cursor: pointer;\n font-size: 11px; color: var(--dsw-alias-label-secondary); text-align: left; min-width: 0;\n}\n.dap-inset:hover { border-color: var(--dsw-alias-border-l3); }\n.dap-inset--on { border-color: var(--dsw-alias-border-l3); box-shadow: 0 0 0 1px var(--dsw-alias-border-l3) inset; color: var(--dsw-alias-label-primary); }\n.dap-inset__name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n\n.dap-tabs { display: flex; gap: 2px; padding: 5px; border-bottom: 1px solid var(--dsw-alias-border-l1); }\n.dap-tab { height: 26px; padding: 0 10px; border-radius: var(--dsw-radius-md); font-size: 12px; color: var(--dsw-alias-label-secondary); cursor: pointer; }\n.dap-tab--on { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); font-weight: 550; }\n.dap-insp { padding: 10px; display: flex; flex-direction: column; gap: 10px; flex: 1; min-height: 0; overflow: auto; }\n.dap-field { display: flex; flex-direction: column; gap: 4px; }\n.dap-field__label { font-size: 10.5px; color: var(--dsw-alias-label-tertiary); letter-spacing: .02em; }\n.dap-box {\n border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-md);\n background: var(--dsw-alias-bg-layer-2); padding: 7px 9px; font-size: 12px; line-height: 1.6;\n white-space: pre-wrap; word-break: break-word;\n}\n.dap-box__meta { display: flex; align-items: center; justify-content: space-between; margin-top: 6px; font-size: 10.5px; color: var(--dsw-alias-label-tertiary); }\n\n.dap-qa { display: flex; flex-direction: column; gap: 8px; }\n.dap-msg { display: flex; flex-direction: column; gap: 3px; }\n.dap-msg__who { font-size: 10.5px; color: var(--dsw-alias-label-tertiary); }\n.dap-msg__bubble {\n border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-2);\n border-radius: var(--dsw-radius-md); padding: 7px 9px; font-size: 12px; line-height: 1.55;\n white-space: pre-wrap; word-break: break-word;\n}\n.dap-msg--me .dap-msg__bubble { background: var(--dsw-alias-bg-layer-1); }\n.dap-msg__acts { display: flex; gap: 5px; flex-wrap: wrap; margin-top: 2px; }\n.dap-ask { display: flex; gap: 6px; align-items: flex-end; padding: 9px 10px; border-top: 1px solid var(--dsw-alias-border-l1); position: sticky; bottom: 0; background: var(--dsw-alias-bg-layer-1); z-index: 2; }\n.dap-ask textarea {\n flex: 1; resize: none; min-height: 40px; max-height: 120px; padding: 7px 9px;\n border: 1px solid var(--dsw-alias-border-l2); border-radius: var(--dsw-radius-md);\n background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary);\n font: inherit; font-size: 12px; outline: none;\n}\n.dap-ask textarea:focus { border-color: var(--dsw-alias-border-l3); }\n.dap-hint { font-size: 10.5px; color: var(--dsw-alias-label-tertiary); line-height: 1.5; }\n\n.dap-runbar {\n display: flex; align-items: center; gap: 8px; margin-bottom: 8px; padding: 7px 10px;\n border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-md);\n background: var(--dsw-alias-bg-layer-1); font-size: 11.5px; color: var(--dsw-alias-label-secondary);\n flex: none;\n}\n.dap-runbar b { color: var(--dsw-alias-label-primary); font-weight: 600; }\n.dap-runbar__step { color: var(--dsw-alias-label-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.dap-runbar__bar { flex: 1; max-width: 240px; height: 4px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2); overflow: hidden; }\n.dap-runbar__bar i { display: block; height: 100%; background: var(--dsw-alias-state-business-primary); border-radius: inherit; }\n.dap-runbar__pct { font-variant-numeric: tabular-nums; color: var(--dsw-alias-label-tertiary); }\n.dap-runbar__tok { margin-left: auto; color: var(--dsw-alias-label-tertiary); font-variant-numeric: tabular-nums; white-space: nowrap; }\n.dap-spinner { width: 11px; height: 11px; border-radius: 50%; border: 2px solid var(--dsw-alias-state-business-primary); border-right-color: transparent; animation: dap-spin 1s linear infinite; flex: none; display: inline-block; }\n@keyframes dap-spin { to { transform: rotate(360deg); } }\n\n.dap-todos { display: flex; flex-direction: column; gap: 3px; border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-md); background: var(--dsw-alias-bg-layer-2); padding: 6px 8px; }\n.dap-todo { display: flex; align-items: center; gap: 7px; font-size: 11.5px; color: var(--dsw-alias-label-secondary); }\n.dap-todo__mark { width: 13px; flex: none; display: inline-flex; align-items: center; justify-content: center; color: var(--dsw-alias-state-success-primary); }\n.dap-todo--done { color: var(--dsw-alias-label-tertiary); }\n.dap-todo--now { color: var(--dsw-alias-label-primary); font-weight: 550; }\n.dap-todo--now .dap-todo__mark { color: var(--dsw-alias-state-business-primary); }\n\n.dap-usage { border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-md); background: var(--dsw-alias-bg-layer-2); padding: 7px 9px; display: flex; flex-direction: column; gap: 4px; font-size: 11.5px; }\n.dap-usage > div { display: flex; align-items: baseline; gap: 6px; }\n.dap-usage span { color: var(--dsw-alias-label-tertiary); }\n.dap-usage b { font-size: 13px; }\n.dap-usage em { font-style: normal; color: var(--dsw-alias-label-tertiary); font-size: 10.5px; }\n.dap-usage__detail { justify-content: space-between; font-size: 10.5px; }\n\n/* ---------------------------------------------------------------- 对话框 */\n.dap-mask { position: fixed; inset: 0; background: var(--dsw-alias-bg-mask-1); display: grid; place-items: center; z-index: 40; }\n.dap-dialog {\n width: 540px; max-width: calc(100vw - 40px); max-height: calc(100vh - 60px); overflow: auto;\n background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-lg); box-shadow: var(--dsw-shadow-lv2); padding: 16px;\n}\n.dap-dialog__title { margin: 0 0 2px; font-size: 15px; font-weight: 600; }\n.dap-dialog__sub { font-size: 11.5px; color: var(--dsw-alias-label-tertiary); margin-bottom: 12px; }\n.dap-dialog__row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 10px 0 4px; font-size: 12px; }\n.dap-dialog__foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 14px; }\n.dap-seg { display: inline-flex; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-md); padding: 2px; }\n.dap-seg__btn { height: 26px; padding: 0 9px; border-radius: var(--dsw-radius-md); font-size: 11.5px; color: var(--dsw-alias-label-secondary); cursor: pointer; }\n.dap-seg__btn--on { background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); font-weight: 550; box-shadow: var(--dsw-shadow-lv2); }\n.dap-input, .dap-textarea {\n width: 100%; padding: 7px 9px; border: 1px solid var(--dsw-alias-border-l2); border-radius: var(--dsw-radius-md);\n background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary);\n font: inherit; font-size: 12.5px; outline: none;\n}\n.dap-input:focus, .dap-textarea:focus { border-color: var(--dsw-alias-border-l3); }\n.dap-textarea { resize: vertical; min-height: 84px; line-height: 1.6; }\n.dap-concepts { display: flex; flex-wrap: wrap; gap: 4px; }\n.dap-concept {\n height: 20px; display: inline-flex; align-items: center; padding: 0 7px; border-radius: 999px;\n font-size: 11px; border: 1px solid var(--dsw-alias-border-l2); color: var(--dsw-alias-label-secondary); cursor: pointer;\n}\n.dap-concept--on { border-color: transparent; background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-state-business-primary); }\n\n\n.dap-dialog--wide { width: 680px; }\n.dap-typebar { display: flex; flex-wrap: wrap; gap: 5px; margin: 6px 0 8px; }\n.dap-type-tag {\n display: inline-flex; align-items: center; gap: 5px; height: 27px; padding: 0 9px;\n border-radius: 999px; border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-secondary);\n font-size: 11.5px; cursor: pointer; white-space: nowrap;\n}\n.dap-type-tag:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }\n.dap-type-tag--on { background: var(--dsw-alias-label-primary); color: var(--dsw-alias-bg-base); border-color: transparent; }\n.dap-type-tag__k { font-size: 9.5px; opacity: .65; font-family: ui-monospace, monospace; }\n.dap-type-detail {\n border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-md);\n background: var(--dsw-alias-bg-layer-2); padding: 8px 10px; display: flex;\n flex-direction: column; gap: 5px; min-height: 82px;\n}\n.dap-type-detail__empty { font-size: 11.5px; color: var(--dsw-alias-label-secondary); line-height: 1.6; margin: auto 0; }\n.dap-type-detail__head { display: flex; align-items: center; gap: 7px; font-size: 12.5px; }\n.dap-type-detail__head code { font: 10px/1 ui-monospace, monospace; color: var(--dsw-alias-label-tertiary); }\n.dap-type-detail__row { display: grid; grid-template-columns: 56px 1fr; gap: 8px; font-size: 11.5px; line-height: 1.55; }\n.dap-type-detail__row span { color: var(--dsw-alias-label-tertiary); }\n.dap-type-detail__row p { margin: 0; color: var(--dsw-alias-label-primary); }\n\n/* ---------------------------------------------------------------- 空状态 / 提示 */\n.dap-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; padding: 44px 20px; text-align: center; color: var(--dsw-alias-label-secondary); }\n.dap-empty__title { font-size: 14px; font-weight: 600; color: var(--dsw-alias-label-primary); }\n.dap-empty__body { max-width: 460px; font-size: 12px; }\n.dap-notice { display: flex; align-items: center; gap: 8px; margin: 0 0 10px; padding: 7px 10px; border-radius: var(--dsw-radius-md); font-size: 11.5px; border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-secondary); }\n.dap-notice--error { border-color: var(--dsw-alias-state-error-primary); color: var(--dsw-alias-state-error-primary); }\n\n@media (max-width: 1180px) {\n .dap-wb { grid-template-columns: minmax(0, 1fr); }\n .dap-wb > .dap-panel:last-child { grid-column: 1 / -1; max-height: 420px; }\n .dap-atlas { grid-template-columns: repeat(2, 1fr); }\n .dap-stats { grid-template-columns: repeat(2, 1fr); }\n}\n`\n\n/** 把样式挂到 document;返回卸载函数(HMR / 插件停用时摘标签)。 */\nexport function installStyles(): () => void {\n const style = document.createElement('style')\n style.dataset.plugin = 'dsh-archify-projects'\n style.textContent = PAGE_CSS\n document.head.appendChild(style)\n return () => style.remove()\n}\n","/**\n * 客户端入口。\n *\n * 产物里的 `exports.apply` / `exports.inject` 就是 `window.__ModuleLoader__` 要的\n * Cordis 插件面;`load({ id, factory })` 的壳由 tsdown 的 banner/footer 生成\n * (见 tsdown.config.ts),这里只管导出。\n *\n * 两个槽:\n * ① `sidebar.panellist` —— 左侧顶栏的入口图标,`id` 就是面板 id;\n * ② `main` —— 点开后的整页,`key` 与 `id` 必须一模一样。\n */\n\nimport { StudioPage } from './page.js'\nimport { PanelIcon } from './panel.js'\nimport { DICT, LOCALE_NS } from './locale.js'\nimport { installStyles } from './styles.js'\n\n/** 面板 id:侧栏入口与主面板共用的唯一标识。 */\nexport const PANEL_ID = 'archify-projects'\n\n/** 需要客户端服务:插槽注册表 + 文案目录。 */\nexport const inject = ['slots', 'locale']\n\n/** 绑好的 `t`。 */\nexport type Translate = (key: string, vars?: Record<string, string | number>) => string\n\n/** 客户端需要的最小 ctx 面。 */\nexport interface ClientCtx {\n readonly slots: {\n inject: (owner: string, install: () => unknown) => void\n register: (options: Record<string, unknown>, component: unknown) => unknown\n }\n readonly locale: {\n register: (ns: string, dict: Record<string, Record<string, string>>) => () => void\n bind: (ns: string) => Translate\n }\n effect: (fn: () => (() => void) | void, label?: string) => unknown\n}\n\n/** 插件入口。 */\nexport function apply(ctx: ClientCtx): void {\n // 文案目录必须先注册:侧栏 label 在 slot 注册时求值一次,\n // 目录还没挂上时 t() 会退回返回原始 key,界面上就出现 \"panel.title\"。\n ctx.effect(() => ctx.locale.register(LOCALE_NS, DICT), 'dsh-archify-projects: dictionaries')\n const t = ctx.locale.bind(LOCALE_NS)\n\n // 组件里要拿 t,但 slot 的 props 由框架决定(`main` 不传 t)。\n // 在 apply 里包一层得到稳定身份,不在渲染期新建组件类型。\n const Page = (): ReturnType<typeof StudioPage> => <StudioPage t={t} />\n\n ctx.slots.inject('sidebar.panellist', () =>\n ctx.slots.register(\n {\n name: 'sidebar.panellist',\n id: PANEL_ID,\n order: 16,\n locale: LOCALE_NS,\n label: () => t('panel.title'),\n },\n PanelIcon,\n ),\n )\n\n ctx.slots.inject('main', () =>\n ctx.slots.register(\n {\n name: 'main',\n key: PANEL_ID,\n locale: LOCALE_NS,\n },\n Page,\n ),\n )\n\n // 样式随插件生命周期装卸,HMR 重挂不会叠标签。\n ctx.effect(installStyles, 'dsh-archify-projects: styles')\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["useState","useMemo","useState","useRef","useCallback","useMemo","Fragment"],"sources":["../src/shared/protocol.ts","../src/shared/types.ts","../src/shared/format.ts","../src/client/api.ts","../src/client/components.tsx","../src/client/page.tsx","../src/client/panel.tsx","../src/client/locale.ts","../src/client/styles.ts","../src/client/entry.tsx"],"sourcesContent":["/**\n * HTTP 协议:路由、请求/响应类型、错误码。\n *\n * host 与 client 共用;改这里等于改接口契约,两侧同时失配会立刻被类型检查抓到。\n */\n\nimport type { Pricing } from './format.js'\nimport type {\n ArchifyType,\n Concept,\n DiagramKind,\n Persistence,\n Project,\n QaEntry,\n RunKind,\n} from './types.js'\n\n/** 所有路由的前缀。 */\nexport const ROUTE_PREFIX = '/dsh-archify-projects'\n\n/** 路由表。host 注册与 client 请求都从这里取,避免字符串各写一份。 */\nexport const ROUTES = {\n health: `${ROUTE_PREFIX}/health`,\n projects: `${ROUTE_PREFIX}/projects`,\n project: (id: string) => `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}`,\n check: (id: string) => `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}/check`,\n update: (id: string) => `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}/update`,\n /** 兼容旧接口:按 kind 重画默认图。 */\n diagram: (id: string, kind: DiagramKind) =>\n `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}/diagram/${kind}`,\n diagrams: (id: string) => `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}/diagrams`,\n customDiagram: (id: string, diagramId: string) =>\n `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}/diagrams/${encodeURIComponent(diagramId)}`,\n regenerate: (id: string, diagramId: string) =>\n `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}/diagrams/${encodeURIComponent(diagramId)}/regenerate`,\n ask: (id: string, diagramId: string) =>\n `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}/diagrams/${encodeURIComponent(diagramId)}/ask`,\n mergeQa: (id: string, qaId: string) =>\n `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}/qa/${encodeURIComponent(qaId)}/merge`,\n childQa: (id: string, qaId: string) =>\n `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}/qa/${encodeURIComponent(qaId)}/child`,\n runs: (id: string) => `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}/runs`,\n artifact: (relativePath: string) =>\n `${ROUTE_PREFIX}/artifact/${relativePath\n .split(/[\\\\/]+/)\n .map((part) => encodeURIComponent(part))\n .join('/')}`,\n} as const\n\n/** 错误码。页面按码决定文案与重试策略,不解析 message。 */\nexport type ApiErrorCode =\n | 'bad-request'\n | 'forbidden'\n | 'not-found'\n | 'conflict'\n | 'path-invalid'\n | 'git-failed'\n | 'agent-unavailable'\n | 'internal'\n\n/** 统一响应外壳。 */\nexport type ApiResult<T> =\n | { readonly ok: true; readonly value: T }\n | {\n readonly ok: false\n readonly error: { readonly code: ApiErrorCode; readonly message: string }\n }\n\n/** GET /health */\nexport interface HealthValue {\n readonly name: string\n readonly version: string\n readonly workspacePath: string\n readonly hasGit: boolean\n readonly hasSessionService: boolean\n readonly presetId: string\n readonly archifySkillPath: string | null\n}\n\n/** POST /projects */\nexport interface CreateProjectRequest {\n readonly name?: string\n readonly repoPath: string\n}\n\n/** POST /projects/:id 的响应:建项目即触发首次运行,所以回整条记录。 */\nexport interface ProjectValue {\n readonly project: Project\n}\n\n/** GET /projects */\nexport interface ProjectsValue {\n readonly projects: readonly Project[]\n readonly workspacePath: string\n /** token 单价;null 表示页面只显示 token,不估算费用。 */\n readonly pricing: Pricing | null\n}\n\n/** PATCH /projects/:id */\nexport interface PatchProjectRequest {\n readonly name?: string\n readonly repoPath?: string\n}\n\n/** POST /projects/:id/update */\nexport interface UpdateValue {\n readonly project: Project\n readonly git: {\n readonly skipped: boolean\n readonly reason: string | null\n readonly from: string | null\n readonly to: string | null\n }\n}\n\n/** POST /projects/:id/check */\nexport interface CheckValue {\n readonly project: Project\n readonly drift: GitDrift\n}\n\n/** Git 漂移:页面用它决定要不要提示「更新」。分支 + 工作区,不看 commit。 */\nexport interface GitDrift {\n readonly branchChanged: boolean\n readonly treeChanged: boolean\n readonly dirty: boolean\n readonly behind: number\n readonly summary: string | null\n}\n\n/** POST /projects/:id/diagram/:kind(旧的按 kind 重画) */\nexport interface RegenerateRequest {\n readonly kind: DiagramKind\n}\n\n/** POST /projects/:id/diagrams:新建一张自定义图。 */\nexport interface CreateDiagramRequest {\n readonly title: string\n readonly prompt: string\n readonly persistence?: Persistence\n readonly parentId?: string | null\n readonly setId?: string | null\n readonly conceptIds?: readonly string[]\n /** archify 渲染器;不传 = 由 agent 判断。 */\n readonly renderer?: ArchifyType | null\n /** 限定取证的目录(相对仓库根);不传 = 全仓库。 */\n readonly pathScope?: readonly string[]\n /** 建完立刻跑;默认 true。 */\n readonly run?: boolean\n}\n\n/** PATCH /projects/:id/diagrams/:diagramId */\nexport interface PatchDiagramRequest {\n readonly title?: string\n readonly prompt?: string\n readonly persistence?: Persistence\n readonly conceptIds?: readonly string[]\n readonly renderer?: ArchifyType | null\n readonly pathScope?: readonly string[]\n}\n\n/** POST .../diagrams/:diagramId/ask */\nexport interface AskRequest {\n readonly question: string\n}\n\n/** 提问回执。 */\nexport interface AskValue {\n readonly sessionId: string | null\n readonly entry: QaEntry\n readonly project: Project\n}\n\n/** 「补进图里」/「创建子图」的回执。 */\nexport interface QaActionValue {\n readonly project: Project\n readonly qa: QaEntry\n /** 「创建子图」时返回新图的 id。 */\n readonly diagramId: string | null\n}\n\n/** 运行发起的统一回执。 */\nexport interface RunValue {\n readonly project: Project\n readonly run: { readonly id: string; readonly kind: RunKind; readonly sessionId: string | null }\n}\n\n/** 概念表回执(GET /projects/:id 里已带 concepts,这里留给未来单独刷新)。 */\nexport interface ConceptsValue {\n readonly concepts: readonly Concept[]\n}\n\n/** 生成产物在 res 里的机器标记(prompt 要求 agent 输出,runner 解析)。 */\nexport const ARTIFACT_MARKER = 'ARCHIFY_ARTIFACT'\n","/**\n * 台账数据模型(纯类型 + 纯构造函数,零 IO)。\n *\n * 这里是 host 与 client 唯一的「事实形状」来源:宿主写盘用它,页面渲染用它,\n * 序列化契约(protocol.ts 的请求/响应)也复用它。改这里的字段等于改协议版本。\n *\n * v2(图册):版本口径从 commit 级降到分支级;图谱从「两张固定图」变成\n * 「默认两张 + 任意自定义图」;概念(Concept)成为项目级一等公民;\n * 图可以临时(只跑这一次)或永久(下次更新沿用同一 prompt)。\n */\n\n/** 台账文件格式版本。字段形状不兼容时 +1,并在 store 里做迁移。 */\nexport const LEDGER_VERSION = 2\n\n/** 两张默认图谱(自定义图统一用 `custom`)。 */\nexport const DIAGRAM_KINDS = ['structure', 'domain'] as const\nexport type DiagramKind = (typeof DIAGRAM_KINDS)[number]\n\n/** 图种类:默认两张 + 自定义。 */\nexport type DiagramMode = DiagramKind | 'custom'\n\n/** Archify 的真实渲染器(见 skills/archify 的 Type router)。 */\nexport const ARCHIFY_TYPES = [\n 'architecture',\n 'workflow',\n 'sequence',\n 'dataflow',\n 'lifecycle',\n] as const\nexport type ArchifyType = (typeof ARCHIFY_TYPES)[number]\n\n/** 图的时效。永久图会在「更新图册」时用同一 prompt 重画;临时图只跑这一次。 */\nexport type Persistence = 'permanent' | 'temporary'\n\n/** 一次运行因何而起。 */\nexport type RunKind = 'create' | 'update' | 'regenerate'\n\n/** 运行状态。 */\nexport type RunStatus = 'running' | 'succeeded' | 'failed'\n\n/** 单张图谱的产物状态机:idle → queued → running → ready | failed。 */\nexport type DiagramStatus = 'idle' | 'queued' | 'running' | 'ready' | 'failed'\n\n/** 工作区状态(暂存 / 未暂存 / 未跟踪)。 */\nexport interface TreeState {\n readonly staged: number\n readonly unstaged: number\n readonly untracked: number\n}\n\n/**\n * 代码库的 Git 快照。\n *\n * 无 Git 目录时 `isRepo=false`,其余字段一律 `null` ——「不知道」和「空」必须能区分,\n * 否则页面会把 `null` 渲染成 0 或空串,用户会以为仓库干净。\n *\n * `commit` 仍保留(同步、排障用),但**不再是版本键**:图的「过期」看分支 + 工作区。\n */\nexport interface GitSnapshot {\n readonly isRepo: boolean\n readonly root: string | null\n readonly branch: string | null\n readonly detached: boolean\n readonly commit: string | null\n readonly commitFull: string | null\n readonly commitTime: string | null\n readonly commitSubject: string | null\n readonly upstream: string | null\n readonly ahead: number | null\n readonly behind: number | null\n readonly dirty: boolean | null\n /** 工作区明细;无 Git 或探测失败时为 null。 */\n readonly tree: TreeState | null\n readonly remoteUrl: string | null\n readonly checkedAt: string\n}\n\n/** 一张图谱的产物与元信息。`htmlPath` / `specPath` 都是相对插件工作区的路径。 */\nexport interface DiagramState {\n /** 稳定 id:默认两张是 `structure` / `domain`,自定义图是 `dgm_xxx`。 */\n readonly id: string\n readonly kind: DiagramMode\n readonly title: string\n /** 自定义图必填;默认图可为 null(用内置口径)。 */\n readonly prompt: string | null\n readonly persistence: Persistence\n /** 父子图:挂在哪张图下。 */\n readonly parentId: string | null\n /** 套图:同一 setId 的图在页面上归为一组(大图 + 细节小图)。 */\n readonly setId: string | null\n /** 这张图引用的概念 id。 */\n readonly conceptIds: readonly string[]\n /** 用户选的 archify 渲染器;null = 由 agent 按 prompt 判断。 */\n readonly renderer: ArchifyType | null\n /** 限定取证的目录(相对仓库根);空数组 = 全仓库。 */\n readonly pathScope: readonly string[]\n readonly status: DiagramStatus\n readonly htmlPath: string | null\n readonly specPath: string | null\n readonly generatedAt: string | null\n /** 生成时的分支(版本键的一半)。 */\n readonly branch: string | null\n /** 生成时的工作区状态(版本键的另一半)。 */\n readonly tree: TreeState | null\n readonly error: string | null\n}\n\n/** 从代码里提取出来的架构概念;项目级,可被多张图引用。 */\nexport interface Concept {\n readonly id: string\n readonly name: string\n readonly summary: string\n /** 支撑这个概念的代码路径(相对仓库根),追问时用来定位。 */\n readonly evidence: readonly string[]\n readonly tags: readonly string[]\n readonly updatedAt: string\n}\n\n/** 一条 agent 进度(`todo_write` 的快照)。 */\nexport interface TodoItem {\n readonly content: string\n readonly status: 'pending' | 'in_progress' | 'completed'\n}\n\n/** 一次运行消耗的 token(来自宿主的 tokenUsage projection)。 */\nexport interface TokenUsage {\n readonly uncachedInput: number\n readonly output: number\n readonly cacheRead: number\n readonly cacheWrite: number\n}\n\n/** 一次运行/一次提问的 token 合计。 */\nexport function usageTotalOf(usage: TokenUsage | null): number {\n if (usage === null) return 0\n return usage.uncachedInput + usage.output + usage.cacheRead + usage.cacheWrite\n}\n\n/** 对一张图的提问与回答。 */\nexport interface QaEntry {\n readonly id: string\n readonly diagramId: string\n readonly question: string\n readonly answer: string\n readonly at: string\n /** 回答是否已经并进图里。 */\n readonly applied: boolean\n /** 问答会话的运行状态;运行中页面据此显示进度。 */\n readonly status: 'running' | 'done' | 'failed'\n /** 问答会话的 todo 快照。 */\n readonly todos: readonly TodoItem[]\n readonly usage: TokenUsage | null\n}\n\n/** 一次 Agent 运行。会话 id 留着是为了能从页面跳回去看日志。 */\nexport interface RunRecord {\n readonly id: string\n readonly kind: RunKind\n readonly status: RunStatus\n readonly sessionId: string | null\n readonly startedAt: string\n readonly finishedAt: string | null\n readonly error: string | null\n readonly note: string | null\n /** 这次运行涉及哪些图(默认图用 kind,自定义图用 id)。 */\n readonly diagramIds: readonly string[]\n /** agent 的 todo 快照(最后写入的一份,用于运行结束后回看)。 */\n readonly todos: readonly TodoItem[]\n readonly usage: TokenUsage | null\n}\n\n/** 一个项目。 */\nexport interface Project {\n readonly id: string\n readonly name: string\n readonly repoPath: string\n readonly createdAt: string\n readonly updatedAt: string\n readonly git: GitSnapshot\n /** 两张默认图(结构图 / 领域图)。 */\n readonly diagrams: Readonly<Record<DiagramKind, DiagramState>>\n /** 用户自定义的图(任意数量,可父子、可套图)。 */\n readonly custom: readonly DiagramState[]\n /** 项目级概念表。 */\n readonly concepts: readonly Concept[]\n /** 提问记录(按图归属)。 */\n readonly qa: readonly QaEntry[]\n /** 最近一次运行(`runs` 的末条,单独冗余一份方便渲染)。 */\n readonly lastRun: RunRecord | null\n /** 运行历史,旧→新,上限由 config.keepRuns 控制。 */\n readonly runs: readonly RunRecord[]\n /** 历史累计 token(不随 runs 裁剪丢失)。 */\n readonly usageTotal: number\n}\n\n/** 台账文件根。 */\nexport interface ProjectLedger {\n readonly version: number\n readonly updatedAt: string\n readonly projects: readonly Project[]\n}\n\n/** 空 Git 快照(无 Git 或尚未探测)。 */\nexport function emptyGitSnapshot(checkedAt: string): GitSnapshot {\n return {\n isRepo: false,\n root: null,\n branch: null,\n detached: false,\n commit: null,\n commitFull: null,\n commitTime: null,\n commitSubject: null,\n upstream: null,\n ahead: null,\n behind: null,\n dirty: null,\n tree: null,\n remoteUrl: null,\n checkedAt,\n }\n}\n\n/** 默认两张图的标题。 */\nconst DEFAULT_TITLES: Record<DiagramKind, string> = {\n structure: '项目结构图',\n domain: '领域图',\n}\n\n/** 空图谱状态。 */\nexport function emptyDiagramState(kind: DiagramKind = 'structure'): DiagramState {\n return {\n id: kind,\n kind,\n title: DEFAULT_TITLES[kind],\n prompt: null,\n persistence: 'permanent',\n parentId: null,\n setId: kind === 'structure' ? 'structure' : null,\n conceptIds: [],\n renderer: 'architecture',\n pathScope: [],\n status: 'idle',\n htmlPath: null,\n specPath: null,\n generatedAt: null,\n branch: null,\n tree: null,\n error: null,\n }\n}\n\n/** 空台账。 */\nexport function emptyLedger(now: string): ProjectLedger {\n return { version: LEDGER_VERSION, updatedAt: now, projects: [] }\n}\n\n/** 默认两张 + 自定义图,统一成一条列表(默认在前)。 */\nexport function allDiagrams(project: Project): readonly DiagramState[] {\n return [...DIAGRAM_KINDS.map((kind) => project.diagrams[kind]), ...project.custom]\n}\n\n/** 按 id 找图;`structure` / `domain` 也能命中默认图。 */\nexport function findDiagram(project: Project, id: string): DiagramState | undefined {\n if ((DIAGRAM_KINDS as readonly string[]).includes(id)) {\n return project.diagrams[id as DiagramKind]\n }\n return project.custom.find((diagram) => diagram.id === id)\n}\n\n/** 不可变地替换一张图(默认图走 record,自定义图走数组)。 */\nexport function withDiagram(project: Project, next: DiagramState): Project {\n if ((DIAGRAM_KINDS as readonly string[]).includes(next.kind)) {\n return {\n ...project,\n diagrams: { ...project.diagrams, [next.kind as DiagramKind]: next },\n }\n }\n return { ...project, custom: project.custom.map((d) => (d.id === next.id ? next : d)) }\n}\n\n/** 需要「更新图册」的图:默认两张 + 所有永久自定义图。 */\nexport function runnableDiagrams(project: Project): readonly DiagramState[] {\n return allDiagrams(project).filter(\n (diagram) =>\n diagram.persistence === 'permanent' || DIAGRAM_KINDS.includes(diagram.kind as DiagramKind),\n )\n}\n\n/** 工作区状态签名,用于判断图是否过期。 */\nexport function treeSignature(tree: TreeState | null): string {\n if (tree === null) return 'none'\n return `${tree.staged}:${tree.unstaged}:${tree.untracked}`\n}\n\n/** 由 Git 快照派生「图谱是否落后于代码」——按分支 + 工作区,不看 commit。 */\nexport function isStale(project: Project): boolean {\n if (!project.git.isRepo) return false\n const ready = allDiagrams(project).filter((d) => d.status === 'ready')\n if (ready.length === 0) return false\n const currentTree = treeSignature(project.git.tree)\n return ready.some((d) => d.branch !== project.git.branch || treeSignature(d.tree) !== currentTree)\n}\n\n/** 运行记录超过这个时长还没结束,就当它已经死了。\n *\n * 宿主被 kill / 断电 / 重启时,`running` 会被原样留在台账里。页面如果照字面\n * 相信它,就会永远显示「生成中」并把按钮全部禁用——用户只能去手改 JSON。 */\nexport const RUN_STALE_MS = 25 * 60 * 1000\n\n/** 这条运行记录还算不算「活着」。 */\nexport function isRunAlive(run: RunRecord, now: number = Date.now()): boolean {\n if (run.status !== 'running') return false\n const started = Date.parse(run.startedAt)\n if (Number.isNaN(started)) return false\n return now - started < RUN_STALE_MS\n}\n\n/** 项目是否有正在进行的运行。 */\nexport function isRunning(project: Project, now: number = Date.now()): boolean {\n return (\n project.runs.some((run) => isRunAlive(run, now)) ||\n allDiagrams(project).some((d) => d.status === 'queued' || d.status === 'running')\n )\n}\n\n/** 有运行记录卡在 running、但其实早就过期了的项目。 */\nexport function hasStaleRun(project: Project, now: number = Date.now()): boolean {\n return project.runs.some((run) => run.status === 'running' && !isRunAlive(run, now))\n}\n\n/** 项目名兜底:名称空时用仓库目录名。 */\nexport function defaultProjectName(repoPath: string): string {\n const parts = repoPath.split(/[\\\\/]+/).filter((part) => part.length > 0)\n return parts.length > 0 ? (parts[parts.length - 1] as string) : repoPath\n}\n\n/** 构造项目时要给的输入(全部显式,便于测试确定性)。 */\nexport interface CreateProjectInput {\n readonly id: string\n readonly name: string\n readonly repoPath: string\n readonly now: string\n readonly git: GitSnapshot\n}\n\n/** 纯构造:不读盘、不探测,把已知事实拼成一条台账记录。 */\nexport function createProject(input: CreateProjectInput): Project {\n return {\n id: input.id,\n name: input.name,\n repoPath: input.repoPath,\n createdAt: input.now,\n updatedAt: input.now,\n git: input.git,\n diagrams: { structure: emptyDiagramState('structure'), domain: emptyDiagramState('domain') },\n custom: [],\n concepts: [],\n qa: [],\n lastRun: null,\n runs: [],\n usageTotal: 0,\n }\n}\n\n/** 当前正在进行的 todo(没有就是 null)。 */\nexport function activeTodo(run: RunRecord | null): TodoItem | null {\n if (run === null) return null\n return run.todos.find((todo) => todo.status === 'in_progress') ?? null\n}\n","/**\n * 展示与文件名用的纯函数(零 IO)。\n *\n * 这些函数被 host(拼目录名、写日志)和 client(渲染 Git 条、时间)同时使用,\n * 所以不能碰 Date.now() —— 当前时间一律由调用方传入,测试才能确定性。\n */\n\nimport { allDiagrams, type DiagramKind, type GitSnapshot, type Project } from './types.js'\n\n/** 空值占位符,页面统一用它,避免各处出现 '' / '-' / '—' 三种。 */\nexport const PLACEHOLDER = '—'\n\n/** commit 短写(7 位);空值给占位符。 */\nexport function shortCommit(commit: string | null | undefined): string {\n if (commit === null || commit === undefined || commit.length === 0) return PLACEHOLDER\n return commit.slice(0, 7)\n}\n\n/** 本地时间戳 `YYYYMMDD-HHMMSS`,用作 archify 目录名后缀。 */\nexport function formatStamp(date: Date): string {\n const pad = (n: number): string => String(n).padStart(2, '0')\n return (\n `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}` +\n `-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`\n )\n}\n\n/** 相对时间的结构化形态:单位与数值分开,交给调用方决定用哪种语言说。 */\nexport interface RelativeSpan {\n readonly kind: 'now' | 'minutes' | 'hours' | 'days' | 'date'\n readonly value: number\n readonly date?: string\n}\n\n/**\n * 相对时间(结构化)。\n *\n * 页面要中英双语,日文/德文同理,所以这里**不拼句子**,只回答\n * 「多久以前、以什么粒度」—— 拼句子是渲染层的事。\n */\nexport function relativeSpan(iso: string | null | undefined, now: Date): RelativeSpan | null {\n if (iso === null || iso === undefined || iso.length === 0) return null\n const then = new Date(iso).getTime()\n if (Number.isNaN(then)) return null\n const deltaSec = Math.round((now.getTime() - then) / 1000)\n if (deltaSec < 60) return { kind: 'now', value: 0 }\n if (deltaSec < 3600) return { kind: 'minutes', value: Math.floor(deltaSec / 60) }\n if (deltaSec < 86400) return { kind: 'hours', value: Math.floor(deltaSec / 3600) }\n if (deltaSec < 86400 * 30) return { kind: 'days', value: Math.floor(deltaSec / 86400) }\n return { kind: 'date', value: 0, date: new Date(then).toISOString().slice(0, 10) }\n}\n\n/** 相对时间文案(中文)。宿主日志与兜底用;页面走 `relativeSpan`。 */\nexport function formatRelative(iso: string | null | undefined, now: Date): string {\n if (iso === null || iso === undefined || iso.length === 0) return PLACEHOLDER\n const then = new Date(iso).getTime()\n if (Number.isNaN(then)) return PLACEHOLDER\n const deltaSec = Math.round((now.getTime() - then) / 1000)\n if (deltaSec < 0) return '刚刚'\n if (deltaSec < 60) return '刚刚'\n if (deltaSec < 3600) return `${Math.floor(deltaSec / 60)} 分钟前`\n if (deltaSec < 86400) return `${Math.floor(deltaSec / 3600)} 小时前`\n if (deltaSec < 86400 * 30) return `${Math.floor(deltaSec / 86400)} 天前`\n return new Date(then).toISOString().slice(0, 10)\n}\n\n/** ahead / behind 文案。两者都为 null(无上游)时返回 null,页面据此隐藏整块。 */\nexport function aheadBehindText(ahead: number | null, behind: number | null): string | null {\n if (ahead === null && behind === null) return null\n const parts: string[] = []\n if (ahead !== null && ahead > 0) parts.push(`↑${ahead}`)\n if (behind !== null && behind > 0) parts.push(`↓${behind}`)\n return parts.length > 0 ? parts.join(' ') : '同步'\n}\n\n/** Git 单行摘要:`branch @ shortcommit`,无 Git 时说明来源是当前目录。 */\nexport function gitSummary(git: GitSnapshot): string {\n if (!git.isRepo) return '非 Git 目录 · 基于当前目录分析'\n const where = git.detached ? `detached ${shortCommit(git.commit)}` : (git.branch ?? PLACEHOLDER)\n return `${where} @ ${shortCommit(git.commit)}`\n}\n\n/** 目录名安全化:只留 ASCII 字母数字与短横,其它折叠成单个短横。 */\nexport function slugify(input: string): string {\n const ascii = input\n .normalize('NFKD')\n .replace(/[^\\x20-\\x7E]/g, '')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n if (ascii.length === 0) return 'project'\n return ascii.slice(0, 40).replace(/-+$/g, '')\n}\n\n/** archify 目录名:`<type>-<slug>-<stamp>`,与 Skill 的约定一致。 */\nexport function archifyDirName(kind: DiagramKind, slug: string, stamp: string): string {\n return `${kind}-${slugify(slug)}-${stamp}`\n}\n\n/** 图谱类型的中文标题。 */\nexport function diagramTitle(kind: DiagramKind): string {\n return kind === 'structure' ? '项目结构图' : '领域图'\n}\n\n/** 两张图里最新的生成时间(用于「上次分析」)。 */\nexport function latestGeneratedAt(project: Project): string | null {\n const stamps = allDiagrams(project)\n .map((diagram) => diagram.generatedAt)\n .filter((v): v is string => v !== null)\n .sort()\n return stamps.length > 0 ? (stamps[stamps.length - 1] as string) : null\n}\n\n/** token 数的紧凑写法:1234 → 1.2k,12_345_678 → 12.3M。 */\nexport function formatTokens(n: number): string {\n if (!Number.isFinite(n) || n <= 0) return '0'\n if (n < 1000) return String(Math.round(n))\n if (n < 1_000_000) return `${(n / 1000).toFixed(n < 10_000 ? 1 : 0)}k`\n return `${(n / 1_000_000).toFixed(1)}M`\n}\n\n/** 费用估算:按每百万 token 的输入/输出单价(0 表示不显示)。 */\nexport interface Pricing {\n readonly inputPerMTok: number\n readonly outputPerMTok: number\n readonly cacheReadPerMTok: number\n}\n\n/** 估算费用(单位:货币,由配置决定是人民币还是美元)。 */\nexport function estimateCost(\n usage: { uncachedInput: number; output: number; cacheRead: number; cacheWrite: number } | null,\n pricing: Pricing | null,\n): number | null {\n if (usage === null || pricing === null) return null\n const perM = 1_000_000\n const cost =\n (usage.uncachedInput / perM) * pricing.inputPerMTok +\n (usage.output / perM) * pricing.outputPerMTok +\n (usage.cacheRead / perM) * pricing.cacheReadPerMTok\n return cost\n}\n\n/** 把绝对路径缩成「末两段」,页面里放不下全路径时用。 */\nexport function tailPath(fullPath: string, segments = 2): string {\n const parts = fullPath.split(/[\\\\/]+/).filter((p) => p.length > 0)\n if (parts.length <= segments) return fullPath\n return `…/${parts.slice(-segments).join('/')}`\n}\n","/**\n * 客户端 HTTP 封装。\n *\n * 只用同源 fetch(`credentials: 'same-origin'`),不引入任何请求库 ——\n * 插件产物必须能在外置 11 个 baseline 模块之外零依赖运行。\n */\n\nimport type { ApiResult } from '../shared/protocol.js'\n\n/** 带错误码的失败。 */\nexport class ApiError extends Error {\n readonly code: string\n\n constructor(code: string, message: string) {\n super(message)\n this.name = 'ApiError'\n this.code = code\n }\n}\n\n/** 发一次请求并解包 `{ok, value}`。 */\nexport async function api<T>(path: string, init?: RequestInit): Promise<T> {\n const response = await fetch(path, {\n credentials: 'same-origin',\n headers: init?.body === undefined ? undefined : { 'Content-Type': 'application/json' },\n ...init,\n })\n let payload: unknown\n try {\n payload = await response.json()\n } catch {\n throw new ApiError('bad-response', `服务返回了非 JSON 内容(HTTP ${response.status})`)\n }\n const result = payload as ApiResult<T>\n if (result.ok === true) return result.value\n const code = result.error?.code ?? 'unknown'\n const message = result.error?.message ?? '未知错误'\n throw new ApiError(code, message)\n}\n\n/** POST/PATCH 的便捷封装。 */\nexport function send<T>(\n path: string,\n method: 'POST' | 'PATCH' | 'DELETE',\n body?: unknown,\n): Promise<T> {\n return api<T>(path, {\n method,\n body: body === undefined ? undefined : JSON.stringify(body),\n })\n}\n","/**\n * 页面里可复用的小组件:标签、状态点、两个对话框。\n *\n * 只做展示与受控输入;请求与状态全部留在 page.tsx —— 组件不碰 fetch,\n * 测试才能只渲染、不 mock 网络。\n */\n\nimport { useMemo, useState } from 'react'\nimport type { ReactElement, ReactNode } from 'react'\nimport {\n ARCHIFY_TYPES,\n type ArchifyType,\n type Concept,\n type Persistence,\n type Project,\n} from '../shared/types.js'\nimport type { Translate } from './panel.js'\n\n/** 运行记录里的时钟:按本地时区显示 HH:mm:ss。 */\nexport function clockText(iso: string): string {\n const at = new Date(iso)\n if (Number.isNaN(at.getTime())) return iso.slice(11, 19)\n const pad = (n: number): string => String(n).padStart(2, '0')\n return `${pad(at.getHours())}:${pad(at.getMinutes())}:${pad(at.getSeconds())}`\n}\n\n/** 小标签。 */\nexport function Chip({\n tone = 'default',\n dot = false,\n children,\n}: {\n tone?: 'default' | 'ok' | 'warn' | 'accent'\n dot?: boolean\n children: ReactNode\n}): ReactElement {\n const cls = tone === 'default' ? 'dap-chip' : `dap-chip dap-chip--${tone}`\n return (\n <span className={cls}>\n {dot ? <span className=\"dap-chip__dot\" aria-hidden=\"true\" /> : null}\n {children}\n </span>\n )\n}\n\n/** 状态点。 */\nexport function Dot({ tone = 'ok' }: { tone?: 'ok' | 'warn' | 'err' | 'run' }): ReactElement {\n return (\n <span className={tone === 'ok' ? 'dap-dot' : `dap-dot dap-dot--${tone}`} aria-hidden=\"true\" />\n )\n}\n\n/** 新增项目对话框。 */\nexport function NewProjectDialog({\n t,\n busy,\n onClose,\n onSubmit,\n}: {\n t: Translate\n busy: boolean\n onClose: () => void\n onSubmit: (name: string, repoPath: string) => void\n}): ReactElement {\n const [name, setName] = useState('')\n const [repoPath, setRepoPath] = useState('')\n const [invalid, setInvalid] = useState(false)\n\n const submit = (): void => {\n if (repoPath.trim().length === 0) {\n setInvalid(true)\n return\n }\n onSubmit(name.trim(), repoPath.trim())\n }\n\n return (\n <div\n className=\"dap-mask\"\n role=\"presentation\"\n onClick={(e) => e.target === e.currentTarget && onClose()}\n >\n <div className=\"dap-dialog\" role=\"dialog\" aria-modal=\"true\" aria-label={t('dialog.addTitle')}>\n <h2 className=\"dap-dialog__title\">{t('dialog.addTitle')}</h2>\n <p className=\"dap-dialog__sub\">{t('empty.body')}</p>\n <label className=\"dap-field\">\n <span className=\"dap-field__label\">{t('field.name')}</span>\n <input\n className=\"dap-input\"\n value={name}\n placeholder={t('field.namePlaceholder')}\n onChange={(e) => setName(e.target.value)}\n />\n </label>\n <label className=\"dap-field\" style={{ marginTop: 8 }}>\n <span className=\"dap-field__label\">{t('field.path')}</span>\n <input\n className=\"dap-input\"\n value={repoPath}\n placeholder={t('field.pathPlaceholder')}\n aria-invalid={invalid}\n onChange={(e) => {\n setRepoPath(e.target.value)\n setInvalid(false)\n }}\n />\n </label>\n {invalid ? <p className=\"dap-notice dap-notice--error\">{t('field.pathRequired')}</p> : null}\n <div className=\"dap-dialog__foot\">\n <span className=\"dap-hint\">{t('git.noRepoHint')}</span>\n <div style={{ display: 'flex', gap: 6 }}>\n <button className=\"dap-btn dap-btn--sm\" onClick={onClose} disabled={busy}>\n {t('action.cancel')}\n </button>\n <button\n className=\"dap-btn dap-btn--sm dap-btn--primary\"\n onClick={submit}\n disabled={busy}\n >\n {t('action.submit')}\n </button>\n </div>\n </div>\n </div>\n </div>\n )\n}\n\nexport interface DiagramDraft {\n readonly title: string\n readonly prompt: string\n readonly persistence: Persistence\n readonly parentId: string | null\n readonly conceptIds: readonly string[]\n readonly renderer: ArchifyType | null\n readonly pathScope: readonly string[]\n}\n\n/** 从概念证据里推几个目录候选,够弹窗做「目录范围」。 */\nexport function scopeOptions(project: Project): readonly string[] {\n const dirs = new Set<string>()\n for (const concept of project.concepts) {\n for (const evidence of concept.evidence) {\n const parts = evidence.split('/').filter((part) => part.length > 0)\n if (parts.length === 0) continue\n const top = parts[0] as string\n dirs.add(\n parts.length >= 2 && (top === 'src' || top === 'packages' || top === 'apps')\n ? `${top}/${parts[1] as string}`\n : top,\n )\n }\n }\n const out = [...dirs].slice(0, 8)\n return out.length > 0 ? out : ['src']\n}\n\n/** 新建图对话框。 */\nexport function NewDiagramDialog({\n t,\n project,\n selectedId,\n busy,\n onClose,\n onSubmit,\n}: {\n t: Translate\n project: Project\n selectedId: string | null\n busy: boolean\n onClose: () => void\n onSubmit: (draft: DiagramDraft) => void\n}): ReactElement {\n const [prompt, setPrompt] = useState('')\n const [persistence, setPersistence] = useState<Persistence>('permanent')\n const [underCurrent, setUnderCurrent] = useState(selectedId !== null)\n const [chosen, setChosen] = useState<readonly string[]>([])\n const [renderer, setRenderer] = useState<ArchifyType | null>(null)\n const [pathScope, setPathScope] = useState<readonly string[]>([])\n const concepts = useMemo(() => project.concepts.slice(0, 24), [project.concepts])\n const scopes = useMemo(() => scopeOptions(project), [project])\n\n const toggleConcept = (id: string): void => {\n setChosen((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]))\n }\n const toggleScope = (dir: string): void => {\n setPathScope((prev) => (prev.includes(dir) ? prev.filter((x) => x !== dir) : [...prev, dir]))\n }\n\n const submit = (): void => {\n onSubmit({\n title: prompt.trim().slice(0, 24),\n prompt: prompt.trim(),\n persistence,\n parentId: underCurrent && selectedId !== null ? selectedId : null,\n conceptIds: chosen,\n renderer,\n pathScope,\n })\n }\n\n return (\n <div\n className=\"dap-mask\"\n role=\"presentation\"\n onClick={(e) => e.target === e.currentTarget && onClose()}\n >\n <div\n className=\"dap-dialog dap-dialog--wide\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={t('dialog.newDiagram')}\n >\n <h2 className=\"dap-dialog__title\">{t('dialog.newDiagram')}</h2>\n <p className=\"dap-dialog__sub\">{t('dialog.newDiagramSub')}</p>\n\n <div className=\"dap-dialog__row\" style={{ marginBottom: 4 }}>\n <span className=\"dap-field__label\">{t('field.renderer')}</span>\n <span className=\"dap-hint\">{t('field.rendererHint')}</span>\n </div>\n <div className=\"dap-typebar\">\n <button\n className={renderer === null ? 'dap-type-tag dap-type-tag--on' : 'dap-type-tag'}\n onClick={() => setRenderer(null)}\n >\n {t('renderer.auto')} <span className=\"dap-type-tag__k\">agent</span>\n </button>\n {ARCHIFY_TYPES.map((kind) => (\n <button\n key={kind}\n className={renderer === kind ? 'dap-type-tag dap-type-tag--on' : 'dap-type-tag'}\n onClick={() => setRenderer(kind)}\n >\n {t(`renderer.${kind}`)} <span className=\"dap-type-tag__k\">{kind}</span>\n </button>\n ))}\n </div>\n <div className=\"dap-type-detail\">\n {renderer === null ? (\n <div className=\"dap-type-detail__empty\">{t('renderer.autoHint')}</div>\n ) : (\n <>\n <div className=\"dap-type-detail__head\">\n <b>{t(`renderer.${renderer}`)}</b>\n <code>{renderer}</code>\n </div>\n <div className=\"dap-type-detail__row\">\n <span>{t('renderer.fit')}</span>\n <p>{t(`renderer.${renderer}.fit`)}</p>\n </div>\n <div className=\"dap-type-detail__row\">\n <span>{t('renderer.show')}</span>\n <p>{t(`renderer.${renderer}.show`)}</p>\n </div>\n <div className=\"dap-type-detail__row\">\n <span>{t('renderer.grasp')}</span>\n <p>{t(`renderer.${renderer}.grasp`)}</p>\n </div>\n </>\n )}\n </div>\n\n <div className=\"dap-field\" style={{ marginTop: 10 }}>\n <span className=\"dap-field__label\">{t('field.prompt')}</span>\n <textarea\n className=\"dap-textarea\"\n value={prompt}\n placeholder={\n renderer === null\n ? t('field.promptPlaceholder')\n : `${t(`renderer.${renderer}`)}:${t('field.promptPlaceholder')}`\n }\n onChange={(e) => setPrompt(e.target.value)}\n />\n </div>\n\n <div className=\"dap-dialog__row\" style={{ alignItems: 'flex-start' }}>\n <span className=\"dap-field__label\" style={{ flex: 'none' }}>\n {t('field.concepts')}\n </span>\n <div>\n <div className=\"dap-concepts\">\n {concepts.map((concept: Concept) => (\n <button\n key={concept.id}\n className={\n chosen.includes(concept.id) ? 'dap-concept dap-concept--on' : 'dap-concept'\n }\n onClick={() => toggleConcept(concept.id)}\n title={concept.summary}\n >\n {concept.name}\n </button>\n ))}\n </div>\n <p className=\"dap-hint\">{t('scope.conceptHint')}</p>\n </div>\n </div>\n\n <div className=\"dap-dialog__row\" style={{ alignItems: 'flex-start' }}>\n <span className=\"dap-field__label\" style={{ flex: 'none' }}>\n {t('field.pathScope')}\n </span>\n <div>\n <div className=\"dap-concepts\">\n {scopes.map((dir) => (\n <button\n key={dir}\n className={\n pathScope.includes(dir) ? 'dap-concept dap-concept--on' : 'dap-concept'\n }\n onClick={() => toggleScope(dir)}\n >\n {dir}\n </button>\n ))}\n </div>\n <p className=\"dap-hint\">{t('scope.pathHint')}</p>\n </div>\n </div>\n\n <div className=\"dap-dialog__row\">\n <span className=\"dap-field__label\">{t('field.persistence')}</span>\n <div className=\"dap-seg\">\n <button\n className={\n persistence === 'permanent' ? 'dap-seg__btn dap-seg__btn--on' : 'dap-seg__btn'\n }\n onClick={() => setPersistence('permanent')}\n >\n {t('persistence.permanent')}\n </button>\n <button\n className={\n persistence === 'temporary' ? 'dap-seg__btn dap-seg__btn--on' : 'dap-seg__btn'\n }\n onClick={() => setPersistence('temporary')}\n >\n {t('persistence.temporary')}\n </button>\n </div>\n </div>\n <p className=\"dap-hint\">\n {persistence === 'permanent'\n ? t('persistence.permanentHint')\n : t('persistence.temporaryHint')}\n </p>\n\n <div className=\"dap-dialog__row\">\n <span className=\"dap-field__label\">{t('field.parent')}</span>\n <div className=\"dap-seg\">\n <button\n className={!underCurrent ? 'dap-seg__btn dap-seg__btn--on' : 'dap-seg__btn'}\n onClick={() => setUnderCurrent(false)}\n >\n {t('parent.none')}\n </button>\n <button\n className={underCurrent ? 'dap-seg__btn dap-seg__btn--on' : 'dap-seg__btn'}\n onClick={() => setUnderCurrent(true)}\n disabled={selectedId === null}\n >\n {t('parent.current')}\n </button>\n </div>\n </div>\n\n <div className=\"dap-dialog__foot\">\n <span className=\"dap-hint\">{t('qa.mergeHint')}</span>\n <div style={{ display: 'flex', gap: 6 }}>\n <button className=\"dap-btn dap-btn--sm\" onClick={onClose} disabled={busy}>\n {t('action.cancel')}\n </button>\n <button\n className=\"dap-btn dap-btn--sm dap-btn--primary\"\n onClick={submit}\n disabled={busy || prompt.trim().length === 0}\n >\n {t('action.submit')}\n </button>\n </div>\n </div>\n </div>\n </div>\n )\n}\n","/**\n * 插件主页面:图册(总览) + 工作台(三栏)。\n *\n * 数据全部走插件自己的 HTTP 路由;有任务在跑时 2.5s 轮询,跑完自动停 ——\n * 不新增 WebSocket、不新增宿主契约。所有动作都有「进行中」状态与错误回执,\n * 失败不吞。\n */\n\nimport { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport type { ReactElement } from 'react'\nimport { ROUTES } from '../shared/protocol.js'\nimport type {\n AskValue,\n ProjectValue,\n ProjectsValue,\n QaActionValue,\n RunValue,\n UpdateValue,\n} from '../shared/protocol.js'\nimport {\n allDiagrams,\n findDiagram,\n isRunning,\n isStale,\n usageTotalOf,\n type DiagramState,\n type Project,\n type TodoItem,\n type TokenUsage,\n} from '../shared/types.js'\nimport { estimateCost, formatTokens, relativeSpan, type Pricing } from '../shared/format.js'\nimport { api, ApiError, send } from './api.js'\nimport {\n Chip,\n Dot,\n NewDiagramDialog,\n NewProjectDialog,\n clockText,\n type DiagramDraft,\n} from './components.js'\nimport type { Translate } from './panel.js'\n\n/** 页面状态。 */\ninterface PageState {\n readonly loading: boolean\n readonly error: string | null\n readonly projects: readonly Project[]\n readonly workspacePath: string | null\n readonly pricing: Pricing | null\n}\n\nconst INITIAL: PageState = {\n loading: true,\n error: null,\n projects: [],\n workspacePath: null,\n pricing: null,\n}\n\n/** 有任务在跑时的轮询间隔。 */\nconst POLL_MS = 2500\n\n/** 图册 / 工作台。 */\ntype Screen = 'atlas' | 'wb'\n\n/** 右栏页签。 */\ntype Tab = 'info' | 'qa'\n\n/** 页面组件。 */\nexport function StudioPage({ t }: { t: Translate }): ReactElement {\n const [state, setState] = useState<PageState>(INITIAL)\n const [screen, setScreen] = useState<Screen>('atlas')\n const [projectId, setProjectId] = useState<string | null>(null)\n const [diagramId, setDiagramId] = useState<string | null>(null)\n const [tab, setTab] = useState<Tab>('info')\n const [dialog, setDialog] = useState<'project' | 'diagram' | null>(null)\n const [busy, setBusy] = useState<string | null>(null)\n const [notice, setNotice] = useState<{ text: string; error: boolean } | null>(null)\n const [question, setQuestion] = useState('')\n const mounted = useRef(true)\n\n useEffect(() => {\n mounted.current = true\n return () => {\n mounted.current = false\n }\n }, [])\n\n const load = useCallback(async (): Promise<void> => {\n try {\n const value = await api<ProjectsValue>(ROUTES.projects)\n if (!mounted.current) return\n setState({\n loading: false,\n error: null,\n projects: value.projects,\n workspacePath: value.workspacePath,\n pricing: value.pricing ?? null,\n })\n } catch (err) {\n if (!mounted.current) return\n setState((prev) => ({ ...prev, loading: false, error: messageOf(err) }))\n }\n }, [])\n\n useEffect(() => {\n void load()\n }, [load])\n\n const running = state.projects.some((project) => isRunning(project))\n useEffect(() => {\n if (!running) return undefined\n const timer = setInterval(() => void load(), POLL_MS)\n return () => clearInterval(timer)\n }, [running, load])\n\n const project = useMemo(\n () => state.projects.find((item) => item.id === projectId) ?? null,\n [state.projects, projectId],\n )\n const diagram = useMemo(\n () => (project !== null && diagramId !== null ? findDiagram(project, diagramId) : undefined),\n [project, diagramId],\n )\n\n // 数据刷新后选中的图可能被删:回落到第一张默认图\n useEffect(() => {\n if (project === null) return\n if (diagramId !== null && findDiagram(project, diagramId) !== undefined) return\n setDiagramId(project.diagrams.structure.id)\n }, [project, diagramId])\n\n const applyProject = useCallback((next: Project): void => {\n if (!mounted.current) return\n setState((prev) => ({\n ...prev,\n projects: prev.projects.map((item) => (item.id === next.id ? next : item)),\n }))\n }, [])\n\n const fail = useCallback(\n (err: unknown): void => {\n setNotice({ text: t('error.generic', { message: messageOf(err) }), error: true })\n },\n [t],\n )\n\n const openProject = useCallback((id: string): void => {\n setProjectId(id)\n setScreen('wb')\n setTab('info')\n setDiagramId('structure')\n }, [])\n\n const runUpdate = async (target: Project): Promise<void> => {\n setBusy(`update:${target.id}`)\n setNotice(null)\n try {\n const value = await send<UpdateValue>(ROUTES.update(target.id), 'POST')\n applyProject(value.project)\n } catch (err) {\n fail(err)\n } finally {\n setBusy(null)\n }\n }\n\n const updateAll = async (): Promise<void> => {\n for (const item of state.projects) {\n if (isRunning(item)) continue\n await runUpdate(item)\n }\n }\n\n const regenerate = async (): Promise<void> => {\n if (project === null || diagram === undefined) return\n setBusy(`regen:${diagram.id}`)\n setNotice(null)\n try {\n const value = await send<RunValue>(ROUTES.regenerate(project.id, diagram.id), 'POST')\n applyProject(value.project)\n } catch (err) {\n fail(err)\n } finally {\n setBusy(null)\n }\n }\n\n const createProject = async (name: string, repoPath: string): Promise<void> => {\n setBusy('create:project')\n setNotice(null)\n try {\n const value = await send<ProjectValue>(ROUTES.projects, 'POST', { name, repoPath })\n setDialog(null)\n if (!mounted.current) return\n setState((prev) => ({\n ...prev,\n projects: [value.project, ...prev.projects.filter((p) => p.id !== value.project.id)],\n }))\n setProjectId(value.project.id)\n setDiagramId('structure')\n setScreen('wb')\n } catch (err) {\n fail(err)\n } finally {\n setBusy(null)\n }\n }\n\n const createDiagram = async (draft: DiagramDraft): Promise<void> => {\n if (project === null) return\n setBusy('create:diagram')\n setNotice(null)\n try {\n const value = await send<ProjectValue>(ROUTES.diagrams(project.id), 'POST', draft)\n setDialog(null)\n applyProject(value.project)\n const newest = value.project.custom[value.project.custom.length - 1]\n if (newest !== undefined) setDiagramId(newest.id)\n setNotice({ text: t('notice.running'), error: false })\n } catch (err) {\n fail(err)\n } finally {\n setBusy(null)\n }\n }\n\n const ask = async (): Promise<void> => {\n if (project === null || diagram === undefined || question.trim().length === 0) return\n setBusy('ask')\n setNotice(null)\n try {\n const value = await send<AskValue>(ROUTES.ask(project.id, diagram.id), 'POST', {\n question: question.trim(),\n })\n setQuestion('')\n applyProject(value.project)\n } catch (err) {\n fail(err)\n } finally {\n setBusy(null)\n }\n }\n\n const applyQa = async (qaId: string, action: 'merge' | 'child'): Promise<void> => {\n if (project === null) return\n setBusy(`${action}:${qaId}`)\n setNotice(null)\n try {\n const path =\n action === 'merge' ? ROUTES.mergeQa(project.id, qaId) : ROUTES.childQa(project.id, qaId)\n const value = await send<QaActionValue>(path, 'POST')\n applyProject(value.project)\n setNotice({ text: t(action === 'merge' ? 'notice.merged' : 'notice.child'), error: false })\n if (action === 'child' && value.diagramId !== null) setDiagramId(value.diagramId)\n } catch (err) {\n fail(err)\n } finally {\n setBusy(null)\n }\n }\n\n const setPersistence = async (persistence: 'permanent' | 'temporary'): Promise<void> => {\n if (project === null || diagram === undefined) return\n setBusy('persistence')\n try {\n const value = await send<ProjectValue>(\n ROUTES.customDiagram(project.id, diagram.id),\n 'PATCH',\n {\n persistence,\n },\n )\n applyProject(value.project)\n } catch (err) {\n fail(err)\n } finally {\n setBusy(null)\n }\n }\n\n const removeDiagram = async (): Promise<void> => {\n if (project === null || diagram === undefined) return\n setBusy('delete:diagram')\n try {\n const value = await send<ProjectValue>(ROUTES.customDiagram(project.id, diagram.id), 'DELETE')\n applyProject(value.project)\n setDiagramId('structure')\n } catch (err) {\n fail(err)\n } finally {\n setBusy(null)\n }\n }\n\n const ago = (iso: string | null): string => {\n const span = relativeSpan(iso, new Date())\n if (span === null) return t('status.none')\n if (span.kind === 'now') return t('time.now')\n if (span.kind === 'minutes') return t('time.minutes', { n: span.value })\n if (span.kind === 'hours') return t('time.hours', { n: span.value })\n if (span.kind === 'days') return t('time.days', { n: span.value })\n return span.date ?? t('status.none')\n }\n\n if (screen === 'wb' && project !== null) {\n return (\n <div className=\"dap-page\">\n <header className=\"dap-head\">\n <div>\n <button\n className=\"dap-btn dap-btn--ghost dap-btn--sm\"\n onClick={() => setScreen('atlas')}\n >\n ← {t('action.back')}\n </button>\n <h1 className=\"dap-head__title\" style={{ marginTop: 6 }}>\n {project.name}\n </h1>\n <p className=\"dap-head__sub\">{project.repoPath}</p>\n </div>\n <div className=\"dap-head__actions\">\n <button\n className=\"dap-btn dap-btn--sm dap-btn--primary\"\n onClick={() => void runUpdate(project)}\n disabled={busy !== null || isRunning(project)}\n >\n {t('action.update')}\n </button>\n </div>\n </header>\n <div className=\"dap-scroll dap-scroll--wb\">\n {notice !== null ? (\n <p className={notice.error ? 'dap-notice dap-notice--error' : 'dap-notice'}>\n {notice.text}\n </p>\n ) : null}\n <Workbench\n t={t}\n project={project}\n diagram={diagram}\n tab={tab}\n busy={busy}\n question={question}\n pricing={state.pricing}\n ago={ago}\n onTab={setTab}\n onQuestion={setQuestion}\n onAsk={() => void ask()}\n onSelect={setDiagramId}\n onRegenerate={() => void regenerate()}\n onPersistence={(p) => void setPersistence(p)}\n onDelete={() => void removeDiagram()}\n onQa={(id, action) => void applyQa(id, action)}\n onNewDiagram={() => setDialog('diagram')}\n />\n </div>\n {dialog === 'diagram' ? (\n <NewDiagramDialog\n t={t}\n project={project}\n selectedId={diagram?.id ?? null}\n busy={busy !== null}\n onClose={() => setDialog(null)}\n onSubmit={(draft) => void createDiagram(draft)}\n />\n ) : null}\n </div>\n )\n }\n\n return (\n <div className=\"dap-page\">\n <header className=\"dap-head\">\n <div>\n <h1 className=\"dap-head__title\">{t('page.title')}</h1>\n <p className=\"dap-head__sub\">{t('page.subtitle')}</p>\n </div>\n <div className=\"dap-head__actions\">\n <button\n className=\"dap-btn dap-btn--sm\"\n onClick={() => void updateAll()}\n disabled={busy !== null || state.projects.length === 0}\n >\n {t('action.updateAll')}\n </button>\n <button\n className=\"dap-btn dap-btn--sm dap-btn--primary\"\n onClick={() => setDialog('project')}\n >\n + {t('action.add')}\n </button>\n </div>\n </header>\n <div className=\"dap-scroll\">\n {notice !== null ? (\n <p className={notice.error ? 'dap-notice dap-notice--error' : 'dap-notice'}>\n {notice.text}\n </p>\n ) : null}\n {state.error !== null ? (\n <p className=\"dap-notice dap-notice--error\">{state.error}</p>\n ) : null}\n {state.projects.length === 0 && !state.loading ? (\n <div className=\"dap-empty\">\n <div className=\"dap-empty__title\">{t('empty.title')}</div>\n <div className=\"dap-empty__body\">{t('empty.body')}</div>\n <button\n className=\"dap-btn dap-btn--sm dap-btn--primary\"\n onClick={() => setDialog('project')}\n >\n {t('empty.action')}\n </button>\n </div>\n ) : (\n <>\n <div className=\"dap-stats\">\n <div className=\"dap-stat\">\n <div className=\"dap-stat__k\">{t('stat.repos')}</div>\n <div className=\"dap-stat__v\">{state.projects.length}</div>\n </div>\n <div className=\"dap-stat\">\n <div className=\"dap-stat__k\">{t('stat.concepts')}</div>\n <div className=\"dap-stat__v\">\n {state.projects.reduce((sum, item) => sum + item.concepts.length, 0)}\n </div>\n </div>\n <div className=\"dap-stat\">\n <div className=\"dap-stat__k\">{t('stat.diagrams')}</div>\n <div className=\"dap-stat__v\">\n {state.projects.reduce((sum, item) => sum + allDiagrams(item).length, 0)}\n </div>\n </div>\n <div className=\"dap-stat\">\n <div className=\"dap-stat__k\">{t('stat.running')}</div>\n <div className=\"dap-stat__v\">\n {state.projects.filter((item) => isRunning(item)).length}\n </div>\n </div>\n </div>\n <div className=\"dap-atlas\">\n {state.projects.map((item) => (\n <AtlasCard\n key={item.id}\n t={t}\n project={item}\n busy={busy}\n ago={ago}\n onOpen={() => openProject(item.id)}\n onUpdate={() => void runUpdate(item)}\n />\n ))}\n </div>\n </>\n )}\n </div>\n {dialog === 'project' ? (\n <NewProjectDialog\n t={t}\n busy={busy !== null}\n onClose={() => setDialog(null)}\n onSubmit={(name, repoPath) => void createProject(name, repoPath)}\n />\n ) : null}\n </div>\n )\n}\n\n// ------------------------------------------------------------------ 总览卡片\n\nfunction AtlasCard({\n t,\n project,\n busy,\n ago,\n onOpen,\n onUpdate,\n}: {\n t: Translate\n project: Project\n busy: string | null\n ago: (iso: string | null) => string\n onOpen: () => void\n onUpdate: () => void\n}): ReactElement {\n const diagrams = allDiagrams(project)\n const stale = isStale(project)\n const running = isRunning(project)\n const tree = project.git.tree\n return (\n <article\n className=\"dap-card\"\n role=\"button\"\n tabIndex={0}\n onClick={onOpen}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') onOpen()\n }}\n >\n <div className=\"dap-card__top\">\n <div className=\"dap-repo\">\n <Dot tone={running ? 'run' : stale ? 'warn' : 'ok'} />\n <div style={{ minWidth: 0 }}>\n <div className=\"dap-repo__name\">{project.name}</div>\n <div className=\"dap-repo__path\">{project.repoPath}</div>\n </div>\n </div>\n </div>\n <div className=\"dap-chips\">\n <Chip dot>\n {project.git.isRepo ? (project.git.branch ?? t('git.detached')) : t('git.noRepo')}\n </Chip>\n {tree !== null && tree.staged + tree.unstaged + tree.untracked > 0 ? (\n <Chip tone=\"warn\">\n {t('git.treeShort', { staged: tree.staged, unstaged: tree.unstaged })}\n </Chip>\n ) : (\n <Chip tone=\"ok\">{t('git.clean')}</Chip>\n )}\n <Chip tone=\"accent\">\n {project.concepts.length} {t('concept.title')}\n </Chip>\n {stale ? <Chip tone=\"warn\">{t('status.stale')}</Chip> : null}\n </div>\n <div className=\"dap-thumbs\">\n {diagrams.slice(0, 3).map((item) => (\n <div\n key={item.id}\n className={item.kind === 'structure' ? 'dap-thumb dap-thumb--wide' : 'dap-thumb'}\n >\n <div className=\"dap-thumb__title\">\n <b>{item.title}</b>\n <span className=\"dap-thumb__count\">\n {item.persistence === 'temporary' ? t('persistence.temporary') : t('status.ready')}\n </span>\n </div>\n </div>\n ))}\n </div>\n <div className=\"dap-card__foot\">\n <span>\n {running\n ? t('runbar.step', { step: activeStep(project) ?? t('runbar.unknown') })\n : `${project.git.branch ?? t('git.noRepo')} · ${ago(\n diagrams.find((d) => d.generatedAt)?.generatedAt ?? null,\n )}`}\n {project.usageTotal > 0 ? ` · ${formatTokens(project.usageTotal)} tokens` : ''}\n </span>\n <button\n className=\"dap-btn dap-btn--sm\"\n onClick={(event) => {\n event.stopPropagation()\n onUpdate()\n }}\n >\n {t('action.update')}\n </button>\n </div>\n {busy === `update:${project.id}` ? (\n <span className=\"dap-hint\">{t('notice.running')}</span>\n ) : null}\n </article>\n )\n}\n\n// ------------------------------------------------------------------ 工作台\n\nfunction Workbench({\n t,\n project,\n diagram,\n tab,\n busy,\n question,\n pricing,\n ago,\n onTab,\n onQuestion,\n onAsk,\n onSelect,\n onRegenerate,\n onPersistence,\n onDelete,\n onQa,\n onNewDiagram,\n}: {\n t: Translate\n project: Project\n diagram: DiagramState | undefined\n tab: Tab\n busy: string | null\n question: string\n pricing: Pricing | null\n ago: (iso: string | null) => string\n onTab: (tab: Tab) => void\n onQuestion: (value: string) => void\n onAsk: () => void\n onSelect: (id: string) => void\n onRegenerate: () => void\n onPersistence: (persistence: 'permanent' | 'temporary') => void\n onDelete: () => void\n onQa: (qaId: string, action: 'merge' | 'child') => void\n onNewDiagram: () => void\n}): ReactElement {\n const custom = project.custom\n const children =\n diagram === undefined ? [] : custom.filter((item) => item.parentId === diagram.id)\n const parent = diagram?.parentId != null ? findDiagram(project, diagram.parentId) : undefined\n const thread = project.qa.filter((entry) => entry.diagramId === diagram?.id)\n\n const tree = project.git.tree\n return (\n <>\n <div className=\"dap-chips\" style={{ marginBottom: 10 }}>\n <Chip dot>\n {project.git.isRepo ? (project.git.branch ?? t('git.detached')) : t('git.noRepo')}\n </Chip>\n {tree !== null ? (\n <Chip tone=\"warn\">\n {t('git.tree', {\n staged: tree.staged,\n unstaged: tree.unstaged,\n untracked: tree.untracked,\n })}\n </Chip>\n ) : null}\n <span className=\"dap-hint\">\n {t('meta.branchLevel', { branch: project.git.branch ?? '—' })}\n </span>\n </div>\n\n <RunBar t={t} project={project} />\n\n <div className=\"dap-wb\">\n <div className=\"dap-panel dap-viewer\">\n <ThumbStrip\n t={t}\n project={project}\n selectedId={diagram?.id ?? null}\n onSelect={onSelect}\n onNewDiagram={onNewDiagram}\n />\n <div className=\"dap-viewer__head\">\n <div className=\"dap-viewer__title\">\n {diagram?.title ?? t('status.none')}\n {parent !== undefined ? <span>/ {parent.title}</span> : null}\n </div>\n <div className=\"dap-viewer__tools\">\n {diagram?.htmlPath != null ? (\n <>\n <a\n className=\"dap-btn dap-btn--ghost dap-btn--sm\"\n href={ROUTES.artifact(diagram.htmlPath)}\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n {t('action.openTab')}\n </a>\n <a\n className=\"dap-btn dap-btn--ghost dap-btn--sm\"\n href={ROUTES.artifact(diagram.htmlPath)}\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n {t('action.fullscreen')}\n </a>\n </>\n ) : null}\n <button\n className=\"dap-btn dap-btn--ghost dap-btn--sm\"\n onClick={onRegenerate}\n disabled={busy !== null}\n >\n {t('action.regenerate')}\n </button>\n </div>\n </div>\n <div className=\"dap-stage\">\n {diagram?.status === 'ready' && diagram.htmlPath !== null ? (\n <div className=\"dap-frame\">\n <div className=\"dap-frame__cap\">\n <span>\n {project.git.branch ?? t('git.noRepo')} · {ago(diagram.generatedAt)}\n </span>\n <span>{diagram.title}</span>\n </div>\n <iframe\n className=\"dap-iframe\"\n title={diagram.title}\n src={ROUTES.artifact(diagram.htmlPath)}\n sandbox=\"allow-scripts allow-same-origin\"\n onLoad={(event) => hideArtifactHeader(event.currentTarget)}\n />\n </div>\n ) : (\n <div className=\"dap-frame\">\n <div className=\"dap-empty\">\n <div className=\"dap-empty__title\">\n {diagram === undefined\n ? t('status.none')\n : t(`status.${diagram.status}` as never)}\n </div>\n {diagram?.error != null ? (\n <div className=\"dap-empty__body\">{diagram.error}</div>\n ) : (\n <div className=\"dap-empty__body\">{t('notice.running')}</div>\n )}\n <button\n className=\"dap-btn dap-btn--sm dap-btn--primary\"\n onClick={onRegenerate}\n disabled={busy !== null}\n >\n {t('action.regenerate')}\n </button>\n </div>\n </div>\n )}\n {children.length > 0 ? (\n <div className=\"dap-insets\">\n {children.map((child) => (\n <button\n key={child.id}\n className={child.id === diagram?.id ? 'dap-inset dap-inset--on' : 'dap-inset'}\n onClick={() => onSelect(child.id)}\n >\n <Dot\n tone={\n child.status === 'ready' ? 'ok' : child.status === 'failed' ? 'err' : 'run'\n }\n />\n <span className=\"dap-inset__name\">{child.title}</span>\n </button>\n ))}\n </div>\n ) : null}\n </div>\n </div>\n\n <div className=\"dap-panel\">\n <div className=\"dap-tabs\">\n <button\n className={tab === 'info' ? 'dap-tab dap-tab--on' : 'dap-tab'}\n onClick={() => onTab('info')}\n >\n {t('field.status')}\n </button>\n <button\n className={tab === 'qa' ? 'dap-tab dap-tab--on' : 'dap-tab'}\n onClick={() => onTab('qa')}\n >\n {t('qa.title')}\n {thread.length > 0 ? ` ${thread.length}` : ''}\n </button>\n </div>\n {tab === 'info' ? (\n <div className=\"dap-insp\">\n {diagram === undefined ? (\n <p className=\"dap-hint\">{t('status.none')}</p>\n ) : (\n <>\n <div className=\"dap-field\">\n <span className=\"dap-field__label\">{t('field.status')}</span>\n <div className=\"dap-chips\">\n <Chip\n tone={\n diagram.status === 'ready'\n ? 'ok'\n : diagram.status === 'failed'\n ? 'warn'\n : 'default'\n }\n >\n {t(`status.${diagram.status}` as never)}\n </Chip>\n <Chip tone={diagram.persistence === 'permanent' ? 'accent' : 'default'}>\n {diagram.persistence === 'permanent'\n ? t('persistence.permanent')\n : t('persistence.temporary')}\n </Chip>\n <Chip>{ago(diagram.generatedAt)}</Chip>\n </div>\n </div>\n {diagram.kind === 'custom' ? (\n <div className=\"dap-field\">\n <span className=\"dap-field__label\">{t('field.prompt')}</span>\n <div className=\"dap-box\">\n {diagram.prompt ?? t('status.none')}\n <div className=\"dap-box__meta\">\n <button\n className=\"dap-btn dap-btn--ghost dap-btn--sm\"\n onClick={() =>\n onPersistence(\n diagram.persistence === 'permanent' ? 'temporary' : 'permanent',\n )\n }\n disabled={busy !== null}\n >\n {diagram.persistence === 'permanent'\n ? t('persistence.temporary')\n : t('persistence.permanent')}\n </button>\n <button\n className=\"dap-btn dap-btn--ghost dap-btn--sm\"\n onClick={onDelete}\n disabled={busy !== null}\n >\n {t('action.delete')}\n </button>\n </div>\n </div>\n </div>\n ) : null}\n {diagram.error !== null ? (\n <p className=\"dap-notice dap-notice--error\">{diagram.error}</p>\n ) : null}\n <div className=\"dap-field\">\n <span className=\"dap-field__label\">{t('field.branch')}</span>\n <div className=\"dap-chips\">\n <Chip dot>{diagram.branch ?? project.git.branch ?? t('git.noRepo')}</Chip>\n {diagram.tree !== null ? (\n <Chip>\n {t('git.treeShort', {\n staged: diagram.tree.staged,\n unstaged: diagram.tree.unstaged,\n })}\n </Chip>\n ) : null}\n </div>\n </div>\n <div className=\"dap-field\">\n <span className=\"dap-field__label\">{t('field.concepts')}</span>\n <div className=\"dap-chips\">\n {diagram.conceptIds.length === 0\n ? project.concepts\n .slice(0, 6)\n .map((concept) => <Chip key={concept.id}>{concept.name}</Chip>)\n : diagram.conceptIds.map((id) => {\n const concept = project.concepts.find((item) => item.id === id)\n return <Chip key={id}>{concept?.name ?? id}</Chip>\n })}\n </div>\n </div>\n <div className=\"dap-field\">\n <span className=\"dap-field__label\">{t('progress.title')}</span>\n <TodoList t={t} todos={project.lastRun?.todos ?? []} />\n </div>\n <div className=\"dap-field\">\n <span className=\"dap-field__label\">{t('usage.title')}</span>\n <UsageBlock t={t} project={project} pricing={pricing} />\n </div>\n <div className=\"dap-field\">\n <span className=\"dap-field__label\">{t('run.history')}</span>\n <div className=\"dap-hint\">\n {project.runs\n .slice(-3)\n .reverse()\n .map((run) => (\n <div key={run.id}>\n {clockText(run.startedAt)} · {t(`run.kind.${run.kind}` as never)} ·{' '}\n {t(`run.status.${run.status}` as never)}\n </div>\n ))}\n </div>\n </div>\n </>\n )}\n </div>\n ) : (\n <>\n <div className=\"dap-insp\">\n {thread.length === 0 ? <p className=\"dap-hint\">{t('qa.empty')}</p> : null}\n {thread.map((entry) => (\n <div key={entry.id} className=\"dap-qa\">\n <div className=\"dap-msg dap-msg--me\">\n <span className=\"dap-msg__who\">{t('qa.me')}</span>\n <div className=\"dap-msg__bubble\">{entry.question}</div>\n </div>\n <div className=\"dap-msg\">\n <span className=\"dap-msg__who\">{t('qa.answer')}</span>\n <div className=\"dap-msg__bubble\">{entry.answer}</div>\n <div className=\"dap-msg__acts\">\n {entry.applied ? (\n <Chip tone=\"ok\">{t('qa.applied')}</Chip>\n ) : (\n <>\n <button\n className=\"dap-btn dap-btn--sm\"\n onClick={() => onQa(entry.id, 'merge')}\n disabled={busy !== null}\n >\n {t('action.merge')}\n </button>\n <button\n className=\"dap-btn dap-btn--sm\"\n onClick={() => onQa(entry.id, 'child')}\n disabled={busy !== null}\n >\n {t('action.child')}\n </button>\n </>\n )}\n </div>\n </div>\n </div>\n ))}\n </div>\n <div className=\"dap-ask\">\n <textarea\n value={question}\n placeholder={t('qa.placeholder')}\n onChange={(event) => onQuestion(event.target.value)}\n disabled={busy === 'ask'}\n />\n <button\n className=\"dap-btn dap-btn--sm dap-btn--primary\"\n onClick={onAsk}\n disabled={busy !== null || question.trim().length === 0}\n >\n {busy === 'ask' ? t('qa.working') : t('action.send')}\n </button>\n </div>\n <p className=\"dap-hint\" style={{ padding: '0 10px 10px' }}>\n {t('qa.mergeHint')}\n </p>\n </>\n )}\n </div>\n </div>\n </>\n )\n}\n\n/** 图片上方的图名 tab 条:默认两张 + 自定义图(子图挂在父图后面)。 */\nexport function ThumbStrip({\n t,\n project,\n selectedId,\n onSelect,\n onNewDiagram,\n}: {\n t: Translate\n project: Project\n selectedId: string | null\n onSelect: (id: string) => void\n onNewDiagram: () => void\n}): ReactElement {\n const diagrams = allDiagrams(project)\n const defaults = diagrams.filter((item) => item.kind !== 'custom')\n const custom = diagrams.filter((item) => item.kind === 'custom')\n const roots = custom.filter((item) => item.parentId === null)\n const childrenOf = (id: string): readonly DiagramState[] =>\n custom.filter((item) => item.parentId === id)\n\n return (\n <div className=\"dap-strip\" role=\"tablist\" aria-label={t('page.title')}>\n {defaults.map((item) => (\n <Thumb\n key={item.id}\n t={t}\n diagram={item}\n active={item.id === selectedId}\n onSelect={onSelect}\n />\n ))}\n {roots.length > 0 ? <span className=\"dap-strip__sep\" aria-hidden=\"true\" /> : null}\n {roots.map((item) => (\n <Fragment key={item.id}>\n <Thumb t={t} diagram={item} active={item.id === selectedId} onSelect={onSelect} />\n {childrenOf(item.id).map((child) => (\n <Thumb\n key={child.id}\n t={t}\n diagram={child}\n active={child.id === selectedId}\n child\n onSelect={onSelect}\n />\n ))}\n </Fragment>\n ))}\n <button className=\"dap-strip__new\" onClick={onNewDiagram} title={t('action.newDiagram')}>\n + {t('action.newDiagram')}\n </button>\n </div>\n )\n}\n\n/**\n * 内嵌查看器里去掉成品自带的页头(大标题 + 状态圆点)。\n *\n * 成品 HTML 顶部有一行「<项目名> · 图名」的大标题和一个 pulse-dot,而插件自己的\n * 查看器标题栏已经写了图名、帧头也写了分支与时间——重复一遍只是白吃画布高度。\n * 只在 iframe 里注入,点「新窗口」单独打开时仍是成品原样(那里没有外层标题)。\n * 同源才改得动;被沙箱或跨域挡住就静默跳过,不影响看图。\n */\nexport function hideArtifactHeader(frame: HTMLIFrameElement): void {\n try {\n const doc = frame.contentDocument\n if (doc === null) return\n if (doc.head.querySelector('style[data-embedded=\"compact\"]') !== null) return\n const style = doc.createElement('style')\n style.dataset.embedded = 'compact'\n style.textContent = '.header{display:none!important}'\n doc.head.appendChild(style)\n } catch {\n // 跨域或沙箱拦截:保留成品原样\n }\n}\n\n/**\n * 一个图名 tab:只有文字,没有缩略图。\n *\n * 之前用 0.25 倍 iframe 做真预览,一条占 91px,把图片本身压下去;改成纯文字后\n * 整条 32px,状态用一个 5px 圆点表达,临时图挂一个小标签。\n */\nfunction Thumb({\n t,\n diagram,\n active,\n child = false,\n onSelect,\n}: {\n t: Translate\n diagram: DiagramState\n active: boolean\n child?: boolean\n onSelect: (id: string) => void\n}): ReactElement {\n const tone = diagram.status === 'ready' ? 'ok' : diagram.status === 'failed' ? 'err' : 'warn'\n return (\n <button\n className={['dap-tabitem', active ? 'dap-tabitem--on' : '', child ? 'dap-tabitem--child' : '']\n .filter((part) => part.length > 0)\n .join(' ')}\n role=\"tab\"\n aria-selected={active}\n onClick={() => onSelect(diagram.id)}\n title={`${diagram.title} · ${t(`status.${diagram.status}` as never)}`}\n >\n <Dot tone={tone} />\n <span className=\"dap-tabitem__name\">{diagram.title}</span>\n {diagram.persistence === 'temporary' ? (\n <span className=\"dap-tabitem__tag\">{t('persistence.temporaryShort')}</span>\n ) : null}\n </button>\n )\n}\n\nfunction messageOf(err: unknown): string {\n if (err instanceof ApiError) return err.message\n return err instanceof Error ? err.message : String(err)\n}\n\n// ------------------------------------------------------------------ 进度与用量\n\n/** 运行中当前步骤;没有 todo 时返回 null。 */\nfunction activeStep(project: Project): string | null {\n const todo = project.lastRun?.todos.find((item) => item.status === 'in_progress')\n return todo?.content ?? null\n}\n\n/** 顶部运行进度条:只在有 agent 在跑时出现。 */\nfunction RunBar({ t, project }: { t: Translate; project: Project }): ReactElement | null {\n const run = project.lastRun\n const running = isRunning(project)\n const qaRunning = project.qa.find((entry) => entry.status === 'running')\n if (!running && qaRunning === undefined) return null\n\n const todos = running ? (run?.todos ?? []) : (qaRunning?.todos ?? [])\n const current = todos.find((todo) => todo.status === 'in_progress')\n const done = todos.filter((todo) => todo.status === 'completed').length\n const percent = todos.length === 0 ? 0 : Math.round((done / todos.length) * 100)\n const usage = running ? (run?.usage ?? null) : (qaRunning?.usage ?? null)\n\n return (\n <div className=\"dap-runbar\">\n <span className=\"dap-spinner\" aria-hidden=\"true\" />\n <b>{t('runbar.running')}</b>\n <span className=\"dap-runbar__step\">\n {t('runbar.step', { step: current?.content ?? todos[0]?.content ?? t('runbar.unknown') })}\n {todos.length > 0 ? `(${done}/${todos.length})` : ''}\n </span>\n {todos.length > 0 ? (\n <>\n <span className=\"dap-runbar__bar\">\n <i style={{ width: `${percent}%` }} />\n </span>\n <span className=\"dap-runbar__pct\">{percent}%</span>\n </>\n ) : null}\n {usage !== null ? (\n <span className=\"dap-runbar__tok\">\n {t('runbar.tokens', { n: formatTokens(usageTotalOf(usage)) })}\n </span>\n ) : null}\n </div>\n )\n}\n\n/** todo 列表:✓ / spinner / ○。 */\nexport function TodoList({ t, todos }: { t: Translate; todos: readonly TodoItem[] }): ReactElement {\n if (todos.length === 0) return <p className=\"dap-hint\">{t('progress.none')}</p>\n return (\n <div className=\"dap-todos\">\n {todos.map((todo) => (\n <div\n key={todo.content}\n className={\n todo.status === 'completed'\n ? 'dap-todo dap-todo--done'\n : todo.status === 'in_progress'\n ? 'dap-todo dap-todo--now'\n : 'dap-todo'\n }\n >\n <span className=\"dap-todo__mark\" aria-hidden=\"true\">\n {todo.status === 'completed' ? (\n '✓'\n ) : todo.status === 'in_progress' ? (\n <span className=\"dap-spinner\" />\n ) : (\n '○'\n )}\n </span>\n {todo.content}\n </div>\n ))}\n </div>\n )\n}\n\n/** 用量块:本次 / 累计 tokens,配了单价才算钱。 */\nexport function UsageBlock({\n t,\n project,\n pricing,\n}: {\n t: Translate\n project: Project\n pricing: Pricing | null\n}): ReactElement {\n const current: TokenUsage | null = project.lastRun?.usage ?? null\n const cost = estimateCost(current, pricing)\n return (\n <div className=\"dap-usage\">\n <div>\n <span>{t('usage.current')}</span>\n <b>{formatTokens(usageTotalOf(current))}</b>\n {cost !== null ? <em>{t('usage.estimated', { cost: cost.toFixed(3) })}</em> : null}\n </div>\n <div>\n <span>{t('usage.total')}</span>\n <b>{formatTokens(project.usageTotal)}</b>\n </div>\n {current !== null ? (\n <div className=\"dap-usage__detail\">\n <span>\n {t('usage.detail', {\n input: formatTokens(current.uncachedInput),\n cacheRead: formatTokens(current.cacheRead),\n output: formatTokens(current.output),\n })}\n </span>\n </div>\n ) : null}\n {pricing === null ? <span className=\"dap-hint\">{t('usage.estimateNote')}</span> : null}\n </div>\n )\n}\n","/**\n * 侧栏入口图标。\n *\n * 契约:`sidebar.panellist` 只给 `{ size, active }`,组件**只画图标** ——\n * 外层容器、点击、aria、选中态全部由 sidebar 自己负责,插件不要抢。\n */\n\nimport type { CSSProperties, ReactElement } from 'react'\n\n/** 文案取值函数。 */\nexport type Translate = (key: string, vars?: Record<string, string | number>) => string\n\nexport interface PanelIconProps {\n readonly size?: number\n readonly active?: boolean\n}\n\n/** 三点连接的结构图小图标,用 currentColor 跟随侧栏配色。 */\nexport function PanelIcon({ size = 16 }: PanelIconProps): ReactElement {\n const style: CSSProperties = { display: 'block' }\n return (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.6}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n style={style}\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path d=\"M3.6 6.3V5.2c0-.9.7-1.6 1.6-1.6h3\" opacity=\"0.55\" />\n <path d=\"M20.4 17.7v1.1c0 .9-.7 1.6-1.6 1.6h-3\" opacity=\"0.55\" />\n <circle cx=\"7.6\" cy=\"9.4\" r=\"2.1\" />\n <circle cx=\"16.4\" cy=\"7.1\" r=\"1.7\" />\n <circle cx=\"12.6\" cy=\"16.6\" r=\"1.7\" />\n <path d=\"M9.5 8.4l5.1-1\" />\n <path d=\"M8.6 11.2l2.9 3.6\" />\n <path d=\"M15.9 8.8l-2.2 5.9\" opacity=\"0.7\" />\n </svg>\n )\n}\n","/**\n * 页面文案。中英双语,键集合必须完全一致(`tests/contract/locale.test.ts` 守着)。\n *\n * 不放在 `locale/*.json` 里:那两个文件是**包级元数据**(宿主用来显示插件名与描述),\n * 页面内的即时文案走 `ctx.locale.register(ns, DICT)`。\n */\n\nexport const LOCALE_NS = 'dsh-archify-projects'\n\n/** 中文(默认)。 */\nconst zh: Record<string, string> = {\n 'panel.title': '架构建模',\n 'panel.desc': '代码库概念与图册',\n\n 'page.title': '架构建模',\n 'page.subtitle': '代码库 → 概念 → 图册 · 按分支维护,工作区改动一起纳入',\n\n 'stat.repos': '在册仓库',\n 'stat.concepts': '已提取概念',\n 'stat.diagrams': '图与套图',\n 'stat.running': '正在生成',\n 'tab.info': '图信息',\n 'tab.qa': '提问',\n\n 'action.add': '新增项目',\n 'action.refresh': '刷新',\n 'action.update': '更新图册',\n 'action.updateAll': '全部更新',\n 'action.check': '重探 Git',\n 'action.openTab': '新窗口',\n 'action.fullscreen': '全屏',\n 'action.regenerate': '重画',\n 'action.delete': '删除',\n 'action.openSession': '查看会话',\n 'action.cancel': '取消',\n 'action.submit': '创建并分析',\n 'action.close': '关闭',\n 'action.back': '返回图册',\n 'action.newDiagram': '新建图',\n 'action.ask': '提问',\n 'action.send': '发送',\n 'action.merge': '补进图里',\n 'action.child': '创建子图',\n 'action.save': '保存',\n\n 'empty.title': '还没有项目',\n 'empty.body': '新增一个项目:填名称和本地代码库路径,插件会先提取概念,再生成默认两张图。',\n 'empty.action': '新增第一个项目',\n\n 'dialog.addTitle': '新增项目',\n 'field.name': '项目名称',\n 'field.namePlaceholder': '留空则用目录名',\n 'field.path': '代码库路径',\n 'field.pathPlaceholder': '/home/me/projects/your-repo',\n 'field.pathRequired': '请填写代码库路径',\n\n 'dialog.newDiagram': '新建图',\n 'dialog.newDiagramSub': '用自然语言描述要什么图,agent 读代码后按这个 prompt 画。',\n 'field.title': '标题',\n 'field.titlePlaceholder': '例如:一次更新的调用链',\n 'field.prompt': '出图要求',\n 'field.promptPlaceholder':\n '例如:画出一次「更新」从按钮到产物回收的完整调用链,标出每一步失败会怎样。',\n 'field.persistence': '时效',\n 'field.parent': '归属',\n 'field.concepts': '概念范围',\n 'field.branch': '分支',\n 'field.status': '状态',\n 'field.generatedAt': '生成时间',\n 'field.owner': '所属',\n 'field.tree': '工作区',\n\n 'field.renderer': '图型',\n 'field.pathScope': '目录范围',\n 'field.rendererHint': '点一下看它展示什么、能让你掌握什么',\n 'renderer.auto': '不限',\n 'renderer.autoHint':\n '不选也行:Archify 会按你的 prompt 自行判断图型;选了只是让 agent 少猜一步,图会更稳。',\n 'renderer.fit': '适合看',\n 'renderer.show': '会展示',\n 'renderer.grasp': '能掌握',\n 'renderer.architecture': '架构图',\n 'renderer.architecture.fit': '系统由什么组成、谁依赖谁',\n 'renderer.architecture.show': '组件与服务、进程/网络/信任边界、外部依赖',\n 'renderer.architecture.grasp': '分层与职责、关键依赖、边界外的风险',\n 'renderer.workflow': '流程图',\n 'renderer.workflow.fit': '一个流程怎么走、有哪些判断与审批',\n 'renderer.workflow.show': '步骤、分支条件、失败与回退',\n 'renderer.workflow.grasp': '主流程与异常路径、谁在什么条件下做什么',\n 'renderer.sequence': '时序图',\n 'renderer.sequence.fit': '一次请求在模块之间怎么往返',\n 'renderer.sequence.show': '参与者、消息顺序、返回与异步',\n 'renderer.sequence.grasp': '调用链、时序依赖、失败重试点',\n 'renderer.dataflow': '数据流',\n 'renderer.dataflow.fit': '数据从哪来、经过谁、到哪去',\n 'renderer.dataflow.show': '数据源、转换、存储、消费者',\n 'renderer.dataflow.grasp': '数据血缘与口径、敏感数据流向',\n 'renderer.lifecycle': '状态机',\n 'renderer.lifecycle.fit': '一个对象有哪些状态、怎么流转',\n 'renderer.lifecycle.show': '状态、转移条件、终态',\n 'renderer.lifecycle.grasp': '状态机全貌、等待与重试、非法流转',\n 'scope.conceptHint':\n '选中概念会限制取证范围:agent 只围绕这几个概念读代码,图更聚焦。不选则自行取舍。',\n 'scope.pathHint': '只让 agent 在这些目录里取证;不选则扫全仓库。',\n\n 'persistence.permanent': '永久 · 下次更新沿用',\n 'persistence.temporary': '临时 · 仅这一次',\n 'persistence.temporaryShort': '临时',\n 'persistence.permanentHint': '存住 prompt,下次「更新图册」自动用同一 prompt 重画。',\n 'persistence.temporaryHint': '只跑这一次,不占下次更新的时间;产物仍可查看。',\n 'parent.none': '独立套图',\n 'parent.current': '挂在当前图下',\n\n 'git.noRepo': '非 Git 目录',\n 'git.noRepoHint': '不做分支切换,直接基于当前目录分析',\n 'git.branch': '分支',\n 'git.dirty': '工作区有未提交改动',\n 'git.clean': '工作区干净',\n 'git.detached': '分离头指针',\n 'git.checkedAt': '探测于 {time}',\n 'git.behind': '落后 {n}',\n 'git.ahead': '领先 {n}',\n 'git.inSync': '与上游同步',\n 'git.tree': '暂存 {staged} · 未暂存 {unstaged} · 未跟踪 {untracked}',\n 'git.treeShort': '{staged} 暂存 · {unstaged} 未暂存',\n\n 'status.idle': '未生成',\n 'status.queued': '排队中',\n 'status.running': '生成中',\n 'status.ready': '已就绪',\n 'status.failed': '失败',\n 'status.stale': '工作区有变化',\n 'status.none': '—',\n\n 'runbar.running': '生成中',\n 'runbar.step': '正在:{step}',\n 'runbar.tokens': '本次 {n} tokens',\n 'runbar.unknown': '正在准备…',\n 'progress.title': '进度(agent 的 todo)',\n 'progress.none': 'agent 还没写 todo',\n 'usage.title': '用量',\n 'usage.current': '本次',\n 'usage.total': '累计',\n 'usage.detail': '输入 {input} · 缓存读 {cacheRead} · 输出 {output}',\n 'usage.estimated': '≈ {cost}',\n 'usage.estimateNote': '按配置单价估算,不是账单',\n 'usage.tokens': '{n} tokens',\n 'qa.running': 'agent 正在读代码回答…',\n\n 'qa.title': '提问',\n 'qa.empty': '对这张图提问;agent 只读代码与这张图来回答。',\n 'qa.placeholder': '例如:路由注册失败时插件会怎样?',\n 'qa.answer': 'agent',\n 'qa.me': '我',\n 'qa.applied': '已补进图里',\n 'qa.working': 'agent 正在读代码回答…',\n 'qa.mergeHint': '「补进图里」会把这次回答并进这张图并重画;「创建子图」会挂一张新图。',\n\n 'concept.title': '概念',\n\n 'run.history': '运行记录',\n 'run.none': '还没有运行记录',\n 'run.kind.create': '首次生成',\n 'run.kind.update': '更新',\n 'run.kind.regenerate': '重生成',\n 'run.status.running': '运行中',\n 'run.status.succeeded': '成功',\n 'run.status.failed': '失败',\n\n 'meta.repoPath': '代码库',\n 'meta.createdAt': '创建于 {time}',\n 'meta.generatedAt': '生成于 {time}',\n 'meta.branchLevel': '按分支维护 {branch}',\n\n 'confirm.update': '更新会先快进到上游最新提交,然后用同一 prompt 重画所有永久图。继续?',\n 'confirm.delete': '删除只移除台账和产物目录,不会动你的代码库。继续?',\n 'confirm.updateOk': '更新',\n 'error.generic': '出错了:{message}',\n 'notice.dirty': '工作区有未提交改动,已跳过自动同步,本次基于当前内容分析。',\n 'notice.noGit': '这个目录不是 Git 仓库,跳过分支切换。',\n 'notice.running': 'Agent 正在生成图谱,完成后会自动刷新。',\n 'notice.merged': '已把回答并进图里,正在重画。',\n 'notice.child': '已创建子图,正在生成。',\n 'notice.temporary': '这是临时图:只跑这一次,下次更新不会重画。',\n\n 'time.now': '刚刚',\n 'time.minutes': '{n} 分钟前',\n 'time.hours': '{n} 小时前',\n 'time.days': '{n} 天前',\n}\n\n/** English. */\nconst en: Record<string, string> = {\n 'panel.title': 'Archify',\n 'panel.desc': 'Concepts and diagram atlas',\n\n 'page.title': 'Architecture Studio',\n 'page.subtitle': 'Codebase → concepts → atlas · per branch, working tree included',\n\n 'stat.repos': 'Repositories',\n 'stat.concepts': 'Concepts',\n 'stat.diagrams': 'Diagrams & sets',\n 'stat.running': 'Generating',\n 'tab.info': 'Info',\n 'tab.qa': 'Ask',\n\n 'action.add': 'New project',\n 'action.refresh': 'Refresh',\n 'action.update': 'Update atlas',\n 'action.updateAll': 'Update all',\n 'action.check': 'Re-check Git',\n 'action.openTab': 'Open',\n 'action.fullscreen': 'Fullscreen',\n 'action.regenerate': 'Redraw',\n 'action.delete': 'Delete',\n 'action.openSession': 'Open session',\n 'action.cancel': 'Cancel',\n 'action.submit': 'Create and analyse',\n 'action.close': 'Close',\n 'action.back': 'Back to atlas',\n 'action.newDiagram': 'New diagram',\n 'action.ask': 'Ask',\n 'action.send': 'Send',\n 'action.merge': 'Merge into diagram',\n 'action.child': 'Create child diagram',\n 'action.save': 'Save',\n\n 'empty.title': 'No projects yet',\n 'empty.body':\n 'Add a project with a local repository path. Concepts are extracted first, then the two default diagrams are generated.',\n 'empty.action': 'Add your first project',\n\n 'dialog.addTitle': 'New project',\n 'field.name': 'Project name',\n 'field.namePlaceholder': 'Defaults to the folder name',\n 'field.path': 'Repository path',\n 'field.pathPlaceholder': '/home/me/projects/your-repo',\n 'field.pathRequired': 'Repository path is required',\n\n 'dialog.newDiagram': 'New diagram',\n 'dialog.newDiagramSub':\n 'Describe the diagram you want. The agent reads the code and draws from this prompt.',\n 'field.title': 'Title',\n 'field.titlePlaceholder': 'e.g. Update call chain',\n 'field.prompt': 'Prompt',\n 'field.promptPlaceholder':\n 'e.g. Draw the full call chain of an update, from the button to artifact collection, including failure modes.',\n 'field.persistence': 'Lifetime',\n 'field.parent': 'Placement',\n 'field.concepts': 'Concept scope',\n 'field.branch': 'Branch',\n 'field.status': 'Status',\n 'field.generatedAt': 'Generated',\n 'field.owner': 'Owner',\n 'field.tree': 'Working tree',\n\n 'field.renderer': 'Diagram type',\n 'field.pathScope': 'Directories',\n 'field.rendererHint': 'Click one to see what it shows and what you get',\n 'renderer.auto': 'Any',\n 'renderer.autoHint':\n 'Optional: Archify picks a type from your prompt on its own. Choosing one just saves the agent a guess.',\n 'renderer.fit': 'Best for',\n 'renderer.show': 'Shows',\n 'renderer.grasp': 'You learn',\n 'renderer.architecture': 'Architecture',\n 'renderer.architecture.fit': 'What the system is made of and who depends on whom',\n 'renderer.architecture.show':\n 'Components and services, process/network/trust boundaries, external deps',\n 'renderer.architecture.grasp':\n 'Layers and responsibilities, key dependencies, outside-boundary risks',\n 'renderer.workflow': 'Workflow',\n 'renderer.workflow.fit': 'How a process runs, which branches and approvals exist',\n 'renderer.workflow.show': 'Steps, branch conditions, failures and fallbacks',\n 'renderer.workflow.grasp': 'Main and exception paths, who does what under which condition',\n 'renderer.sequence': 'Sequence',\n 'renderer.sequence.fit': 'How one request round-trips between modules',\n 'renderer.sequence.show': 'Participants, message order, returns and async',\n 'renderer.sequence.grasp': 'Call chains, ordering dependencies, retry points',\n 'renderer.dataflow': 'Dataflow',\n 'renderer.dataflow.fit': 'Where data comes from, passes through, and ends up',\n 'renderer.dataflow.show': 'Sources, transforms, storage, consumers',\n 'renderer.dataflow.grasp': 'Lineage and definitions, sensitive-data movement',\n 'renderer.lifecycle': 'Lifecycle',\n 'renderer.lifecycle.fit': 'Which states an object has and how it transitions',\n 'renderer.lifecycle.show': 'States, transition conditions, terminal states',\n 'renderer.lifecycle.grasp': 'The state machine, waits and retries, illegal transitions',\n 'scope.conceptHint':\n 'Selected concepts narrow the evidence: the agent reads only around them, so the diagram stays focused. Optional.',\n 'scope.pathHint':\n 'Only let the agent read these directories; leave empty to scan the whole repository.',\n\n 'persistence.permanent': 'Permanent · reuse next update',\n 'persistence.temporary': 'Temporary · this run only',\n 'persistence.temporaryShort': 'TEMP',\n 'persistence.permanentHint':\n 'The prompt is kept; the next atlas update redraws it with the same prompt.',\n 'persistence.temporaryHint': 'Runs once, skipped on future updates; artifacts stay available.',\n 'parent.none': 'Own set',\n 'parent.current': 'Under selected diagram',\n\n 'git.noRepo': 'Not a Git repository',\n 'git.noRepoHint': 'No branch switching; analysis runs on the current directory',\n 'git.branch': 'Branch',\n 'git.dirty': 'Uncommitted changes',\n 'git.clean': 'Clean',\n 'git.detached': 'Detached HEAD',\n 'git.checkedAt': 'Checked {time}',\n 'git.behind': '{n} behind',\n 'git.ahead': '{n} ahead',\n 'git.inSync': 'Up to date',\n 'git.tree': '{staged} staged · {unstaged} unstaged · {untracked} untracked',\n 'git.treeShort': '{staged} staged · {unstaged} unstaged',\n\n 'status.idle': 'Not generated',\n 'status.queued': 'Queued',\n 'status.running': 'Generating',\n 'status.ready': 'Ready',\n 'status.failed': 'Failed',\n 'status.stale': 'Working tree changed',\n 'status.none': '—',\n\n 'runbar.running': 'Generating',\n 'runbar.step': 'Now: {step}',\n 'runbar.tokens': '{n} tokens this run',\n 'runbar.unknown': 'Preparing…',\n 'progress.title': 'Progress (agent todos)',\n 'progress.none': 'The agent has not written todos yet',\n 'usage.title': 'Usage',\n 'usage.current': 'This run',\n 'usage.total': 'Total',\n 'usage.detail': '{input} input · {cacheRead} cache read · {output} output',\n 'usage.estimated': '≈ {cost}',\n 'usage.estimateNote': 'Estimated from configured unit prices, not a bill',\n 'usage.tokens': '{n} tokens',\n 'qa.running': 'The agent is reading code…',\n\n 'qa.title': 'Ask',\n 'qa.empty': 'Ask about this diagram; the agent answers from the code and this diagram only.',\n 'qa.placeholder': 'e.g. What happens when route registration fails?',\n 'qa.answer': 'agent',\n 'qa.me': 'me',\n 'qa.applied': 'Merged into diagram',\n 'qa.working': 'The agent is reading code…',\n 'qa.mergeHint':\n 'Merge folds the answer into this diagram and redraws it; Create child attaches a new diagram.',\n\n 'concept.title': 'Concepts',\n\n 'run.history': 'Runs',\n 'run.none': 'No runs yet',\n 'run.kind.create': 'Initial',\n 'run.kind.update': 'Update',\n 'run.kind.regenerate': 'Regenerate',\n 'run.status.running': 'Running',\n 'run.status.succeeded': 'Succeeded',\n 'run.status.failed': 'Failed',\n\n 'meta.repoPath': 'Repository',\n 'meta.createdAt': 'Created {time}',\n 'meta.generatedAt': 'Generated {time}',\n 'meta.branchLevel': 'Maintained per branch {branch}',\n\n 'confirm.update':\n 'Update fast-forwards to the latest upstream commit, then redraws every permanent diagram with its own prompt. Continue?',\n 'confirm.delete':\n 'Deleting removes the ledger entry and artifact folder only; your repository is untouched. Continue?',\n 'confirm.updateOk': 'Update',\n 'error.generic': 'Error: {message}',\n 'notice.dirty':\n 'Working tree has uncommitted changes; automatic sync was skipped and this run uses the current content.',\n 'notice.noGit': 'This directory is not a Git repository; branch switching is skipped.',\n 'notice.running': 'The agent is generating diagrams. This refreshes automatically.',\n 'notice.merged': 'Answer merged into the diagram; redrawing.',\n 'notice.child': 'Child diagram created; generating.',\n 'notice.temporary': 'Temporary diagram: runs once and is skipped on future updates.',\n\n 'time.now': 'just now',\n 'time.minutes': '{n} min ago',\n 'time.hours': '{n} h ago',\n 'time.days': '{n} d ago',\n}\n\n/** 注册给 `ctx.locale.register` 的字典。 */\nexport const DICT: Record<'zh' | 'en', Record<string, string>> = { zh, en }\n\n/** 文案键(供类型与测试使用)。 */\nexport type MessageKey = keyof typeof zh\n","/**\n * 页面样式。\n *\n * 两条硬规则:\n * 1. **只用 DSH alias 令牌**(`--dsw-alias-*` / `--dsw-radius-*`),不写死任何色值 ——\n * 宿主切深浅色时 alias 自动翻转,页面不需要第二条样式分支;\n * 2. 所有类名带 `dap-` 前缀,样式以 `<style data-plugin>` 注入并在卸载时摘掉,\n * 不污染宿主。\n */\n\nexport const PAGE_CSS = `\n.dap-page {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n background: var(--dsw-alias-bg-base);\n color: var(--dsw-alias-label-primary);\n font-family: var(--dsw-font-family, inherit);\n font-size: 13px;\n line-height: 1.5;\n}\n.dap-scroll { flex: 1; min-height: 0; overflow: auto; padding: 18px 22px 26px; }\n/* 工作台整屏:三栏各自滚动,右栏再长也不撑高页面 */\n.dap-scroll--wb { overflow: hidden; display: flex; flex-direction: column; padding-bottom: 12px; }\n\n/* ---------------------------------------------------------------- 顶栏 */\n.dap-head {\n display: flex; align-items: flex-start; justify-content: space-between; gap: 20px;\n padding: 18px 22px 12px; border-bottom: 1px solid var(--dsw-alias-border-l1); flex: none;\n}\n.dap-head__title { margin: 0; font-size: 17px; font-weight: 600; letter-spacing: -0.01em; }\n.dap-head__sub { margin: 3px 0 0; font-size: 12px; color: var(--dsw-alias-label-tertiary); }\n.dap-head__actions { display: flex; gap: 8px; flex: none; }\n\n/* ---------------------------------------------------------------- 通用 */\n.dap-btn {\n display: inline-flex; align-items: center; gap: 5px; height: 30px; padding: 0 11px;\n border-radius: var(--dsw-radius-md); border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary);\n font-size: 12.5px; font-weight: 500; white-space: nowrap; cursor: pointer;\n}\n.dap-btn:hover { background: var(--dsw-alias-interactive-bg-hover); }\n.dap-btn:disabled { opacity: .45; cursor: not-allowed; }\n.dap-btn--primary { background: var(--dsw-alias-button-primary-fill); color: var(--dsw-alias-label-primary-inverted); border-color: transparent; }\n.dap-btn--primary:hover { background: var(--dsw-alias-button-primary-hover); }\n.dap-btn--ghost { border-color: transparent; background: transparent; color: var(--dsw-alias-label-secondary); }\n.dap-btn--ghost:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }\n.dap-btn--sm { height: 26px; padding: 0 8px; font-size: 11.5px; border-radius: var(--dsw-radius-md); }\n.dap-btn__icon { width: 13px; height: 13px; display: block; }\n\n.dap-chip {\n display: inline-flex; align-items: center; gap: 4px; height: 20px; padding: 0 7px;\n border-radius: 999px; border: 1px solid var(--dsw-alias-border-l1);\n background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-secondary);\n font-size: 11px; white-space: nowrap; font-variant-numeric: tabular-nums;\n}\n.dap-chip--ok { color: var(--dsw-alias-state-success-primary); border-color: transparent; }\n.dap-chip--warn { color: var(--dsw-alias-state-warn-primary); border-color: transparent; }\n.dap-chip--accent { color: var(--dsw-alias-state-business-primary); border-color: transparent; }\n.dap-chip__dot { width: 5px; height: 5px; border-radius: 50%; background: currentColor; }\n\n.dap-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--dsw-alias-state-success-primary); flex: none; }\n.dap-dot--warn { background: var(--dsw-alias-state-warn-primary); }\n.dap-dot--err { background: var(--dsw-alias-state-error-primary); }\n.dap-dot--run { background: var(--dsw-alias-state-warn-primary); animation: dap-pulse 1.4s ease-in-out infinite; }\n@keyframes dap-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }\n\n/* ---------------------------------------------------------------- 总览 */\n.dap-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin: 0 0 14px; }\n.dap-stat { border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-lg); background: var(--dsw-alias-bg-layer-1); padding: 11px 13px; }\n.dap-stat__k { font-size: 11.5px; color: var(--dsw-alias-label-tertiary); }\n.dap-stat__v { margin-top: 3px; font-size: 20px; font-weight: 600; letter-spacing: -0.02em; }\n.dap-stat__v small { font-size: 11.5px; font-weight: 500; color: var(--dsw-alias-label-tertiary); margin-left: 6px; letter-spacing: 0; }\n\n.dap-atlas { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }\n.dap-card {\n border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-lg);\n background: var(--dsw-alias-bg-layer-1); padding: 14px; display: flex; flex-direction: column; gap: 10px;\n cursor: pointer; text-align: left; width: 100%; transition: border-color .15s, box-shadow .15s;\n}\n.dap-card:hover { border-color: var(--dsw-alias-border-l3); box-shadow: var(--dsw-shadow-lv2); }\n.dap-card__top { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }\n.dap-repo { display: flex; align-items: center; gap: 7px; min-width: 0; }\n.dap-repo__name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.dap-repo__path { font-size: 11px; color: var(--dsw-alias-label-tertiary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-family: ui-monospace, monospace; }\n.dap-chips { display: flex; flex-wrap: wrap; gap: 5px; }\n.dap-thumbs { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }\n.dap-thumb {\n border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-md);\n background: var(--dsw-alias-bg-layer-2); padding: 6px 8px; min-width: 0;\n}\n.dap-thumb--wide { grid-column: 1 / -1; }\n.dap-thumb__title { display: flex; align-items: center; justify-content: space-between; gap: 6px; font-size: 11px; color: var(--dsw-alias-label-secondary); }\n.dap-thumb__title b { font-weight: 550; color: var(--dsw-alias-label-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.dap-thumb__count { font-size: 10.5px; color: var(--dsw-alias-label-tertiary); }\n.dap-card__foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 11.5px; color: var(--dsw-alias-label-tertiary); }\n\n/* ---------------------------------------------------------------- 工作台 */\n.dap-wb { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 12px; align-items: stretch; flex: 1; min-height: 0; }\n.dap-panel { border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-lg); background: var(--dsw-alias-bg-layer-1); overflow: hidden; display: flex; flex-direction: column; min-height: 0; }\n.dap-panel__body { overflow: auto; flex: 1; min-height: 0; }\n.dap-panel__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 9px 11px; border-bottom: 1px solid var(--dsw-alias-border-l1); font-size: 11.5px; color: var(--dsw-alias-label-tertiary); }\n.dap-panel__body { padding: 7px; }\n\n.dap-strip {\n display: flex; gap: 2px; align-items: center; padding: 4px 8px;\n overflow-x: auto; border-bottom: 1px solid var(--dsw-alias-border-l1);\n background: var(--dsw-alias-bg-layer-1); flex: none;\n}\n.dap-strip__sep { flex: none; width: 1px; height: 16px; background: var(--dsw-alias-border-l1); margin: 0 4px; }\n.dap-tabitem {\n display: inline-flex; align-items: center; gap: 6px; flex: none; max-width: 220px;\n height: 26px; padding: 0 9px; border-radius: var(--dsw-radius-md);\n border: 1px solid transparent; background: transparent;\n color: var(--dsw-alias-label-secondary); font-size: 12px; cursor: pointer; white-space: nowrap;\n}\n.dap-tabitem:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }\n.dap-tabitem--on {\n background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary);\n font-weight: 550; border-color: var(--dsw-alias-border-l1);\n}\n.dap-tabitem--child { margin-left: 10px; }\n.dap-tabitem--child::before { content: \"└\"; color: var(--dsw-alias-label-tertiary); font-size: 10px; }\n.dap-tabitem__name { overflow: hidden; text-overflow: ellipsis; }\n.dap-tabitem__tag {\n font-size: 9.5px; padding: 0 4px; border-radius: 4px;\n background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-tertiary); font-weight: 400;\n}\n.dap-strip__new {\n flex: none; margin-left: auto; height: 26px; padding: 0 9px; border-radius: var(--dsw-radius-md);\n border: 1px dashed var(--dsw-alias-border-l2); background: transparent;\n color: var(--dsw-alias-label-tertiary); font-size: 11.5px; cursor: pointer; white-space: nowrap;\n}\n.dap-strip__new:hover { color: var(--dsw-alias-label-primary); border-color: var(--dsw-alias-border-l3); }\n\n.dap-viewer { display: flex; flex-direction: column; min-width: 0; }\n.dap-viewer__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px 11px; border-bottom: 1px solid var(--dsw-alias-border-l1); }\n.dap-viewer__title { display: flex; align-items: center; gap: 6px; font-size: 12.5px; font-weight: 600; min-width: 0; }\n.dap-viewer__title span { color: var(--dsw-alias-label-tertiary); font-weight: 400; font-size: 11.5px; }\n.dap-viewer__tools { display: flex; gap: 2px; }\n.dap-stage { background: var(--dsw-alias-bg-layer-2); padding: 11px; flex: 1; min-height: 0; overflow: auto; }\n.dap-frame { background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-md); overflow: hidden; }\n.dap-frame__cap { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 7px 10px; font-size: 11px; color: var(--dsw-alias-label-tertiary); border-bottom: 1px solid var(--dsw-alias-border-l1); }\n.dap-iframe { display: block; width: 100%; height: clamp(420px, 56vh, 720px); border: 0; background: var(--dsw-alias-bg-base); }\n.dap-insets { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin-top: 8px; }\n.dap-inset {\n display: flex; align-items: center; gap: 6px; padding: 6px 8px; border-radius: var(--dsw-radius-md);\n border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-1); cursor: pointer;\n font-size: 11px; color: var(--dsw-alias-label-secondary); text-align: left; min-width: 0;\n}\n.dap-inset:hover { border-color: var(--dsw-alias-border-l3); }\n.dap-inset--on { border-color: var(--dsw-alias-border-l3); box-shadow: 0 0 0 1px var(--dsw-alias-border-l3) inset; color: var(--dsw-alias-label-primary); }\n.dap-inset__name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n\n.dap-tabs { display: flex; gap: 2px; padding: 5px; border-bottom: 1px solid var(--dsw-alias-border-l1); }\n.dap-tab { height: 26px; padding: 0 10px; border-radius: var(--dsw-radius-md); font-size: 12px; color: var(--dsw-alias-label-secondary); cursor: pointer; }\n.dap-tab--on { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); font-weight: 550; }\n.dap-insp { padding: 10px; display: flex; flex-direction: column; gap: 10px; flex: 1; min-height: 0; overflow: auto; }\n.dap-field { display: flex; flex-direction: column; gap: 4px; }\n.dap-field__label { font-size: 10.5px; color: var(--dsw-alias-label-tertiary); letter-spacing: .02em; }\n.dap-box {\n border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-md);\n background: var(--dsw-alias-bg-layer-2); padding: 7px 9px; font-size: 12px; line-height: 1.6;\n white-space: pre-wrap; word-break: break-word;\n}\n.dap-box__meta { display: flex; align-items: center; justify-content: space-between; margin-top: 6px; font-size: 10.5px; color: var(--dsw-alias-label-tertiary); }\n\n.dap-qa { display: flex; flex-direction: column; gap: 8px; }\n.dap-msg { display: flex; flex-direction: column; gap: 3px; }\n.dap-msg__who { font-size: 10.5px; color: var(--dsw-alias-label-tertiary); }\n.dap-msg__bubble {\n border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-2);\n border-radius: var(--dsw-radius-md); padding: 7px 9px; font-size: 12px; line-height: 1.55;\n white-space: pre-wrap; word-break: break-word;\n}\n.dap-msg--me .dap-msg__bubble { background: var(--dsw-alias-bg-layer-1); }\n.dap-msg__acts { display: flex; gap: 5px; flex-wrap: wrap; margin-top: 2px; }\n.dap-ask { display: flex; gap: 6px; align-items: flex-end; padding: 9px 10px; border-top: 1px solid var(--dsw-alias-border-l1); position: sticky; bottom: 0; background: var(--dsw-alias-bg-layer-1); z-index: 2; }\n.dap-ask textarea {\n flex: 1; resize: none; min-height: 40px; max-height: 120px; padding: 7px 9px;\n border: 1px solid var(--dsw-alias-border-l2); border-radius: var(--dsw-radius-md);\n background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary);\n font: inherit; font-size: 12px; outline: none;\n}\n.dap-ask textarea:focus { border-color: var(--dsw-alias-border-l3); }\n.dap-hint { font-size: 10.5px; color: var(--dsw-alias-label-tertiary); line-height: 1.5; }\n\n.dap-runbar {\n display: flex; align-items: center; gap: 8px; margin-bottom: 8px; padding: 7px 10px;\n border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-md);\n background: var(--dsw-alias-bg-layer-1); font-size: 11.5px; color: var(--dsw-alias-label-secondary);\n flex: none;\n}\n.dap-runbar b { color: var(--dsw-alias-label-primary); font-weight: 600; }\n.dap-runbar__step { color: var(--dsw-alias-label-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.dap-runbar__bar { flex: 1; max-width: 240px; height: 4px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2); overflow: hidden; }\n.dap-runbar__bar i { display: block; height: 100%; background: var(--dsw-alias-state-business-primary); border-radius: inherit; }\n.dap-runbar__pct { font-variant-numeric: tabular-nums; color: var(--dsw-alias-label-tertiary); }\n.dap-runbar__tok { margin-left: auto; color: var(--dsw-alias-label-tertiary); font-variant-numeric: tabular-nums; white-space: nowrap; }\n.dap-spinner { width: 11px; height: 11px; border-radius: 50%; border: 2px solid var(--dsw-alias-state-business-primary); border-right-color: transparent; animation: dap-spin 1s linear infinite; flex: none; display: inline-block; }\n@keyframes dap-spin { to { transform: rotate(360deg); } }\n\n.dap-todos { display: flex; flex-direction: column; gap: 3px; border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-md); background: var(--dsw-alias-bg-layer-2); padding: 6px 8px; }\n.dap-todo { display: flex; align-items: center; gap: 7px; font-size: 11.5px; color: var(--dsw-alias-label-secondary); }\n.dap-todo__mark { width: 13px; flex: none; display: inline-flex; align-items: center; justify-content: center; color: var(--dsw-alias-state-success-primary); }\n.dap-todo--done { color: var(--dsw-alias-label-tertiary); }\n.dap-todo--now { color: var(--dsw-alias-label-primary); font-weight: 550; }\n.dap-todo--now .dap-todo__mark { color: var(--dsw-alias-state-business-primary); }\n\n.dap-usage { border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-md); background: var(--dsw-alias-bg-layer-2); padding: 7px 9px; display: flex; flex-direction: column; gap: 4px; font-size: 11.5px; }\n.dap-usage > div { display: flex; align-items: baseline; gap: 6px; }\n.dap-usage span { color: var(--dsw-alias-label-tertiary); }\n.dap-usage b { font-size: 13px; }\n.dap-usage em { font-style: normal; color: var(--dsw-alias-label-tertiary); font-size: 10.5px; }\n.dap-usage__detail { justify-content: space-between; font-size: 10.5px; }\n\n/* ---------------------------------------------------------------- 对话框 */\n.dap-mask { position: fixed; inset: 0; background: var(--dsw-alias-bg-mask-1); display: grid; place-items: center; z-index: 40; }\n.dap-dialog {\n width: 540px; max-width: calc(100vw - 40px); max-height: calc(100vh - 60px); overflow: auto;\n background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-lg); box-shadow: var(--dsw-shadow-lv2); padding: 16px;\n}\n.dap-dialog__title { margin: 0 0 2px; font-size: 15px; font-weight: 600; }\n.dap-dialog__sub { font-size: 11.5px; color: var(--dsw-alias-label-tertiary); margin-bottom: 12px; }\n.dap-dialog__row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 10px 0 4px; font-size: 12px; }\n.dap-dialog__foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 14px; }\n.dap-seg { display: inline-flex; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-md); padding: 2px; }\n.dap-seg__btn { height: 26px; padding: 0 9px; border-radius: var(--dsw-radius-md); font-size: 11.5px; color: var(--dsw-alias-label-secondary); cursor: pointer; }\n.dap-seg__btn--on { background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); font-weight: 550; box-shadow: var(--dsw-shadow-lv2); }\n.dap-input, .dap-textarea {\n width: 100%; padding: 7px 9px; border: 1px solid var(--dsw-alias-border-l2); border-radius: var(--dsw-radius-md);\n background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary);\n font: inherit; font-size: 12.5px; outline: none;\n}\n.dap-input:focus, .dap-textarea:focus { border-color: var(--dsw-alias-border-l3); }\n.dap-textarea { resize: vertical; min-height: 84px; line-height: 1.6; }\n.dap-concepts { display: flex; flex-wrap: wrap; gap: 4px; }\n.dap-concept {\n height: 20px; display: inline-flex; align-items: center; padding: 0 7px; border-radius: 999px;\n font-size: 11px; border: 1px solid var(--dsw-alias-border-l2); color: var(--dsw-alias-label-secondary); cursor: pointer;\n}\n.dap-concept--on { border-color: transparent; background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-state-business-primary); }\n\n\n.dap-dialog--wide { width: 680px; }\n.dap-typebar { display: flex; flex-wrap: wrap; gap: 5px; margin: 6px 0 8px; }\n.dap-type-tag {\n display: inline-flex; align-items: center; gap: 5px; height: 27px; padding: 0 9px;\n border-radius: 999px; border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-secondary);\n font-size: 11.5px; cursor: pointer; white-space: nowrap;\n}\n.dap-type-tag:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }\n.dap-type-tag--on { background: var(--dsw-alias-label-primary); color: var(--dsw-alias-bg-base); border-color: transparent; }\n.dap-type-tag__k { font-size: 9.5px; opacity: .65; font-family: ui-monospace, monospace; }\n.dap-type-detail {\n border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-md);\n background: var(--dsw-alias-bg-layer-2); padding: 8px 10px; display: flex;\n flex-direction: column; gap: 5px; min-height: 82px;\n}\n.dap-type-detail__empty { font-size: 11.5px; color: var(--dsw-alias-label-secondary); line-height: 1.6; margin: auto 0; }\n.dap-type-detail__head { display: flex; align-items: center; gap: 7px; font-size: 12.5px; }\n.dap-type-detail__head code { font: 10px/1 ui-monospace, monospace; color: var(--dsw-alias-label-tertiary); }\n.dap-type-detail__row { display: grid; grid-template-columns: 56px 1fr; gap: 8px; font-size: 11.5px; line-height: 1.55; }\n.dap-type-detail__row span { color: var(--dsw-alias-label-tertiary); }\n.dap-type-detail__row p { margin: 0; color: var(--dsw-alias-label-primary); }\n\n/* ---------------------------------------------------------------- 空状态 / 提示 */\n.dap-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; padding: 44px 20px; text-align: center; color: var(--dsw-alias-label-secondary); }\n.dap-empty__title { font-size: 14px; font-weight: 600; color: var(--dsw-alias-label-primary); }\n.dap-empty__body { max-width: 460px; font-size: 12px; }\n.dap-notice { display: flex; align-items: center; gap: 8px; margin: 0 0 10px; padding: 7px 10px; border-radius: var(--dsw-radius-md); font-size: 11.5px; border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-secondary); }\n.dap-notice--error { border-color: var(--dsw-alias-state-error-primary); color: var(--dsw-alias-state-error-primary); }\n\n@media (max-width: 1180px) {\n .dap-wb { grid-template-columns: minmax(0, 1fr); }\n .dap-wb > .dap-panel:last-child { grid-column: 1 / -1; max-height: 420px; }\n .dap-atlas { grid-template-columns: repeat(2, 1fr); }\n .dap-stats { grid-template-columns: repeat(2, 1fr); }\n}\n`\n\n/** 把样式挂到 document;返回卸载函数(HMR / 插件停用时摘标签)。 */\nexport function installStyles(): () => void {\n const style = document.createElement('style')\n style.dataset.plugin = 'dsh-archify-projects'\n style.textContent = PAGE_CSS\n document.head.appendChild(style)\n return () => style.remove()\n}\n","/**\n * 客户端入口。\n *\n * 产物里的 `exports.apply` / `exports.inject` 就是 `window.__ModuleLoader__` 要的\n * Cordis 插件面;`load({ id, factory })` 的壳由 tsdown 的 banner/footer 生成\n * (见 tsdown.config.ts),这里只管导出。\n *\n * 两个槽:\n * ① `sidebar.panellist` —— 左侧顶栏的入口图标,`id` 就是面板 id;\n * ② `main` —— 点开后的整页,`key` 与 `id` 必须一模一样。\n */\n\nimport { StudioPage } from './page.js'\nimport { PanelIcon } from './panel.js'\nimport { DICT, LOCALE_NS } from './locale.js'\nimport { installStyles } from './styles.js'\n\n/** 面板 id:侧栏入口与主面板共用的唯一标识。 */\nexport const PANEL_ID = 'archify-projects'\n\n/** 需要客户端服务:插槽注册表 + 文案目录。 */\nexport const inject = ['slots', 'locale']\n\n/** 绑好的 `t`。 */\nexport type Translate = (key: string, vars?: Record<string, string | number>) => string\n\n/** 客户端需要的最小 ctx 面。 */\nexport interface ClientCtx {\n readonly slots: {\n inject: (owner: string, install: () => unknown) => void\n register: (options: Record<string, unknown>, component: unknown) => unknown\n }\n readonly locale: {\n register: (ns: string, dict: Record<string, Record<string, string>>) => () => void\n bind: (ns: string) => Translate\n }\n effect: (fn: () => (() => void) | void, label?: string) => unknown\n}\n\n/** 插件入口。 */\nexport function apply(ctx: ClientCtx): void {\n // 文案目录必须先注册:侧栏 label 在 slot 注册时求值一次,\n // 目录还没挂上时 t() 会退回返回原始 key,界面上就出现 \"panel.title\"。\n ctx.effect(() => ctx.locale.register(LOCALE_NS, DICT), 'dsh-archify-projects: dictionaries')\n const t = ctx.locale.bind(LOCALE_NS)\n\n // 组件里要拿 t,但 slot 的 props 由框架决定(`main` 不传 t)。\n // 在 apply 里包一层得到稳定身份,不在渲染期新建组件类型。\n const Page = (): ReturnType<typeof StudioPage> => <StudioPage t={t} />\n\n ctx.slots.inject('sidebar.panellist', () =>\n ctx.slots.register(\n {\n name: 'sidebar.panellist',\n id: PANEL_ID,\n order: 16,\n locale: LOCALE_NS,\n label: () => t('panel.title'),\n },\n PanelIcon,\n ),\n )\n\n ctx.slots.inject('main', () =>\n ctx.slots.register(\n {\n name: 'main',\n key: PANEL_ID,\n locale: LOCALE_NS,\n },\n Page,\n ),\n )\n\n // 样式随插件生命周期装卸,HMR 重挂不会叠标签。\n ctx.effect(installStyles, 'dsh-archify-projects: styles')\n}\n"],"mappings":"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"}
package/docs/HANDOVER.md CHANGED
@@ -389,3 +389,9 @@ cacheWriteTokens`)写 `RunRecord.usage`、`QaEntry.usage`,并累加 `Project
389
389
  不再有额外 iframe),「新建图」是条尾的虚线按钮。
390
390
  查看器宽度从 ≈566px 增到 784px(1440 窗口)。组件测试里断言了「没有 `.dap-rail__group`」,
391
391
  别把左栏加回来。
392
+
393
+ **内嵌查看器隐藏成品页头**:archify 成品 HTML 顶部有一行
394
+ `<项目名> · <图名>` 大字 + `pulse-dot`,而插件自己的查看器标题栏和帧头已经写了图名、
395
+ 分支与时间。`page.tsx` 的 `hideArtifactHeader()` 在 iframe `onLoad` 时往同源文档 head
396
+ 注入一条 `.header{display:none!important}`,只影响嵌入视图;点「新窗口」仍是成品原样。
397
+ 同源被沙箱挡住就静默跳过,不影响看图。
package/lib/host/index.js CHANGED
@@ -20,7 +20,7 @@ import { reconcileArtifacts, Runner } from './runner.js';
20
20
  import { ProjectStore } from './store.js';
21
21
  import { ensureWorkspace } from './workspace.js';
22
22
  /** 与 package.json 的 version 保持一致(有单测守着)。 */
23
- export const VERSION = '0.1.0';
23
+ export const VERSION = '0.1.1';
24
24
  /** 插件入口。 */
25
25
  export function apply(ctx, rawConfig) {
26
26
  const warnings = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-archify-projects",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "DSH 架构建模工作台:项目台账、Git 感知、复用 DSH agent 调 archify 产出项目结构图与领域图",
5
5
  "type": "module",
6
6
  "main": "lib/host/index.js",