Imported from StrayDragon/crystalith (
apps/web/AGENTS.md). Install upstream withnpx skills add StrayDragon/crystalith --skill web. Copyright stays with the author.
apps/web — Rsbuild (Rspack) + React + TypeScript SPA
Project Structure & Module Organization
- Source code:
src/ - Static assets:
public/ DESIGN.md— Crystalith Web 设计系统(token + 视觉原则;改 UI 观感前先读)src/features/workspace/— main workspace featuredomains/— business domains (notebooks, sessions, messages, sources, outputs, refine, studio, research)layout/,shared/,app/— workspace scaffolding
src/features/research-lab/— Deep Research 产品 Lab(Eden ResearchRun + SSE)src/features/research-lab-demo/— Deep Research 演示 Lab(xlsx-lib / fake;仅 DEV 或VITE_LAB_DEMO=1)src/api/— API client:Edentreaty<App>(eden.ts)一等src/shared/— shared utilities, Layer system, types
Design System
- SSOT 文档:包根
DESIGN.md(YAML tokens + 中文原则) - 实现侧:Tailwind /
src/app/tailwind.css、src/shared/ui基础件(展示件自研,Menu/Popover/Tooltip 为 Radix headless + 样式壳);图标可用@mui/icons-material。新表面优先对齐 DESIGN token,避免另起一套色板。
Research Lab(产品 Eden;演示隔离)
/research-lab/:nid(及 /report)是深研产品主表面,仅 Eden ResearchRun + SSE(useEdenLabController / EdenLabReportPage)。
产品 /research-lab |
演示 /demo/research-lab(DEV || VITE_LAB_DEMO=1) |
|---|---|
POST …/research · GET …/research list |
fixtureLabSessionPort demo 列表 |
Run 图 SSOT + SSE graph_patch |
fake/* + phase 定时回放 |
useEdenLabController · ?rid= 切换 Run |
useLabController · sessionStorage |
| 服务端 report + checkpoints | labSession / revisions |
演示路由在 App.tsx 中仅当 isLabDemoMode()(import.meta.env.DEV || VITE_LAB_DEMO === '1')注册;生产默认构建不挂载。流程见 .agents/skills/cl-prd-demo/SKILL.md。
闭环入口:头像旁烧瓶(任务抽屉)→「新建研究」→ Compose → 创建 Run 并 stream。笔记本切换器旁不再放直达烧瓶。入口 MUST 只导航产品 /research-lab,不得链到 /demo/...。
辅助入口(c99)— workspace chat slash(不取代烧瓶 / Compose / 任务抽屉):
| 命令 | 行为 |
|---|---|
/research · /深研 |
打开 /research-lab/:nid Compose(不创建 Run) |
/research <topic> · /深研 <topic> |
同上,并用 ?topic= 预填主题 |
/research-open <rid> |
打开 Lab 并带 ?rid= |
发送时吞掉(不进 QA)。命令列表来自 GET /v2/commands(kind: 'nav')。不做 @ 提及。
- 共享 Lab model:类型与纯函数 SSOT 在
research-lab/model/(types、layout、graphMutations、report*、node chat、pruneClosure 等)。Demo 不得作为产品类型 SSOT;产品不得 importresearch-lab-demo/fake/*。Demo 直接从产品model/导入;不要为已迁入model/的模块再写fake/re-export shim。 - 剪枝闭包 B 在
research-lab/model/pruneClosure(与 servercollectResearchPruneClosure对齐);demofake/deriveLabState内部复用之。变更走llmanspec/changes/update-research-prune-cascade LabController接口在research-lab/model/labController(Eden / demo 各自实现);demo 页从该模块 import 类型,勿从useLabController再导出- 展示层(
LabGraph、Compose、任务抽屉壳)可被 demo 复用;产品页无LabController | Eden联合分支 - 顶栏搜索仅为 Fast 网搜;勿恢复为深研主入口
Build, Test, and Development Commands
bun install # Install dependencies
bun dev # Rsbuild dev server (:3000; chunk-level HMR)
bun test # Rstest (watch mode)
bun run test:ci # Frontend Rstest CI suite (also via root `just test-web` / `just qa`)
bun run test:core # Minimal UI core regression suite
bun run lint # Incremental oxlint
bun run lint:all # Full oxlint
bun run format # oxfmt
bun run format:check # Format check (no write)
bun typecheck # TypeScript typecheck
bun run build # Production build → dist/
bun preview # Preview production build
API Client
- Primary: Elysia Eden RPC via
src/api/eden.ts(treaty<App>,零 codegen) - Types: 优先 Eden 推断;跨端标称类型用
@crystalith/shared;UI-only 类型放features/workspace/shared/types.ts - OpenAPI: 浏览器用
/openapi;其他语言 client 从/openapi.json衍生,不反向生成一等 TS client - Removed:
api/generated/(c14)、api/shared-types.ts(P1.4)
Coding Style & Naming Conventions
i18n 双字典(有意为之,D4 决策记录):UI 文案走本包 src/shared/i18n.ts 手写字典;
Zod schema 描述走 @crystalith/shared 的 desc()(zh/index.json SSOT)。两套键空间
互不重叠、各自类型安全。出现第二语言需求时再做运行时 locale 切换与字典合并设计,
当前单 zh 场景不引入 i18n 运行时框架。
- TypeScript/React: 2-space indentation
- Components:
PascalCase - Hooks:
useX - Tests:
*.test.tsx(colocated with source) - CSS/Tailwind: global styles in
src/app/index.css; feature styles alongside components - Formatter:
oxfmt— keep reformatting scoped - 拆分准则:见根
AGENTS.md「Component / module split」。默认不拆单处使用的小 JSX;优先 hook + 少数大步组件 / 可复用模块。
Layer System (z-index Management)
Never use hardcoded z-index values. Use the unified Layer system:
| Level | Value | Usage |
|---|---|---|
base |
0 | Normal content |
dropdown |
100 | Dropdown menus (MenuList) |
popover |
200 | Popovers (PopoverContent, Select menus) |
modal |
300 | Modal dialogs |
toast |
400 | Toast notifications |
tooltip |
500 | Tooltips (always on top) |
// React components — use the hook
import { useLayer } from '../shared/layer';
const { style } = useLayer('modal');
// shared/ui overlays already call useLayer internally
import { MenuList } from '../shared/ui';
LayerProvideris already wrapped inApp.tsx- Each level has 100 slots for future expansion
Testing Guidelines
- Rstest + React Testing Library (
rstest.config.tsextendsrsbuild.config.ts) - Colocate tests with
*.test.tsxnaming - Run targeted tests for changed areas
test:ciruns deterministic quality gate;test:corecovers critical paths
Configuration & Security
- Settings via
config/app.yaml - Never commit API keys or tokens