dsh-maoying 0.1.18 → 0.1.19

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.en.md CHANGED
@@ -16,7 +16,7 @@ Once installed, the main agent becomes the **editor-in-chief of your novel**: it
16
16
  - **Context on demand**: the chief reads index.yml for the big picture, then pulls only what the chapter at hand needs (previous chapter's ending ≈1500 chars + its outline + on-stage character cards + active foreshadow excerpts) — never the whole book.
17
17
  - **Narrow tool surface**: the chief sees only "role delegation + internal retrieval + coordination"; helpers carry their line's tools (reviewer read-only, researcher web+notes only) — less per-request tool overhead and context weight.
18
18
  - **Research**: the research helper searches the web for historical/geographic/domain material, files notes under `novel/research/`, separating fact from common lore from legend, with sources — a dividend of the DSH architecture.
19
- - **Creator panel**: a full-page web panel from the DSH sidebar — Overview (with in-place manuscript metadata editing and volume plan) / Chapters / Characters / Foreshadowing / Relations / Timeline / Worldbuilding / Index Board; full-page chapter reading (outline side-by-side + five-dimension fields + status flow), form-based project.yml editing, UI language (中文/English), six font-size steps, and automatic new-version detection.
19
+ - **Creator panel**: a full-page web panel from the DSH sidebar — Overview (with in-place manuscript metadata editing and volume plan) / Chapters / Characters / Foreshadowing / Relations / Timeline / Worldbuilding / Style / Index Board, nine tabs; full-page chapter reading (outline side-by-side + manuscript/outline editing with save + five-dimension fields + status flow), form-based project.yml editing, style baseline viewing & editing (created from the default baseline when missing), UI language (中文/English), six font-size steps, and automatic new-version detection.
20
20
  - **CLI fallback**: `maoying init / stats / index / export / brief / check` — six zero-dependency commands for scaffolding, stats, index rebuild, export, per-chapter writing cards and static checks.
21
21
 
22
22
  ## Quick Start
@@ -73,11 +73,11 @@ A reply of "ok / sure / go on" counts as confirmation; the chief advances direct
73
73
 
74
74
  Open "**毛颖创作台**" at the bottom of the DSH sidebar — a full-page cockpit for the human eye, beyond what agents do:
75
75
 
76
- - **Chapters**: status filters + the writing queue; click a chapter for full-page reading — back button, outline side-by-side (Markdown rendered), read-only manuscript, five narrative-dimension fields, one-click status flow.
76
+ - **Chapters**: status filters + the writing queue; click a chapter for full-page reading — back button, outline side-by-side (Markdown rendered), in-place editing with save for the manuscript and the outline (textarea with manual save; if the file was changed externally while editing, saving is rejected and the latest content restored, never overwriting manual edits), five narrative-dimension fields, one-click status flow.
77
77
  - **Overview**: the "Edit" button on the manuscript card expands form-based editing of project.yml — title / author / genre / planned length / language / workflow phase / co-writing mode / synopsis (the former "Novel Settings" tab, merged in); saving preserves the file's existing comments. If project.yml was changed externally after the form loaded (manual edits or an agent advancing the phase), saving is rejected with a prompt to re-edit from the latest content instead of overwriting. A separate "Volume plan" card lists each volume's positioning and planned chapter range from volumes.yml — signed-off volume plans are visible in the panel.
78
78
  - **Top bar**: current version always visible; a "new version ↗" badge links to the release page when npm has a newer one, next to an "Upgrade now" button that installs the latest release into the current DSH profile with pnpm (profile-directory installs only, with an automatic corepack fallback when pnpm is missing; when a global `maoying` CLI is detected it is updated to the same exact version, and skipped if absent; a DeepSeek restart is required to take effect); the gear opens Settings — UI language (中文/English), six font-size steps (100%–150%), all applied immediately and remembered.
79
79
 
80
- The panel only writes state (chapter status, project.yml, index.yml rebuilds), never prose — prose belongs to the author and the chief.
80
+ The panel writes state (chapter status, project.yml, index.yml rebuilds) and offers prose/outline editing to humans only (hash-based stale-write detection — external changes are never overwritten); the agent boundary is unchanged: in co-writing, prose belongs to the author and the chief.
81
81
 
82
82
  ## Co-writing Mode (Author Writes)
83
83
 
package/README.md CHANGED
@@ -16,7 +16,7 @@
16
16
  - **上下文按需取**:主编先读 index.yml 概览,写到哪章读哪章(前章结尾约 1500 字 + 该章大纲 + 出场角色卡 + 活跃伏笔节选),不把整本书塞进上下文。
17
17
  - **工具面收窄**:主编只见「角色委派 + 内部检索 + 协调」白名单,帮手各带本行工具(审校只读、考据只带联网+落笔记);每次请求的工具开销与上下文占用随之下降。
18
18
  - **素材考据**:research 帮手联网检索历史/地理/专业领域素材,笔记落 `novel/research/`,区分史实/通说/演义并附来源——DSH 架构的红利。
19
- - **创作台面板**:DSH 侧栏一键打开的全页可视化面板——概览(含书稿元信息编辑与分卷规划)/章节/角色/伏笔/关系图/时间线/设定/索引板八个 tab;章节整页阅读(章纲对照 + 五维 + 状态流转)、project.yml 表单化编辑、顶栏字号六档缩放与自动新版检测。
19
+ - **创作台面板**:DSH 侧栏一键打开的全页可视化面板——概览(含书稿元信息编辑与分卷规划)/章节/角色/伏笔/关系图/时间线/设定/写作风格/索引板九个 tab;章节整页阅读(章纲对照 + 正文/章纲编辑保存 + 五维 + 状态流转)、project.yml 表单化编辑、写作风格查看与编辑(缺失时用默认基线创建)、顶栏字号六档缩放与自动新版检测。
20
20
  - **CLI 兜底**:`maoying init / stats / index / export / brief / check` 六条零依赖命令管脚手架、统计、索引重建、导出、写作卡打包与静态体检。
21
21
 
22
22
  ## 快速开始
@@ -73,11 +73,11 @@ npm install -g dsh-maoying # 或装成全局 maoying 命令
73
73
 
74
74
  DSH 左侧栏底部点「**毛颖创作台**」打开全页面板(改文件、跑流程之外,给肉眼看的驾驶舱):
75
75
 
76
- - **章节**:状态筛选 + 按单写作清单;点章节进整页阅读——返回按钮、章纲对照(Markdown 渲染)、只读正文、五维叙事字段、一键状态流转。
76
+ - **章节**:状态筛选 + 按单写作清单;点章节进整页阅读——返回按钮、章纲对照(Markdown 渲染)、正文与章纲就地编辑保存(textarea 手动保存;文件在编辑期间被外部改过时拒绝保存并放回磁盘最新内容,不覆盖人工改动)、五维叙事字段、一键状态流转。
77
77
  - **概览**:书稿卡右上「编辑」按钮展开 project.yml 的书名/作者/题材/篇幅/语言/工作流阶段/共创模式/简介表单化编辑(原「小说设置」tab 并入),保存保留文件里原有注释;表单载入后 project.yml 被外部(作者手改 / agent 推进阶段)改过时拒绝保存并提示重改,不覆盖人工改动。概览另设「分卷规划」卡,列出 volumes.yml 各卷定位与计划章节范围——拍板过的分卷有处可查。
78
78
  - **顶栏**:当前版本号常驻;npm 上有新版时亮「有新版 ↗」徽章直连发布页,旁边「一键升级」按钮直接用 pnpm 把最新包装进当前 DSH 档(仅支持 profile 目录安装,pnpm 缺失时自动回落 corepack;检测到全局 `maoying` CLI 时顺带以同一精确版本更新,未装则跳过;装完需重启 DeepSeek 才生效);齿轮打开系统设置,界面语言中文/English 可切、字号六档(100%–150%)整体缩放,均立即生效并记忆。
79
79
 
80
- 面板只写状态(章节状态、project.yml、index.yml 重建),不改正文——正文永远归作者与主编。
80
+ 面板写状态(章节状态、project.yml、index.yml 重建),正文/章纲的编辑入口只给人用(hash 旧版检测,外部改动一律拒绝覆盖)——agent 的创作边界不变:共创模式下正文永远归作者与主编。
81
81
 
82
82
  ## 共创模式(作者亲笔)
83
83
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## 0. 定位与边界
6
6
 
7
- 面板管**可视化与状态**,不管正文创作:小说内容由主编(agent)与作者在编辑器里写,面板负责「一眼看清全局 + 状态快捷流转」。v1 读全量、写少量(状态流转 + 索引重建);正文编辑不做(那是编辑器和 agent 的活,面板里点「让主编改」提示语即可)。
7
+ 面板管**可视化、状态,以及作者本人的稿件编辑**:小说内容的生产仍归主编(agent)与作者在编辑器/共创流程里协作,面板负责「一眼看清全局 + 状态快捷流转」,同时给作者本人一个带护栏的正文/章纲编辑入口(textarea + 手动保存 + hash 旧版检测,见 §2 的 `chapter-save` / `outline-save`)。agent 的创作边界不变:共创模式下 agent 依旧不得碰 author 章节的正文(spec §作者稿圣域)——面板编辑是**人**用的刀,不是给 agent 开的新通道。
8
8
 
9
9
  ## 1. 包接线(单包形态,不建子包)
10
10
 
@@ -44,16 +44,20 @@ webServer.register({ kind: 'prefix', path: '/api/maoying', handler: async (req,
44
44
  | `workspaces` | GET | — | `{ ok, workspaces: [{ path, name, hasNovel }] }`——workspaceRegistry.list() 逐根探测 `<root>/novel/project.yml` |
45
45
  | `state` | GET | `path=<root>` | 全量快照(见 §3 契约) |
46
46
  | `check` | GET | `path=<root>` | `{ ok, problems: [{ level, file, message }] }`——静态体检:与 CLI check 同源的 checkProject,读章节 frontmatter 原文(快照会把越枚举 status 归一成 pending,这里透出原始问题清单;level ∈ error/warn,可为空数组),概览「静态体检」黄条取数 |
47
- | `chapter` | GET | `path`, `file`(chapters/ 下文件名) | `{ ok, file, frontmatter, body, outline }`——正文全文 + 对应章纲(`{ file, words, body }`,outlines/ 无对应文件时 null),章节抽屉用 |
47
+ | `chapter` | GET | `path`, `file`(chapters/ 下文件名) | `{ ok, file, frontmatter, body, hash, outline }`——正文全文 + 原文 sha256 指纹(正文编辑保存 `chapter-save` 的 `base_hash` 基线)+ 对应章纲(`{ file, words, body, hash }`,outlines/ 无对应文件时 null,hash 同为章纲编辑保存的基线),章节阅读页用 |
48
48
  | `outline` | GET | `path`, `file`(outlines/ 下 vXX-cXXX 命名的文件名) | `{ ok, file, words, body }`——章纲全文(章纲纯 markdown 无 frontmatter,body 即原文,words 为 CJK 计数),章节 tab 的章纲弹层用;非章纲文件名 422、章纲不存在 404 |
49
+ | `style` | GET | `path=<root>` | `{ ok, found, text, hash, template }`——写作风格基准 novel/style.md 全文 + sha256 指纹(hash 为 style-save `base_hash` 的基线);`found: false` = 文件尚未创建(常态非错误,text/hash 为 null,不回 404);`template` 恒为插件内置默认基线(templates/novel/style.md)全文,读不到为 null(客户端据此禁用「用默认基线创建」) |
49
50
  | `chapter-status` | POST | `{ path, file, status }` | `{ ok }`——frontmatter status 行原地替换;status 必须 ∈ pending/writing/review/accepted |
50
51
  | `chapter-create` | POST | `{ path, file }`(outlines/ 下章纲文件名) | `{ ok, file, title }`——从章纲建章:chapters/ 落同 stem 的 status=pending 空章节(frontmatter 按 chapters/vXX-cXXX.md 模板,summary 与五维留空、正文待写;title 取章纲首条 `#` 标题行、去结尾「章纲」后缀),建后走既有 pending 流转;目标章节已存在 409、章纲不存在 404、非章纲文件名 422 |
51
52
  | `foreshadow-status` | POST | `{ path, id, status }` | `{ ok }`——foreshadows.yml 对应条目 status 行替换;∈ planted/progressing/resolved/abandoned |
52
53
  | `phase` | POST | `{ path, phase }` | `{ ok }`——project.yml phase 行替换;七态枚举 |
53
54
  | `project-save` | POST | `{ path, values, base_hash? }`(values 为 8 平键 title/author/genre/length/language/phase/mode/description;base_hash 为表单载入时的 project.yml 指纹,即快照 `project_hash`) | `{ ok, project_hash }`——mutateProject 逐键改写保行尾注释、缺键按模板补齐;phase/mode 越枚举与空书名 422;base_hash 与写入前现读原文不一致 → 409「外部修改冲突」拒写不落盘(人工改动保护:表单载入后文件被作者/agent 改过即拒绝静默覆盖;缺省 base_hash 跳过检查,兼容旧调用方)。project_hash 为写后新指纹,客户端可作下一轮基线 |
55
+ | `style-save` | POST | `{ path, text, base_hash? }`(text 为 style.md 全文;base_hash 为编辑器载入时的 style.md 指纹,即 `style` 路由的 `hash`) | `{ ok, hash }`——style.md 整文件覆写:纯 markdown 基准文档、无行级键位可言,整文件替换即最小手术(写前读、原子写,与 agent 的写法兼容);text 非 string 422,空文本允许保存(空文件 = 无基准,agent 侧优雅降级);base_hash 与写入前现读原文不一致 → 409「外部修改冲突」拒写不落盘(人工改动保护,与 project-save 同口径;文件不存在且带 base_hash 同样 409;缺省 base_hash 跳过检查 = 创建路径)。hash 为写后新指纹,客户端作下一轮基线 |
56
+ | `chapter-save` | POST | `{ path, file, body, base_hash? }`(file 为 chapters/ 下文件名;body 为正文全文;base_hash 为进入编辑态时 `chapter` 路由的 `hash`) | `{ ok, hash }`——正文整段替换:frontmatter 围栏(含闭合行)之前逐字节保留、围栏后整段替换(正文是自由形态文本,无行级键位可手术,整段替换即最小干预,对 git diff 的扰动天然只落在正文段内);body 非 string / 章节缺 frontmatter 围栏 422、章节不存在 404、穿越 403;正文末尾无换行补一个 `\n`;base_hash 与写入前现读原文不一致 → 409「章节文件已被外部修改…」拒写不落盘(人工改动保护,与 project-save 同口径;缺省 base_hash 跳过检查,兼容旧调用方)。hash 为写后新指纹,客户端作下一轮基线 |
57
+ | `outline-save` | POST | `{ path, file, body, base_hash? }`(file 为章节文件名或 stem,目标恒为 outlines/ 下同 stem 章纲;body 为章纲全文;base_hash 为章纲载入时的指纹,即 `chapter` 路由的 `outline.hash`) | `{ ok, hash }`——章纲整文件替换:纯 markdown 无 frontmatter(spec §outlines),整文件替换即最小手术(与 style-save 同一判断);body 非 string / stem 不合 vXX-cXXX 规范 422、章纲不存在 404(面板不新建章纲文件)、穿越 403;base_hash 检测同上 → 409「章纲文件已被外部修改…」拒写不落盘。hash 为写后新指纹,客户端作下一轮基线 |
54
58
  | `rebuild-index` | POST | `{ path }` | `{ ok, chapters }`——照 CLI index 同一逻辑重建 index.yml |
55
59
 
56
- 共 15 条。写操作全部「行级原地替换」:只在 frontmatter/目标 yml 的对应行上做字符串手术,写前读、写后校验(替换计数 ≠1 即 409 拒写并报错),不做整文件重排——把对 git diff 的扰动压到最小,也与 agent 的写法兼容。(`self-update` 是例外:它不碰小说文件,跑的是 pnpm 安装;`chapter-create` 是另一例外:它从章纲落一个全新章节文件,不动任何既有文件的字节。)
60
+ 共 19 条。写操作全部「行级原地替换」:只在 frontmatter/目标 yml 的对应行上做字符串手术,写前读、写后校验(替换计数 ≠1 即 409 拒写并报错),不做整文件重排——把对 git diff 的扰动压到最小,也与 agent 的写法兼容。(`self-update` 是例外:它不碰小说文件,跑的是 pnpm 安装;`chapter-create` 是另一例外:它从章纲落一个全新章节文件,不动任何既有文件的字节;`style-save` 亦是例外:style.md 是纯 markdown 基准文档、无行级键位可言,整文件覆写就是它的最小手术,写前读、原子写;`chapter-save` 亦是例外:正文是自由形态文本,写原语是「frontmatter 围栏定位 + 围栏后整段替换」,围栏(含闭合行)之前逐字节保留;`outline-save` 同理:章纲纯 markdown,整文件替换。)
57
61
 
58
62
  ### host-debug 端点(现场诊断)
59
63
 
@@ -109,16 +113,17 @@ webServer.register({ kind: 'prefix', path: '/api/maoying', handler: async (req,
109
113
  - 会话中栏全页 overlay:`html[data-dsh-maoying-active]` 驱动,`dsh-panel-activate` 事件与 mnemon/taskboard/ssh 等全页面板互斥,点侧栏会话行自动关闭;
110
114
  - 顶部工作区选择器(workspaces 路由取数,hasNovel 的排前;无 novel 工作区显示引导卡:一句 `maoying init` 提示 + 复制按钮)。面板懒挂载、关闭不卸载,故每次打开都借 `dsh-panel-activate` 广播重拉一次 workspaces——外部新建/删除的工作区重开即见。
111
115
 
112
- 八个 tab(顺序即默认排布):
116
+ 九个 tab(顺序即默认排布):
113
117
 
114
118
  1. **概览**:project 卡(书名大字 + 类型/篇幅/语言 pills + 阶段徽章 + 简介,卡右上「编辑」按钮展开书稿元信息表单——原「小说设置」tab 并入,见 §4.1「小说设置并入概览」条目);统计卡(总字数/章节数/状态分布条);最近更新(chapters 按 mtime 前 5);warnings 黄条;静态体检黄条(check 路由问题清单,见 §4.1「人工改动保护 + 静态体检黄条」(0.1.14)条目)。
115
- 2. **章节**:表格(卷-章/标题/状态徽章/字数/梗概),状态筛选 pills;表格上方「按单写作」区块——有章纲且 status=pending 的章按序列出(章纲节选 + 状态徽章),未建章的孤儿章纲照列(「未建章」徽章 + 「建章」一键:POST chapter-create 落 pending 空章并重拉快照;行点击开章纲全文弹层,0.1.15 起),共创模式的主工作清单;行点击开整页阅读视图(0.1.12 起替代原右侧抽屉——fixed 侧弹层会被 DSH 侧边栏遮挡):返回按钮 + GET chapter 正文(只读,等宽滚动)+ 章纲对照栏(左右分栏或折叠段)+ 复制文件路径按钮 + 五维字段卡 + 状态流转按钮(当前态 → 下一态一键,pending→writing→review→accepted)。
119
+ 2. **章节**:表格(卷-章/标题/状态徽章/字数/梗概),状态筛选 pills;表格上方「按单写作」区块——有章纲且 status=pending 的章按序列出(章纲节选 + 状态徽章),未建章的孤儿章纲照列(「未建章」徽章 + 「建章」一键:POST chapter-create 落 pending 空章并重拉快照;行点击开章纲全文弹层,0.1.15 起),共创模式的主工作清单;行点击开整页阅读视图(0.1.12 起替代原右侧抽屉——fixed 侧弹层会被 DSH 侧边栏遮挡):返回按钮 + GET chapter 正文 + 章纲对照栏(左右分栏或折叠段)+ 复制文件路径按钮 + 五维字段卡 + 状态流转按钮(当前态 → 下一态一键,pending→writing→review→accepted);正文与章纲对照栏均可就地编辑保存(textarea + 手动保存,POST chapter-save / outline-save 携 base_hash 409 人工改动检测——frontmatter 围栏逐字节保留,外部改动拒写并放回磁盘最新内容,0.1.19 起)。
116
120
  3. **角色**:卡墙(姓名/role 徽章四色/年龄性别/性格一句/动机一行),点击看全卡(含 arc/markers/background)。
117
121
  4. **伏笔**:四列看板(埋设/推进/已回收/弃用),卡片含 priority 色点(high 红/normal 灰/low 淡)、`source: backfill` 的「回填」小徽章(账本回填来的条目,区别于写前规划)、埋设章→预期回收章、notes 全文直显(0.1.10 去掉两行截断);卡片按钮:推进到下一状态(planted→progressing→resolved;abandoned 只能从看板拖不动、按钮给「弃用」)。列头计数。
118
- 5. **关系图**:canvas 力导向图——节点=角色(radius 随出场无关、固定 18px,名字 label 下方),边=relations(intensity 1-5 → 线宽 1-4px,type 标在边中点 hover 显示 description);拖拽节点、空白处滚轮缩放、双击复位;intensity 缺省 3。角色多于 30 个时只画与 relations 相连的节点,其余进「未入图」列表。
122
+ 5. **关系图**:canvas 力导向图——节点=角色(radius 随出场无关、固定 18px,名字 label 下方),边=relations(intensity 1-5 → 线宽 1-4px,type 标在边中点 hover 显示 description);拖拽节点、空白处滚轮缩放、双击复位;右上角 +/- 缩放按钮(钳制同滚轮 0.4-2.5)与全屏按钮(Fullscreen API 能力检测,不支持则不渲染;进入全屏走 ResizeObserver 自动重铺);intensity 缺省 3。角色多于 30 个时只画与 relations 相连的节点,其余进「未入图」列表。
119
123
  6. **时间线**:垂直轴,importance 1-5 → 点大小/颜色深浅,year 左列右卡片。
120
- 7. **设定**:world 按 category 分组列表 + science 三色标签(known 绿/extrapolation 黄/hypothesis 紫,标签下 references 链接列表)。
121
- 8. **索引板**:clues 按 kind 分组(线索/红鲱鱼/推理/疑问,resolved 划线置灰)+ memories 按 category 分组(promise 类高亮)。
124
+ 7. **设定**:子 tab 三分区切换(世界观/科学设定/资料检索,各带条目计数,仅渲染当前激活分区)——world 按 category 分组列表;science 三色标签(known 绿/extrapolation 黄/hypothesis 紫,标签下 references 链接列表);research 逐条渲染,空态也常驻可切。
125
+ 8. **写作风格**:novel/style.md 写作风格基准的查看与编辑——视图态 MdView 渲染全文,卡头「编辑」进整文件编辑态(textarea + 取消/保存,保存 POST style-save 携 base_hash 409 人工改动保护,写后新指纹接下一轮基线);文件缺失时显示空态卡,可用插件内置默认基线一键预填创建(保存即落盘),空文本允许保存。
126
+ 9. **索引板**:clues 按 kind 分组(线索/红鲱鱼/推理/疑问,resolved 划线置灰)+ memories 按 category 分组(promise 类高亮)。
122
127
 
123
128
  状态徽章色(全局统一):pending 灰 / writing 蓝 / review 橙 / accepted 绿;phase 七态沿用七色 pill。文案全中文、禁 emoji 图标(用 ui-primitives 图标或纯 CSS)。
124
129
 
@@ -127,7 +132,7 @@ webServer.register({ kind: 'prefix', path: '/api/maoying', handler: async (req,
127
132
  - **MaskPortal**:抽屉/弹层统一 portal 到 `document.body`——面板容器挂在宿主会话中栏,中栏 transform/overflow 会把 `position: fixed` 劫持成相对自身并连同裁剪(桌面端「点了没反应」实测病灶);无 createPortal 的老宿主退回原地渲染。
128
133
  - **顶栏版本区**:当前版本号常驻;`update-check` 路由(模仿 dsh-paoding:npm registry → npmmirror → GitHub 三级源,1h 缓存 + 7 天 stale-if-error,全败静默)发现新版时亮「有新版 ↗」徽章。
129
134
  - **系统设置**:齿轮弹窗,字号六档(100/110/120/130/140/150%)——面板根与 portal 节点读同一 `--myt-zoom` 变量整体 zoom,localStorage 记忆。
130
- - **界面语言**:系统设置可选中文/English——全部 UI 文案走 `I18N` 词典(zh/en 各 211 条,`tt`/`tf` 取词 + `{x}` 插值,zh 值与历史硬编码逐字一致保证中文界面零回归),切换立即生效整树重渲并 localStorage 记忆;语言自称(中文/日本語)与侧栏入口品牌名不翻译。
135
+ - **界面语言**:系统设置可选中文/English——全部 UI 文案走 `I18N` 词典(zh/en 各 258 条,`tt`/`tf` 取词 + `{x}` 插值,zh 值与历史硬编码逐字一致保证中文界面零回归),切换立即生效整树重渲并 localStorage 记忆;语言自称(中文/日本語)与侧栏入口品牌名不翻译。
131
136
  - **Markdown 渲染**:零依赖渲染器(先整体转义再变换),章纲对照与世界观/科学设定描述不再裸显 md 源码;正文手稿仍走 pre。
132
137
  - **章节整页阅读**:替代右侧抽屉(侧边栏遮挡问题),返回按钮 + 章纲对照 + 五维 + 状态流转原位保留;阅读态隐藏 tab 条。
133
138
  - **小说设置 tab**(第 9 个):project.yml 平键表单化编辑,`POST project-save` → `mutateProject` 逐键改写保行尾注释、缺键按模板补齐、phase/mode 越枚举与空书名 422。
@@ -141,6 +146,10 @@ webServer.register({ kind: 'prefix', path: '/api/maoying', handler: async (req,
141
146
  - **pnpm 候选链对齐 dsh-paoding**(0.1.18):修复一键升级「未找到 pnpm」误报。病因(实机验证):本机 pnpm 由 corepack 管理但未 `corepack enable`,PATH 里没有裸 pnpm,PNPM_HOME 落位也没有——旧版 existsSync+accessSync(X_OK) 文件预筛(PATH 目录扫描 + homebrew/pnpm 官方落位)全落空直接报 pnpm-missing;且 desktop Electron 进程 PATH 可能是 launchd 最小集(实测最小 PATH 下裸 corepack 也 ENOENT),`/opt/homebrew/bin/corepack` 不在,只有 `/opt/homebrew/opt/node@24/bin/corepack` 在盘上。机制照 dsh-paoding tools/lib/upgrade.mjs 对齐:① **spawn ENOENT 探测链**替代文件预筛——逐候选真 spawn,仅 err.code==='ENOENT' 换下一路(EACCES / 超时 / 非零退出是真实运行结果,如实返回不换通道),绝对路径候选才 existsSync 预筛(不存在不浪费 spawn);新候选链:裸 pnpm → 裸 corepack(+`pnpm` 参数前缀)→ `/opt/homebrew/bin/corepack` → `/opt/homebrew/opt/node@XX/bin/corepack`(readdirSync 过滤 node 前缀目录拼路径,多版本按序全收;目录不存在跳过)→ execPath 同目录 corepack(桌面 Electron 下无用但无害,headless/web profile 有用)→ `~/Library/pnpm`、`~/.local/share/pnpm`。② **升级前版本预检**:fetchLatestFromRegistry(10s) 失败再 fetchLatestFromMirror(10s),拿不到 latest 不做半截升级(kind: no-target,message 带两路错误);compareVersions(target, current)<=0 短路 kind: 'up-to-date'(`{ ok: true, kind: 'up-to-date', from }`,前端 toast「已是最新版」后按钮恢复可点、不进已安装态);装精确版本 `dsh-maoying@<target>` 而非 @latest(避开 pnpm 新鲜度防护 / 镜像 packument 滞后的静默空转)。③ **cwd = profile 目录**(pnpm 操作对象就是它),去掉 --dir。④ profile 目录缺 package.json → 诚实失败 kind: 'profile-missing'。⑤ win32 pnpm/corepack 是 .cmd shim:spawn shell:true(CVE-2024-27980 加固)+ 命令补 .cmd 后缀、参数保持数组;darwin/linux 恒 shell:false。⑥ 子进程输出尾部 2KB → 8KB(合并 stdout+stderr);全候选 ENOENT 的 pnpm-missing message 带可操作出路:「可先在终端执行 corepack enable 并重启 DSH 后重试;或手动升级:cd <profileDir> && corepack pnpm add dsh-maoying@<目标版本>」,dev-link 的 message 保留 git pull 出路。spawnImpl 契约改 paoding 形状 `(command, args, options, timeoutMs, timeoutLabel) → { code, output, error?, errorCode? }`(缺省 defaultSpawn 合并输出、超时 SIGTERM、5s 宽限后 SIGKILL 兜底、收敛 errorCode 'ETIMEDOUT');退出码非零 kind 从 pnpm-failed 更名 nonzero。前端 pnpm-missing 等失败 message 服务端已带出路文案,toast 直显不截断。安全闸(realpath 形态两道闸 / 模块级 in-flight 去重 / 成功后 invalidateVersionCache)与依赖全注入风格不变。路由仍 15 条;I18N zh/en 各 237 → 238 条(toast.selfUpdate.upToDate)。
142
147
  - **概览补显共创模式**(0.1.18):书稿卡阶段行 phase pill 后补「共创模式」短标签徽章(悬浮出全称;快照 mode 缺失或越枚举时不显示,老项目无该键不误标)。路由不变;I18N zh/en 各 238 → 241 条(modeShort.aiDraft / coWrite / authorDraft)。
143
148
  - **一键升级顺带全局 CLI**(0.1.18):本机常见两份独立安装——desktop profile(`<dshHome>/profiles/desktop/node_modules/dsh-maoying`,一键升级的更新对象)与全局 CLI(`npm i -g` 装的 `<npm 全局>/node_modules/dsh-maoying`,如 `/opt/homebrew/bin/maoying` → `/opt/homebrew/lib/node_modules/dsh-maoying/bin/maoying.mjs`)——只更 profile 会版本偏斜;方案 B:profile 阶段落定(installed 装完 / up-to-date 短路)后跑独立全局阶段,把两份收敛到同一精确版本。**检测**(detectGlobalCli,零 spawn 纯文件系统):PATH 各目录 + `/opt/homebrew/bin` + `/usr/local/bin` 找 `maoying`(win32 `maoying.cmd`),existsSync 预筛;命中后 realpathSync 该 bin 两级上溯得包根(`bin/maoying.mjs` → 包根),包根 package.json 的 name 必须是 `dsh-maoying` 才认(防怪链接);包根 realpath 落在本 profile 目录内(如 profile 自带 `.bin/maoying`)→ 不算全局,跳过该候选继续找;全落空 → 不 spawn。**更新**走 npm 候选链(与 pnpm 链同款 spawn ENOENT 探测):裸 npm → execPath 同目录 → `/opt/homebrew/bin` → `/usr/local/bin` → `/opt/homebrew/opt/node@XX/bin`,spawn `npm i -g dsh-maoying@<target>`(cwd=os.tmpdir()、独立 120s 超时,profile pnpm 仍 180s)。**返回形状**:成功对象新增 `globalCli` 字段——`{ ok: true, skipped: 'missing' }`(没装全局,跳过)/ `{ ok: true, skipped: 'same', version }`(已同版)/ `{ ok: true, from, to }` / `{ ok: false, error, output? }`(error 中文、含手动出路 `npm i -g dsh-maoying@<target>`)——**全局失败不拖累 profile 安装**:整体仍 ok:true,重启照常生效,失败如实透出;dev-link / unsupported-layout / no-target / profile-missing 等失败路径维持整单拒绝,不碰全局。路由把 globalCli 原样透传(up-to-date 与 installed 两个成功分支)。前端成功 toast 主文案不变、按 globalCli 拼第二句(missing / same 不提零噪音,真更新了显「全局 maoying CLI a → b」,失败如实报错),up-to-date 分支同样拼接,toast 文本容器加 `white-space: pre-line` 承接换行;self-update 请求前端超时 200s → 340s(180 + 120 + 40 余量)。路由不变;I18N zh/en 各 241 → 243 条(toast.selfUpdate.globalOk / globalFail)。
149
+ - **写作风格 tab**(0.1.19):novel/style.md 写作风格基准(0.1.18 落的数据文件与四角色加载纪律)在面板有了入口——设定与索引板之间新增「写作风格」tab:视图态 MdView 渲染全文,卡头「编辑」进整文件编辑态(textarea + 取消/保存;保存 POST `style-save` 携 base_hash,409 人工改动保护链路照 ProjectMetaForm——成功接写后新指纹防连续保存假冲突,409 重拉磁盘真值把编辑器原地重置、停留编辑态让用户自行取舍);文件缺失时空态卡「用默认基线创建」——插件模板(templates/novel/style.md)预填编辑态、保存即创建(模板读不到时按钮禁用);空文本允许保存(空文件 = 无基准,agent 侧优雅降级),挂载/换工作区按需拉取 GET `style`(请求序号防过期响应)。路由 15 → 17 条(style GET / style-save POST);I18N zh/en 各 243 → 250 条(tabs.style / style.note / style.editTip / style.empty / style.createFromTpl / style.loadFailed / toast.styleSaved);九个 tab。
150
+ - **正文/章纲面板编辑**(0.1.19):作者本人直接在面板写章节正文与章纲——章节阅读页「编辑」进正文编辑态(textarea + 取消/保存,无自动保存),章纲对照栏卡头「编辑」进章纲编辑态;保存 POST `chapter-save` / `outline-save`:正文写原语是「frontmatter 围栏定位 + 围栏后整段替换」(正文自由形态无行级键位可手术,围栏含闭合行之前逐字节保留),章纲纯 markdown 整文件替换;`GET chapter` 出参补顶层 hash 与 outline.hash(保存响应接写后新指纹作下一轮基线,防连续保存假冲突),base_hash 与写入前现读不一致 → 409 拒写不落盘(人工改动保护链路照 StyleTab:草稿丢弃、重拉磁盘真值放回编辑器,正文末尾无换行自动补齐;缺围栏/坏 stem 422、章纲不存在 404——面板不新建章纲文件、穿越 403);切章自动退出编辑态清草稿,防 A 章文字写进 B 章。路由 17 → 19 条(chapter-save / outline-save POST);I18N zh/en 各 250 → 258 条(reader.edit / reader.editTip / reader.save / reader.bodyEditHint / outline.editTip / outline.editHint / toast.chapterSaved / toast.outlineSaved);顶栏副标题与 README 的「只读正文」边界声明同步改写(spec §作者稿圣域不变——面板编辑是给人用的刀,agent 的圣域规则原样)。
151
+ - **设定 tab 改子 tab 展示**(0.1.19):设定 tab 内世界观/科学设定/资料检索三张卡从纵向堆叠改为子 tab 条切换(各带条目计数,仅渲染当前激活分区);research 空时原实现整卡隐藏,现子 tab 恒在、空态文案常驻可切。路由不变;I18N zh/en 各 258 → 260 条(world.researchEmpty / world.subTabsAria)。
152
+ - **关系图工具角标**(0.1.19):关系图右上角新增工具条——+/- 缩放按钮(步进 ×1.25 / ÷1.25,钳制与滚轮同口径 0.4-2.5;React 按钮够不着绘图 effect 闭包里的 sim,经 zoomApiRef 桥调用,effect 清理时置 null 防旧闭包误触)与全屏切换按钮(Fullscreen API 能力检测——wrap 有 requestFullscreen/webkitRequestFullscreen 且 document.fullscreenEnabled 非假才渲染,否则不占位;监听 fullscreenchange/webkitfullscreenchange 同步「全屏/退出全屏」文案,浏览器 ESC 退出也跟上;进/出全屏 wrap 尺寸变化走既有 ResizeObserver 自动 resize 重铺 canvas,Chromium UA 全屏样式覆盖 480px 定高)。纯前端,路由不变;I18N zh/en 各 260 → 264 条(relations.zoomIn / zoomOut / fs / exitFs),relations.help 文案同步更新(提缩放按钮与右上角全屏)。
144
153
 
145
154
  ## 5. 验收基线
146
155
 
package/docs/design.md CHANGED
@@ -172,7 +172,7 @@ persona prefix 内容骨架(agent.cordis.yml 内联,中文书写):
172
172
  - ✅ **技能包(黄金三章/爽点节奏/审校清单/伏笔管理)**——已做:skills/ 四目录随包分发,安装器复制到 `<home>/skills/maoying-*`(逐字节比较幂等 + .installed-version 标记,见 index.mjs installSkills)。
173
173
  - ✅ **导出(MD/TXT/EPUB)**——已做:`maoying export [dir] --format md|txt|epub [--out <目录>]`;EPUB 由 lib/epub.mjs 零依赖打包(手写 stored zip + CRC32 表格法,EPUB 3 最小合法结构)。
174
174
  - ✅ **0.2.1-rc.2+ AgentPreset 形态适配**——已做:presets/maoying/agent.cordis.v2.yml(主编 + 5 角色 preset 的 v2 组合源)+ index.mjs composeManagedRows 按宿主版本双形态切换(v1 内联 / v2 AgentPreset,宿主升降级重启即整块换形状;防漂移由 test/composition.test.mjs 逐字节断言,见 README「兼容面」)。
175
- - ✅ **Web 创作台面板**——已做:/api/maoying/* 十五条路由 + 左侧栏入口 + 会话中栏全页面板,八视图(概览(含书稿元信息编辑)/章节/角色/伏笔看板/关系图/时间线/设定/索引板);设计与契约见 docs/creator-panel.md。
175
+ - ✅ **Web 创作台面板**——已做:/api/maoying/* 十七条路由 + 左侧栏入口 + 会话中栏全页面板,九视图(概览(含书稿元信息编辑)/章节/角色/伏笔看板/关系图/时间线/设定/写作风格/索引板);设计与契约见 docs/creator-panel.md。
176
176
 
177
177
  ## 9. 五期:共创模式(作者亲笔稿)
178
178
 
package/index.mjs CHANGED
@@ -65,15 +65,19 @@ import { fileURLToPath } from 'node:url'
65
65
  import {
66
66
  createChapterFromOutline,
67
67
  listWorkspaces,
68
+ mutateChapterBody,
68
69
  mutateChapterStatus,
69
70
  mutateForeshadowStatus,
71
+ mutateOutlineBody,
70
72
  mutatePhase,
71
73
  mutateProject,
72
74
  parseStateSnapshot,
73
75
  readChapter,
74
76
  readOutlineFile,
77
+ readStyleState,
75
78
  rebuildIndex,
76
79
  resolveNovelRoot,
80
+ saveStyleText,
77
81
  } from './lib/panel-core.mjs'
78
82
  // 静态体检(check 路由)与 CLI check 同源:直接复用 novel-parse 的检查器。
79
83
  import { checkProject } from './lib/novel-parse.mjs'
@@ -942,7 +946,7 @@ export async function registerPrefixRouteWithRetry(webServer, route, { attempts
942
946
  }
943
947
 
944
948
  /**
945
- * 注册创作台全部路由(docs/creator-panel.md §2 的 15 条)。返回宿主 disposer;
949
+ * 注册创作台全部路由(docs/creator-panel.md §2 的 19 条)。返回宿主 disposer;
946
950
  * webServer 缺席(非 web 部署)时抛错,由 apply 侧统一降级为 warn。
947
951
  */
948
952
  export async function registerCreatorRoutes(ctx) {
@@ -1102,7 +1106,8 @@ export async function registerCreatorRoutes(ctx) {
1102
1106
  if (route === 'chapter' && req.method === 'GET') {
1103
1107
  // 章节正文全文(抽屉用):file 只认 chapters/ 下纯文件名;响应组装
1104
1108
  // 直传 readChapter 结果(五期起含对应章纲 outline 字段,无章纲为
1105
- // null),照 §2 契约 { ok, file, frontmatter, body, outline }。
1109
+ // null;0.1.19 起顶层与 outline 各带原文 hash——正文/章纲编辑保存
1110
+ // 的 base_hash 基线),照 §2 契约 { ok, file, frontmatter, body, hash, outline }。
1106
1111
  const novelDir = resolveNovelRoot(url.searchParams.get('path'), ctx.workspaceRegistry)
1107
1112
  json(res, 200, readChapter(novelDir, url.searchParams.get('file')))
1108
1113
  return
@@ -1117,6 +1122,16 @@ export async function registerCreatorRoutes(ctx) {
1117
1122
  return
1118
1123
  }
1119
1124
 
1125
+ if (route === 'style' && req.method === 'GET') {
1126
+ // 写作风格基准(0.1.19,写作风格 tab 取数):novel/style.md 全文 +
1127
+ // 指纹(保存路由 base_hash 的基线)+ 插件模板全文(客户端用它预填
1128
+ // 默认基线创建)。found:false = 文件尚未创建,是常态不是错误。
1129
+ const novelDir = resolveNovelRoot(url.searchParams.get('path'), ctx.workspaceRegistry)
1130
+ const style = readStyleState(novelDir)
1131
+ json(res, 200, { ok: true, found: style.found, text: style.text, hash: style.hash, template: style.template })
1132
+ return
1133
+ }
1134
+
1120
1135
  if (route === 'chapter-status' && req.method === 'POST') {
1121
1136
  const body = await readBody(req, res)
1122
1137
  const novelDir = resolveNovelRoot(body.path, ctx.workspaceRegistry)
@@ -1157,6 +1172,38 @@ export async function registerCreatorRoutes(ctx) {
1157
1172
  return
1158
1173
  }
1159
1174
 
1175
+ if (route === 'style-save' && req.method === 'POST') {
1176
+ // 写作风格保存(0.1.19):style.md 整文件覆写(纯 markdown 基准
1177
+ // 文档,无行级键位,整文件替换即最小手术);base_hash 过期 → 409
1178
+ // 拒写(人工改动保护,与 project-save 同口径);text 非 string 422。
1179
+ const body = await readBody(req, res)
1180
+ const novelDir = resolveNovelRoot(body.path, ctx.workspaceRegistry)
1181
+ json(res, 200, saveStyleText(novelDir, body.text, { baseHash: body.base_hash }))
1182
+ return
1183
+ }
1184
+
1185
+ if (route === 'chapter-save' && req.method === 'POST') {
1186
+ // 章节正文保存(0.1.19,面板章节阅读页编辑态):frontmatter 围栏
1187
+ // (含)之前逐字节保留、围栏后整段替换(正文是自由形态文本,无行级
1188
+ // 键位可手术);base_hash 过期 → 409 拒写(人工改动保护,与
1189
+ // project-save 同口径);body 非 string / 缺围栏 422、章节缺 404。
1190
+ const body = await readBody(req, res)
1191
+ const novelDir = resolveNovelRoot(body.path, ctx.workspaceRegistry)
1192
+ json(res, 200, mutateChapterBody(novelDir, body.file, body.body, { baseHash: body.base_hash }))
1193
+ return
1194
+ }
1195
+
1196
+ if (route === 'outline-save' && req.method === 'POST') {
1197
+ // 章纲保存(0.1.19,章节阅读页章纲对照栏编辑态):章纲纯 markdown
1198
+ // 无 frontmatter,整文件替换;file 认章节文件名或 stem,目标恒为
1199
+ // outlines/<stem>.md(不存在 404,v1 不做面板新建章纲);base_hash
1200
+ // 检测同上;stem 不合 vXX-cXXX 规范 422。
1201
+ const body = await readBody(req, res)
1202
+ const novelDir = resolveNovelRoot(body.path, ctx.workspaceRegistry)
1203
+ json(res, 200, mutateOutlineBody(novelDir, body.file, body.body, { baseHash: body.base_hash }))
1204
+ return
1205
+ }
1206
+
1160
1207
  if (route === 'rebuild-index' && req.method === 'POST') {
1161
1208
  const body = await readBody(req, res)
1162
1209
  const novelDir = resolveNovelRoot(body.path, ctx.workspaceRegistry)
package/lib/base.css CHANGED
@@ -116,12 +116,13 @@ html[data-dsh-maoying-active]:not([data-dsh-mnemon-active]):not([data-dsh-taskbo
116
116
  .myt-closeBtn:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
117
117
  .myt-closeGlyph { font-size: 18px; line-height: 1; }
118
118
 
119
- /* 内容区:自身滚动容器 */
119
+ /* 内容区:自身滚动容器(顶部 16px 与 tab 条拉开距离,角色卡墙等
120
+ 首元素带边框时不贴条;左右 24 与 tab 条 padding 对齐)*/
120
121
  .myt-body {
121
122
  flex: 1 1 auto;
122
123
  min-height: 0;
123
124
  overflow-y: auto;
124
- padding: 0 24px 32px;
125
+ padding: 16px 24px 32px;
125
126
  }
126
127
  /* 宽度随宿主中栏自适应:不设上限(980 上限已去,宽屏撑满);min-width
127
128
  兜底极窄中栏,低于时 .myt-body(overflow-y:auto 连带 overflow-x:auto)
@@ -203,6 +204,38 @@ html[data-dsh-maoying-active]:not([data-dsh-mnemon-active]):not([data-dsh-taskbo
203
204
  font-weight: 600;
204
205
  }
205
206
 
207
+ /* ── 设定 tab 内的子分区条(比主导航 tab 小一号)─────────────────────── */
208
+ .myt-subTabs {
209
+ flex: none;
210
+ display: flex;
211
+ align-items: center;
212
+ gap: 4px;
213
+ overflow-x: auto;
214
+ scrollbar-width: none;
215
+ }
216
+ .myt-subTab {
217
+ display: inline-flex;
218
+ align-items: center;
219
+ gap: 6px;
220
+ padding: 4px 10px;
221
+ border: none;
222
+ border-radius: 7px;
223
+ background: transparent;
224
+ color: var(--dsw-alias-label-secondary);
225
+ font: inherit;
226
+ font-size: 12.5px;
227
+ line-height: 18px;
228
+ cursor: pointer;
229
+ white-space: nowrap;
230
+ }
231
+ .myt-subTab:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
232
+ .myt-subTab[data-active="true"] {
233
+ background: color-mix(in srgb, var(--dsw-alias-state-business-primary) 14%, transparent);
234
+ color: var(--dsw-alias-state-business-primary);
235
+ font-weight: 600;
236
+ }
237
+ .myt-subTab .myt-boardCount { line-height: 16px; }
238
+
206
239
  /* ── 卡片 / 栅格 ───────────────────────────────────────────────────────── */
207
240
  .myt-card {
208
241
  display: flex;
@@ -424,6 +457,59 @@ html[data-dsh-maoying-active]:not([data-dsh-mnemon-active]):not([data-dsh-taskbo
424
457
  .myt-dimLabel { font-size: 11px; line-height: 16px; color: var(--dsw-alias-label-tertiary); margin-bottom: 2px; }
425
458
  .myt-dimText { font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-secondary); overflow-wrap: anywhere; }
426
459
 
460
+ /* ── 0.1.19:正文/章纲面板编辑态(textarea + 手动保存)────────────────────
461
+ 与只读态的区分要一眼可辨:全宽等宽 textarea + 主题色边框强调;只读态是
462
+ 无边框淡底的 pre(.myt-chapterBody)与 MdView。 */
463
+ .myt-bodyEdit { display: flex; flex-direction: column; gap: 8px; }
464
+ .myt-chapterEditArea {
465
+ width: 100%;
466
+ box-sizing: border-box;
467
+ padding: 12px;
468
+ border: 1px solid color-mix(in srgb, var(--dsw-alias-state-business-primary) 45%, transparent);
469
+ border-radius: 10px;
470
+ background: var(--dsw-alias-bg-layer-1, var(--dsw-alias-bg-layer-2));
471
+ color: var(--dsw-alias-label-primary);
472
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
473
+ font-size: 13px;
474
+ line-height: 1.7;
475
+ resize: vertical;
476
+ min-height: 320px;
477
+ }
478
+ .myt-editHint { font-size: 11px; line-height: 16px; color: var(--dsw-alias-label-tertiary); }
479
+ .myt-outlineEditBtn {
480
+ display: inline-flex;
481
+ align-items: center;
482
+ gap: 3px;
483
+ padding: 0 6px;
484
+ border-radius: 6px;
485
+ color: var(--dsw-alias-state-business-primary);
486
+ font-size: 11px;
487
+ line-height: 18px;
488
+ cursor: pointer;
489
+ user-select: none;
490
+ }
491
+ .myt-outlineEditBtn:hover { background: var(--dsw-alias-interactive-bg-hover); }
492
+ .myt-outlineEdit {
493
+ display: flex;
494
+ flex-direction: column;
495
+ gap: 8px;
496
+ padding: 10px 12px 12px;
497
+ border-top: 1px solid color-mix(in srgb, var(--dsw-alias-state-business-primary) 18%, transparent);
498
+ }
499
+ .myt-outlineArea {
500
+ width: 100%;
501
+ box-sizing: border-box;
502
+ padding: 10px;
503
+ border: 1px solid color-mix(in srgb, var(--dsw-alias-state-business-primary) 45%, transparent);
504
+ border-radius: 8px;
505
+ background: var(--dsw-alias-bg-layer-1, var(--dsw-alias-bg-layer-2));
506
+ color: var(--dsw-alias-label-primary);
507
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
508
+ font-size: 12px;
509
+ line-height: 1.6;
510
+ resize: vertical;
511
+ }
512
+
427
513
  /* ── 五期:章纲对照栏(章节抽屉)──────────────────────────────────────────
428
514
  与正文视觉区分:主题色左边线 + 淡主题色底,深色随令牌自动跟随。 */
429
515
  .myt-outlinePanel {
@@ -623,6 +709,33 @@ html[data-dsh-maoying-active]:not([data-dsh-mnemon-active]):not([data-dsh-taskbo
623
709
  overflow-wrap: anywhere;
624
710
  }
625
711
  .myt-graphFoot { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; color: var(--dsw-alias-label-tertiary); font-size: 11px; }
712
+
713
+ /* ── 关系图工具角标:+/- 缩放常驻;全屏按钮能力检测通过才渲染 ───────── */
714
+ .myt-graphTools { position: absolute; top: 10px; right: 10px; display: flex; gap: 6px; z-index: 3; }
715
+ .myt-graphTool {
716
+ display: inline-flex;
717
+ align-items: center;
718
+ justify-content: center;
719
+ width: 26px;
720
+ height: 26px;
721
+ padding: 0;
722
+ border: 1px solid var(--dsw-alias-border-l2);
723
+ border-radius: 7px;
724
+ background: var(--dsw-alias-bg-layer-2);
725
+ color: var(--dsw-alias-label-secondary);
726
+ font: inherit;
727
+ font-size: 14px;
728
+ line-height: 1;
729
+ cursor: pointer;
730
+ }
731
+ .myt-graphTool:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
732
+ .myt-graphToolText { width: auto; padding: 0 10px; font-size: 12px; line-height: 18px; }
733
+ .myt-graphWrap:fullscreen,
734
+ .myt-graphWrap:-webkit-full-screen {
735
+ border: none;
736
+ border-radius: 0;
737
+ background: var(--dsw-alias-bg-base, var(--dsw-alias-bg-layer-1));
738
+ }
626
739
  .myt-unlinked { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
627
740
  .myt-unlinkedTag { padding: 1px 8px; border-radius: 999px; border: 1px dashed var(--dsw-alias-border-l2); color: var(--dsw-alias-label-tertiary); font-size: 11px; line-height: 16px; }
628
741
 
@@ -899,3 +1012,8 @@ html[data-dsh-maoying-active]:not([data-dsh-mnemon-active]):not([data-dsh-taskbo
899
1012
  .myt-setArea { resize: vertical; }
900
1013
  .myt-setSelect { cursor: pointer; }
901
1014
  .myt-setActions { display: flex; justify-content: flex-end; gap: 8px; }
1015
+ /* 写作风格 tab:编辑器撑满卡宽 + 等宽字形(编辑的是 md 源文件,对照正文
1016
+ pre 的口径);空态卡居中引导(缺失时用默认基线创建)。 */
1017
+ .myt-styleArea { width: 100%; box-sizing: border-box; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; line-height: 1.6; }
1018
+ .myt-styleEmpty { display: flex; flex-direction: column; align-items: flex-start; gap: 10px; padding: 8px 0 4px; }
1019
+ .myt-styleEmpty p { margin: 0; max-width: 560px; }