From 088735b948d46896b8af30efcb0a2dc5d362b97f Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Fri, 18 Sep 2026 08:27:41 +0800 Subject: docs(release): 全周期交接文档入库(含 ui-shadcn 迁移交付说明) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [变更性质] 纯文档提交,无运行时逻辑。 [文档内容] 补齐此前各轮未入库的交接/验收文档:backend-auth/backend-domain/ domain-authorization-user(认证与授权域)、etf-recovery-release- handoff(ETF 修复 + ops 演练定稿与生产部署命令)、recovery-* 系列、 frontend/parent-ui-findings(UI 迁移上下文)、worker/integration 等, 以及本轮 docs/ui-shadcn-handoff.md(shadcn-svelte 迁移交接,含 Chart.svelte 契约、runes $state 踩坑记录与 375/768/1440 验证证据)。 [更新方案] 按主题分文;每份文档只记录可复现的命令、验证结果与语义边界, 不导出密钥或生产敏感路径。 [影响范围] 文档渠道:后续 leader/client 审阅入口;与代码提交一一对应便于回溯。 --- docs/frontend.md | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 docs/frontend.md (limited to 'docs/frontend.md') diff --git a/docs/frontend.md b/docs/frontend.md new file mode 100644 index 0000000..255c04b --- /dev/null +++ b/docs/frontend.md @@ -0,0 +1,64 @@ +# docs/frontend.md — 策研 Strategy Lab 前端实现记录 + +Last updated during frontend delivery. Scope: `frontend/` only. Styling: Svelte 5 + TypeScript + Vite, hash routing, CodeMirror 6 + @codemirror/lang-python editor, @codemirror view/state, `diff` for versions, ECharts (SVG renderer, dynamically imported chunk), no other runtime deps. + +## Page map + +- Routes (hash): `#/login`, `#/register`, `#/reset`, `#/projects` (list/create), `#/projects/:id/{data,strategy,backtest,results}`, `#/datasets`, `#/runs`, `#/usage`, `#/account`, `#/admin`. +- Sidebar project tabs mirror spec 数据 / 策略 / 回测 / 结果; global nav = 我的项目 / 数据集 / 实验记录 / AI 用量; bottom = 账户与安全 / 管理员. +- "Admin" nav is only shown when `session.me.role === 'admin'`; non-admin is redirected to `#/projects`. +- App guards: `loadSession` is required once before LAYOUT routes; if no session, redirect to login screen. PublicAuth routes bypass. +- Distinct route-load flow avoids the old bug where root rendered the protected Workspace without login. + +## Files + +- `src/App.svelte` global router mount/redirect; `src/components/Layout.svelte` sidebar; `src/components/Modal.svelte`, `Loading.svelte` +- `src/pages/…` +- `src/lib/api.ts` thin `fetch` client; `src/lib/state.ts` API error extra messages; `src/lib/session.svelte.ts` **runes module** (`.svelte.ts`); same for `src/lib/listsStore.svelte.ts` +- Editor: `src/components/CodeEditor.svelte` uses `@codemirror/lang-python`; diff view `DiffView.svelte` based on `diff`; chart `Chart.svelte` lazy-loads `echarts` on result views. +- scripts/browser-smoke.mjs — Playwright smoke with real pages; can be pointed at the QA URL. + +## Build/test commands + +- `npm run test` — 21 vitest (TDD first) — this does not require a server. +- `npm run check` (svelte-check) — 0 errors / 0 warnings currently. +- `npm run build` — production build in `frontend/dist/`. + +## Visual + +- Modern light teal & slate design; no dark glass or dashboard fluff. System font stack: `-apple-system, SF Pro Text, ……, Noto Sans SC` fallback for CJK; no bundled licensed fonts. + +## State views + +- Every qualifying mutator (login, register, editor autosave, run lifecycle, datasets - wizard) shows loading / error / empty / success banners from backend error codes and shapes `{error:{code,message,details?}}`. Mutations that would be performed by non-admin users get a 403 with the honest reason instead of a dead button. + +## Backend contract notes + +This is the working snapshot of API routes the frontend uses (all prefixed `/api`); mismatches with SPEC/partial server are called out below so the backend team can reconcile. + +1. `GET /health` and `GET /capabilities` are public; frontend will show "本服务内部使用" labels and use `worker_available`/`ai_configured` flags to gate Run and AI panels. Server leaks no internal path/keys. +2. `POST /auth/login`, `POST /auth/logout`, `GET /auth/me` — per SPEC & amendment. `GET /auth/me` returns `{user}` where `role`/`active` are surfaced. Registration is `POST /auth/register {invite_token,name,email,password}` using a URL-supplied `?invite=` token (password never in URL). `POST /auth/reset-password {token,new_password}` requires the admin-issued token. +3. Sessions: `GET /auth/sessions` items `{id,created_at,last_seen_at,user_agent?,current?}` — amend: expose `current` so UI can label "当前会话"; if not provided we derive none. `DELETE /auth/sessions/:id` (404 if foreign). +4. Draft/save semantics: `PUT /projects/:id/draft {code,expected_generation}`; HTTP 409 with `code:'stale_generation'` triggers the compare-and-choose conflict UI; spec-compliant. +5. Versions: `POST /projects/:id/versions` with `{message}`. `GET /projects/:id/versions` returns `{items:[…]}`. `GET /projects/:id/versions/:versionId → {code}` — **frontend-implied extra**; without it the version diff/restore flows degrade to metadata-only; backend should confirm (added to `docs/backend-questions.md` needs backend info). **Note to backend reviewer: no secret content, full code only for the owner.** +6. Restore: `POST /projects/:id/restore {version_id,expected_generation}` returns Project with the new draft generation. +7. Datasets: `GET /datasets`, `POST /datasets`, `GET /datasets/:id`. Status polling every 2s only while `pending|running`. **DELETE /datasets is NOT implemented** in the UI (not in contract). `GET /datasets/:id/preview` shape `{columns,rows,coverage,warnings}` where each coverage row is `{instrument,actual_start,actual_end,row_count,warnings?,requested_start,requested_end,market?,asset_type?}`; the UI synthesizes warnings like "标的 X 起始数据较其他标的更晚" from those values and lists per-instrument lecturer warnings — keep these field names. +8. Runs: list/filter with plain `?project_id=`; `POST /runs` body includes optional `acknowledge_warnings` (sent only when the dataset preview reported coverage warnings), `parameters` as object. Cancel is `POST /runs/:id/cancel`; rerun is `POST /runs/:id/rerun {use_original_data:true}`. Statuses surfaced in Chinese: queued/running/succeeded/failed/cancelled. +9. AI: `POST /ai/assist {project_id,instruction,expected_generation}` → `{id,model,explanation,proposed_code,diff,base_generation,usage:{input_tokens?output_tokens?},status}`. Accept is `POST /ai/:id/accept {expected_generation}`; deliberate 409 stale-guard message shown when generation moved. The `diff` field is optional; UI computes it locally from `proposed_code` when missing. +10. AI usage: `GET /ai/usage` `{items,totals,internal_poc}` — usage line is metering only; costs/prices are NOT displayed. +11. Admin: `GET /admin/users`, `PATCH /admin/users/:id {active?,role?,daily_run_limit?,ai_enabled?}`; last-active-admin is protected server-side (we also guard in UI); `POST /admin/users/:id/reset-password → {reset_token,expires_at}`; invitations `POST/GET /admin/invitations` and `DELETE /admin/invitations/:id`; `GET /admin/audit` sanitized `{items:[{actor,action,target,status,time}]}` — we render `ok/success` as ok; frontend tolerates `status` values. +12. `GET /instruments?q=…` — `{items,source,status}`; when the catalog has nothing we surface a link to the "手动录入标的" form; **never invented identities**. `POST /datasets` requires the fields `[open,high,low,close,volume]` for the engine plus any user-checked raw fields. + +## Integration/QA notes + +- Vite dev proxy `/api` → `127.0.0.1:8787` is configured in `vite.config.ts` (parent QA server on 8788 already reloads these changes). +- browser-smoke (scripts/browser-smoke.mjs) checks login/register/reset + the protected routes; screenshot evidence in `artifacts/qa` should be added by the integration agent when backend is live. +- No fake endpoints are wired; empty/error states are behavioral (no stub data). The frontend never fabricates metrics; the "还没有…" empty-states reflect a genuinely empty backend list rather than mocked fixtures. +- The run engine warnings/limitations are always shown with respect to the current `runStatusBadge/Label` mapping — no `metricLock` reference to suppress real issues. +- ECharts is **only** loaded when a result/comparison view opens (dynamic `import()` inside `Chart.svelte`) → it lands in a separate ~1.2MB chunk that loads on demand; the main bundle is ~500 kB minified (gzip ~175 kB). +- All network failures turn into `网络请求失败` banner + retry-friendly state; no silent swallowing, no dead buttons: each disabled button exposes a tooltip or hint on what needs to happen, or the attempt surfaces the returned error. + +## Known remaining edge cases (honest list) + +- Chart rendering: echarts svg renderer, mobile-sized charts can look cramped; desktop is the intended editing surface (responsive 375px viewing is supported but charts prefer ≥ 720px). +- If backend ships additional fields (e.g. `Dataset.progress`, version "comment" flows) the UI will ignore unknown fields gracefully but they should be re-added when stabilized. -- cgit v1.2.3