fix(ux): surface hidden notices, bound report waits, add root boundaries
Independent Staging Quality Gate / validate (push) Successful in 12m8s
Independent Staging Quality Gate / publish (push) Successful in 14m29s

Framework-level UX fixes found while auditing staging (BUG-216..220).

- chat: route 44 previously discarded composer notices to sonner with
  dedupe, so recovery, cancel and archive feedback is actually visible
  (BUG-216)
- chat: anchor stream auto-scroll to bottom proximity and add a
  jump-to-latest control, so reading history is no longer interrupted
  on every token (BUG-218)
- reports: replace the silent 120s poll cutoff with an explicit
  timed-out state, an 8m budget, stepped backoff and an elapsed
  counter (BUG-217)
- reports: pause polling while the tab is hidden, via a shared hook
- app: add root error, global-error and not-found boundaries (BUG-219)
- admin: add antd SSR style extraction and the React 19 render adapter,
  and move admin-only css out of the global stylesheet (BUG-220)
- membership: run bootstrap fetches concurrently and pause payment
  polling while hidden
- build: configure optimizePackageImports

Verified on top of 2d370f2e: tsc, eslint, next build, and the related
frontend contract suites.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-17 13:14:15 +08:00
co-authored by Cursor
parent 2d370f2e9d
commit 9c296f1e3f
23 changed files with 1144 additions and 88 deletions
@@ -0,0 +1,69 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
const read = (relative: string) => readFileSync(new URL(relative, import.meta.url), "utf8");
const error = read("../src/app/error.tsx");
const globalError = read("../src/app/global-error.tsx");
const notFound = read("../src/app/not-found.tsx");
const chinese = /[\u4e00-\u9fff]/;
test("the app root ships error, global-error and not-found boundaries", () => {
for (const source of [error, globalError, notFound]) {
assert.ok(source.length > 0);
assert.match(source, /export default function \w+\(/);
}
});
test("root error boundary is a client component that can reset and route home", () => {
assert.match(error, /^"use client";/);
assert.match(error, /reset,\s*\}: \{[\s\S]*reset: \(\) => void;/);
assert.match(error, /onClick=\{\(\) => reset\(\)\}/);
assert.match(error, /href="\/"/);
assert.match(error, /error\.digest/);
assert.doesNotMatch(error, /error\.stack/);
});
test("global error boundary replaces the root layout without external styling", () => {
assert.match(globalError, /^"use client";/);
assert.match(globalError, /<html lang="zh-CN">/);
assert.match(globalError, /<body/);
assert.match(globalError, /<\/body>/);
assert.match(globalError, /<\/html>/);
assert.doesNotMatch(globalError, /^import /m);
assert.doesNotMatch(globalError, /className="[a-z-]*(flex|text-|bg-|min-h-)/);
assert.match(globalError, /minHeight: "44px"/);
assert.match(globalError, /onClick=\{\(\) => reset\(\)\}/);
assert.match(globalError, /window\.location\.assign\("\/"\)/);
});
test("root not found stays a server component and links back to the chat home", () => {
assert.doesNotMatch(notFound, /"use client"/);
assert.doesNotMatch(notFound, /useState|useEffect|onClick/);
assert.match(notFound, /404/);
assert.match(notFound, /render=\{<Link href="\/" \/>\}/);
});
test("every boundary announces itself accessibly with one heading", () => {
for (const source of [error, globalError]) {
assert.match(source, /role="alert"/);
}
for (const source of [error, globalError, notFound]) {
assert.equal(source.match(/<h1/g)?.length, 1);
assert.doesNotMatch(source, /<h[3-6]/);
}
});
test("boundary copy is simplified chinese in the product voice", () => {
for (const source of [error, globalError, notFound]) {
const copy = source.match(/>\s*([^<>{}]*[\u4e00-\u9fff][^<>{}]*)\s*</g) ?? [];
assert.ok(copy.length >= 2);
for (const line of copy) {
assert.match(line, chinese);
assert.doesNotMatch(line, /[!!]/);
assert.doesNotMatch(line, /\p{Extended_Pictographic}/u);
}
}
});