Compare commits

...

26 Commits

Author SHA1 Message Date
kang
d10679ea51 修 Ch7 hint 字段 ASCII 单引号提前终止字符串 —— JS 语法错误导致 CHAPTERS 数组后续代码全部挂掉
现象:Console 报 Unexpected identifier '你是修理工吗'。
原因:Ch7(n=-7)hint 里用了 ASCII 单引号 '你是修理工吗?...' 作为内部引用,外层字符串也是 ASCII 单引号包裹——被提前终止。
修:改为中文双引号 "你是修理工吗?..."。

这才是'一直载入中'的真正原因:JS 报错 → renderChapterList/renderGallery/loadNovel 从未被执行到 → reader-content 一直停在初始的'载入中 …'。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 15:58:12 +08:00
481b47b0b2 auto-save 2026-04-18 15:54 (~1) 2026-04-18 15:55:26 +08:00
kang
33c60aa08a 修阅读'一直载入中':novel.md gzip + 章节索引 + loading 状态
问题定位:
- novel.md 285KB 线上 Content-Type 被 nginx 默认识别为 application/octet-stream,gzip_types 无法命中,未压缩直传
- 用户网络慢时全本下载数秒甚至更久,一直'载入中 …'
- 附带问题:renderNovel 的 sections forEach idx 和 CHAPTERS 数组错位——'终章之后·当世界再次变聪明'这个非章节 ## heading 被 skip 掉后,之后所有第二部章节的 CHAPTERS[idx] 索引都偏移一位,映射到错误的章节图(不崩溃但显示错)

修复:
1. Dockerfile nginx conf 加 location = /novel.md 用 default_type 'text/markdown; charset=utf-8',覆盖默认 octet-stream,让 gzip_types 命中。gzip_comp_level 6 / gzip_min_length 512 / gzip_vary on。预计 285KB → ~90KB
2. renderNovel 里 ch 的解析从 CHAPTERS[idx] 改为 CHAPTERS.find(c => c.n === chN),按语义 n 精确匹配(主部 1-20 + 0 终章 / 第二部 -1 到 -8)。不再受 section 索引漂移影响
3. loadNovel 细化 loading 文案'正在下载全本 …' → '正在渲染章节 …';await setTimeout(0) 让浏览器先 paint;失败时补兜底直链'直接打开 novel.md'
4. imgPath 从 './images/${ch.img}' 加防守 (ch && ch.img),第二部 img=null 的章节不渲染 broken img src

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 15:50:40 +08:00
15b2812507 auto-save 2026-04-18 15:48 (~2) 2026-04-18 15:48:56 +08:00
d46152ccb8 auto-save 2026-04-18 15:40 (~1) 2026-04-18 15:42:03 +08:00
kang
3e10890ed1 worklog.json auto-save tail 2026-04-18 15:40:38 +08:00
c3cacbb5a9 auto-save 2026-04-18 15:34 (~1) 2026-04-18 15:34:38 +08:00
063675e848 auto-save 2026-04-18 15:26 (~1) 2026-04-18 15:28:26 +08:00
kang
d78383e222 第二部 · 第八章《接下来这二十年,换我来写》(+3500 字) —— 全书·完
海明威式的流速压缩收束。听证会后七年的 7 个切片。

- 3 个月:v0.7 落地 34 国,主动暂停 3 次都被长期看住小组 6 小时内回应。沈陌群里只一句'它又一次把笔递了回来'
- 半年:周以谦脑梗后住院叫他去。'我最近觉得我也快看够这二十多年了。我准备把剩下那几年留给我自己和我女儿'。顾沉舟握他手说'您的部分已经够了'
- 1 年:沈陌发来女儿入学门口举着《操作系统基础 I》教材的照片。那晚他把'归墟·2038'从书桌常年摆的位置拿出来,不扔不藏,只是放回床头柜上层最深的抽屉——它回到它该回到的位置:一本一个年轻工程师在漏雨出租屋替自己写过的私人笔记
- 2 年:母亲走了。他回老屋一夜没哭(他在那碗面里哭完了)。凌晨 3 点多用旧铁锅自己煮一小碗葱花面坐母亲当年坐过的板凳慢慢吃完
- 3 年:和苏青禾回当年的城中村。'整片区域被夷平成了一块空地准备改建区级公园'。苏青禾问进去看看吗。'不用了。它已经替我走到下一步了'。没回头
- 5 年:沈陌第一次作为联盟核心评议组主理人坐在曾经属于顾沉舟的位置。他汇报完对所有人说'今天这个位置不是我赢来的。是顾先生五年前在这张桌子前自己起身让出来的'。许幼宁走廊里替他整领子:'那根刺这些年慢慢被替换成别的东西了' '比如什么' '比如还能再替这个世界多看几年的耐心'
- 7 年:71 岁他出书《那三个夜晚和之后的事》。扉页三个名字:顾沉舟·沈陌·周以谦(周以谦 5 个月前在潮屿小屋安静离开,代签)。他自己在扉页下方补写'献给那个在 2037 年冬天替我们所有人写下一句我们这一代,没看住的人'

沈陌新笔记 2062 黑硬皮寄到他家。第一页只有一行字:不是他写的——
'顾先生,接下来这二十年,换我来写。'
他把这本放在'归墟·2038'原本一直待过的位置上。

最终镜头:阳台摇椅。苏青禾端一杯茶来。远处江面一艘很晚的货船鸣汽笛——和第一部漏雨城中村凌晨四点的那一声几乎一模一样。他问她'你说我这一辈子是不是终于算替自己过完了?' 她笑:'你慢慢过吧。反正我也在。' 两只摇椅在风里一同晃。

—— 全书·完 ——

网页:CHAPTERS 加 n=-8 第 29 条(第二部·收束)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 15:23:25 +08:00
c053bfc4f4 auto-save 2026-04-18 15:20 (~1) 2026-04-18 15:21:04 +08:00
kang
fe9f24b895 第二部 · 第七章《她记得的那碗面》(+3800 字)
全书最私密的一章。他不只是古法程序员,他还是那个曾经被母亲煮过面的儿子。

核心设定:
- 苏青禾轻描淡写抛出:妈养老院打电话说这一个月她一直在问——她以为你还在读高三,要给你煮面
- 顾沉舟大半年没真正陪母亲吃过一顿饭。订早班高铁回北方小城
- 护工提醒:阿姨这两年基本认不出家里人,但对几个旧场景记得特别清楚

养老院戏:
- 母亲 79 岁白发酱红色罩衫,藤椅,搪瓷杯磕掉一小块漆
- 她抬头看他笑一下很空:'小伙子你是修理工吗?我家那台老电视机最近总是雪花'
- 他本能想纠正('妈,是我,阿舟'),嘴边话到一半忽然想起潮屿阳台上周以谦的话'不是所有人都需要被带回舞台。也可以是——让他们待在自己现在选好的位置上'
- 他对母亲笑:'是。我是修理工。'
- 她非常高兴地讲他小时候——讲'我家那孩子'——盯着蚂蚁看一下午,拆爸爸计算器,高三数学模考考砸那碗葱花挂面'放一点点猪油撒多一点点葱花,那孩子小时候就爱吃这种面'
- '这孩子后来啊没读最赚钱的专业,他爸当时不太高兴。可是我觉得——那孩子自己喜欢就让他去弄吧。就是他那个专业后来没让他过上很好的日子。他吃了不少苦。小伙子我偶尔会想是不是当年我们做爹妈的多劝他两句他就不至于这么苦了'
- 他只稳着声音说:'那孩子现在过得挺好的。您放心'——她怔了七八秒,拍了拍他的手背'嗯。这样就好'

临别那一瞬清醒(全章最重):
- 母亲眼神清了一下,很轻声叫'……阿舟?'
- 他胸口顿一下,没说'妈是我',没解释,没补述
- 只是走过去弯下腰把母亲那只很凉的手轻轻握进自己手里手心对手心
- 20 秒后她又忘了:'小伙子你是修理工吗?我家那台老电视机最近总是雪花'
- 他这次没笑,只轻声说'嗯'

葱花面:
- 走两条街那家小餐馆招牌画一只不精致的小碗写'葱花面'
- 他坐最靠窗那桌点一碗'多放一点葱'
- 一碗清汤两勺猪油撒一层切得并不均匀的小葱
- 他很多年没吃过这样一碗面——这些年他吃日料套餐、1000 块一杯的茶、沈知意酒会半杯年份酒,全部没吃出什么味道
- 慢慢夹一口二口三口——忽然一滴水落到碗里
- 伸手摸自己脸:原来是他自己的眼泪
- '他这一生里所有的哭都是因为别人。为被辜负的关系哭,为被忽视的努力哭,为二十年没人回应的那个夜晚哭。他从来都不是在为他自己哭'
- '这一次——只有他自己坐在一家县城街边的小餐馆里一个人,吃一碗二十多年没吃过的葱花面'
- 把汤都喝了。桌边坐很久没擦脸。让眼泪自己凉下来

临走对话:
- 阿姨问'你以前吃过这家面吗?' '没吃过这家。但吃过一碗很像的' '什么时候?' '大概高三。那年我数学模考考砸了我妈给我煮了一碗'
- 阿姨朝背影轻轻说'下次再来'
- 他没回头只举手轻轻挥了挥

回家:
- 高铁上靠窗睡着——近 20 年极少数一次不靠疲惫不靠酒不靠把自己扔进座位晃
- 梦里一阵非常非常淡的葱花味道从远处飘过来
- 苏青禾洗完最后一棵青菜关水龙头转身看他一眼全都明白了
- 什么都没问牵到饭桌边'先坐下'
- 饭后沙发他把头轻轻靠到她肩上她伸手极轻摸他头顶(呼应很多年前冬天她给他盖毯子的力道)
- 很久很久她才轻声说'回来了就好'

网页:CHAPTERS 加 n=-7 第 28 条。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 15:17:20 +08:00
cf4033eb1d auto-save 2026-04-18 15:14 (~2) 2026-04-18 15:14:50 +08:00
kang
1d3034b75e 第二部 · 第六章《那封邮件的作者》(+3800 字)
Ch5 分权动作的具体化。老总监周以谦被找到。第二部命题再加一层 —— 看光也不等于把每个人拽回舞台。

核心设定:
- 联盟找到:周以谦、现年 72 岁左右、居东南沿海小城潮屿、身体大致健康、拒绝出席听证会
- 顾沉舟没告诉任何人(甚至不告诉苏青禾具体去哪),坐了 3h48min 高铁去潮屿
- 小城'渔盐三巷'一栋三层老楼三楼。敲门。老人笑一下'我就知道你会来'

阳台戏(全章最重):
- 周以谦泡茶手瘦骨节清晰,二十三年前签并购的也是这双手
- 他主动揭开:'那封邮件我后来很多年都后悔发过。不是上级该对下级说的话。那是一个认输的人在找替身'
- 自述当年 50 岁要替十一个下属发工资、女儿学费、父亲养老院——'那个代价我承担不起'
- 核心更新:'你这二十年扛的东西里有很大一部分是我那一代当年没扛完硬塞给你们这一代的'

最锋利一次翻转 —— 他拒绝上台的真正理由:
- 不是嫌远不是身体不好
- '如果我下周坐在最后一排媒体必然会找到我。第二天所有新闻标题都会写:当年那封沉默二十三年的邮件作者今天终于现身。那时候你前几天在评议桌上说的三个日期就不再是被压下的夜晚,会变成一场看我们当年那个受害者也回来了的仪式'
- '光还给我们不等于把我们拽回舞台中央。也可以是——让我们继续待在自己如今选好的位置上安安静静过完剩下的几年'
- '你去替我那一代,给下一代开一个头。不需要我这张老脸'

对顾沉舟的收束(本章最后一击):
- '这几天我在电视上看你。我发现一件事——你那根刺这次我看见你真的开始把它收起来了'
- '你收起来的那一天也是我这辈子最安心的一天。因为这说明——不是所有我们这一代没看住的东西都必须靠你一个人死扛到底'

顾沉舟的内在领悟:
- 他第一次意识到自己这一生或许从来没有真正替自己哭过
- 二十年所有熬夜所有酒所有信任测试所有过度警觉——都是一种没有哭出来的哭
- 他用了二十年才等到一个老人坐在对面告诉他'这件事可以放下了'
- 但他没哭。只是把凉茶一口喝完

临别:
- 巷口海风大老人白发被吹起一点点:'小顾你走吧不用挥手。你再往前走一点就替我多看几年'
- 顾沉舟不回头

高铁夜车:
- 他发苏青禾'那个邮件的作者见到我笑了一下说我就知道你会来'
- 她问'那你现在心里是什么感觉'
- 他打很多字又删掉,最终发一句:'第一次觉得这一代人真的在告别'
- 她回两个字:'回来'
- 长隧道黑玻璃倒影:64 岁半白瘦眼神最清
- 想起刚毕业 22 岁在招聘会角落看镜子的那个年轻人
- 今天那个自己坐在夜行高铁里替整代看过同一场黑夜的人完成了一次告别
- 家里有人替他留着一盏灯

网页:CHAPTERS 加 n=-6 第 27 条。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 15:10:53 +08:00
47b480b2a9 auto-save 2026-04-18 15:08 (~1) 2026-04-18 15:09:00 +08:00
kang
1f3126d251 第二部 · 第五章《让光回到所有看见的人身上》(+4500 字)
审查会高潮。既不赞同也不否决,把权力中心从自己身上分散出去——第二部命题收束到一个极克制但极锋利的答案。

现场动作:
- 他没打开 PPT,没念准备好的意见,桌上磨破的帆布包里一本一本拿出三样东西
- 归墟·2038(他自己 2037 年那本)/ 沈陌黑色 2037 年笔记 / 许幼宁 86 次攻防 0 成功的牛皮纸信封——一字排开
- 所有镜头从他身上被三件东西吸过去

他开口的第一件事:
- 我昨天把整整三天准备好的审查草稿删掉了,没有保存副本
- 三个日期:2037-11-03(他自己报告) / 2037-11-04(沈陌对照表) / 2037-12-04 02:17(老总监那封被遗忘的邮件)
- '这三个夜晚,后来被整整一代人,用了二十年去还'
- 念邮件最后一行'我们这一代,没看住'
- 核心问句:'我们这一代人,能不能不再重复那件事?'

最终评议(不是赞同,也不是否决):
- 技术层面他三天找不到根本性缺陷,但改进空间存在
- 主屏浮出'首席评议人评议:有条件通过',条件二字底下一条极细红线
- '它可以被推广。但一代人必须一起看住它'

分权行动(第二部反控制欲的具体化):
- 请许幼宁和沈陌上台,站到自己两侧
- 要求联盟用所有渠道找回那位 2037 年发邮件的老总监(若健在请旁听下周公开伦理听证会)
- 要求把当年'让他自己醒醒'的安全侧原负责人也加入长期小组——'不是追责,是让这个小组里同时有看见的人和当年没看见的人。只有这样下一个二十年黑箱才不会重新偷偷长回来'
- 关键表达:'我不是来把这套系统的命运攥在自己手上的。我是来——把光从我一个人身上,还给所有当年也看见过一件事、却被按下去的人'

收尾:
- 掌声不是雷动而是很慢很沉'一声一声散得开也压得住'持续 1 分 07 秒
- 掌声第 30 秒他已经开始收东西回帆布包
- 走出大厅下起极细极细的春雨,专车等他他说'我走一段'
- 沿着石板路走,从口袋摸出手机点开苏青禾对话框敲三个字'我回家',对方回三个字'我等你'
- 二十多年前同样的天空那次他什么都没有;今天他仍然没有完整答案,但有'让问题不再只落在他一个人肩上的夜晚'+两个战友+在家等他的人+一代终于被重新放回光里的人
- 他的背影在春雨里像很多年前那张替整座城重新亮起来的照片——只是这一次他走的方向不是总控中心,是家

第二部命题的完整回答:
- 不否掉它(承认自己二十年坚持不是普适真理)
- 不纯粹赞同(把控制权分散给战友和时代)
- 重新定义'看住'——不是否决,是长程陪伴

网页:CHAPTERS 加 n=-5 第 26 条。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 15:03:08 +08:00
e1c603ff17 auto-save 2026-04-18 15:02 (~3) 2026-04-18 15:02:58 +08:00
b23feaefd6 auto-save 2026-04-18 14:54 (~1) 2026-04-18 14:55:41 +08:00
d7378a08bd auto-save 2026-04-18 14:49 (~1) 2026-04-18 14:49:20 +08:00
cb74d3eb53 auto-save 2026-04-18 14:43 (~1) 2026-04-18 14:43:32 +08:00
1513e37458 auto-save 2026-04-18 14:37 (~1) 2026-04-18 14:37:45 +08:00
a0eb6fd3ae auto-save 2026-04-18 14:31 (~1) 2026-04-18 14:31:20 +08:00
kang
18670d2267 第二部 · 第四章《那一夜,他关掉了屏幕》(+3800 字)
审查会前最后 12 小时。沉淀章——从'他必须写下答案'到'他不再需要一个明确答案'。

核心剧情:
- 苏青禾九点半先睡,留一碗炖鸡在书房门口只说'你自己看着来'——她今晚大概率知道他不会去写'那句话'
- 他做了一件很笨的事——把沈陌 213 页黑色笔记本从第一页读到最后一页,不跳不挑
- 读到第 47 页(2038-07-14 沈陌给两岁女儿画的铅笔画+'爸爸今天没说出那句该说的话')停了一小时
- 意识到'这本笔记不是技术观察手册,是一个人怎么在我看见了却没人听的漫长孤独里把自己继续撑下去的证据'
- 凌晨 2:40 拨通沈陌电话只说'我还在读你的本子',对方只回一个'好'
- 忽然想起 23 年前那封被他遗忘的老总监邮件——查邮箱,发信日期 2037-12-04 02:17,他自己 11-03 沈陌 11-04 老总监 12-04,那个秋冬之交至少三个地方三个人几乎同时看见同一件事

关键领悟:
- 他这 20 多年一直以为自己是孤身一人扛着被忽略的警告走过来。其实不是——他只是那个被留下来替很多人继续咬着不放的人
- 他身上的刺是代价,不是美德,不是尊严,是代价
- 老总监说的是'看住'不是'否掉'——看住是长期在场,是长程陪伴,让这件事在接下来三十年五十年一百年都不至于再被任何一代人轻易松手

决定性动作:
- 按下电源键,'我 怕 的——'连同光标在一瞬间消失
- 他没写完那句话。让问题不再是一个非答不可的问题
- 直接去睡——20 年第一次不靠酒不靠熬不靠咬着牙睡
- 苏青禾 2:57 醒来愣一秒帮他掖被子

第二天早上:
- 穿深灰棉衬衫+15 年没换款式的旧黑大衣(不是定制深色西装)
- 帆布包里塞三样东西:归墟·2038 + 沈陌黑笔记 + 许幼宁 86 次攻防日志
- 早饭桌上苏青禾不问他任何关于审查会的问题
- 门口她替他整理衣领看着他眼睛'我信你' → 他低声'嗯'
- 专车驶向中央大厅,车里他闭眼回放:许幼宁那根刺的话 + 韩锐'不会再站到那个舞台上' + 沈陌最后一页 + 苏青禾'我信你' + 老总监'替我看住'

章末:他不知道一小时后自己走上审查台张开嘴第一个字会是赞同还是否决还是两者都不是。但他知道,自己今天不是一个人上去的。中央技术大厅在城市天际线慢慢浮现。

网页:CHAPTERS 加 n=-4 第 25 条。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 14:31:03 +08:00
2239b6d718 auto-save 2026-04-18 14:25 (~1) 2026-04-18 14:25:11 +08:00
kang
c11b61bfbe 第二部 · 第三章《她说她找不到一个值得攻击的地方》(+3800 字)
核心:三份证据并排摆上桌,从三个不同的人,指向同一个方向。

情节:
- 许幼宁约他到郊北靶场(她私人攻防实验室,完全离线工控机柜)
- 递来牛皮纸大信封——'86 次 - 0 成功 - 全部日志原件'
- 三个月私下攻防:反向链路污染 v7.2/深度注入对抗 v4.5/语义边界爆破 v3.1
- 她播放一段录屏——她用自己多年私藏未公开的反向同步槽位 S3-B 打了 v0.7
- 系统没封堵没反击,而是主动暂停推送结构化审查请求,把她的身份/招数/侧信道路径都列给人类审查团,甚至推测她可能是研究性质
- 她平静说:'我从业三十年,第一次遇到一个让我想保护它的系统'

揭穿戏(最锋利一笔):
- 她调出他三年前给安全核验组的'反向一致性验证框架'底稿第 27 页
- 他把'锚点签名对齐逻辑'故意写成'签名锚点对齐逻辑'——顺序颠倒,他刻意埋的信任测试点
- 她:'你问的是埋在这里的第三层签名偏差对吧?我上个月就看见了。我也故意没告诉你。我想看你什么时候自己能看到它不再需要'
- 他告诉她'我昨天把三天审查草稿全部删掉了没有备份'

2037 回响:
- 她透露二十多年前国家安全侧第二年她 28 岁,内部拿到过他的四十页风险分析报告
- 一个八人小组建议认真回复,被负责人一句'让他自己醒醒'否掉
- 他从没想过'被忽略'的那个夜晚,背后有过这么具体的人和讨论

收束三句(第二部命题最锋利表达):
- '我会替你二十年前那个没人回应的夜晚难过。但不会替你下周一那句话'
- '如果你下周一那句话还是没放下你那根刺,你对抗的就不再是时代了。是这个时代正在尝试变好的那一面'
- '从今天起,你身上那根刺,该轮到你自己决定要不要继续留着'

章末:
- 书房并排三份证据——归墟·2038 / 沈陌 2037 年黑笔记 / 许幼宁 86 次攻防日志
- 他第一次打开沈陌笔记本中间的一页:'今天顾先生那篇谈工控协议不可证伪性的论文发了。我读了三遍。我哭了一次'(2041 年)
- 屏幕上'我 怕 的——'仍然闪。但他第一次觉得,那个问题的答案原本就不在屏幕上
- 而是三本东西并排放在一起时,已经替他说出了一半

网页:CHAPTERS 加 n=-3 第 24 条。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 14:24:52 +08:00
6a4857c097 auto-save 2026-04-18 14:18 (~1) 2026-04-18 14:19:00 +08:00
kang
64b80f1f5f 网页适配第二部多章:正则支持第N章(中文/阿拉伯数字)+ CHAPTERS 加第二部 Ch2 条目
- renderNovel 第二部正则从'第一章'写死改为'第.+?章'通用捕获,映射中文数字→01/02...
- CHAPTERS 新增 n=-2 第 23 条(第二部 Ch2)
- renderChapterList 负 n 值支持'II·01 / II·02'编号

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 14:18:26 +08:00
5 changed files with 6676 additions and 15 deletions

View File

@@ -328,6 +328,188 @@
"message": "auto-save 2026-04-18 14:13 (~1)", "message": "auto-save 2026-04-18 14:13 (~1)",
"hash": "b71ec7e", "hash": "b71ec7e",
"files_changed": 1 "files_changed": 1
},
{
"ts": "2026-04-18T14:17:40+08:00",
"type": "commit",
"message": "第二部 · 第二章《他差一点把那些刺长回来》(+3800 字)",
"hash": "368fcfa",
"files_changed": 3
},
{
"ts": "2026-04-18T14:18:26+08:00",
"type": "commit",
"message": "网页适配第二部多章正则支持第N章中文/阿拉伯数字)+ CHAPTERS 加第二部 Ch2 条目",
"hash": "64b80f1",
"files_changed": 2
},
{
"ts": "2026-04-18T14:19:00+08:00",
"type": "commit",
"message": "auto-save 2026-04-18 14:18 (~1)",
"hash": "6a4857c",
"files_changed": 1
},
{
"ts": "2026-04-18T14:24:52+08:00",
"type": "commit",
"message": "第二部 · 第三章《她说她找不到一个值得攻击的地方》(+3800 字)",
"hash": "c11b61b",
"files_changed": 4
},
{
"ts": "2026-04-18T14:25:11+08:00",
"type": "commit",
"message": "auto-save 2026-04-18 14:25 (~1)",
"hash": "2239b6d",
"files_changed": 1
},
{
"ts": "2026-04-18T14:31:03+08:00",
"type": "commit",
"message": "第二部 · 第四章《那一夜,他关掉了屏幕》(+3800 字)",
"hash": "18670d2",
"files_changed": 4
},
{
"ts": "2026-04-18T14:31:20+08:00",
"type": "commit",
"message": "auto-save 2026-04-18 14:31 (~1)",
"hash": "a0eb6fd",
"files_changed": 1
},
{
"ts": "2026-04-18T14:37:45+08:00",
"type": "commit",
"message": "auto-save 2026-04-18 14:37 (~1)",
"hash": "1513e37",
"files_changed": 1
},
{
"ts": "2026-04-18T14:43:32+08:00",
"type": "commit",
"message": "auto-save 2026-04-18 14:43 (~1)",
"hash": "cb74d3e",
"files_changed": 1
},
{
"ts": "2026-04-18T14:49:20+08:00",
"type": "commit",
"message": "auto-save 2026-04-18 14:49 (~1)",
"hash": "d7378a0",
"files_changed": 1
},
{
"ts": "2026-04-18T14:55:41+08:00",
"type": "commit",
"message": "auto-save 2026-04-18 14:54 (~1)",
"hash": "b23feae",
"files_changed": 1
},
{
"ts": "2026-04-18T15:02:58+08:00",
"type": "commit",
"message": "auto-save 2026-04-18 15:02 (~3)",
"hash": "e1c603f",
"files_changed": 3
},
{
"ts": "2026-04-18T15:03:08+08:00",
"type": "commit",
"message": "第二部 · 第五章《让光回到所有看见的人身上》(+4500 字)",
"hash": "1f3126d",
"files_changed": 2
},
{
"ts": "2026-04-18T15:09:00+08:00",
"type": "commit",
"message": "auto-save 2026-04-18 15:08 (~1)",
"hash": "47b480b",
"files_changed": 1
},
{
"ts": "2026-04-18T15:10:53+08:00",
"type": "commit",
"message": "第二部 · 第六章《那封邮件的作者》(+3800 字)",
"hash": "1d3034b",
"files_changed": 4
},
{
"ts": "2026-04-18T15:14:50+08:00",
"type": "commit",
"message": "auto-save 2026-04-18 15:14 (~2)",
"hash": "cf4033e",
"files_changed": 2
},
{
"ts": "2026-04-18T15:17:20+08:00",
"type": "commit",
"message": "第二部 · 第七章《她记得的那碗面》(+3800 字)",
"hash": "fe9f24b",
"files_changed": 4
},
{
"ts": "2026-04-18T15:21:04+08:00",
"type": "commit",
"message": "auto-save 2026-04-18 15:20 (~1)",
"hash": "c053bfc",
"files_changed": 1
},
{
"ts": "2026-04-18T15:23:25+08:00",
"type": "commit",
"message": "第二部 · 第八章《接下来这二十年,换我来写》(+3500 字) —— 全书·完",
"hash": "d78383e",
"files_changed": 4
},
{
"ts": "2026-04-18T15:28:26+08:00",
"type": "commit",
"message": "auto-save 2026-04-18 15:26 (~1)",
"hash": "063675e",
"files_changed": 1
},
{
"ts": "2026-04-18T15:34:38+08:00",
"type": "commit",
"message": "auto-save 2026-04-18 15:34 (~1)",
"hash": "c3cacbb",
"files_changed": 1
},
{
"ts": "2026-04-18T15:40:38+08:00",
"type": "commit",
"message": "worklog.json auto-save tail",
"hash": "3e10890",
"files_changed": 1
},
{
"ts": "2026-04-18T15:42:03+08:00",
"type": "commit",
"message": "auto-save 2026-04-18 15:40 (~1)",
"hash": "d46152c",
"files_changed": 1
},
{
"ts": "2026-04-18T15:48:56+08:00",
"type": "commit",
"message": "auto-save 2026-04-18 15:48 (~2)",
"hash": "15b2812",
"files_changed": 2
},
{
"ts": "2026-04-18T15:50:40+08:00",
"type": "commit",
"message": "修阅读'一直载入中'novel.md gzip + 章节索引 + loading 状态",
"hash": "33c60aa",
"files_changed": 3
},
{
"ts": "2026-04-18T15:55:26+08:00",
"type": "commit",
"message": "auto-save 2026-04-18 15:54 (~1)",
"hash": "481b47b",
"files_changed": 1
} }
] ]
} }

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,13 @@ RUN printf 'server {\n\
access_log off;\n\ access_log off;\n\
}\n\ }\n\
\n\ \n\
# html / md: always revalidate\n\ # novel.md specifically — force text/markdown so gzip triggers\n\
location = /novel.md {\n\
default_type "text/markdown; charset=utf-8";\n\
add_header Cache-Control "public, max-age=0, must-revalidate";\n\
}\n\
\n\
# html / md catch-all: always revalidate\n\
location ~* \\.(html|md)$ {\n\ location ~* \\.(html|md)$ {\n\
add_header Cache-Control "public, max-age=0, must-revalidate";\n\ add_header Cache-Control "public, max-age=0, must-revalidate";\n\
}\n\ }\n\
@@ -26,8 +32,10 @@ RUN printf 'server {\n\
}\n\ }\n\
\n\ \n\
gzip on;\n\ gzip on;\n\
gzip_types text/plain text/markdown text/css application/javascript application/json image/svg+xml;\n\ gzip_comp_level 6;\n\
gzip_min_length 1024;\n\ gzip_types text/plain text/markdown text/css application/javascript application/json image/svg+xml text/html;\n\
gzip_min_length 512;\n\
gzip_vary on;\n\
}\n' > /etc/nginx/conf.d/default.conf }\n' > /etc/nginx/conf.d/default.conf
EXPOSE 80 EXPOSE 80

View File

@@ -833,6 +833,13 @@ const CHAPTERS = [
{ file: 'extra/s2_blank_second_page.jpg', cap: '我 怕 的——', sub: '第二部 · 序章 · 留白' } { file: 'extra/s2_blank_second_page.jpg', cap: '我 怕 的——', sub: '第二部 · 序章 · 留白' }
] }, ] },
{ n: -1, title: '另一个看见了的人', img: 'extra/s2_blank_second_page.jpg', volume: '第二部 · 起笔', hint: '沈陌登门:顾先生,我写到这里,就等您告诉我,下面该不该写下去' }, { n: -1, title: '另一个看见了的人', img: 'extra/s2_blank_second_page.jpg', volume: '第二部 · 起笔', hint: '沈陌登门:顾先生,我写到这里,就等您告诉我,下面该不该写下去' },
{ n: -2, title: '他差一点把那些刺长回来', img: null, volume: '第二部 · 起笔', hint: '三天读尽白皮书,差一点对一个系统动了信任测试的念头;出门去见了韩锐' },
{ n: -3, title: '她说她找不到一个值得攻击的地方', img: null, volume: '第二部 · 起笔', hint: '许幼宁 86 次攻防 0 成功。她揭穿他三年前埋的第三层签名偏差——"我想看你什么时候自己能看到它不再需要"' },
{ n: -4, title: '那一夜,他关掉了屏幕', img: null, volume: '第二部 · 起笔', hint: '审查会前夜:读完沈陌整本笔记 + 想起二十三年前老总监那封被遗忘的邮件"替我看住这套东西" + 关掉屏幕不写答案直接去睡 + 苏青禾那句"我信你"' },
{ n: -5, title: '让光回到所有看见的人身上', img: null, volume: '第二部 · 起笔', hint: '审查会现场:帆布包里三本东西摆上评议桌。有条件通过。请许幼宁+沈陌上台站到他两侧。请联盟找回那位说"让他自己醒醒"的安全侧负责人旁听。走出大厅下起春雨,他发给苏青禾三个字"我回家"' },
{ n: -6, title: '那封邮件的作者', img: null, volume: '第二部 · 起笔', hint: '老总监周以谦被找到:潮屿海边阳台,"那封邮件我后来很多年都后悔发过。那不是上级该对下级说的话。那是一个认输的人在找替身"。拒绝上台:不是所有被按下去的人都需要被带回舞台。你那根刺这次我看见你真的开始把它收起来了' },
{ n: -7, title: '她记得的那碗面', img: null, volume: '第二部 · 起笔', hint: '北方县城养老院:母亲轻度阿尔茨海默"你是修理工吗?我家那台老电视机最近总是雪花"。他不纠正她。她用讲给陌生人的语气讲他小时候。临别她清了一瞬"阿舟,你最近辛苦了吧"。他去县城街角小餐馆吃葱花挂面——这辈子第一次为自己流眼泪' },
{ n: -8, title: '接下来这二十年,换我来写', img: null, volume: '第二部 · 收束', hint: '七年流速切片v0.7 全球落地 / 周以谦病了看够了 / 沈陌女儿入学挑《操作系统基础 I》/ 母亲走了他在老屋自己煮一碗葱花面 / 城中村被拆成空地他没进去 / 沈陌接替他坐上评议席 / 71 岁出书《那三个夜晚和之后的事》扉页三个名字 / 沈陌新笔记第一页"接下来这二十年,换我来写" / 阳台摇椅"反正我也在" —— 全书·完' },
]; ];
function renderChapterList() { function renderChapterList() {
@@ -845,7 +852,7 @@ function renderChapterList() {
} }
let num; let num;
if (ch.n === 0) num = '终'; if (ch.n === 0) num = '终';
else if (ch.n === -1) num = 'II'; else if (ch.n < 0) num = 'II·' + String(-ch.n).padStart(2, '0');
else num = ch.n.toString().padStart(2, '0'); else num = ch.n.toString().padStart(2, '0');
const title = ch.n === 0 ? ch.title.replace('终章 · ', '') : ch.title; const title = ch.n === 0 ? ch.title.replace('终章 · ', '') : ch.title;
html += ` html += `
@@ -915,13 +922,21 @@ function closeLightbox() {
} }
async function loadNovel() { async function loadNovel() {
const container = document.getElementById('reader-content');
try { try {
const resp = await fetch('./novel.md'); container.innerHTML = '<div class="loading">正在下载全本 …</div>';
const resp = await fetch('./novel.md', { cache: 'no-cache' });
if (!resp.ok) throw new Error('HTTP ' + resp.status);
container.innerHTML = '<div class="loading">正在渲染章节 …</div>';
const text = await resp.text(); const text = await resp.text();
// Yield to the browser so the loading message paints before heavy work
await new Promise(r => setTimeout(r, 0));
renderNovel(text); renderNovel(text);
} catch (e) { } catch (e) {
document.getElementById('reader-content').innerHTML = console.error('loadNovel failed:', e);
'<div class="loading" style="color: var(--red)">小说载入失败:' + e.message + '</div>'; container.innerHTML =
'<div class="loading" style="color: var(--red)">小说载入失败:' + e.message +
'。<a href="./novel.md" style="color: var(--gold); text-decoration: underline;">直接打开 novel.md</a></div>';
} }
} }
@@ -937,22 +952,28 @@ function renderNovel(md) {
let m = heading.match(/^(第(\d+)章|终章)\s+(.+)$/); let m = heading.match(/^(第(\d+)章|终章)\s+(.+)$/);
let isPart2 = false; let isPart2 = false;
let num, title; let num, title;
const cnMap = { '一': '01', '二': '02', '三': '03', '四': '04', '五': '05', '六': '06', '七': '07', '八': '08', '九': '09', '十': '10' };
const cnToInt = { '一':1,'二':2,'三':3,'四':4,'五':5,'六':6,'七':7,'八':8,'九':9,'十':10 };
let kicker, chN;
if (m) { if (m) {
const isEpilogue = !m[2]; const isEpilogue = !m[2];
num = isEpilogue ? '终' : m[2].padStart(2, '0'); num = isEpilogue ? '终' : m[2].padStart(2, '0');
title = m[3]; title = m[3];
// kicker assigned below kicker = isEpilogue ? 'EPILOGUE' : 'CHAPTER ' + num;
var kicker = isEpilogue ? 'EPILOGUE' : 'CHAPTER ' + num; chN = isEpilogue ? 0 : parseInt(m[2]);
} else { } else {
const m2 = heading.match(/^第二部\s*·\s*第章\s*·\s*(.+)$/); const m2 = heading.match(/^第二部\s*·\s*第(.+?)章\s*·\s*(.+)$/);
if (!m2) return; if (!m2) return;
isPart2 = true; isPart2 = true;
num = 'II · 01'; const arabic = cnMap[m2[1]] || m2[1];
title = m2[1]; num = 'II · ' + arabic;
var kicker = 'PART II · CHAPTER 01'; title = m2[2];
kicker = 'PART II · CHAPTER ' + arabic;
chN = -(cnToInt[m2[1]] || parseInt(m2[1])); // -1 = Part II Ch1, -2 = Part II Ch2, ...
} }
const ch = CHAPTERS[idx]; // Match chapter by semantic n (immune to section index drift caused by non-chapter ## headings)
const imgPath = ch ? `./images/${ch.img}` : ''; const ch = CHAPTERS.find(c => c.n === chN);
const imgPath = (ch && ch.img) ? `./images/${ch.img}` : '';
const paragraphs = body.split(/\n\s*\n/).map(p => { const paragraphs = body.split(/\n\s*\n/).map(p => {
const line = p.trim(); const line = p.trim();
if (!line) return ''; if (!line) return '';

File diff suppressed because it is too large Load Diff