Skip to content

为 macOS 伴侣适配完整导出档案,保留原件与档案隔离 - #4

Merged
crownleo merged 1 commit into
crownleo:mainfrom
LiuHangyuWE:feat/macos-export-bundles
Sep 9, 2026
Merged

为 macOS 伴侣适配完整导出档案,保留原件与档案隔离#4
crownleo merged 1 commit into
crownleo:mainfrom
LiuHangyuWE:feat/macos-export-bundles

Conversation

@LiuHangyuWE

Copy link
Copy Markdown
Contributor

新版 Claude 导出由 manifest 与多个分类 ZIP 组成,旧 Mac 实现把每个 ZIP 当成独立档案,无法完整读取一次导出。本次将可选 Mac 伴侣的存储单位调整为整份导出目录:同次导出的原件共同保存、读取和取出,不同导出各自拥有聊天查看状态、收藏和标签;旧版单 ZIP 档案保持兼容。

网页核心保持官方 v6.0 原样,构建 App 时通过独立适配器连接本机文件存储,复用现有解析与渲染。App 将原件保存在 Application Support 的普通目录中,支持显式导入、发现手动放入的独立目录、Finder 访问、原样取出和移至废纸篓。构建脚本固定已验证的上游文件版本,源码与 App 产物分开,不包含私人数据。

验证包含原生文件与迁移测试、10 项 JavaScript 回归测试、真实 WKWebView 的 42 项集成断言,以及 Apple Silicon 构建、签名和既有旧格式档案实际打开检查。新版多 ZIP 验证使用合成数据;尚未验证 Intel Mac、Windows 或用户的真实新版导出。本次范围仅为 macOS 伴侣。

这是 #3 后续单独整理的 macOS 伴侣提案。是否将可选 Mac 外壳放进主仓仍在讨论中,因此先以草稿提供完整源码供审阅。当前分支固定在已验证的 v6.0;主分支后续更新会触发构建脚本的兼容性校验,合入前需要对齐目标版本并重新验证。

@vercel

vercel Bot commented Sep 8, 2026

Copy link
Copy Markdown

@LiuHangyuWE is attempting to deploy a commit to the wanggchn-9639's projects Team on Vercel.

A member of the Team first needs to authorize it.

@crownleo crownleo closed this in 6e08006 Sep 9, 2026
@crownleo
crownleo merged commit 6e08006 into crownleo:main Sep 9, 2026
1 check failed
crownleo added a commit that referenced this pull request Sep 9, 2026
合并 PR #4 时 build.py 的 VIEWER_SHA256 仍钉在 v6.0 发布点 cf82967a,
而 main 的 claude_viewer.html 已是 ec909e33(16e7450 修了上传页文案换行)。
不升锁则 build.py 会在校验处直接抛 ValueError,合并当天就构建不出 App。

v6.0 → main 之间 claude_viewer.html 只改了 4 行,全部是 CSS 与上传页文案,
无任何 JS 改动;适配器覆盖的 18 个上游标识符与引用的 DOM id 全部仍然存在。
已按新锁重跑验证,全部通过:

- macos/tests/run.py                 ArchiveStore 全项 + 构建边界
- macos/tests/adapter.test.cjs       10/10
- macos/tests/run_webview.py         42 断言 / 25 次 scheme 读取 / 35 次原生调用
- build.py --arch arm64              3.4 MB,codesign --verify --deep --strict 通过

同时更新 macos/README.md:伴侣已并入主仓,不再是「尚未被上游接收」;
按贡献指南第五节补上当前锁定的上游版本、实测环境,以及明确未验证的范围
(Intel Mac、其他 macOS 版本、多分片导出)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KxxVndwvxaziBQ5ikJuiTn
@crownleo

crownleo commented Sep 9, 2026

Copy link
Copy Markdown
Owner

已合并到 main。感谢这份贡献,也感谢你在 #3 里把使用目标讲清楚——那段说明帮我看明白了「一个 ZIP 一档案」原本是在用文件边界实现隔离,而不是对导出格式的误判。我们要的是同一件事。

先说结论:Mac 伴侣同仓收下了,而且我把贡献指南改了。


一、组织方式:不是例外,是把规则改了

你在 #3 问「是否愿意讨论把这种可选 Mac 外壳作为同仓的例外」。我不想用「例外」的方式处理——例外没有边界,下一个平台来了还得再吵一次。所以直接改了 CONTRIBUTING.md

底线 2 原文是「重型能力只能做成可选伴侣,不进主仓」,与收下 macos/ 直接矛盾。现在改成:伴侣可以进主仓,但绝不能成为使用查看器的前提——删掉整个伴侣目录,网页版必须照常工作。

新增了「五、可选平台伴侣」,把三条以后容易扯皮的写死:

  1. 主仓只收源码,不收编译产物。 .app 作为独立发布产物提供,不进版本库。
  2. 伴侣不得改动网页核心。 必须复用未经修改的 claude_viewer.html,走注入式适配器,接缝对不上就构建失败,不允许「尽力而为」地降级。你现在的做法就是这个模式,我把它写成了规则。
  3. 主仓发版不等伴侣。 这条是给你的保障,不是限制——

伴侣通过版本锁钉住自己实测通过的那一版网页核心。上游一变,伴侣构建会立刻失败——这是设计如此,不是 bug。伴侣落后于主仓属于正常状态,不算未修复缺陷。由该平台的维护者在方便时重新验证并升锁。

我的主力开发机是 Windows,跑不了 build.py。不把这条写死,以后每次改 claude_viewer.html 都会卡在一个我无法验证的东西上,最后的结果一定是我不敢改、你也不好受。写死之后,我该改就改,你有空再升锁。

关于你说的「允许平台支持逐步补齐,不必把两端同时完成作为 Mac 版可用的前提」——同意,采纳了。Windows 版等有人来做,不做 Mac 版的前置条件。


二、合并时我改了什么

1. VIEWER_SHA256: cf82967a…ec909e33…(必须改,否则合并当天就构建不出来)

你钉的是 v6.0 发布点,main 上有个 16e7450 修了上传页文案换行。不升锁,build.py:67 会直接抛 ValueError

你不必 rebase 重做适配——v6.0 → main 之间 claude_viewer.html 只改了 4 行,全是 .drop-zone 的 CSS 和上传页文案,零 JS 改动。我按新锁重跑了全部验证,见下。

2. macos/README.md — 原文写着「尚未被上游接收」,已更新;并按新规则补上当前锁定的上游版本、实测环境,以及明确未验证的范围。

3. 双语 README — 各加一节「可选:macOS 桌面伴侣」,开头第一句就是「不需要它也能用」,并列清边界:需自行构建、仅 Apple Silicon 实测、本地 ad-hoc 签名未经公证、Intel Mac 与 Windows 未验证。致谢段也加上了。

4. .vercelignore — 在线体验站不部署 macos/


三、实测结果

MacBook Air / Apple Silicon (arm64) / macOS 26.6.2 / Python 3.13.13 / Node 26.5.0,用真实的新版分片导出(manifest + 5 个分类 ZIP,2026-09-09):

结果
macos/tests/run.py ✅ ArchiveStore 全项 + 构建边界
macos/tests/adapter.test.cjs ✅ 10/10
macos/tests/run_webview.py ✅ 42 断言 / 25 次 scheme 读取 / 35 次原生调用
build.py --arch arm64 ✅ 3.4 MB,codesign --verify --deep --strict 通过
真实分片导出导入 cvValidateArchive 没有误报账号冲突
原件保真 ✅ 6 个文件与源 cmp 逐字节一致;record.json 记录的 sha256 全部匹配
源文件夹是否被动 ✅ 大小与时间戳不变,导入是复制不是移动
Claude Code 模式 file:// 源下降级路径能走通
零外部请求 ✅ App 运行时 lsof -i -a -p $(pgrep -x ClaudeViewer) 零输出

最后一条我特别验了,因为它是产品底线。另外静态复查:bundle 内 CSP 是 default-src 'none' + 单 nonce + connect-src claude-archive:,全文只剩 2 个 https:// 且都是署名区块的 <a href>

两点确认给你:

  • 测试确实只用临时资料库。 跑完三组测试后 ~/Library/Application Support/ClaudeViewer 仍不存在,是后来 App 首次启动才建的。
  • 本机构建产物没有 com.apple.quarantine,Gatekeeper 不拦,open 直接起。只有分发下载的包才需要右键打开。

四、三条建议

A. adapter.py 的 fail-closed 名不副实

这条最要紧。adapter.py 只断言 4 个精确字符串替换(arcAll / arcGet / arcPut / arcDel)加一个 IIFE 结尾。但适配器实际重新赋值了 18 个上游标识符:

activeArc  annSave  appData  appMode  arcAdoptPending  arcExportOne
arcExportSet  arcOpen  arcProbe  arcRender  arcRenderStorage  arcRestore
isCached  man  openExportDir  pendingImport  showSavePrompt  zip

另外还调用二十来个上游函数(parseZipfinalizeDataclassifyJsonarcRefreshresetState …)和若干 DOM id。这 18 个里,一个都没有被断言覆盖。

我把 function 换成 const、或给 arcRender 改个名,build.py 照样构建成功,运行时才炸——而且是在用户机器上炸。

这次没出事,是因为 v6.0 → main 那 4 行改动全在 CSS 和文案,一个 JS 标识符都没碰。属于运气,不属于机制。 建议把断言扩到每个被覆盖的标识符,让"上游改名"在构建时就现形。

B. arcRender 是函数级 fork,不是 hook

整个档案列表渲染在适配器里重写了一遍。以后我在 HTML 里给它加按钮、改文案、补无障碍属性,Mac 版一律看不到,而且不报错

这是最大的长期漂移风险。能否保留上游渲染器、只替换数据源?哪怕保留不了,至少让 A 那套断言把它盖住,改了能立刻发现。

C. 打开一份档案要把所有字节读三遍

list() 全量 SHA256 → scheme handler 供字节时再哈希一遍 → JSZip 解析。而 arcRefreshimportSources 都会调 list()

我这份导出只有 7 MB,感觉不出来。但这个 App 的目标用户恰恰是备份很大的人——大账号的 conversations 会切成 -000 / -001 / -002,几百 MB 很正常。建议至少给 list() 加个校验值缓存,别每次刷新都全量重算。


五、还没验证的

我没验的部分,不写进 README 当已支持:

  • 「取出整套原件」的 GUI 路径——入库侧已证逐字节一致,取出侧只有合成数据的测试覆盖
  • 多份档案的隔离——我目前只有一份导出,收藏/标签互不串这条没实测过。这恰恰是你做这套东西的原始动机,等我攒够第二份再补
  • 多分片导出(-001 及以后)——我的账号还没大到会切片
  • Intel Mac、其他 macOS 版本

另外 Swift 外壳的 UI 文案目前全是硬编码中文,和项目的双语惯例不一致。不着急,后面有空再说。


六、

macos/ 现在是主仓的一部分了,规则也写清楚了。你按自己的节奏维护,不必追着我的发版跑——锁失效是正常状态,不是欠债。

再次感谢。#3 那次因为一个 PR 混了三件事,好的部分陪着有争议的部分一起等;这次范围收得干净,验证材料也齐,合起来很省事。

@crownleo

crownleo commented Sep 9, 2026

Copy link
Copy Markdown
Owner

补一条后续:合并之后又加了两样东西,都跟 macos/ 有关,同步给你。

一、给 Mac 伴侣加了 CI

.github/workflows/macos-companion.yml

起因是我在 #3 里说过的那句「我的主力开发机是 Windows,连构建都跑不了,收进主仓的话往后每次改 claude_viewer.html 都会成为我看不见的悬空风险」。现在这个风险由 GitHub 的 macOS runner 替我盯着——公开仓库免费,一轮 41 秒。

关键设计:版本锁过期【不算失败】

这条是给你的保障,不是限制,所以专门说明一下。

run.pyrun_webview.pybuild.py 都会在 pin 与 HTML 不符时抛错。要是直接跑,我每改一次 claude_viewer.html,CI 就变红——那跟我刚写进 CONTRIBUTING.md 第五节的「伴侣落后于主仓是正常状态、不算未修复缺陷,主仓发版不等伴侣」直接打架,而且红久了就没人看了。

所以分成两档:

锁一致 锁过期
adapter.test.cjs(10 项) ✅ 跑,失败即红 仍然跑,但不阻塞
run.py ✅ 跑 ⏸️ 跳过
run_webview.py ✅ 跑 ⏸️ 跳过
build.py + 验签 ✅ 跑 ⏸️ 跳过
结论 红或绿 保持绿,只发 notice

锁过期时仍然跑 adapter.test.cjs 是这套设计的重点。 它把上游 HTML 和适配器一起装进 vm,不经过 build.py 的校验值检查,所以它能回答升锁前最想知道的那个问题。job summary 里会直接给结论:

适配器接缝仍然对得上 — 这次漂移未触及 Mac 伴侣依赖的结构,升锁大概率只需改校验值。

或者:

⚠️ 适配器接缝已对不上 — 这次改动动到了伴侣依赖的结构,升锁前需要先改适配器。

对你的实际意义:你不用追着我的发版跑。 我改了东西之后,是"只需升锁"还是"真把适配器搞坏了",commit 旁边就写着,你不必自己开机验一遍才知道要不要动手。

一个可能对你有用的实测结果

run_webview.py 会起一个真实 WKWebView,我原本不确定 GitHub 的 runner 上能不能跑起来,推上去实跑了两轮——能跑,42 项断言全过。所以你写的这三组测试是完整可 CI 化的,没有哪一项必须留在真机上。

它替代不了什么

CI 只能验"能不能构建、测试过不过"。真实数据的 GUI 验证——导入、切档案、取出原件、⌘Q 保存——仍然只能在真机上人工做。

另外这也不能取代我上一条里的建议 A。CI 在锁一致时能全面把关,锁过期时只剩 adapter.test.cjs 一道防线,而它覆盖的是接缝、不是那 18 个被重新赋值的标识符。把 adapter.py 的断言扩到每个被覆盖的标识符,仍然值得做——那是构建期就拦下,比 CI 事后发现更早一层。

二、改了 macos/README.md,提前说一声

因为暂时不发布编译好的 App,自行构建就成了唯一路径,所以给它补了一份面向普通用户的说明。你的原文我没删,整段挪到「构建选项(进阶)」下面保留着;新加的是前面一节「五分钟装好(给用户)」:三条命令、不会用 git 的替代路径、常见报错对照表。英文半边同样处理。

顺带把一个原本藏在你那段散文末尾的事实提到了显眼位置:

自己构建的 App 不会被系统拦下。 「无法验证开发者」只针对从网上下载的文件;本机构建的产物没有下载标记,双击直接开,不需要改动任何系统设置。

我实测确认了另一半:spctl -a -t exec 对 ad-hoc 签名的产物判 rejected,Signature=adhocTeamIdentifier=not set。也就是说这个 App 恰恰是经网络分发时才会被拦。这正好解释了为什么现阶段推荐自行构建、而不提供下载包——把它写清楚,用户才有理由花五分钟自己构建。

如果你觉得哪里写得不对或者重点偏了,直接提 PR 改,那部分本来就是你的文档。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants