Repository navigation
Releases
修复批次:20+ 处静默缺陷(配置 / 路由 / 导出 / 资产 / 张力 / 时间线 / AI 味)+ 三块重构
修复批次:20+ 处静默缺陷(配置 / 路由 / 导出 / 资产 / 张力 / 时间线 / AI 味)+ 三块重构
本版没有新功能,全部是「会静默坏掉」的修复与内部收口。 两轮改动都走完 tsc、全量 vitest 与真机重启验证;行为变化集中在错误路径与边缘数据。
一、配置与路由
fallbackMaxTokens全链路接通:此前协议声明、model-capability读取,但 Config schema /resolveConfig/patchConfig三处白名单都不认,设置页也没有入口——永远 undefined。现补 schema、resolve、patch 与设置页输入框。savedModels接通:「我的模型」条目此前同样在协议声明后被三处白名单静默丢弃。- 设置页补传
analysisReasoning:分析推理档位此前改完保存即丢。 - 21 处路由统一收 body 的
bookId:requireProjectAt(req,res)全部改为先读 body 再取项目;三个无 body 的路由(人物志刷新 / 事实回填 / 反推大纲)也补读。多书切换后 POST 不再落到激活书目录。 adapt/analyze的filePath加扩展名白名单(txt / md / markdown),不再能当任意文件读取器。
二、数据与资产
- 导出防覆盖:同名文件依次追加
(2)、(3);全本导出跳过「文件缺失 / 为空」的章并回传skipped,面板如实提示跳过章号。 - 作者资产新增补唯一 id:前端新增传空 id,此前每条都以
''入库——第二条会覆盖第一条。服务端现在补aa-…-随机。 global-assets缓存按 mtime 失效:外部进程 / 手改文件立即可见,不再永不刷新。- 复盘回灌统一:
/generate与/review/backfill(单章 + 全书流)都把stateChanges/clues写进编年录,与生产单同口径;此前单独补跑复盘的章节在后续生成里「失忆」。 factCount改为实际入库条数(去重后),不再报模型原始条数。
三、生成质量与判定
- 作者复盘改用
reviewModel:与审稿同模型同档位;此前回落主模型,同一次生产里两把尺子。 - 张力 custom 按章号取(
custom[no-1],不再按下标错位);无自定义值时回落到本章目标,参考线不断。 - 时间线 AI 复核分批(150 条 / 批),长篇不再一次全量送模型超上下文;跨批倒流由规则初筛兜底。
- AI 味扫描去重权:长模式优先占用字符区间,包含关系(「心中」/「心中暗道」)不再对同一段文字重复计权。
- 删除
reviewing死状态(协议 / 阶段契约 / 面板 / 文案)。 - 拆章修复:中文数字补
零 / 〇 / 万并改为段位进位(「一万零一」=10001);第X卷不再被当章切。 - 移除运行时 skill 死代码(
enableRuntimeSkill=false整段 + 过时文案)。
四、重构与界面
ChapterWorkbench抽成独立文件;WorkspaceView(编辑器全页)抽成受控组件;总编台纯计算层抽成home-model.ts(computeChapterStats/computeJourneyStages,新增 8 条单测)。NovelPanel从 6345 行降到约 5440 行。- NDJSON 三份解析合并为
streamNdjson:统一注入 bookId、流结束时消费无换行符的最后一帧(不再丢 done / error)。 - 清理死 setter(
setCharCards/setNpmLatest及其 npm 检测请求)、assets.ts过期注释、README 三处过期版本号。
验证
tsc --noEmit0 错;vitest41 个文件 / 524 用例全过;pnpm build成功(lib/index.js733 KB、lib/client.js1.95 MB)。- 重启后实测:
/status200 且新配置字段生效;作者资产连加两条 id 非空且不再覆盖;张力设目标 50 时参考值回落到 50、清除后归空;面板各视图正常渲染,服务端日志无 novel-forge 报错。
取名器 + 风格目标卡:两块「资产/学习型」能力落地(对标社区头部竞品后的填空)
取名器 + 风格目标卡:两块「资产/学习型」能力落地(对标社区头部竞品后的填空)
本版补上竞品普查(7 组关键词、10 个头部项目 README 逐家比对)确认的两处高价值缺口。 都是「把作者意图变成可复用资产」的品类:取名器管专有名字的生产与沉淀,风格目标卡管写法的量化定型。另有「作者偏好记忆 / AI 封面 / 短篇模式」三项经评估本版不做(形态冲突或依赖外部通道,决策留档于工作日志)。
一、取名器(name-forge)
- 五类别批量取名:人名 / 地名 / 功法 / 势力 / 宝物,每类可出 5-20 个候选;书籍语境感知——题材基调、世界观既有名(境界/区域/势力/角色)、库内已采纳名三重避重,另有作者补充要求输入框(如「冷僻复姓」「反派名雅致但有压迫感」)。
- 候选不落盘,采纳才入库:生成结果只活在面板上;作者逐条(或全部)采纳后才写入本书名字库(
novel-project.json的nameBank),换书即换库。 - 容量纪律:每类 ≤ 60、全书 ≤ 200,超限明确报错而非静默截断——「刚取的名字去哪了」比「库满了」更难排查。(全书上限必须小于 5×60,否则永远被单类规则抢先命中、成为死代码;测试专构造「单类未满但总数超」场景证明可达。)
- 注入写作提示词:已采纳名字进本书层「已定名资产」块——正文涉及上表类别的新专有名优先从表中取,表外创新名须同语感、不得近音重复。防跑偏也防撞名。
- 新提示词域
nameforge.ts已过注册表四条纪律;面板三入口(工具菜单 / 资料侧柜 / 抽屉);20 条单测覆盖解析宽容度(围栏/废话/截断/去重)、容量与注入边界。
二、风格目标卡(文风蒸馏)
- 把本书写法固化成确定性指标:句均长 / 句长起伏 / 短句占比 / 对话占比 / 段落均长 / 意象密度六项,以区间形式注入每一次正文生成——比「句式凝练」这类定性描述可执行得多(底层复用 2.1.3 起的风格指纹体系,容忍度取自 1279 章实测 p90)。
- 两种蒸馏:① 本书已过审章节的等距抽样中位数(≥3 章,把「我现在实际写出来的样子」固化成目标);② 粘贴参考样文本(≥200 字,开书定调)。可随时恢复默认(回落绑定资产的样本文本)。
- 与审稿尺子刻意分开(本版最重要的设计决定):审稿端一直用「本书已过审章节实时中位数」当自适应尺子;若生成目标也随书自适应,会出棘轮效应——某章指标偏高 → 进中位数 → 下一章目标跟着抬高 → 越写越偏且「生成符合目标、审稿也符合尺子」互相认证,没有任何一方报警。因此目标卡只在作者点「蒸馏」时变:是决定,不是派生。采样与审稿基线同源(
sampleArchivedFingerprint),测试断言两边指纹一致。 - 面板「风格」抽屉新增目标卡区:当前生效说明 + 六项指标 + 双蒸馏入口 + 恢复默认;12 条单测覆盖优先级 / 同源 / 数据不足抛错 / 注入替换。
三、本版明确不做(及理由)
- 多 Agent 并行写作:与「全局单实例、一次一本书、逐章串行」的一致性设计正面冲突;
- 作者偏好记忆(跨书学习):采集面横跨多个交互点,v1 容易学成噪音,需单独设计信号清单;
- AI 封面生成:插件不能依赖作者本机图像管线,需先定用户自配图像端点通道;
- 短篇模式:1-3 万字盐言风与 3000-4000 字/章的长篇架构差异大,值得单独排期而非塞进小版本。
验证
- 双配置 tsc 0 错;third-party / css-vars / fallback-tiers / theme-sizes / contrast 门禁全绿。
- 全量 vitest 515 用例通过(2.4.0 的 483 + 本版 32);双面打包产物含全部新功能字符串。
许可证切换为 AGPL-3.0-only,补齐分发署名;换证不涉代码逻辑
许可证切换为 AGPL-3.0-only,补齐分发署名;换证不涉代码逻辑
自本版起,本项目以 GNU Affero 通用公共许可证第三版(AGPL-3.0-only)发布。 LICENSE 换为许可全文,package.json 的 license 字段同步;同时新建 NOTICE 把随包分发与安装期修补的第三方组件署名补齐。本次发布不含任何功能与逻辑改动,纯粹是许可与合规层面的收口。
一、许可证切换(Apache-2.0 → AGPL-3.0-only)
- LICENSE 全文替换为 GNU Affero 通用公共许可证第三版(官方文本,含「仅第三版」的 only 表述);
package.json→"license": "AGPL-3.0-only";README 徽章与「许可证」章节同步更新。 - 为什么选它:本插件是服务端组件,其第 13 条保证任何修改后的版本若作为网络服务运行,其使用者都能拿到对应源码——对写作工具工作台来说,这是「改进流回上游」的硬保证,而不是口头约定。版本号钉死为
-only,不随时间漂移。 - 版本边界:v2.3.0 及以前的发布版仍以其发布时的 Apache-2.0 许可为准;自 v2.4.0 起为 AGPL-3.0-only。许可证在分发时授予,已发布旧版的既有权利不受影响。边界同时写在 README 与 PROVENANCE.md 的决策记录里。
- 换证决策记录(理由、兼容性审计、社区先例、门禁收窄说明)完整留档于
PROVENANCE.md文末「许可证变更决策」一节。
二、分发署名(NOTICE 新建)
- 随产物打包:lucide-react(ISC,Copyright (c) 2026 Lucide Icons and Contributors)与 fflate(MIT,Copyright (c) 2026 Arjun Barrett)进入
lib/client.js浏览器半产物,NOTICE 第一节逐条署名并指向各自包内许可文本。 - 安装期修补:schemastery(MIT,Author: Shigma)不进入产物,仅在用户本机
node_modules由 postinstall 脚本补exports字段(修复 Node ESM 解析);该修补只改分发元数据、不改源码。其包内未随 LICENSE 文件,NOTICE 中只写可查的 author 字段,不编造版权年份。 - 运行时依赖:react / react-dom 与宿主 SDK 系列不在本包产物内、不随包再分发,NOTICE 第三节说明各自许可证自始至终有效。
三、发布门禁同步收窄
scripts/check-third-party.mjs的「不相容许可证标记」规则原为命中裸许可证字样一律拦截(本意是拦外部不相容许可证的引入特征)。换证后它会误伤本项目自己的许可证指纹,故收窄而非加豁免:命中AGPL-3.0-only(含徽章 URL 的下划线转义形式)放行,无版本号、无-only后缀的写法仍拦截。- 判别力未塌:真正要拦的「含糊引用外部同类许可证项目」依然是无版本写法;「来源标注措辞」两道规则独立兜底,行级例外只抑制所属规则,不抑制来源标注规则。
tests/repo-hygiene.test.ts同步镜像该规则,配双向用例:4 条放行(package.json 字段 / 徽章 URL / 正文引用 / 下划线转义形式)、3 条拦截(裸词 / 无版本号 / 来源标注措辞组合)、1 条跨规则测试(精确形式不解除来源标注拦截)。原有五契约测试不动。
四、依赖兼容性审计
- 直接依赖 24 个:MIT ×19 / ISC ×1 / Apache-2.0 ×1 / MPL-2.0 ×1——与本许可证组合分发全部兼容;无 Copyleft 直接依赖。间接依赖随 npm 依赖树按同许可证条款分发。
五、社区先例调研
- 对 DSH 插件生态做了全量普查(npm 分页 35 页 5250 个去重条目,并对样本 tarball 抽验真实 LICENSE 文件):该许可证家族 38 个(约 0.72%),是生态内成熟实践而非孤例。
验证
- 来源卫生门禁 0 命中(225 个文件);双配置 tsc 0 错;全量 vitest 475 用例通过(含 repo-hygiene 新增双向用例与 prompt-registry 四纪律)。
提示词资产化:三层组装(缓存命中)+ 按域拆分注册表,engine.ts 瘦身 690 行
提示词资产化:三层组装(缓存命中)+ 按域拆分注册表,engine.ts 瘦身 690 行
系统提示词从 engine.ts 整体迁出。 一期把提示词组装收敠成「固定 → 本书 → 本章」三层,重复前缀有机会命中厂商输入缓存,命中量进实况账本;二期把全部文案按域拆成 15 个文件 + 一份可检索的注册表,engine.ts 只剩组装与调用。改文案去域文件,不在 engine.ts 新起一段。
一、提示词三层组装(固定 / 本书 / 本章)
- 分层模型(
src/prompt-cache.ts):固定层(写作硬性要求 + 合规红线)永不变;本书层(道藏 / 世界 / 大纲 / 资产 / 槽位)只随作者编辑变;本章层(伏笔 / 剧情线 / 骨架 / 时间线 / 张力)随创作进度逐章变。顺序由模块写死,调用方只标层——前缀缓存命不命中,不依赖调用方书写顺序。 - 缓存命中记账:
cacheReadTokens进协议与实况账本,面板可直接看到命中量——省钱效果从「感觉」变成「数字」。 - 空白块不留空段:「无伏笔」时不在结果里留空分隔,避免同一本书前后两次调用的前缀在这里分叉。
二、提示词注册表(按域拆分)
- 15 个域文件:common(共享合规红线)/ world / bible / plan / writing / review / polish / characters / plotlines / foreshadow / outline / digest / facts / audit / adapt / market,文案逐字迁移;
catalog.ts汇总为 46 条登记(id = 域.名称),describePrompt可查中文短描述。 - 四条纪律由
tests/prompt-registry.test.ts强制:目录完整(新域文件必须登记)/ 双向完整(id 前缀 = 文件名域)/ id 唯一 / 短描述不照抄提示词原文;另含分层口径抽查(固定层必须排在本书层之前)。 - engine.ts 4989 → 4295 行:提示词定义与调用点全部改造完,只保留组装逻辑。
- 迁移安全性(校验脚本留在工作区):golden 字面量覆盖(基线 377 条逐字在册)+ 载荷比对(33 处「内联 → 域函数」替换的首字面量必须落在被替换区间内——抓出一个真 bug:反推大纲阶段二误调了阶段一的提示词,已修)+ 手术脚本区间重叠检查(resolve-all-first,杜绝行号漂移导致的过度删除)。
验证
- 双配置 tsc 0 错;third-party / css-vars / fallback-tiers / theme-sizes / contrast 门禁全绿。
- 新增 tests/prompt-registry.test.ts;本机 vitest 受已知 esbuild 环境问题影响,改用 node 类型剥离等价预飞(46 条登记 + 四条纪律 + 分层抽查全过),CI 上跑正式用例。
v2.2.0 — 总编台重做 + 皮肤回退到 v2.1.5
总编台重做 + 视觉皮肤整层回退到 v2.1.5。 首页从「仪表盘」改回「案头」:进度不再由五个仪表各说一遍,而是收敛成一件实物(工件池);数字只在一个地方出现;要你动手的清单独占左列,参考与日志集中到右列。皮肤按作者要求回到 v2.1.5 的样子(衬线字体栈、等圆角、无光晕与斜织纹),但保留已修掉的对比度欠账。
本版同时包含此前未发布的 2.1.6 – 2.1.11 全部内容(逐条见下方历史条目)。
一、皮肤整层回退到 v2.1.5
- 做法:从 GitHub 取 v2.1.5 的
panel.module.css作基准(6,297 行),按类名嫁接「v2.1.5 里不存在的类」的规则(231 段)→ 新文件约 7,6xx 行。v2.1.5 已有的类一律用它的值,所以观感是旧的,而 hero / 批注栏 / 侧柜快照 / 生产单队列等新组件照常渲染。 - 删掉的皮肤层:楷体标题字体栈、非对称圆角令牌、光晕 + 稿纸横线 + 斜织纹、色板重校准、案头 parity 附录 ①–⑩。
- 保留的无障碍修正:
--nf-success#4f8750(3.86:1)与--nf-warn#aa741d(3.63:1)—— v2.1.5 的原值对纸面只有约 2.3:1。 - display 字体 Georgia → 宋体优先(
Noto Serif SC / Source Han Serif SC / Songti SC / SimSun):去掉「奶油底 + 高对比衬线 + 陶土色」这组通用默认解,接回铅字 / 印刷的语感。
二、总编台重新排版
- 工件池(签名元素):一格 = 一章,色 = 状态,当前章压一道朱砂刻度(不换色,避免与语义色打架)。600 章时「一格一章」会变成噪声栅栏,改为按真实结构聚合:有卷按卷(段宽 = 该卷章数),没有卷按块聚到 ≤60 段;全书过审时收成一行事实(
600 章 · 5 卷 · 全部过审)。 - 决策条:四行网格恢复层级 —— 眉标 / 标题 23px + 主 CTA 右对齐 / 一句话原因 / 发丝线 + meta 脚注行。删掉与工件池重复的「上一章 · 现在 · 下一步」节点行,右栏只留印章 + 阶段。
- 工序轨道移到顶部:它是「整本书走到哪一步」的刻度,属顶部状态区(原来埋在批注栏下面,读起来像页脚)。
- 账目行:四个等宽指标卡 → 一行账目(发丝分隔、无卡面、tabular 数字);页脚信号条并入其中独有的三项(出错 / 质检 / 复盘钩子)—— 同一条数字不再有第二处表达。
- 两列重排:左列 = 要你动手的(作者批注栏);右列 = 参考 / 日志 / 机器(生产单 → 当前环节坐标 → 最近活动)。
- 批注栏:卡片网格 → 行列表(长度不齐的内容用网格必然凿洞),标题与说明折两行、逐条「展开 / 收起」,摘要只显示 8 条 + 「全部待办 →」。
- 面板内容溢出 356 → 307px:各列表封顶 + 内部滚动,摘要区一屏放完;需要滚动的是它下面的参考区。
三、一并修掉的静默缺陷
- CSS 花括号不平衡(1061
{/ 1060}):一个未闭合的@container把其后所有规则吞进容器查询,表现是「改了样式没反应」。 - CSS Modules 压缩会拍平后代选择器:
.parent[data-x] .child被编译成单类、祖先条件丢失 → 折叠开关形同虚设;改为属性选择器 + 由 React 决定标记。 @media (pointer: coarse)选择器被截断(:is(未闭合):44px 触屏命中区扩展从未生效。- 设计注记渲染进产品界面:页脚曾对用户显示「信号一行放完,不再摊成四张并列大卡」。
- 占位残留:「待生成 0 · 第 …」→ 现在说「全部已生成」。
- 死 CSS:删除状态池与页脚后清掉 16 条规则;删除用括号感知的解析器校验(配平、
@规则数不变、删除数吻合)。
四、新增门禁
npm run check:classes:扫 TSX 里每个css.X并在 CSS 里找定义。这类引用缺失时不报错,React 只会把undefined写进 class 属性(本项目踩过css.chWorkMainSurface)。全仓 42 个 TSX / 645 个类名全部命中。
五、验证
tsc --noEmit 0 错 · vitest 36 文件 / 462 用例全绿 · tsdown 构建通过 · check:classes / check:vars / check:fallback(0) / check:styles / check:contrast(FAIL 0) 全过 · CSS 括号平衡 1124/1124。
实机验证用作者自己的两本书:《归墟玉主》(600 章 / 5 卷 / 182.7 万字,完成态)与《我养的废柴师妹,竟是重生女帝》(1000 章 / 10 卷 / 在写,38 项待办)。
六、已知遗留
check-dead-classes49 条:多为 2.1.5 及更早的历史死类,含 CHANGELOG 里明确标注「故意保留」的panelNav / navGroup。- 两列高度差约 200px:左列内容确实比右列少一块,抹平只有「多显示几条批注」或「挪一个块过去」两条路,各有代价。
- 后代选择器风险:压缩器的拍平行为对全仓
.a .b写法都可能成立,建议后续加核对脚本,或约定样式只挂单类 / 属性。 /status的writtenAt(今日产出的数据源)需要重载插件 / 重启 dsh 才生效。
面板 UI 全量修复(`impeccable` 审查落地)。审查结论是这个面板**能用,但正在悄悄磨损作者**:辅助文字对比度不足、四套色板各说各话、九处只给两个按钮的确认框,外加一批「编译通过、运行时无提示、视觉悄悄跑偏」的失效样式。本轮系统性收掉,并给每一类装上防回归的静态检查。
面板 UI 全量修复(impeccable 审查落地)。审查结论是这个面板能用,但正在悄悄磨损作者:辅助文字对比度不足、四套色板各说各话、九处只给两个按钮的确认框,外加一批「编译通过、运行时无提示、视觉悄悄跑偏」的失效样式。本轮系统性收掉,并给每一类装上防回归的静态检查。
对比度:辅助文字从「看不清」提到 WCAG AA
--nf-text-3(meta / 时间戳 / 次要说明,全文件 28 处引用)是长时段写作里眼睛最先累的一层,原值在四种纸面上都不达标:
| 基准 | 原值 | 原对比度 | 新值 | 新对比度 |
|---|---|---|---|---|
浅色 --nf-bg |
#8f8679 |
3.24:1 | #726858 |
4.94:1 |
浅色 --nf-bg-inset |
— | 3.00:1 | — | 4.52:1 |
深色 --nf-bg |
#8a7f6c |
4.66:1 | #9d927b |
5.97:1 |
深色 --nf-bg-inset |
— | 3.90:1 | — | 5.00:1 |
与 --nf-text-2 的层级差改由字号(12 vs 14)+ 字重承担,不再单靠对比度 —— 这样提对比度不会让辅助文字变成第二主角。
新增文字版语义色层。--nf-error / --nf-success 这些是图形版(圆点、进度条填充),直接拿来写正文只有 3.2:1。新增四个同色相的 -ink 变体专供文字:浅色下 success 2.00 → 4.87、error 3.20 → 5.87、warn 1.98 → 5.35。
修掉一个会骗人的审计脚本
audit-contrast.mjs 原先把「强制明暗色板」与「跟随系统基准」合并计算 —— 后写入的值覆盖前面的,脚本量到的不是界面真实使用的色值。这就是「审计全 PASS、界面仍不达标」的成因。
排查时发现面板实际有四套并列色板,且强制色板不是基准的复刻:
| 令牌 | .view 深色 |
强制深色 |
|---|---|---|
--nf-text-3 |
#8a7f6c |
#9a917e |
--nf-accent |
#c0492d |
#a63e2a |
现拆成 4 个独立上下文 × 13 项 = 52 项检查(原先 11 套 × 6 项 = 66 项,但那是假绿灯:其中 11 套色板根本不存在)。新增的检查里就抓出强制深色的 text-3 其实也不达标(5.5:1 上下),已单独提到 6.85:1。
两条新的静态检查(防回归)
check-css-vars.mjs—— 专抓「被var()引用但从未定义」的令牌。这是最阴的一类缺陷:var(--nf-typo)不报错、不警告,页面只是「跟设计稿不一样」。本项目历史踩过三次:--nf-text-12(通知条字号全部失效,success 与 error 共用同一字号,视觉权重完全相同)、--nf-muted(TSX 侧borderColor拿到undefined,整条描边消失)、--nf-text-1(.importFileInfo颜色回落继承色)。脚本会 strip 注释、跳过模板字符串拼接以避免误报。check-dead-classes.mjs—— 死类名检测,保守策略,仅报告不删。
两者均已接入 npm run check:vars 与 CI。
组件层:三个出口统一
| 新组件 | 取代 | 关键设计 |
|---|---|---|
NoticeBar |
4 个视图各写一套的内联错误态 | 四态;error 走 role="alert" + aria-live="assertive";支持 detail(原因)+ onRetry(原地重试)—— 错误态从死胡同变可恢复 |
ConfirmDialog |
9 处 window.confirm |
见下 |
CardHead |
19 处手写区块页头(内联 8 种变体) | 标题层级 / 对齐 / 换行策略只剩一处定义 |
Sparkline |
—— | 让「全书字数」有形状,见下 |
ConfirmDialog 的关键决策:动作以 onConfirm 回调传入,而不是「先 resolve(true) 再由调用方自己跑」。后者在执行失败时会退化成「关掉弹窗 + 一句红字」,前者让弹窗留在原地显示错误 + 重试按钮。同时给不可逆操作加了 effects(具体后果清单)、assurance(可逆性说明)、requirePhrase(逐字输入确认词)三段显式信息 —— 原生 window.confirm 的问题不在「丑」,在它只给两个按钮和一句问话,「重置项目?」既没说会丢什么,也没说能不能撤销,作者只能盲点确定。
Sparkline 挂在总编台的「全书字数」卡上。作者看到「87,431 字」无法判断是均匀分布还是前五章写了六成、中间塌了十章;132×26 的柱状走势让节奏一眼可见,而不必点进任何视图。纯装饰(aria-hidden),真数据在相邻 <b> 里由读屏播报。
图标语言统一
顶栏 5 个 Unicode 字形(▣ ✎ ❖ ▦ ⚙)与抽屉标题 15 个(✎❖▦⚙⌘⚲⧗◠✻⌗…)全部换成 lucide。这串字形在无 ligature 的字体里退化成豆腐块,且与项目其他地方不是一套视觉语言。顶栏同时按作者真实的创作推进顺序重排(总编台 → 总纲 → 设定 → 写作 → 资产 → 生产单 → 长线),而非原先文件夹式的功能序 —— 点「写作」时作者还不知道该先做什么。
收编内联样式
style={{ 从 694 → 628:43 处两端对齐行收成 .rowBetween、18 处纯冗余内联删除(值与 .row 默认完全相同)、8 处 flex-end 收成 .rowEnd。
密度档此前只覆盖内距与控件高度,字号与行高纹丝不动 —— 于是「紧凑模式」等于「控件更小但字一样大、行一样松」,切了档几乎无感。现补齐字号 / 行高 / 圆角,让三档之间真有信息密度梯度。
进度条动画从 width 改为 transform: scaleX:width 每帧触发 layout,scaleX 只走合成层。(panelNav / pillNavSlider 是真实尺寸动画,刻意保留。)
两条设计红线(写进注释防复发)
- 不用 3px 单侧色条。通知条、引用块、分析条的严重度改由「底色 + 图标 + 文字色 +
color-mix描边」四层承担。原因是静态检测器把「单侧 3~4px 色条」判为 AI 生成界面的典型特征 —— 我第一版写了 3px 左边条,检测器的side-tab命中数立刻从 2 涨到 4。 - 确认类交互不用 overshoot 回弹。项目原有的入场动画 keyframes 带
cubic-beziery > 1.12 的回弹,用在「你要丢掉十章稿子」这种时刻显得轻佻。确认弹窗因此单独写了@keyframes nfConfirmIn(纯淡入上浮)。
踩坑记录(避免重犯)
- 批量删死类名时删错了。
.tab/.runChip是被[data-active]/[data-status]驱动的组合基类,一个类名对应 4 个规则块(主规则 +:hover+[data-attr]+::after)。按「类名块」批量删会把后续块孤立删掉。已git checkout回滚重做,这两个类名现在正确保留着。 - CRLF 骗了我两次。
panel.module.css工作区是 CRLF、git 索引是 LF(core.autocrlf)。第一次只查工作区就误判「CSS 是 LF」;第二次看到git checkout报Updated 0 paths却以为已回滚。判断行尾要查 HEAD 版本 + checkout 后两处。 WORKBENCH_NAV声明位置。数组定义在openDrawer之后,但依赖的bible/pendingCount/foreshadows/reviewPendingCount都在 2000 行后才声明 —— 6 个 TS2448/TS2454。
维护工具
scripts/replay/replay-tokens.py —— 按选择器定位四套色板、逐条断言命中数、原子写入、幂等。新增颜色令牌必须四处同步,靠人肉复制粘贴迟早漏一处;这个脚本把「漏一处 = 切主题后颜色不对」这类只在运行时才看得见的 bug 变成编译期失败。
验证
tsc --noEmit0 错;tsdown构建通过(682 kB ESM / 1.82 MB CJS)。check-css-varsPASS;check-theme-sizesPASS;audit-contrast52 项全 PASS,FAIL 0。window.confirm真实调用 0 处(残留命中全是解释为何替换的注释)。npm test本机已跑通:33 文件 / 436 用例全绿。(先前记录的esbuild: Cannot read file "package.json": winapi error #5是当时的子进程权限问题,已不复现;类型正确性另有tsc --noEmit。)- 补修一处密度档失效:新增的
--nf-fs-13: 13px是非规范档位(规范档位是 10/12/14/16…,数字即标准档 px)。它不随密度档重映射 —— 「紧凑模式」在它身上失效 —— 且让check-fallback-tiers以 8 处 fallback 不匹配阻断发布。已全部改用--nf-fs-14(14px,紧凑档下本就是 13px),并同步两处 TSX 与scripts/replay/维护源(否则下次重放会把 bug 带回来)。同时补上check:fallbacknpm 脚本。 - 补修一处构建阻断:
Sparkline.tsx里未使用的局部变量peak在tsconfig.build.json(noUnusedLocals: true)下报 TS6133,导致pnpm build失败(tsc --noEmit用的是基础 tsconfig,所以此前没暴露)。已删除该死变量并同步注释 —— 发布前务必以pnpm build为准,不能只看 typecheck。 - 经验:发布门禁要以
pnpm release的脚本链为准 —— 逐条手跑容易漏(本轮就漏了check-fallback-tiers,直到 release 才暴露)。
两件事:修掉一条**规则数据**的范畴错误,以及新增**风格漂移曲线**。
两件事:修掉一条规则数据的范畴错误,以及新增风格漂移曲线。
规则数据修正:「句式重复率偏高」
这条规则的 detectPatterns 是 ['首先','然后','接着','最后'],但 avoid 说的是「连续使用同构句式」—— 把单独出现的「然后/最后」也算命中。实测它们覆盖 70.1% / 72.3% 的已发布章节,于是这条规则在 94.4% 的章上误报(每章都在告诉模型「你违反了这条规则」)。
现收窄为只留「首先」(覆盖率 0.000,才是机械序列真正可信的标记);「比率偏高」那一面本来就该由扫描器的 sentenceRepetitionRate 度量,不该靠词表。
新增:风格漂移曲线(工具 → 风格漂移曲线)
- 新增
GET /api/dsh-novel-forge/style-history:逐章风格指标 + 参照基线。数据即时算、不落盘 —— 数据源就是章节文件本身,存一份快照只会带来陈旧问题。 - 新增界面
StyleView:折线=逐章实际,虚线=基线,红点=该指标超出容忍度的章;可切换句均长 / 短句占比 / 对话占比 / 段落均长。复用既有 CSS 类与 SVG 画法,不引新依赖、不新增样式类。 - 基线与审稿同源(同一份
styleBaseline)—— 若各画各的,作者会看到两个互相矛盾的结论。为此把它从styleFidelityBlock里抽成了具名导出。 - 只画四项指标:句长起伏与意象密度是诊断用的,画成曲线反而难读。
验证
- 新增
tests/style-targets.test.ts3 条,锁住生成端的目标注入(此前只在renderStyleAssets里接了一行,属于「接了线没验」):带样本时注入数字区间、无样本时不注入、样本过短时也不注入。 - 测试 433 → 436;typecheck / 主题尺寸 / 来源卫生 / 对比度 / 构建 全绿。
- 实机验证(重载插件后进行):
GET /api/dsh-novel-forge/style-history对验证书返回 200,六章的偏离与设计意图逐条吻合(对照章不报警、第 3 章三项 ↑、第 4 章段落整齐、第 5/6 章短句与起伏);基线正确走兜底路径「绑定写法资产的样本文本」(该验证书没有已过审章节)。- 界面(折线 / 基线虚线 / 红点 / 切换指标)经作者确认正常。
- 记一笔操作事实:宿主端代码在 dsh 进程里是启动时载入的,所以这类改动要重载插件或重启 dsh 才会在已打开的会话里生效 —— 发布不等于生效。
写法资产从「只说不用」变成可度量:新增**确定性风格指纹**,同一套指标给生成端当数字目标、给审稿端做符合度比对。
写法资产从「只说不用」变成可度量:新增确定性风格指纹,同一套指标给生成端当数字目标、给审稿端做符合度比对。
背景:写法资产此前只有「注入」,没有「校验」
写法资产(StyleAsset)是一堆由 LLM 写出来的文字规则 —— proseRules / dialogueRules / descriptionRules。生成时整段注入,润色时要求「不得丢失」,但全书没有一处检查成稿是否符合:规则是定性描述,模型每次理解都可能不同,作者也无从判断「这一章偏离了多少」。
新增 src/style-fingerprint.ts(零依赖)
- 指标:句均长、句长起伏(CV)、短句占比、对话占比、段落均长、意象密度
- 参照物取自写法资产已持久化的
sourceText(提取时存下样本前 3000 字),所以不必改数据结构、也不必再让模型「描述一遍风格」 - 与 AI 味扫描共用段落/引号口径(从
ai-scan.ts引入),不再出现第二份拷贝
同一套指标,两头用
- 生成端:写法资产块里追加数字区间(如「句均长 11.6–28.4 字」)。「句式凝练」这种定性要求无法执行,区间可以。
- 审稿端:新增「风格指纹比对(确定性事实)」区块,列出超出容忍度的指标与幅度;审稿提示词加一条——据此判定,比对说没有显著偏差时不要报风格问题。
容忍度是标定出来的,不是拍的(第一版我错了)
第一版按「句均长 ±25%、对话占比 ±50%」手工定阈值,实测结果是 98% 的章都被判偏离 —— 与刚修好的 AI 味扫描是同一个病:阈值没经过真实分布标定就等于噪声。用 6 本书 / 1279 章标定「同书各章相对样本」的真实偏差分布后:
| 指标 | 实测偏差 p50 | 实测偏差 p90 | 第一版容忍度 | 标定后 |
|---|---|---|---|---|
| 对话占比 | 0.539 | 1.250 | 0.50(太紧) | 1.25 |
| 句长起伏 | 0.067 | 0.159 | 0.35(太松) | 0.16 |
| 短句占比 | 0.226 | 0.655 | 0.35(太紧) | 0.66 |
| 段落均长 | 0.250 | 0.579 | 0.35(太紧) | 0.58 |
| 句均长 | 0.178 | 0.419 | 0.25(太紧) | 0.42 |
| 意象密度 | 1.000 | 1.000 | 0.50(退化) | 绝对阈值 1.39 |
- 意象密度必须用绝对阈值:实测同一本书里多数章节根本没有意象标记词,相对偏差恒为 -100%,完全退化。
- 标定后:偏离 ≥1 项的章从 98% 降到 34.3%,单项命中率 9.5–9.9%(这正是 p90 的定义)。
参照物改稳:本书已过审章节的中位数 + 绝对下限
初版参照物是单一样本(写法资产 sourceText)——样本自身长什么样会整个混进比对。现改为:
- 优先用本书已过审章节的中位数(等距抽样最多 12 章;不足 3 章时退回单一样本)。用中位数而非平均值:单章指标会被极端章拉偏。
- 等距抽样,不取最近 N 章:实测「最近 12 章」在《还债疯了》上把段落均长的误报从 33 抬到 98 —— 最近那一卷的行文与全书不同时,中位数就不代表全书。
- 补绝对下限:参照值接近 0 时相对容忍度会退化。实测《归墟玉主》的中位数基线对话占比约 0.5%,一章写到 3% 就是「+500%」,误报从 115 章涨到 175 章;而 2.5 个百分点在创作上毫无意义。现对参照值可能接近 0 的指标(两个比率 + 意象密度)叠加实测 p90 的绝对下限。
四本书实测(偏离 ≥1 项的章占比):
| 书 | 单一样本 | 中位数基线 + 绝对下限 |
|---|---|---|
| 还债疯了(236 章) | 44% | 22% |
| 归墟玉主(600 章) | 24% | 18% |
| “救”命钱(12 章) | 67% | 33% |
| 我养的废柴师妹(294 章) | 25% | 12% |
- 意象密度阈值仍刻意保守:它的参照值经常是 0(多数章本就没有意象标记词),此时整项不参与比对。
验证书:自建回归样本(下面三个 bug 就是它当场抓出来的)
在 H:/novels/_验证书_风格指纹 建了一本 6 章的验证书,每章刻意带一种已知缺陷,并给第 1 章绑写法资产当参照:
| 章 | 设计意图 |
|---|---|
| 1 雨夜来客 | 样本本身(其余章都跟它比) |
| 2 匣中之物 | 对照:风格接近样本,不该报警 |
| 3 长夜独坐 | 句均长↑、几乎无对话 |
| 4 更鼓 | 段落长度完全一致(14 段等长) |
| 5 旧账 | 套话密度高(难以言喻 / 不由得 / 生活就是 / 归根结底 …) |
| 6 天亮之前 | 连续 6 段解释性叙事(原来 / 因为 / 所以 / 因此 / 于是 / 也就是说) |
首轮跑出三个真 bug,全部修掉:
- 本章过短时给出一整片假偏离:不足 200 字(统计下限)的章,指纹被整体归零,却被报成「句均长↓-100%、段落均长↓-100%…」。现改为跳过比对(
actual.sentences === 0直接返回空)。 - 生成端目标出现负下限:「对话占比 −3.3%–29.5%」—— 参照 13% × (1 − 1.25) 算出来是负数,写进提示词是荒谬的。现下限夹到 0。
- 绝对阈值指标给出「0.00–0.00」:意象密度参照为 0 时,相对公式等于告诉模型「请写 0 个意象」。现该指标参照为 0 时不出这一行,非 0 时给绝对带宽。
修后复跑,六章逐一对上设计意图(第 3 章句均长↑412%、第 4 章段落过于整齐、第 5 章 AI 味 55/100 + 规则命中 2 条、第 6 章连续解说 6 段),且对照章(第 2 章)不报警。
验证
- 新增
tests/style-fingerprint.test.ts20 条:确定性、口径共用(全角弯引号)、过短文本返回零值、参照值为 0 时跳过(避免无穷大)、绝对阈值门槛、「没有显著偏差时必须明确写出来」(防模型为凑维度编「文风不符」),以及上述三个 bug 的回归。 - 测试 408 → 428;typecheck / 主题尺寸 / 来源卫生 / 对比度 / 构建 全绿。
- 真实语料回归:1279 章,标定前后对比见上表。
本地 AI 味扫描器(确定性事实锚点)的度量口径修复:**三条实测缺陷**,外加两条基线问题。扫描结果会被塞进审稿提示词,并明确告诉模型「你只需复核判断,不必再逐字统计」——所以它报错比不报更糟:模型会把错数据当事实去推理。
本地 AI 味扫描器(确定性事实锚点)的度量口径修复:三条实测缺陷,外加两条基线问题。扫描结果会被塞进审稿提示词,并明确告诉模型「你只需复核判断,不必再逐字统计」——所以它报错比不报更糟:模型会把错数据当事实去推理。
标定语料:1279 章 / 6 本已发布书稿。
修掉的三条实测缺陷
- 段落模型与真实书稿不符(P0):书稿正文的自然段由单换行分隔(
run.ts写盘即如此),而扫描器按/\n{2,}/切——1279 章无一例外只切出 2 段。后果是段落方差、过长段、连续解说段三项指标全部失效,其中「连续解说段 ≥ 3」永不成立、「+15 段落过于整齐」因段数门槛永不触发。现按/\n+/切并剔除标题行,实测每章 p50 = 68 段(p5 35 / p95 162),段落均长 p50 = 50 字。 - 对话引号漏掉全角弯引号(P0):语料 6 本里 5 本用
“”,而字符类只认 ASCII 双引号与角括号。后果是这些书的对话占比恒为 0,并因此每章误加 10 分。修后对话占比中位 0.00 → 0.14;判「对话过低」的章从 1265/1279(99%)降到 300(23.5%)。 - 段落整齐度用了方差(量纲错误):方差量纲是「字符²」,随段长平方缩放,段落均长 50 与均长 500 的章用同一个绝对阈值不可比。改用变异系数 CV = σ/μ,阈值 0.45(低于语料 p5 = 0.55,误报率 0.6%)。注意实现里有个反向陷阱:守卫不能写成
cv > 0,因为段落长度完全一致时 CV 恰为 0,那正是要抓的情况(已写成测试守住)。
顺带修掉的两条基线问题
- 套话按稀有度加权,不再分「重度/轻度」两档。实测「知道」覆盖 78.4% 的已发布章节、「微微」48.2%,而「难以言喻」0.0%、「不由得」0.1%——把前者与后者等同计分,会让所有正常章节的基线虚高。现按覆盖率给权重(≥50% 记 0,即视为功能词;0% 记 1),覆盖率表随代码发布、可复算。
- 句首重复率的阈值形同虚设:中文「句首二字」天然高频(语料 p5 0.16 / p50 0.37 / p95 0.56),而旧阈值是
> 0.15——几乎每章都命中,且min(15, rate*30)让大多数章固定拿约 11 分。现改为以语料 p50(0.37)为零点、p99(0.63)为满分的线性映射。
行为变化(升级前必读)
- 分数刻度变了,新旧不可比:旧口径对已发布语料的中位分是 53、94% 的章 ≥ 40 分(等于没有区分度);新口径中位 16、p95 38、仅 4% ≥ 40。历史审稿记录里存的
aiFlavor是旧刻度,不要与新章节直接比较。 - 短章不再爆炸:密度分母取
max(字数, 2000)。此前同样「1 次套话」,在 444 字的章值 18 分、在 3576 字的章值 2.2 分(同一现象 8 倍权重差)。 - 新增
scoreParts(分项得分)与paragraphCount:用于回答「这章凭什么这么多分」,也是标定与单测的抓手。分项之和等于总分,已用测试锁住。
验证
- 新增
tests/ai-scan.test.ts21 条,每条对应上面一个实测缺陷(含 CV=0 的反向陷阱、全角引号、长度归一下限、权重 0/1、空文本健壮性)。 - 前后对比用的是真实旧实现(
git show HEAD:src/ai-scan.ts)与新实现跑同一批 1279 章,不是凭感觉。 - 测试 377 → 398(+21);typecheck / 主题尺寸 / 来源卫生 / 对比度 / 构建 全绿。
第二批:让规则真正驱动确定性扫描
- 规则的
detectPatterns从「只用于 UI 显示」变成真正的检测依据:scanAiFlavor(text, rules)接收作者生效的规则,逐条给出确定性命中(ruleHits)并写进注入审稿的摘要。审稿提示词第 7 条随之改写——从「逐条核对下方清单」改为「以本地确定性命中为事实依据;未列出的说明本章没有命中,不必凭印象补报」,既省 token 又更稳。 - 度量与规则刻意解耦:
aiScore是独立标定的测量(用户删掉一条规则,并不会让正文的 AI 味变少),规则命中另走ruleHits。两者都注入审稿,但互不污染。 - 模式表扩为 63 个模式的并集 = 规则声明的 42 个
detectPatterns+ 度量基线词表。因此「生活就是 / 归根结底 / 人总要学会」这类结构性升华句(语料覆盖率 0.000)终于被纳入——原先的实现根本没在看它们。 - 加了一道区分度门槛(覆盖率 < 10% 才算命中证据)。这门槛是实测逼出来的:「句式重复率偏高」这条规则的
detectPatterns是['首先','然后','接着','最后'],而「最后」覆盖 72.3%、「然后」覆盖 70.1% 的已发布章节——不设门槛时它会在 94.4% 的章上命中,等于每章都告诉模型「你违反了这条规则」,比不报还糟。
| 规则命中(1279 章) | 加门槛前 | 加门槛后 |
|---|---|---|
| 命中 ≥1 条规则的章 | 1264(98.8%) | 384(30.0%) |
| 平均每章命中条数 | 1.96 | 0.41 |
| 「AI 高频套话」 | 896(70.1%) | 132(10.3%) |
| 「句式重复率偏高」 | 1208(94.4%) | 0(该规则本质是比率,应由 sentenceRepetitionRate 度量) |
留一个规则数据问题给作者(未擅自改)
- 「句式重复率偏高」用一串连接词(
首先/然后/接着/最后)当命中模式是范畴错误:它描述的是比率,不是词表。现由区分度门槛兜住(实际上不再命中),但规则的detectPatterns本身建议改掉。
工作台仪表盘的两个**互相独立**的布局缺陷:断点测错了参照物(窄容器不收栏),以及页面被 flex 压扁且无法滚动(看不全 + 没有滚动条)。
工作台仪表盘的两个互相独立的布局缺陷:断点测错了参照物(窄容器不收栏),以及页面被 flex 压扁且无法滚动(看不全 + 没有滚动条)。
缺陷一:断点测的是视口,不是容器
- 症状:容器变窄时仪表盘不收栏。实测窗口 1386px、容器 760px:
.wfBody仍是219px 380px两栏——右侧固定 380px 不让步,左栏被压到 219px,「规划全书卷结构」的说明一个字一行竖着排,工序轨道挤成「卷…/章…」。 - 根因:面板停靠在宿主的会话列里,宽度由列决定而非浏览器视口;而
.wfBody的断点@media (max-width: 1080px)测的是视口(1386px,没变),所以永远命中不了窄档。不是 2.1.0 / 2.0.0 引入的,是这套断点从一开始参照物就错了。 - 修法:给面板根
.view声明container-type: inline-size; container-name: nfpanel,把面板内部 12 处宽度断点由@media改为@container nfpanel:仪表盘 8 处(.wfBody/.wfColB/.wfMetrics/.wfRail/.wfCard2/.wfDrawerList/.wfBook/ 顶栏窄档)+ 书架指标 / 章节计划 / 张力 / 章节工作台 4 处。面板现在按自身宽度分档,与窗口大小解耦。
缺陷二:页面被 flex 压扁,多出的内容被自身裁掉且无法滚动
- 症状:进入工作台后仪表盘下面一截看不到,且没有任何滚动条。
- 实测:窗口 1386×810 时
.panelContent可视高 659px,而.wfStage只有 619px 高、内容却有 1014px —— 396px 被静默裁掉;panelContent.scrollHeight === clientHeight,所以永远不出现滚动条。 - 根因:
.panelContent是display: flex; flex-direction: column的滚动容器,.wfStage用默认flex-shrink: 1被压到容器高度;而.wfStage为圆角裁切带overflow: hidden—— flex 项一旦overflow非visible,其自动最小尺寸归零,于是它既不撑高自身、也不向父容器产生溢出。两条独立路径同样能修(panelContent改display:block、或.wfStage改overflow:visible),互相印证了机制。 - 修法:
.panelContent > * { flex-shrink: 0 }—— 滚动容器里的页面根按内容取高,滚动交还给.panelContent。 - 复验:
.wfStage619 → 1015px;.panelContent变为可滚(maxScroll 396,实测滚到 397,能到底);右侧滚动条出现;原先被裁的「本书资料侧柜」「当前环节坐标」可见。
复验(真实渲染 · 无头 Chromium + CDP,逐档改容器宽)
- 断点:1106px → 两栏(桌面档保留,零视觉回归);970 / 944 / 870 / 844 / 800 / 764px → 单栏;≤760px → 指标行 2 列 + 工序轨道 3 格;全程无横向滚动(
scrollWidth === clientWidth) - 门禁:typecheck / 主题尺寸 / 来源卫生 / 对比度 / 构建 / 测试(30 文件 377 用例)全绿
- 顺带确认:
container-type的 containment 未改变两个全屏遮罩(导入 / 设置)的包含块——实测position:fixed遮罩仍是 1386×773(视口),而.view只有 1106 宽,弹窗行为不变。 - 工具侧:本机
dsh-builtin-browser的 Electron 二进制从未安装成功(electron@44.5.1有包无dist//path.txt),已用ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/补装,browser_*工具恢复可用(直连 GitHub 下载会卡死:Node 不走本机代理)。