Commit Graph

16 Commits

Author SHA1 Message Date
anonsaber 0b27ca03a6 ui: drop the build-version display and fix the callback card
The value rendered was nogit+<UTC build time>, which carried no
information for the operator, so remove all three display sites
(topbar next to the secure-channel note, setup card, page footer).
Clears the three querySelectors, renderVersion, and the two dead i18n
keys. /api/status is untouched and still returns version - the API is
now the only way to confirm a deploy landed.

The <footer> element is removed with its span rather than left empty:
it had no other child, and an empty footer would still render a
visible separator band (border-top plus 41px padding).

The callback section's field was the only one in the SPA sitting
directly on the panel, with no padded ancestor. It spanned the full
card width with zero side padding and pressed the button bar, while
the copy above it was indented 25px. Wrap it in .field-grid and pad it
to match its card siblings on both desktop and the 720px breakpoint;
.field-wide's grid-column now has a grid ancestor.

Rewrite the callback copy to name the Cloudflare Worker front proxy
explicitly instead of the bare word Worker, and state that registering
a new address retires the previous subscription.
2026-10-01 11:17:50 +08:00
anonsaber 0c84f8ef56 ui: simplify footer to build version 2026-10-01 09:23:33 +08:00
anonsaber af89a17f7f feat(auth): extend admin session to 30 minutes 2026-10-01 09:19:49 +08:00
anonsaber b410dc4257 ui: trim Chinese admin copy 2026-10-01 09:11:23 +08:00
anonsaber 7a27f7ff00 docs: add bilingual guides and callback setup in SPA 2026-10-01 02:36:03 +08:00
anonsaber 98d62e490c docs(spa): 登录卡文案去重,只改字符串不动结构与逻辑
授权卡里同一件事被讲了三遍:CONFIG_ENCRYPTION_KEY 在正文、占位符、
顶部横幅各出现一次;「不写 localStorage」在提示里说、在 setup 隐私说明里
又说一遍;「只在页面内存」在正文、提示、会话过期默认文案里各出现一次。

收敛到每句话只出现一处(正文讲动作与 TTL,占位符只提示粘贴,提示只讲
重新授权,存储与内存的事实保留在正文与 setup 隐私说明里):

- auth.bodyPre   输入启动必填的环境变量 -> 输入
- auth.bodyPost  服务端会签发有效期 15 分钟的管理会话;该值与会话只在当前页面内存中使用
                -> 管理会话有效期 15 分钟,只存在当前页面内存中
- auth.placeholder 输入 CONFIG_ENCRYPTION_KEY 的值 -> 粘贴该值
- auth.hint      不会写入 localStorage、sessionStorage 或 Cookie。关闭或刷新页面后需要重新授权
                -> 关闭或刷新页面后需要重新授权

EN 字典同步,web/index.html 里 3 处 data-i18n 回退文案跟着改。
键名、DOM 结构、data-i18n 键、逻辑与校验全部未动。
node --check web/config.js 通过;node --test web/config.test.mjs 6/6 通过。
2026-09-30 23:35:32 +08:00
anonsaber b764d6dc30 feat: reject stale business-config submits with a 409
revision becomes a request-body control field on BusinessConfigPatch. It is
never a setting: apply() and is_complete() ignore it, so the stored wire
never gains a revision key, and a PUT body that omits it keeps
last-write-wins for callers that never read the revision back.

The SPA records the revision it read, echoes it on every partial submit, and
on a 409 re-reads the stored values into the form and tells the operator to
recheck what they changed. Without that reload every retry would 409
forever; if the reload itself fails the SPA falls back to last-write-wins
rather than trapping the operator. The control field is excluded from the
empty-patch check so echoing it never counts as a change.

The compare-then-write is deliberately not atomic. Without a Lua script an
optimistic lock still has a residual window in which two requests racing
inside the same tick both pass and the later one wins; the window stays
small because a patch carries only the fields the operator actually changed.

notify.rs gained 18 lines before line 700, so its anchors in deployment.md,
opengaps.md and retired.md shifted by +18. Every one was checked against the
real line content, since the anchor audit only flags blank targets.

Gates: rust fmt/clippy clean, cargo test 87 passed 2 ignored, node --test
web/config.test.mjs 18 passed, docs_check/run_all.sh 6/6.
2026-09-30 02:56:15 +08:00
anonsaber 0cee50b4c9 feat: read business config back and accept partial patches
GET /api/business-config returns the stored values with presence-only secrets, and PUT now merges a patch against the stored config instead of replacing it, so the SPA can show what is already set and send back only the fields that changed.

The SPA marks secrets as set-but-unreadable, edits chat_allowlist as an unordered set, and omits fields it did not touch; blank required fields keep the stored value, blank optional fields clear it. The read-back is Serialize-only, so a client cannot inject secrets_present.

Forward both paths at the gateway: /api/business-config accepted PUT only, so the read-back returned 405, and the preflight path was missing from the safelist entirely, so it returned 404. The SPA is served from the Worker domain, so the whole edit experience was unreachable behind the production gateway. Route methods are now asserted per path, which the safelist test never checked.

Also: /debug/config reports timezone again, the timezone select is styled like the other fields, and the docs follow the new contract.
2026-09-30 00:53:58 +08:00
anonsaber 61de5f985f feat(spa): add a time zone selector to the business configuration page
Section 04 "Notification display" holds a single select populated from a
frozen catalogue of the 16 zones the server accepts, with Asia/Shanghai
selected by default. Labels are per-zone (`label.en` / `label.zh`) so they
follow the existing browser-language switch.

readBusinessConfig now submits the selection under the `timezone` key.
2026-09-29 21:48:04 +08:00
anonsaber fc686abc22 config: persist business config regardless of client build, add preflight probe
PUT /api/business-config and POST /api/bootstrap now treat validation as the only
write gate: a valid wire is always persisted, and only then are the clients built
and the running worker swapped. A failed build returns 200 with persisted=true,
runtime_applied=false and a warnings array of {component, step, detail} instead of
503, so an unreachable JMAP/LLM no longer turns a valid configuration into a silent
data loss. The previous worker keeps serving.

POST /api/business-config/preflight runs the same validation and client build
against the submitted wire without writing anything and without touching the
running worker. It answers 200 even when the configuration is bad, because that
is the answer the caller asked for; components is null after a validation
failure, and llm is null rather than vacuously ok when LLM is disabled.
Authentication is the admin-session gate, not DEBUG_TOKEN.

The SPA drops the account_id field (the backend still accepts it; omitted falls
back to the session primary account), adds a Test connection button, and shows
the build fingerprint in the footer, the topbar and the setup card. The
503 message no longer claims JMAP/LLM build failure, which is not what produces
it.

BusinessConfigWire.account_id gains #[serde(default)]; without it, omitting the
field in the wire body made deserialization fail outright.

build.rs bakes BUILD_VERSION (git sha or nogit, plus a UTC build timestamp) which
GET /api/status exposes as version.
2026-09-29 18:25:56 +08:00
anonsaber 638c3a8cbc docs: consume all four self-checks; make the file-size rule enforceable
Every member's audit is now read and each real finding fixed, not just reported.

AGENTS.md §2.1
- The 500-line soft cap now has a machine-checkable exception: an over-limit
  file must carry a one-line `SPLIT-EVAL:` marker stating the split decision.
- Six files were over the cap with no justification: notify.rs 2036, state.rs
  1586, worker.rs 1045, config.js 1079, client.rs 601, config.rs 521. All now
  carry the marker. client.rs was missed by my own earlier line count, which
  did not descend into src/domain/jmap/.
- New validator scripts/docs_check/check_file_size.py; GATE-DOCS 4/4 -> 5/5.
  It proves the marker exists, not that the written reason is sound.
- Re-based 144 line anchors across six docs for the +1 line each file gained;
  every shifted anchor verified against the code, not just for non-blankness.

audit-arch.md
- design.md §6.1 module matrix omitted the `debug` module entirely; added.
- Seven numeric duplications in design.md now defer to reference.md §6.1 as
  the single authority instead of restating the values.
- reference.md §5.1 was missing DEBUG_TOKEN; §5.3 listed six recognised names
  and guessed `JMAP_*`. It now lists all 20 exactly as config.rs reads them.
- Corrected two wrong claims in the audit itself: debug.rs is compiled
  unconditionally, not under #[cfg(debug_assertions)] (there are no cargo
  features at all), and /debug/* exposes 7 routes, not the invented session
  list / resend / rebuild surface.

audit-fileplanner.md
- reference.md cited a nonexistent `jmap_service.rs`; replaced with the real
  adapter chain worker.rs -> domain/jmap.rs -> domain/jmap/client.rs.
- cloudflare-worker/README.md was outside every validator's scope; all four
  doc validators now include it (208 section refs, 250 line refs).
- README.zh-CN.md stated only half of what README.md says about RUN_MODE; the
  forward-placeholder sentence is now bilingual.
- charter.md §7 boundary table was missing rows for itself and for web/.

audit-rust.md / audit-frontend.md
- Error envelope corrected: `error` is a bare machine code, no `message`.
- Status table, AI-consent labels and ~15 stale or phantom facts fixed against
  notify.rs; `TelegramClient::SendMessage` and `web_config` phantoms removed.
- GET|PUT /api/enabled forwarded by the worker so the SPA kill switch works.
- Ten UI strings that called the admin credential the Redis ACL password now
  say CONFIG_ENCRYPTION_KEY.
- POST /healthz returns 405; dead HTTP_STATUS removed.
- audit_anchors.py now flags identifiers that do not exist in the file they
  are anchored to.

charter.md
- `SAF-ENABLE-FLAG` registered (85 IDs); `SAF-ADMIN-SESSION` row corrected;
- `REQ-PUSH-TYPES` registered; the stale `admin_session_digest` removed.

Gates: GATE-DOCS PASS 5/5 (250 line-refs, 208 section refs, 0 errors);
GATE-P0 62 passed / 0 failed / 1 ignored, baseline unchanged; worker and SPA
36 tests, 0 failures.

Real Stalwart / Telegram / managed Redis verification stays blocked on missing
credentials and remains labelled as unverified.
2026-09-29 01:16:11 +08:00
anonsaber 6868d47cd0 feat(web): add SPA i18n with browser-language detection (zh/en, default en)
- detect locale from navigator.language (zh* -> zh, otherwise en) at startup;
  <html lang> switches accordingly; English is the default
- index.html carries English default text plus data-i18n /
  data-i18n-placeholder / data-i18n-aria attributes
- config.js holds inline en/zh dictionaries, t(key, vars) and detectLocale();
  dynamic Chinese strings become t() calls with {var} interpolation
- no new asset files (src/web.rs serves only config.js and styles.css);
  stays a build-less vanilla-JS single page
- config.test.mjs updated to the English defaults; adds dictionary key-parity
  and markup key-coverage tests
2026-09-27 12:53:36 +08:00
anonsaber 956ef6a1d3 feat(config): add global Redis-backed enable switch 2026-09-25 20:11:35 +08:00
anonsaber 9064fc5f1d fix: hide admin authorization in setup mode 2026-09-25 10:05:47 +08:00
anonsaber e5ec3aef4c feat: provide configuration setup mode 2026-09-25 09:30:45 +08:00
anonsaber dceecaf2f4 feat: initial MessageWeave release 2026-09-25 09:14:04 +08:00