diff --git a/BLOCKED.md b/BLOCKED.md index 9d0f073a..df1e169c 100644 --- a/BLOCKED.md +++ b/BLOCKED.md @@ -8,7 +8,7 @@ - **`web/index.html`、`web/rectification.html`、`web/evidence_packet.html` 不得按“无人引用”删除。** `scripts/jyotish_api_server.py` 仍把它们当作 `/`、`/evidence`、`/rectification` 的调试页;`tests/test_api_async_job_contract.py` 锁路径。任务书全仓检索漏了 Python API。移到 `scripts/` 必须改 `tests/**` 既有断言,本轮禁止。它们不是产品 UI,继续留在 `web/`。 - **admin 已登录目视(`/admin/users|orders|roles|feature-flags`)未做。** 执行环境没有受控后台会话,与上方 staging 两角色冒烟同一缺口。本轮用 `next build` 客户端 CSS 清单代替:上述四页不再包含 globals(`message-list`)那 33.7 KB gzip,只剩 admin.css 1.8 KB + Inter `@font-face` 0.8 KB = 2.5 KB。登录后的视觉塌陷仍需人补一眼。 -- **`error.tsx` / `not-found.tsx` 不能 import `site-styles`。** 它们挂在根布局段,import globals 会把聊天 CSS 重新打进每一条 admin 路由。根错误/404 因此不再带 Tailwind 工具类的编译产物;有 `Button` className,但 admin 不依赖这两页的观感。真要恢复这两页的工具类样式,必须另开一条不与 admin 共享的布局链(会移动 `app/page.tsx`,与既有源码锁冲突)。 +- **`error.tsx` / `not-found.tsx` 不能 import `site-styles`。** 它们挂在根布局段,import globals 会把聊天 CSS 重新打进每一条 admin 路由。样式用内联 `style` + fallback 色值(与 `global-error.tsx` 同一模式),不必另开布局链,也不必移动 `app/page.tsx`。见 2026-08-29 收尾与 BUG-433。 - React Compiler 仍关闭,见下方 2026-08-17 记录。本轮未重开。 ## React Compiler 接管 page.tsx(2026-08-17,分支 codex/react-compiler-20260817) diff --git a/PROGRESS-frontend-followup-20260829.md b/PROGRESS-frontend-followup-20260829.md new file mode 100644 index 00000000..5c9fdabd --- /dev/null +++ b/PROGRESS-frontend-followup-20260829.md @@ -0,0 +1,113 @@ +# PROGRESS · 前端优化九条收尾(2026-08-29) + +工作树:`.worktrees/frontend-followup-20260829` +分支:`codex/frontend-followup-20260829` +基线开工:`origin/staging` @ `31b54aa7`(任务书写的 `3198fb6b` 之后已有 BUG-431;开工后远端又快进了 `86d6923e` 生时校正死锁修复)。交付前 rebase 到最新 `origin/staging`。 + +不要写成 `PROGRESS.md`。 + +## 任务 A · 根 404 / 错误页内联样式 + +`error.tsx` / `not-found.tsx` 改为与 `global-error.tsx` 相同的内联 `style` + fallback 色值(已核 `globals.css` `:root`:`--color-canvas: #fbfaf7`、`--color-ink: #1d1d1f`、`--color-ink-secondary: #5f5f59`、`--color-border: #d8d6cf`、`--color-action: #85432f`、`--color-danger: #9a2f2f`)。未 import `site-styles` / `globals.css`。 + +404 为保住 `root-error-boundaries-contract` 的 `render={}` 锁,用 `cloneElement` 套内联样式,不引入 `Button`。`error.tsx` 仍 `"use client"`,`console.error` 未动;重试用原生 ` - + 返回对话 - + ); diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index ba5da917..01af899a 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -364,12 +364,11 @@ button:disabled { cursor: default; opacity: .45; } .birth-place-status { min-height: 20px; display: flex; align-items: flex-start; gap: 6px; margin: 0; color: var(--color-ink-tertiary); font-size: 11px; line-height: 1.5; } .birth-place-status.is-ready { color: var(--color-success); } .birth-place-status.is-error { color: var(--color-danger); } -.birth-place-status > svg { width: 14px; height: 14px; flex: 0 0 auto; margin-top: 1px; } +.birth-place-status > svg, +.birth-place-status > .inline-spinner { width: 14px; height: 14px; flex: 0 0 auto; margin-top: 1px; } .birth-place-status > span { min-width: 0; text-wrap: pretty; word-break: auto-phrase; } -.birth-place-status .is-spinning { animation: birth-place-spin .8s linear infinite; } .birth-place-retry { display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto; border: 0; background: transparent; color: var(--color-action); font: inherit; cursor: pointer; text-decoration: underline; text-underline-offset: 2px; } .birth-place-retry svg { width: 12px; height: 12px; } -@keyframes birth-place-spin { to { transform: rotate(360deg); } } .save-profile { justify-self: end; } .form-error { background: var(--color-danger-muted); color: var(--color-danger); } .form-success { background: var(--color-success-muted); color: var(--color-success); } @@ -392,7 +391,7 @@ button:disabled { cursor: default; opacity: .45; } .new-chat:not(:disabled):active, .session-main:not(:disabled):active, .session-menu-trigger:not(:disabled):active, .session-action-item:not([data-disabled]):active, .profile-trigger:not(:disabled):active, .credit-button:not(:disabled):active, .account-menu-item:not([data-disabled]):active, .starter-list > button:not(:disabled):active, .composer button:not(:disabled):active, .button-primary:not(:disabled):active, .button-secondary:not(:disabled):active, .dialog-close:not(:disabled):active, .generated-list button:not(:disabled):active, .inline-actions button:not(:disabled):active { transform: scale(.96); } @keyframes app-loading-orbit { to { transform: rotate(360deg); } } -@keyframes pulse { from { opacity: .28; transform: translateY(1px); } to { opacity: 1; transform: translateY(-1px); } } +@keyframes inline-spin { to { transform: rotate(360deg); } } @keyframes message-enter { from { opacity: 0; transform: translateY(4px); } } @keyframes onboarding-card-enter { from { opacity: 0; transform: translateY(6px); } } @keyframes onboarding-caret { 50% { opacity: 0; } } @@ -439,8 +438,26 @@ button:disabled { cursor: default; opacity: .45; } .auth-brand { margin-bottom: var(--space-6); } } +.inline-spinner { + display: inline-block; + box-sizing: border-box; + flex: 0 0 auto; + vertical-align: middle; + border: 2px solid color-mix(in srgb, currentColor 22%, transparent); + border-top-color: currentColor; + border-radius: 50%; + animation: inline-spin 0.8s linear infinite; +} + @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto !important; transition-duration: .01ms !important; transition-delay: 0s !important; } + .inline-spinner { + animation: none !important; + border: 0 !important; + background: currentColor; + width: 8px !important; + height: 8px !important; + } .model-selector-popup[data-starting-style], .model-selector-popup[data-ending-style], .session-actions[data-starting-style], .session-actions[data-ending-style], .account-menu-popup[data-starting-style], .account-menu-popup[data-ending-style], [role="tooltip"][data-starting-style], [role="tooltip"][data-ending-style] { transform: none; } .auth-step { transform: none; transition: opacity 80ms linear !important; } @starting-style { .auth-step { opacity: 0; transform: none; } } @@ -995,12 +1012,6 @@ button:disabled { cursor: default; opacity: .45; } .consultation-run-timeline__details[open] > summary > .consultation-run-timeline__chevron { transform: rotate(180deg); } -.consultation-run-timeline__spinner { - animation: consultation-run-spin 0.8s linear infinite; -} -@keyframes consultation-run-spin { - to { transform: rotate(360deg); } -} .consultation-run-timeline__body { display: grid; gap: var(--space-2); @@ -1775,16 +1786,6 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class color: var(--color-ink-tertiary); } -@media (prefers-reduced-motion: no-preference) { - .product-entrypoint-copy p[aria-busy="true"] { - animation: daily-starlanguage-pending 1.6s ease-in-out infinite; - } -} - -@keyframes daily-starlanguage-pending { - 50% { opacity: .55; } -} - .product-entrypoint-footer { display: flex; align-items: flex-end; diff --git a/frontend/src/app/not-found.tsx b/frontend/src/app/not-found.tsx index 0a493cfd..ab217c45 100644 --- a/frontend/src/app/not-found.tsx +++ b/frontend/src/app/not-found.tsx @@ -1,19 +1,78 @@ +import { cloneElement, type CSSProperties, type ReactElement } from "react"; import Link from "next/link"; -import { Button } from "@/components/ui/button"; +const canvas = "var(--color-canvas, #fbfaf7)"; +const ink = "var(--color-ink, #1d1d1f)"; +const inkSecondary = "var(--color-ink-secondary, #5f5f59)"; +const border = "var(--color-border, #d8d6cf)"; +const focus = "var(--color-focus, #85432f)"; + +const actionStyle = { + alignItems: "center", + background: canvas, + border: `1px solid ${border}`, + borderRadius: "12px", + color: ink, + display: "inline-flex", + fontSize: "14px", + fontWeight: 500, + justifyContent: "center", + minHeight: "44px", + minWidth: "88px", + padding: "0 20px", + textDecoration: "none", +} as const satisfies CSSProperties; + +function HomeAction({ + children, + className, + render, + style, +}: { + children: string; + className: string; + render: ReactElement<{ className?: string; href: string; style?: CSSProperties }>; + style: CSSProperties; +}) { + return cloneElement(render, { className, style }, children); +} export default function RootNotFound() { return ( -
-

404

-

页面不存在

-

+

+ +

+ 404 +

+

页面不存在

+

该地址不存在、已下线,或在当前域名下不可用。可以返回对话首页继续。

-
- +
); diff --git a/frontend/src/app/reports/[reportId]/loading.tsx b/frontend/src/app/reports/[reportId]/loading.tsx index b023d0df..ca74f1c9 100644 --- a/frontend/src/app/reports/[reportId]/loading.tsx +++ b/frontend/src/app/reports/[reportId]/loading.tsx @@ -1,10 +1,10 @@ -import { LoaderCircle } from "lucide-react"; +import { InlineSpinner } from "@/components/inline-spinner"; import "../../site-styles"; export default function ReportLoading() { return (
-