Skip to content

Releases

修复批次:20+ 处静默缺陷(配置 / 路由 / 导出 / 资产 / 张力 / 时间线 / AI 味)+ 三块重构

Choose a tag to compare

@watersxya watersxya released this 17 Oct 13:06

修复批次: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 --noEmit 0 错;vitest 41 个文件 / 524 用例全过;pnpm build 成功(lib/index.js 733 KB、lib/client.js 1.95 MB)。
  • 重启后实测:/status 200 且新配置字段生效;作者资产连加两条 id 非空且不再覆盖;张力设目标 50 时参考值回落到 50、清除后归空;面板各视图正常渲染,服务端日志无 novel-forge 报错。

取名器 + 风格目标卡:两块「资产/学习型」能力落地(对标社区头部竞品后的填空)

Choose a tag to compare

@watersxya watersxya released this 17 Oct 08:01

取名器 + 风格目标卡:两块「资产/学习型」能力落地(对标社区头部竞品后的填空)

本版补上竞品普查(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,补齐分发署名;换证不涉代码逻辑

Choose a tag to compare

@watersxya watersxya released this 17 Oct 06:12

许可证切换为 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 行

Choose a tag to compare

@watersxya watersxya released this 17 Oct 05:06

提示词资产化:三层组装(缓存命中)+ 按域拆分注册表,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

Choose a tag to compare

@watersxya watersxya released this 05 Oct 17:32

总编台重做 + 视觉皮肤整层回退到 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:各列表封顶 + 内部滚动,摘要区一屏放完;需要滚动的是它下面的参考区。

三、一并修掉的静默缺陷

  1. CSS 花括号不平衡(1061 { / 1060 }):一个未闭合的 @container 把其后所有规则吞进容器查询,表现是「改了样式没反应」。
  2. CSS Modules 压缩会拍平后代选择器:.parent[data-x] .child 被编译成单类、祖先条件丢失 → 折叠开关形同虚设;改为属性选择器 + 由 React 决定标记。
  3. @media (pointer: coarse) 选择器被截断(:is( 未闭合):44px 触屏命中区扩展从未生效。
  4. 设计注记渲染进产品界面:页脚曾对用户显示「信号一行放完,不再摊成四张并列大卡」。
  5. 占位残留:「待生成 0 · 第 …」→ 现在说「全部已生成」。
  6. 死 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-classes 49 条:多为 2.1.5 及更早的历史死类,含 CHANGELOG 里明确标注「故意保留」的 panelNav / navGroup。
  • 两列高度差约 200px:左列内容确实比右列少一块,抹平只有「多显示几条批注」或「挪一个块过去」两条路,各有代价。
  • 后代选择器风险:压缩器的拍平行为对全仓 .a .b 写法都可能成立,建议后续加核对脚本,或约定样式只挂单类 / 属性。
  • /status 的 writtenAt(今日产出的数据源)需要重载插件 / 重启 dsh 才生效。

面板 UI 全量修复(`impeccable` 审查落地)。审查结论是这个面板**能用,但正在悄悄磨损作者**:辅助文字对比度不足、四套色板各说各话、九处只给两个按钮的确认框,外加一批「编译通过、运行时无提示、视觉悄悄跑偏」的失效样式。本轮系统性收掉,并给每一类装上防回归的静态检查。

@watersxya watersxya released this 04 Oct 21:38

面板 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 是真实尺寸动画,刻意保留。)

两条设计红线(写进注释防复发)

  1. 不用 3px 单侧色条。通知条、引用块、分析条的严重度改由「底色 + 图标 + 文字色 + color-mix 描边」四层承担。原因是静态检测器把「单侧 3~4px 色条」判为 AI 生成界面的典型特征 —— 我第一版写了 3px 左边条,检测器的 side-tab 命中数立刻从 2 涨到 4。
  2. 确认类交互不用 overshoot 回弹。项目原有的入场动画 keyframes 带 cubic-bezier y > 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 --noEmit 0 错;tsdown 构建通过(682 kB ESM / 1.82 MB CJS)。
  • check-css-vars PASS;check-theme-sizes PASS;audit-contrast 52 项全 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:fallback npm 脚本。
  • 补修一处构建阻断:Sparkline.tsx 里未使用的局部变量 peak 在 tsconfig.build.json(noUnusedLocals: true)下报 TS6133,导致 pnpm build 失败(tsc --noEmit 用的是基础 tsconfig,所以此前没暴露)。已删除该死变量并同步注释 —— 发布前务必以 pnpm build 为准,不能只看 typecheck。
  • 经验:发布门禁要以 pnpm release 的脚本链为准 —— 逐条手跑容易漏(本轮就漏了 check-fallback-tiers,直到 release 才暴露)。

两件事:修掉一条**规则数据**的范畴错误,以及新增**风格漂移曲线**。

Choose a tag to compare

@watersxya watersxya released this 03 Oct 17:33

两件事:修掉一条规则数据的范畴错误,以及新增风格漂移曲线。

规则数据修正:「句式重复率偏高」

这条规则的 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.ts 3 条,锁住生成端的目标注入(此前只在 renderStyleAssets 里接了一行,属于「接了线没验」):带样本时注入数字区间、无样本时不注入、样本过短时也不注入。
  • 测试 433 → 436;typecheck / 主题尺寸 / 来源卫生 / 对比度 / 构建 全绿。
  • 实机验证(重载插件后进行):
    • GET /api/dsh-novel-forge/style-history 对验证书返回 200,六章的偏离与设计意图逐条吻合(对照章不报警、第 3 章三项 ↑、第 4 章段落整齐、第 5/6 章短句与起伏);基线正确走兜底路径「绑定写法资产的样本文本」(该验证书没有已过审章节)。
    • 界面(折线 / 基线虚线 / 红点 / 切换指标)经作者确认正常。
  • 记一笔操作事实:宿主端代码在 dsh 进程里是启动时载入的,所以这类改动要重载插件或重启 dsh 才会在已打开的会话里生效 —— 发布不等于生效。

写法资产从「只说不用」变成可度量:新增**确定性风格指纹**,同一套指标给生成端当数字目标、给审稿端做符合度比对。

Choose a tag to compare

@watersxya watersxya released this 03 Oct 17:16

写法资产从「只说不用」变成可度量:新增确定性风格指纹,同一套指标给生成端当数字目标、给审稿端做符合度比对。

背景:写法资产此前只有「注入」,没有「校验」

写法资产(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)——样本自身长什么样会整个混进比对。现改为:

  1. 优先用本书已过审章节的中位数(等距抽样最多 12 章;不足 3 章时退回单一样本)。用中位数而非平均值:单章指标会被极端章拉偏。
  2. 等距抽样,不取最近 N 章:实测「最近 12 章」在《还债疯了》上把段落均长的误报从 33 抬到 98 —— 最近那一卷的行文与全书不同时,中位数就不代表全书。
  3. 补绝对下限:参照值接近 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,全部修掉:

  1. 本章过短时给出一整片假偏离:不足 200 字(统计下限)的章,指纹被整体归零,却被报成「句均长↓-100%、段落均长↓-100%…」。现改为跳过比对(actual.sentences === 0 直接返回空)。
  2. 生成端目标出现负下限:「对话占比 −3.3%–29.5%」—— 参照 13% × (1 − 1.25) 算出来是负数,写进提示词是荒谬的。现下限夹到 0。
  3. 绝对阈值指标给出「0.00–0.00」:意象密度参照为 0 时,相对公式等于告诉模型「请写 0 个意象」。现该指标参照为 0 时不出这一行,非 0 时给绝对带宽。

修后复跑,六章逐一对上设计意图(第 3 章句均长↑412%、第 4 章段落过于整齐、第 5 章 AI 味 55/100 + 规则命中 2 条、第 6 章连续解说 6 段),且对照章(第 2 章)不报警。

验证

  • 新增 tests/style-fingerprint.test.ts 20 条:确定性、口径共用(全角弯引号)、过短文本返回零值、参照值为 0 时跳过(避免无穷大)、绝对阈值门槛、「没有显著偏差时必须明确写出来」(防模型为凑维度编「文风不符」),以及上述三个 bug 的回归。
  • 测试 408 → 428;typecheck / 主题尺寸 / 来源卫生 / 对比度 / 构建 全绿。
  • 真实语料回归:1279 章,标定前后对比见上表。

本地 AI 味扫描器(确定性事实锚点)的度量口径修复:**三条实测缺陷**,外加两条基线问题。扫描结果会被塞进审稿提示词,并明确告诉模型「你只需复核判断,不必再逐字统计」——所以它报错比不报更糟:模型会把错数据当事实去推理。

@watersxya watersxya released this 03 Oct 17:04

本地 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.ts 21 条,每条对应上面一个实测缺陷(含 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 压扁且无法滚动(看不全 + 没有滚动条)。

Choose a tag to compare

@watersxya watersxya released this 03 Oct 16:40

工作台仪表盘的两个互相独立的布局缺陷:断点测错了参照物(窄容器不收栏),以及页面被 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。
  • 复验:.wfStage 619 → 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 不走本机代理)。