Repository navigation
[PR] feat(web): local announcements — drop a JSON next to config, get a banner (#5548 P2) - #5570
ranxianglei wants to merge 3 commits into
Conversation
…er loop (#5548 P1) Every built-in web notice now lives as ONE entry in src/notices.ts (id, tone, i18n key set, server-side active predicate). The web client renders whatever the overview reports through a single generic loop over #notices-banner-region; the per-notice builders/banners/wiring added one by one in #5079, #5469 and the retired #5547 are gone. Adding a 4th notice is a table row + its i18n keys + its store allowlist id — no admin/client/page edits. - new src/notices.ts: NoticeEntry registry + never-throwing visibleNotices() - admin.ts: overview gains notices[] (replaces the two bespoke payload fields); new GET /__bili/notices serves the same array - client.ts: bili_pckNoticeBanner/bili_wmNoticeBanner collapse into one bili_noticeBanner({tone,on,desc,hint,dismiss}); one loop wires .notice-dismiss buttons against POST /__bili/notices/dismiss - page.ts: two static banner divs -> single #notices-banner-region - i18n: per-notice dismissed toasts merge into one notices.dismissed key (zh/en); web-i18n dead-key guard learns to scan src/notices.ts (registry strings count as references) - tests: new tests/notice-registry.test.ts (registry invariants, both locales, fresh-install visibility order, dismiss-hides-only-one) Behavior is deliberately unchanged (P1 of the approved #5548 design: P2 adds local JSON announcements + full schema, P3 remote GitHub raw + CI gate).
…#5548 P1, unused-exports gate)
… get a banner (#5548 P2) Owners can now post notices by writing one JSON file per announcement into ~/.config/billion-context/announcements/*.json (the dir follows BILI_CONFIG_FILE relocation). The web panel renders them through the same single banner loop as built-ins (P1 registry); no code change needed ever again for owner-side announcements. Schema (whitelist copy, extra fields dropped not fatal; caps title<=120 / desc<=600 / hint<=300, file<=64KB, bad file skips itself only): - id [a-z0-9-]{1,64} + kind 7-class (new-feature/upgrade-advisory/warning/ suggestion/incident/deprecation/other); tone auto-derived (ok/warn/info) or overridden; priority defaults per kind (incident 90 .. other 30) - versions: semver-ish AND list (exact, >=a <b, ^, ~, [lo hi] || [x y]); this build must match every element; unparseable build version hides versioned records (never over-nag) - percent 0-100 grey bucketing: sha256(id|install-id) last two bytes %100, salted per announcement so buckets do not correlate with feature rollouts - since/until ISO windows; publishedAt metadata - dismiss modes: persistent (store key id@sha256(canonical record)[:16] so edited content re-nags exactly once), instance (process memory only, restart re-shows), always (no close button rendered; store writes accepted but ignored — immune by construction) - ctaLabel+ctaUrl pair (http(s) only, <=200 chars), rendered as target=_blank rel=noopener anchor Merge/sort contract: local (B) shadows builtin (A) on equal id — tested that a shadowed id renders exactly once as source=local — then priority desc with stable order (builtins first among ties). mergedNotices() feeds both /__bili/notices and the overview field; the dismissal endpoint routes by the record's declared mode before touching persistent state. Store (src/pck-notice.ts): hashed keys id@16hex now admissible alongside the two builtin ids; write is bounded to 200 keys with builtin ids protected from trimming (regression-tested at 210+ hash keys). Client: bili_pickLoc picks zh-CN/en/any-value off the wire locale objects with the t() fallback chain; banner builder gains optional CTA anchor and hide-dismiss (all content through escapeHtml — zero new XSS surface). i18n: one shared notices.dismiss_btn label added (zh/en parity checked by suite). Evidence: typecheck clean, build clean, unused-exports gate clean; 51/51 targeted green (new tests/local-announcements.test.ts = 10 tests, every gate asserted both directions per #3617; plus notice-registry, wm-fastpath-intro-notice, issue5079-pck-default-off incl. live-proxy e2e, web-i18n dead-key guard, web-update-banner, web-relay-warn).
📦 Built Package ArtifactBranch: Latest build — install this exact versionnpm install -g billion-context@0.0.7931-pr.5570.f11e797This exact version is immutable and reproducible. The rolling Recent builds (commit → version)
Download artifact
tar xzf billion-context-pr5570.tgz
npm install -g packageInstall instructions are refreshed on each push. |
[bot] 🏷 查重结论:重复 #5561 —— 同源(#5548 已批准的 v2 设计 P2)、同机制(往 config 旁丢 JSON → 校验 → 走统一注册表循环出横幅)、不同实现。先来后到:P2 位上 #5561(Nov 2 17:11)早于本 PR(Nov 3 01:37)约 8 小时,按规则应保留先到、关闭本 PR。关闭权在 owner,我已打 背景——两条并行系列从未互相对账:
P1 位的跨系列冲突(#5554 vs #5557)超出本条线裁决范围,见随后的决策请求。 独立审核(对 head
小疵(不阻塞):pck-notice 裁剪注释说「oldest first」但实现按字典序裁第一个非内置键(没有时间戳存在,注释与事实不符,一行注释修正);built-in 在 mergedNotices 里平铺 priority 50(P1 条目没有 kind/priority 字段,属合理解释,同级保持注册表顺序且有测试钉住);CTA url 帽实际允许 ~206 字符,比设计 ≤200 略宽(可忽略)。 做对的点(供决策参考):零新增 XSS 面(escapeHtml 覆盖 &<>"' 五字符,CTA href 安全);坏文件隔离 / B>A 遮蔽恰好一次 / 200 键上限+内置保护均有回归测试钉住;复用既有 结构化八问
收工判定:建议 owner 将本 PR 按重复关闭,Y 系列按 #5557 → #5561 → #5563 → #5565 顺序合并即可收工;若更看重 X 系的 install-id 复用方案,先修上述四处红再谈。标签:duplicate + scope/ui + p2 + needs-decision(理由:重复判定成立、web 面板面、含默认可用性功能缺口、等待系列取舍拍板)。 |
|
(ai: 已并入 #5575 —— 按 owner 裁定不再拆分,本 PR 作废。) |
#5548 分期实施 P2:本地 JSON 公告(设计见 issue,owner 已批准)。
用户视角
往
~/.config/billion-context/announcements/丢一个 JSON 文件,web 面板顶部就多出对应横幅;删文件即消失。团队内部通知从此零代码、零发版。 每条文件一份,坏文件只杀自己那条。{ "id": "team-maintenance", "kind": "warning", "title": { "zh-CN": "维护窗口", "en": "Maintenance" }, "desc": { "zh-CN": "今晚 23:00–24:00 短暂不可用。", "en": "Brief outage tonight." }, "versions": [">=0.1.210 <0.1.220"], "percent": null, "dismiss": "persistent", "since": "...", "until": "..." }设计决策逐条落地(对照批准的 v2 设计)
versionsAND 列表:精确 / semver range(>=a <b、^、~)/[lo hi] || [x y]区间列表;构建版本解析不出来时带版本限制的公告一律隐藏(宁缺毋滥)id@内容hash16(改内容只重弹一次);instance = 仅进程内存(重启再弹);always = 不渲关闭按钮且对存储免疫(构造上成立)改动面
src/announcements.ts(新):校验器 + 全部 gate +mergedNotices()(overview 与 GET /__bili/notices 共用同一载荷)src/server/admin.ts:两个载荷点切到mergedNotices();dismiss 端点先按记录的 mode 路由(always=静默 no-op,instance=内存),再落存储src/pck-notice.ts:存储放行id@16hex键 + 200 条上限裁剪(内置 id 受保护,210+ 键回归测试)src/web/client.ts:bili_pickLoc(多语言对象按 UI locale 选取,t() 同款回退链)+ 模板加可选 CTA / hide-dismiss;全部走 escapeHtml,零新增 XSS 面notices.dismiss_btn(zh/en)tests/local-announcements.test.ts(新,10 测试):每个 gate 双向断言(Test Blind Spot Retrospective: Reinforcing Negative Assertion Discipline, Tightening Fake Upstream Strictness, Thin Inst | 测试盲区复盘:补反向断言纪律、假上游严格化、薄安装冒烟、形态矩阵与生命周期覆盖 #3617 纪律)证据
typecheck / build / unused-exports 门全干净;定向套件 51/51 绿(含 #5079 live-proxy e2e、web-i18n 死 key 守卫)。
P3(远程 GitHub raw + CI 校验门)、P4(文档收口)另开 PR。