diff --git a/CHANGELOG.html b/CHANGELOG.html index 50ac9ae..d067386 100644 --- a/CHANGELOG.html +++ b/CHANGELOG.html @@ -1,4 +1,4 @@ - +
@@ -2545,16 +2545,30 @@ main > *:nth-child(n+6) { animation-delay: 0.46s; }本 Changelog 自 2026-05-22 起以 HTML 格式维护。此前之变更可从 main 分支 git log 取得;下表为近期较显著之 commit。
「OAuth 为账户来源与凭证提供者、cdrop 自维护薄账户层 + 自签会话」的折中架构落地(蓝本见仓库 AUTH.md)。一台设备 = 一条会话,吊销即时生效、被吊销设备自动回到未登录态。
修复 iOS PWA → Mac 桌面客户端大文件 P2P 传输约 15MB 处死锁,及随之暴露的两处发送端语义问题;并修一类前端 CJK 文字累积 bug、为传输卡片增设实时速度。
request_id / approval_code / 仅新设备私有的 poll_secret),偷拍 QR 无法领取会话;会话只经新设备自身轮询连接下发 cookie。两档:「信任此设备」(scope=full、7 天滑动) / 「仅此次」(scope=guest、1 小时受限),默认偏安全的「仅此次」。访客会话服务端强制受限(requireFullSession 守门)wails:// 自定义 scheme 无持久 WebView 存储 → OPFS 降级、createWritable 回压不畅,把接收 promise 链堵死、饿死 dc.onmessage → 接收方 SCTP rwnd=0 → 发送方 bufferedAmount 卡死 16MiB(即“PWA 16.0MB / Mac 0B、约 15MB 卡住”)。改为:小文件(暂存 < 256MB)纯内存、close 整文件过 Go 写盘;超上限大文件流式 spill 到 Go 临时文件(每 64MB 一批),内存恒定有界。浏览器 / iOS 接收仍走 OPFS(其内存预算紧、又无 Go 落盘,OPFS 才有意义)download.go 新增 Begin/Append/Finalize/AbortStreamingDownload(复用 SaveDownload 的目录解析 / 文件名 sanitize / 不可写回退 / reveal,临时文件与目标同目录便于 finalize 原子 rename),app.go 加 4 个绑定 + net/desktop.ts base64 封装;IncomingSink.close() 返回 SinkResult(blob | 已落盘 path),deliverIncoming 统一落地;relay 接收一并改用,桌面大文件经中继也有界落盘。含单测 download_stream_test.gobytesSent(已塞进本地 16MiB buffer 的量),buffer 被秒满后定格、长 backpressure 等待期误报“疑似卡住”。改用 bytesSent − bufferedAmount(已离开本地 buffer ≈ 已交付)+ 250ms 轮询在等待期持续刷新,进度随接收端实际收程平滑爬升、不误判 stalledsetState("completed"),此时仍可能有多达 16MiB 在途,与接收端“仍在下载”状态不一致。改为送完 + done 帧后等 bufferedAmount 落到 0(字节已全部交付网络)再宣告完成DynText(key=文本内容,内容一变即整体卸载旧 span、连同 shim 碎片,再挂新 span 交 shim 处理一次——既消累积又保留中西排版,区别于 data-jz-skip 那样关掉排版),应用于发送按钮 / 设备名 / 在线数 / 相对时间 / 会话与令牌活跃时间 / 消息计数 / 剪贴板来源 / 问候语等就地更新处upsertTransfer 据相邻样本的字节 / 时间差分 + EMA(α=0.3)平滑算 bytesPerSec;TransferRow 仅在字节流动且未卡死、且速率为正时以“X/秒”展示(点分隔 meta 行追加),卡死时既有“似乎卡住了”状态已表达停滞、不显误导性 0 速0451549(main)“OAuth 为账户来源与凭证提供者、cdrop 自维护薄账户层 + 自签会话”的折中架构落地(蓝本见仓库 AUTH.md)。一台设备 = 一条会话,吊销即时生效、被吊销设备自动回到未登录态。
request_id / approval_code / 仅新设备私有的 poll_secret),偷拍 QR 无法领取会话;会话只经新设备自身轮询连接下发 cookie。两档:“信任此设备”(scope=full、7 天滑动) / “仅此次”(scope=guest、1 小时受限),默认偏安全的“仅此次”。访客会话服务端强制受限(requireFullSession 守门)accounts 表(cdrop 侧账户数据、不含凭证,存稳定 match_key 供管理员开启迁移时跨源关联);web_sessions 泛化出 kind / scope / granted_by / stepped_up_at。access token 改 cdrop 自签 HS256(密钥自 CDROP_SESSION_SECRET 域分离派生),脱离 IdP refresh、短 TTL(默认 900s)、载 sid/auth/exchange 先 JWKS 验 id_token 签名(唯一信任锚)再签自签 token,/auth/refresh 仍打 IdP 轮换 refresh_token 但回自签 token。RS256 退出浏览器每请求热路径,仅保留分支供桌面端 loopback 直带 IdP tokenverifySelfToken 每请求按 sid 查会话行,行删即拒 → 吊销在下次请求即生效(不等 TTL)。前端仅在「服务端以 401 确证失效」时清登录态回登录页(短期连接失败 / 5xx 不误清);离线设备下次使用即知,在线设备经 Hub.Kick 关流、约 1s 重连撞 401 即知web_sessions 与原生客户端设备(桌面 / iOS,自 devices 登记呈现,native=true)。吊销网页会话连带删其设备登记、即时从所有列表消失;孤儿 browser 设备由清扫器(DeleteOrphanBrowserDevices)自动清除。原生设备「登出」走 DELETE /api/devices/{name}(同要求 step-up)verifySelfToken 每请求按 sid 查会话行,行删即拒 → 吊销在下次请求即生效(不等 TTL)。前端仅在“服务端以 401 确证失效”时清登录态回登录页(短期连接失败 / 5xx 不误清);离线设备下次使用即知,在线设备经 Hub.Kick 关流、约 1s 重连撞 401 即知web_sessions 与原生客户端设备(桌面 / iOS,自 devices 登记呈现,native=true)。吊销网页会话连带删其设备登记、即时从所有列表消失;孤儿 browser 设备由清扫器(DeleteOrphanBrowserDevices)自动清除。原生设备“登出”走 DELETE /api/devices/{name}(同要求 step-up)CDROP_STEP_UP_ENABLED):批准扫码设备 / 吊销会话 / 注销设备等敏感动作要求一次现场 prompt=login 再认证(provider 的 2FA 即在此过),按会话绑定、StepUpMaxAgeSeconds(默认 300s)窗口内不复要求。auth_time 可选(Casdoor 不下发)getUserMedia + jsqr 扫码(不再外跳系统应用、避免开错浏览器);扫码 / 批准等三状态机页整页跳过聚珍(data-jz-skip),修 MutationObserver 与 React 重渲染同子树冲突的 insertBefore 崩溃AUTH.md;commit 待提交页面 / PWA 关闭、或桌面窗口非前台时也能收到系统通知;判定统一为「页面打开走应用内提示,关闭则系统通知」,三端各自落地。
+页面 / PWA 关闭、或桌面窗口非前台时也能收到系统通知;判定统一为“页面打开走应用内提示,关闭则系统通知”,三端各自落地。
push_subscriptions 表(含 locale,主键为 endpoint 的 SHA-256 以幂等 upsert)+ /api/push/{vapid-key, subscribe} 端点 + internal/push 发送器(404 / 410 自动清理死订阅)。文案按订阅设备语言在服务端渲染(中简 / 中繁 / 英)。覆盖浏览器 / Android / iOS 16.4+ 已安装 PWAUNUserNotificationCenter(未签名时安全空操作,待签名 / 公证后显示)、Windows 走原生 toastHub.SendTo 的投递结果,零额外状态;离线消息改返 202,文本随推送送达CDROP_VAPID_PUBLIC_KEY + CDROP_VAPID_PRIVATE_KEY(just vapid-keygen 生成一对,二者须同时设置,都留空则推送惰性关闭)Hub.SendTo 的投递结果,零额外状态;离线消息改返 202,文本随推送送达CDROP_VAPID_PUBLIC_KEY + CDROP_VAPID_PRIVATE_KEY(just vapid-keygen 生成一对,二者须同时设置,都留空则推送惰性关闭)92a03ae/api(SSE / 剪贴板 / 中继需活连接)、亦不拦导航;PKCE verifier 改 localStorage,使独立 PWA 在 OAuth 回跳重启上下文后仍能完成登录。面向用户短名统一小写 cdrop/api(SSE / 剪贴板 / 中继需活连接)、亦不拦导航;PKCE verifier 改 localStorage,使独立 PWA 在 OAuth 回跳重启上下文后仍能完成登录。面向用户短名统一小写 cdropsetup.js 与头像约 2 分钟拿不到连接(Chrome 正常)。根因是 oauth.callback 登录成功后用路由软导航,复用了 OAuth 流程遗留的 HTTP 连接,这些连接在 Safari 下卡死后续资源请求;改为整页跳转(window.location.replace)拿全新连接绕开。曾误判 service worker / h3 为主因,均已排除5c28ddf(PWA 安装)/ 7082ccc(短名)/ af754dc · 8f550ad(Safari 修复)多 agent 安全审计后的一轮加固,按信任模型(信任账号所有者 + 服务端;不信任跨用户 / 网络 hop peer / 本机其他软件)逐项收紧。
RELAY_ACTIVE 时由服务端登记其两个合法参与方,之后仅已登记会话可接入、且调用方须为 sender / receiver。叠加 per-user 配额(3)+ 全局上限(8)+ 空闲回收(2min)+ ring 单 reader 断言。一举堵住「任意 id 凭空铸造会话耗尽全实例并钉 512 MiB」「幽灵会话」「id 泄露后无第二道防线」「双 reader 撕裂数据」四类问题RELAY_ACTIVE 时由服务端登记其两个合法参与方,之后仅已登记会话可接入、且调用方须为 sender / receiver。叠加 per-user 配额(3)+ 全局上限(8)+ 空闲回收(2min)+ ring 单 reader 断言。一举堵住“任意 id 凭空铸造会话耗尽全实例并钉 512 MiB”“幽灵会话”“id 泄露后无第二道防线”“双 reader 撕裂数据”四类问题security / Windows Credential Manager·DPAPI / Linux Secret Service),同机软件无法直读冒充;会话文件只留短效 access_token。遗留明文文件首次启动自动迁移并抹除CDROP_OIDC_AUDIENCE 为空即拒绝启动 —— 空 audience 会跳过校验、令同一 JWKS 下兄弟应用的 token 也能验过(跨 app 冒充)。部署要求:prod 须设该变量(web + 桌面 client_id 逗号分隔)~/Downloads):设置页加「下载目录」+ 原生目录选择框(ChooseDownloadDir → Wails OpenDirectoryDialog)。机制为 Go 绑定写盘 —— 桌面模式下 downloadBlob 改走 SaveDownload(blob → base64 过桥 → Go 写盘),对端文件名经 sanitizeFileName 剥目录成分防路径穿越、重名加 (n)、落盘后 open -R / explorer /select 揭示<a download> 的 blob:,原「失败回退浏览器下载」在 macOS 是空操作、会静默丢文件;改为写配置目录失败时自动改写系统 ~/Downloads,连默认目录也失败才显式报错(toast.error),不再丢文件POST /transfer/{id}/cancel)、终态「删除」(从 active + history 移除)、发送方仍在试 P2P 时「立即中继」(清 30s ICE watchdog + POST /transfer/{id}/fallback,跳过等待)~/Downloads):设置页加“下载目录”+ 原生目录选择框(ChooseDownloadDir → Wails OpenDirectoryDialog)。机制为 Go 绑定写盘 —— 桌面模式下 downloadBlob 改走 SaveDownload(blob → base64 过桥 → Go 写盘),对端文件名经 sanitizeFileName 剥目录成分防路径穿越、重名加 (n)、落盘后 open -R / explorer /select 揭示<a download> 的 blob:,原“失败回退浏览器下载”在 macOS 是空操作、会静默丢文件;改为写配置目录失败时自动改写系统 ~/Downloads,连默认目录也失败才显式报错(toast.error),不再丢文件POST /transfer/{id}/cancel)、终态“删除”(从 active + history 移除)、发送方仍在试 P2P 时“立即中继”(清 30s ICE watchdog + POST /transfer/{id}/fallback,跳过等待).app + Windows .exe)重构并同步;web 端 prod 部署(drop.commilitia.net)43ddf36(下载路径)/ 10a4c99(传输 UX + 兜底修正)cjk-autospace 升级 a5faec5 → a3c94dd:v1 自动空格 shim 从零重写为「聚珍(Juzhen)」2.0 综合排版引擎 —— 中西间隙 / 标点挤压(全角式)/ 禁则避头尾 / 垂悬字避免 / 长英文断词 / 功能分级;JS 只注入 jz-* 结构、视觉尺寸全在 juzhen.css(必配)cjk-autospace 升级 a5faec5 → a3c94dd:v1 自动空格 shim 从零重写为“聚珍(Juzhen)”2.0 综合排版引擎 —— 中西间隙 / 标点挤压(全角式)/ 禁则避头尾 / 垂悬字避免 / 长英文断词 / 功能分级;JS 只注入 jz-* 结构、视觉尺寸全在 juzhen.css(必配)level:{text:".juzhen"} 令全树默认文本级(cdrop 绝大多数是 UI chrome);消息正文 / 剪贴板内容 / Callout / Toast 正文 / Field hint / 登录说明等 8 处流动正文加 data-jz-level="paragraph" opt-in 段落级,失败模式良性fonts.css,:lang() 切 SC/TC/JP/KR):Sans = Orbit Gothic CJK;Serif = Noto Serif CJK(本地合一)+ Noto Serif <R>(Web 子集、跨区并集覆盖);Mono = Maple Mono + Orbit Gothic CJKfontface.css):CDN 的 @font-face 无 local() 且 family 名与本机安装同名 → 被捕获、永远走网络。本层在 CDN <link> 之后用 src: local(), url() 重声明项目用到的字重,本机命中即零网络、否则回退 CDN(swap)inline-flex gap 中各成 flex item 致 gap 叠加);术语「Pangu」统一改「聚珍」;commit 907d132 / b3c74a6inline-flex gap 中各成 flex item 致 gap 叠加);术语“Pangu”统一改“聚珍”;commit 907d132 / b3c74a6web/src/lib/cjk-autospace/ 为空时 vite build 会因找不到 import 而失败;错误信息虽含路径但不直观README.md 顶部加「克隆」小节,指引 git clone --recurse-submodules <URL> 或补拉 git submodule update --init --recursiveREADME.md 顶部加“克隆”小节,指引 git clone --recurse-submodules <URL> 或补拉 git submodule update --init --recursive1443543本 Changelog 自 2026-05-22 起以 HTML 格式维护。此前之变更可从 main 分支 git log 取得;下表为近期较显著之 commit。
「OAuth 为账户来源与凭证提供者、cdrop 自维护薄账户层 + 自签会话」的折中架构落地(蓝本见仓库 AUTH.md)。一台设备 = 一条会话,吊销即时生效、被吊销设备自动回到未登录态。
修复 iOS PWA → Mac 桌面客户端大文件 P2P 传输约 15MB 处死锁,及随之暴露的两处发送端语义问题;并修一类前端 CJK 文字累积 bug、为传输卡片增设实时速度。
request_id / approval_code / 仅新设备私有的 poll_secret),偷拍 QR 无法领取会话;会话只经新设备自身轮询连接下发 cookie。两档:「信任此设备」(scope=full、7 天滑动) / 「仅此次」(scope=guest、1 小时受限),默认偏安全的「仅此次」。访客会话服务端强制受限(requireFullSession 守门)wails:// 自定义 scheme 无持久 WebView 存储 → OPFS 降级、createWritable 回压不畅,把接收 promise 链堵死、饿死 dc.onmessage → 接收方 SCTP rwnd=0 → 发送方 bufferedAmount 卡死 16MiB(即“PWA 16.0MB / Mac 0B、约 15MB 卡住”)。改为:小文件(暂存 < 256MB)纯内存、close 整文件过 Go 写盘;超上限大文件流式 spill 到 Go 临时文件(每 64MB 一批),内存恒定有界。浏览器 / iOS 接收仍走 OPFS(其内存预算紧、又无 Go 落盘,OPFS 才有意义)download.go 新增 Begin/Append/Finalize/AbortStreamingDownload(复用 SaveDownload 的目录解析 / 文件名 sanitize / 不可写回退 / reveal,临时文件与目标同目录便于 finalize 原子 rename),app.go 加 4 个绑定 + net/desktop.ts base64 封装;IncomingSink.close() 返回 SinkResult(blob | 已落盘 path),deliverIncoming 统一落地;relay 接收一并改用,桌面大文件经中继也有界落盘。含单测 download_stream_test.gobytesSent(已塞进本地 16MiB buffer 的量),buffer 被秒满后定格、长 backpressure 等待期误报“疑似卡住”。改用 bytesSent − bufferedAmount(已离开本地 buffer ≈ 已交付)+ 250ms 轮询在等待期持续刷新,进度随接收端实际收程平滑爬升、不误判 stalledsetState("completed"),此时仍可能有多达 16MiB 在途,与接收端“仍在下载”状态不一致。改为送完 + done 帧后等 bufferedAmount 落到 0(字节已全部交付网络)再宣告完成DynText(key=文本内容,内容一变即整体卸载旧 span、连同 shim 碎片,再挂新 span 交 shim 处理一次——既消累积又保留中西排版,区别于 data-jz-skip 那样关掉排版),应用于发送按钮 / 设备名 / 在线数 / 相对时间 / 会话与令牌活跃时间 / 消息计数 / 剪贴板来源 / 问候语等就地更新处upsertTransfer 据相邻样本的字节 / 时间差分 + EMA(α=0.3)平滑算 bytesPerSec;TransferRow 仅在字节流动且未卡死、且速率为正时以“X/秒”展示(点分隔 meta 行追加),卡死时既有“似乎卡住了”状态已表达停滞、不显误导性 0 速0451549(main)“OAuth 为账户来源与凭证提供者、cdrop 自维护薄账户层 + 自签会话”的折中架构落地(蓝本见仓库 AUTH.md)。一台设备 = 一条会话,吊销即时生效、被吊销设备自动回到未登录态。
request_id / approval_code / 仅新设备私有的 poll_secret),偷拍 QR 无法领取会话;会话只经新设备自身轮询连接下发 cookie。两档:“信任此设备”(scope=full、7 天滑动) / “仅此次”(scope=guest、1 小时受限),默认偏安全的“仅此次”。访客会话服务端强制受限(requireFullSession 守门)accounts 表(cdrop 侧账户数据、不含凭证,存稳定 match_key 供管理员开启迁移时跨源关联);web_sessions 泛化出 kind / scope / granted_by / stepped_up_at。access token 改 cdrop 自签 HS256(密钥自 CDROP_SESSION_SECRET 域分离派生),脱离 IdP refresh、短 TTL(默认 900s)、载 sid/auth/exchange 先 JWKS 验 id_token 签名(唯一信任锚)再签自签 token,/auth/refresh 仍打 IdP 轮换 refresh_token 但回自签 token。RS256 退出浏览器每请求热路径,仅保留分支供桌面端 loopback 直带 IdP tokenverifySelfToken 每请求按 sid 查会话行,行删即拒 → 吊销在下次请求即生效(不等 TTL)。前端仅在「服务端以 401 确证失效」时清登录态回登录页(短期连接失败 / 5xx 不误清);离线设备下次使用即知,在线设备经 Hub.Kick 关流、约 1s 重连撞 401 即知web_sessions 与原生客户端设备(桌面 / iOS,自 devices 登记呈现,native=true)。吊销网页会话连带删其设备登记、即时从所有列表消失;孤儿 browser 设备由清扫器(DeleteOrphanBrowserDevices)自动清除。原生设备「登出」走 DELETE /api/devices/{name}(同要求 step-up)verifySelfToken 每请求按 sid 查会话行,行删即拒 → 吊销在下次请求即生效(不等 TTL)。前端仅在“服务端以 401 确证失效”时清登录态回登录页(短期连接失败 / 5xx 不误清);离线设备下次使用即知,在线设备经 Hub.Kick 关流、约 1s 重连撞 401 即知web_sessions 与原生客户端设备(桌面 / iOS,自 devices 登记呈现,native=true)。吊销网页会话连带删其设备登记、即时从所有列表消失;孤儿 browser 设备由清扫器(DeleteOrphanBrowserDevices)自动清除。原生设备“登出”走 DELETE /api/devices/{name}(同要求 step-up)CDROP_STEP_UP_ENABLED):批准扫码设备 / 吊销会话 / 注销设备等敏感动作要求一次现场 prompt=login 再认证(provider 的 2FA 即在此过),按会话绑定、StepUpMaxAgeSeconds(默认 300s)窗口内不复要求。auth_time 可选(Casdoor 不下发)getUserMedia + jsqr 扫码(不再外跳系统应用、避免开错浏览器);扫码 / 批准等三状态机页整页跳过聚珍(data-jz-skip),修 MutationObserver 与 React 重渲染同子树冲突的 insertBefore 崩溃AUTH.md;commit 待提交页面 / PWA 关闭、或桌面窗口非前台时也能收到系统通知;判定统一为「页面打开走应用内提示,关闭则系统通知」,三端各自落地。
+页面 / PWA 关闭、或桌面窗口非前台时也能收到系统通知;判定统一为“页面打开走应用内提示,关闭则系统通知”,三端各自落地。
push_subscriptions 表(含 locale,主键为 endpoint 的 SHA-256 以幂等 upsert)+ /api/push/{vapid-key, subscribe} 端点 + internal/push 发送器(404 / 410 自动清理死订阅)。文案按订阅设备语言在服务端渲染(中简 / 中繁 / 英)。覆盖浏览器 / Android / iOS 16.4+ 已安装 PWAUNUserNotificationCenter(未签名时安全空操作,待签名 / 公证后显示)、Windows 走原生 toastHub.SendTo 的投递结果,零额外状态;离线消息改返 202,文本随推送送达CDROP_VAPID_PUBLIC_KEY + CDROP_VAPID_PRIVATE_KEY(just vapid-keygen 生成一对,二者须同时设置,都留空则推送惰性关闭)Hub.SendTo 的投递结果,零额外状态;离线消息改返 202,文本随推送送达CDROP_VAPID_PUBLIC_KEY + CDROP_VAPID_PRIVATE_KEY(just vapid-keygen 生成一对,二者须同时设置,都留空则推送惰性关闭)92a03ae/api(SSE / 剪贴板 / 中继需活连接)、亦不拦导航;PKCE verifier 改 localStorage,使独立 PWA 在 OAuth 回跳重启上下文后仍能完成登录。面向用户短名统一小写 cdrop/api(SSE / 剪贴板 / 中继需活连接)、亦不拦导航;PKCE verifier 改 localStorage,使独立 PWA 在 OAuth 回跳重启上下文后仍能完成登录。面向用户短名统一小写 cdropsetup.js 与头像约 2 分钟拿不到连接(Chrome 正常)。根因是 oauth.callback 登录成功后用路由软导航,复用了 OAuth 流程遗留的 HTTP 连接,这些连接在 Safari 下卡死后续资源请求;改为整页跳转(window.location.replace)拿全新连接绕开。曾误判 service worker / h3 为主因,均已排除5c28ddf(PWA 安装)/ 7082ccc(短名)/ af754dc · 8f550ad(Safari 修复)多 agent 安全审计后的一轮加固,按信任模型(信任账号所有者 + 服务端;不信任跨用户 / 网络 hop peer / 本机其他软件)逐项收紧。
RELAY_ACTIVE 时由服务端登记其两个合法参与方,之后仅已登记会话可接入、且调用方须为 sender / receiver。叠加 per-user 配额(3)+ 全局上限(8)+ 空闲回收(2min)+ ring 单 reader 断言。一举堵住「任意 id 凭空铸造会话耗尽全实例并钉 512 MiB」「幽灵会话」「id 泄露后无第二道防线」「双 reader 撕裂数据」四类问题RELAY_ACTIVE 时由服务端登记其两个合法参与方,之后仅已登记会话可接入、且调用方须为 sender / receiver。叠加 per-user 配额(3)+ 全局上限(8)+ 空闲回收(2min)+ ring 单 reader 断言。一举堵住“任意 id 凭空铸造会话耗尽全实例并钉 512 MiB”“幽灵会话”“id 泄露后无第二道防线”“双 reader 撕裂数据”四类问题security / Windows Credential Manager·DPAPI / Linux Secret Service),同机软件无法直读冒充;会话文件只留短效 access_token。遗留明文文件首次启动自动迁移并抹除CDROP_OIDC_AUDIENCE 为空即拒绝启动 —— 空 audience 会跳过校验、令同一 JWKS 下兄弟应用的 token 也能验过(跨 app 冒充)。部署要求:prod 须设该变量(web + 桌面 client_id 逗号分隔)~/Downloads):设置页加「下载目录」+ 原生目录选择框(ChooseDownloadDir → Wails OpenDirectoryDialog)。机制为 Go 绑定写盘 —— 桌面模式下 downloadBlob 改走 SaveDownload(blob → base64 过桥 → Go 写盘),对端文件名经 sanitizeFileName 剥目录成分防路径穿越、重名加 (n)、落盘后 open -R / explorer /select 揭示<a download> 的 blob:,原「失败回退浏览器下载」在 macOS 是空操作、会静默丢文件;改为写配置目录失败时自动改写系统 ~/Downloads,连默认目录也失败才显式报错(toast.error),不再丢文件POST /transfer/{id}/cancel)、终态「删除」(从 active + history 移除)、发送方仍在试 P2P 时「立即中继」(清 30s ICE watchdog + POST /transfer/{id}/fallback,跳过等待)~/Downloads):设置页加“下载目录”+ 原生目录选择框(ChooseDownloadDir → Wails OpenDirectoryDialog)。机制为 Go 绑定写盘 —— 桌面模式下 downloadBlob 改走 SaveDownload(blob → base64 过桥 → Go 写盘),对端文件名经 sanitizeFileName 剥目录成分防路径穿越、重名加 (n)、落盘后 open -R / explorer /select 揭示<a download> 的 blob:,原“失败回退浏览器下载”在 macOS 是空操作、会静默丢文件;改为写配置目录失败时自动改写系统 ~/Downloads,连默认目录也失败才显式报错(toast.error),不再丢文件POST /transfer/{id}/cancel)、终态“删除”(从 active + history 移除)、发送方仍在试 P2P 时“立即中继”(清 30s ICE watchdog + POST /transfer/{id}/fallback,跳过等待).app + Windows .exe)重构并同步;web 端 prod 部署(drop.commilitia.net)43ddf36(下载路径)/ 10a4c99(传输 UX + 兜底修正)cjk-autospace 升级 a5faec5 → a3c94dd:v1 自动空格 shim 从零重写为「聚珍(Juzhen)」2.0 综合排版引擎 —— 中西间隙 / 标点挤压(全角式)/ 禁则避头尾 / 垂悬字避免 / 长英文断词 / 功能分级;JS 只注入 jz-* 结构、视觉尺寸全在 juzhen.css(必配)cjk-autospace 升级 a5faec5 → a3c94dd:v1 自动空格 shim 从零重写为“聚珍(Juzhen)”2.0 综合排版引擎 —— 中西间隙 / 标点挤压(全角式)/ 禁则避头尾 / 垂悬字避免 / 长英文断词 / 功能分级;JS 只注入 jz-* 结构、视觉尺寸全在 juzhen.css(必配)level:{text:".juzhen"} 令全树默认文本级(cdrop 绝大多数是 UI chrome);消息正文 / 剪贴板内容 / Callout / Toast 正文 / Field hint / 登录说明等 8 处流动正文加 data-jz-level="paragraph" opt-in 段落级,失败模式良性fonts.css,:lang() 切 SC/TC/JP/KR):Sans = Orbit Gothic CJK;Serif = Noto Serif CJK(本地合一)+ Noto Serif <R>(Web 子集、跨区并集覆盖);Mono = Maple Mono + Orbit Gothic CJKfontface.css):CDN 的 @font-face 无 local() 且 family 名与本机安装同名 → 被捕获、永远走网络。本层在 CDN <link> 之后用 src: local(), url() 重声明项目用到的字重,本机命中即零网络、否则回退 CDN(swap)inline-flex gap 中各成 flex item 致 gap 叠加);术语「Pangu」统一改「聚珍」;commit 907d132 / b3c74a6inline-flex gap 中各成 flex item 致 gap 叠加);术语“Pangu”统一改“聚珍”;commit 907d132 / b3c74a6web/src/lib/cjk-autospace/ 为空时 vite build 会因找不到 import 而失败;错误信息虽含路径但不直观README.md 顶部加「克隆」小节,指引 git clone --recurse-submodules <URL> 或补拉 git submodule update --init --recursiveREADME.md 顶部加“克隆”小节,指引 git clone --recurse-submodules <URL> 或补拉 git submodule update --init --recursive1443543