diff --git a/.agents/skills/innoseed-apply/SKILL.md b/.agents/skills/innoseed-apply/SKILL.md new file mode 100644 index 0000000..56bf6ad --- /dev/null +++ b/.agents/skills/innoseed-apply/SKILL.md @@ -0,0 +1,89 @@ +--- +name: innoseed-apply +description: Change InnOSeed /apply form fields, tag encode/decode mirrors, interviewers, and Feishu Bitable-related apply payload handling. Use when editing src/content/apply.ts, pages/Apply.tsx, api/apply.ts, scripts/test-tag-codes.mjs, INTERVIEWERS, or tagcode interoperability. +--- + +# InnOSeed Apply + +`/apply` 表单、个性标签 encode/decode、面试官名单、以及三处镜像对齐的可执行流程。上线走 `innoseed-deploy`;招新**文案**若在 `site.ts` 的 Recruit 块,走 `innoseed-content`。 + +## 何时触发 + +- 改表单字段 / 步骤 UI(`Apply.tsx` + `apply.ts` 类型与选项) +- 改 `encodeTagCode` / `decodeTagCode` / `encodeApplyCode` / `decodeApplyCode` +- 改 `INTERVIEWERS` 或需要同步 `INTERVIEWER_MAP` env +- 改 `api/apply.ts` 的 `ApplyRequest` / 服务端校验 / Bitable 写入路径 +- 改 `scripts/test-tag-codes.mjs` 镜像或 `pnpm test:tag-codes` 失败 + +## 必读文件 + +| 文件 | 为何 | +| --- | --- | +| `src/content/apply.ts` | **浏览器侧单源**:categories / tags / INTERVIEWERS / encode·decode | +| `api/apply.ts` | Vercel Function;**不能**跨文件 import 浏览器模块,故含 inlined 副本 + `ApplyRequest` | +| `scripts/test-tag-codes.mjs` | Node 手写镜像;`pnpm test:tag-codes` 验三处对齐 | +| `src/pages/Apply.tsx` (+ CSS) | 4 步 UI / step state | +| `e2e/apply.spec.js` | mock `/api/apply`,勿打真后端 | +| `aliyun-fc/` | 飞书 Bitable 反向解码 webhook(只在此子目录维护) | + +## 提醒(详见 AGENTS.md) + +- payload 不可信:server-side **必须** re-derive tag indices。 +- `INTERVIEWER_MAP` 等 secret 只进 Vercel env,不进前端。 +- `applyHandle(request)` 签名不要为「图方便」改掉。 +- `api/apply.ts` 顶部的 `// mirror of src/content/apply.ts#...` 注释链保持可读。 + +## 分步流程 + +### 改表单字段 / 选项 / 类型 + +1. 改 `src/content/apply.ts`(`ApplyTag` / `APPLY_CATEGORIES` / 相关类型)。 +2. 同步 `src/pages/Apply.tsx` 的 UI 与校验文案。 +3. 同步 `api/apply.ts` 的 `ApplyRequest`(及任何 inlined 选项副本)。 +4. 若选项下标语义变了,更新 `scripts/test-tag-codes.mjs` 样例与断言。 +5. 跑 `pnpm test:tag-codes` + apply e2e。 + +### 改 encode / decode(最高风险) + +这两个(四函数)逻辑是**单源意图**,被三处消费: + +1. `src/content/apply.ts` — 浏览器 +2. `api/apply.ts` — Vercel Function(inlined 副本) +3. `scripts/test-tag-codes.mjs` — Node 手写镜像 + +**改一处 → 必须同步另两处。** 格式契约(勿无故破坏旧 code 可读性): + +- Tag code:`categoryIndex:tagIndices...` 段用 `;` 连接 → base64(`btoa` / `Buffer` 双运行时) +- Apply code:`{ivCode}|{base64TagCode}`;无面试官时 iv 占位 `_` + +改完: + +```bash +pnpm test:tag-codes +``` + +必要时同步 `aliyun-fc/` 若 webhook 内还有解码副本(先读该目录现有实现,勿假设无副本)。 + +### 改面试官 roster + +1. 只改 `src/content/apply.ts` 的 `INTERVIEWERS`(并同步 `api/apply.ts` 的 inlined 列表)。 +2. roster 的 code / 飞书 Person 映射变了 → 更新 Vercel 上的 `INTERVIEWER_MAP` env(**不要**写进仓库)。 +3. 跑 `pnpm test:tag-codes`(含 iv 组合样例时)+ `pnpm test:e2e`(apply spec)。 + +### 接 / 改飞书 Bitable + +1. 读 `api/apply.ts` 顶部注释列出的目标(Bitable / Airtable / Vercel KV / Notion);**Bitable 首选**。 +2. 保持 `applyHandle(request)` 入口稳定。 +3. 反向解码与多选字段写入优先落在 `aliyun-fc/`,不要把 FC 逻辑塞进前端。 + +## 本任务验证子集 + +```bash +pnpm typecheck +pnpm exec tsc -p tsconfig.node.json +pnpm build +pnpm test:tag-codes +pnpm test:e2e +``` + +- apply 相关 UI 断言以 `e2e/apply.spec.js` 为准(已 mock API)。 diff --git a/.agents/skills/innoseed-content/SKILL.md b/.agents/skills/innoseed-content/SKILL.md new file mode 100644 index 0000000..09b9c11 --- /dev/null +++ b/.agents/skills/innoseed-content/SKILL.md @@ -0,0 +1,70 @@ +--- +name: innoseed-content +description: Edit InnOSeed landing copy, SEO meta, NAV_LINKS, section anchors, and MINICAMP content in site.ts / index.html. Use when changing Chinese site copy, Hero/Footer/Recruit text, navigation entries, og tags, or Mini Camp page content (not host/DNS — that is innoseed-deploy). +--- + +# InnOSeed Content + +文案 / SEO / 导航 / section 锚点 / Mini Camp **内容块**的可执行流程。视觉 token 走 `innoseed-visual`;子域名 host 走 `innoseed-deploy`。 + +## 何时触发 + +- 改 `src/content/site.ts` 里的文案常量(Hero / Manifesto / Pillars / Members / Events / Recruit / Footer / MINICAMP 等) +- 改 `NAV_LINKS` 或新增/重命名 section 锚点 +- 改 SEO:`META`、`index.html` head、`og:*` / `meta description` +- 改 Mini Camp **页面文案/结构数据**(不是 DNS / hostname) + +## 必读文件 + +| 文件 | 为何 | +| --- | --- | +| `src/content/site.ts` | **唯一文案源**(`TAGLINE` / `META` / `NAV_LINKS` / 各 section / `MINICAMP`) | +| `index.html` | 静态 head:`og:description` / `meta description` / preload;改 Hero 文案时常要同步 | +| `AGENTS.md` §5 | 路由表与现有锚点列表 | +| `e2e/_shared.js` + 对应 viewport spec | 锚点 / 「渲染存在」回归 | +| `src/hooks/usePageMeta.ts`(若存在) | 路由级 meta 注入 | + +## 提醒(详见 AGENTS.md) + +- 文案只改 `site.ts`,不要在组件里散落硬编码中文(已有 `TAGLINE` 等提取约定)。 +- 新锚点必须:组件 `id` + `NAV_LINKS` + e2e 仍过。 +- 涉及 `spec.md` 视觉契约的改动转 `innoseed-visual`;PR 标题加 `[spec]`。 + +## 分步流程 + +### 改普通文案 + +1. 在 `site.ts` 找到对应导出(`HERO` / `RECRUIT` / `FOOTER` / `EVENTS` / `MINICAMP` …)。 +2. 若改的是 Hero / 品牌一句话:同步 `index.html` 的 `og:description` / `meta description`,并核对 `META`。 +3. 若改 Recruit 时间线 / FAQ:确认只在 `site.ts`(及 Recruit 组件读取处),不要复制第二份。 +4. 跑本任务验证子集。 + +### 改导航 / 新增 section 锚点 + +1. 在目标 section 组件根节点加稳定 `id`(与现有风格一致)。 +2. 更新 `NAV_LINKS`(label + href,如 `#pillars`)。 +3. 确认首页实际渲染该 section(`App.tsx` 组装顺序)。 +4. 跑 `pnpm test:e2e`,确认 `checkNavSmoothScroll` 与各 viewport「渲染存在」断言仍绿。 +5. 现有锚点:`#top` `#manifesto` `#pillars` `#members` `#events` `#recruit`。 + +### 改 SEO meta + +1. `site.ts` 的 `META`(及路由用到的 title/description 字段)。 +2. `index.html` head 静态标签(首屏 / 无 JS 爬虫可见部分)。 +3. 若某路由用 `usePageMeta` / JSON-LD,核对该路由的 `og:url` / `og:title` / `og:description`。 +4. 不要引入新的第三方 SEO CDN。 + +### 改 Mini Camp 内容 + +1. 改 `site.ts` 的 `MINICAMP` 块与 `pages/MiniCamp.tsx` / 相关 CSS。 +2. **不要**在本 skill 里改 DNS、`verify-subdomain`、hostname 分支 —— 那是 `innoseed-deploy`。 +3. 主站 chrome vs 子域名 chrome 的行为回归:若只改文案,e2e 即可;若改了 host 分支,转 deploy skill 跑 `pnpm verify:subdomain`。 + +## 本任务验证子集 + +```bash +pnpm typecheck && pnpm build && pnpm test:e2e +``` + +- 改了锚点 / 新 section → 确认对应 viewport spec 有「渲染存在」覆盖。 +- 不要删 `e2e/_shared.js` 断言来「跳过」。 diff --git a/.agents/skills/innoseed-deploy/SKILL.md b/.agents/skills/innoseed-deploy/SKILL.md index 7db992b..f4b6ceb 100644 --- a/.agents/skills/innoseed-deploy/SKILL.md +++ b/.agents/skills/innoseed-deploy/SKILL.md @@ -5,81 +5,72 @@ description: Deploy workflow for the InnOSeed Lab landing (innoseed.club) — lo # InnOSeed Deploy -End-to-end workflow for shipping changes to the InnOSeed landing site. Three -phases — verify locally, push for review, finish the deploy by hand in -Vercel + DNS. +上线 / 子域名 / DNS / 分支清理的可执行流程。仓库级禁区与验证权威清单见 `AGENTS.md`,本 skill **不复述**长段契约。 -## Repository invariants (do not violate) +## 何时触发 -From `AGENTS.md`: +- 要把改动推上 `main`(Production) +- 新增或验证子域名(如 `minicamp.innoseed.club`) +- 配置 Vercel Domains / DNS CNAME +- 清理已合并的本地/远程分支 -- **Don't `vercel deploy` manually.** CI is the authoritative deploy - path. Push to a branch, open a PR, CI builds + Vercel deploys the - preview, then merge → production. -- **`framework: null` in `vercel.json`** is intentional, do not let - Vercel infer. -- **Branch prefix `codex/`** (or `MciG-ggg/` matching existing style — - see `git for-each-ref`). -- **Conventional commits**: `feat / fix / perf / refactor / chore / docs`. -- **TypeScript strict**: don't loosen `tsconfig.json`. -- **Don't bypass `pnpm-workspace.yaml`**: pnpm 11 needs it for esbuild. +## 必读文件 -## Phase 1 — verify locally +| 文件 | 为何 | +| --- | --- | +| `AGENTS.md` §2 / §4.1 / §4.3 | 禁区、验证权威清单、部署摘要 | +| `vercel.json` | SPA rewrite;新子域名通常**不用**改此文件 | +| `scripts/verify-subdomain.mjs` | 子域名 host 行为断言;改 hostname 列表改顶部 `SUBDOMAIN` | +| `src/App.tsx` | hostname → `` / chrome 分支 | +| `package.json` → `verify:subdomain` | 一键本地验证入口 | + +## 提醒(详见 AGENTS.md) + +- **不要**手动 `vercel deploy`;推 `main` 才是权威上线路径。 +- `vercel.json` 的 `framework: null` 不要让 Vercel 推断掉。 +- 分支前缀 `codex/`(或现有 `MciG-ggg/` 风格);验证命令以 `AGENTS.md` §4.1 为准。 + +## 分步流程 + +### Phase 1 — 本地验证 ```bash -# 1. Static checks + build + tests. Must all be green. +# 权威清单见 AGENTS.md §4.1;上线前至少跑通: pnpm typecheck pnpm exec tsc -p tsconfig.node.json pnpm build pnpm test:e2e -pnpm test:tag-codes +pnpm test:tag-codes # 若改动触及 apply / tagcode,必跑 -# 2. Subdomain behavior check (only if the change touches hostname -# routing or adds a new route / subdomain). Self-contained: starts vite -# preview, runs 4 browser assertions, kills the server. Exit code 0 -# means OK. +# 仅当改动触及 hostname 路由或新增子域名时: pnpm verify:subdomain ``` -`pnpm verify:subdomain` exercises what `e2e/minicamp.spec.js` cannot: -the host-aware `` at `/`. It uses Chromium's -`--host-resolver-rules` to map `minicamp.innoseed.club` → `127.0.0.1` -without touching `/etc/hosts` (no sudo needed). The full flag set it -needs (already wired in the script): +`pnpm verify:subdomain` 会启动 vite preview,用 Chromium `--host-resolver-rules` 把子域名指到 `127.0.0.1`(无需改 `/etc/hosts`),断言 4 件事后退出。Chromium 需要同时带: ``` --host-resolver-rules=MAP minicamp.innoseed.club 127.0.0.1 --no-proxy-server ---proxy-server=direct:// # Chromium needs both flags; --no-proxy-server - # alone doesn't always clear its detected proxy. +--proxy-server=direct:// ``` -If you change the hostname list (e.g. adding a new subdomain), edit -`SUBDOMAIN` near the top of `scripts/verify-subdomain.mjs`. The script -asserts 4 things: +脚本已写好上述 flag。若改 hostname 列表,编辑 `scripts/verify-subdomain.mjs` 顶部的 `SUBDOMAIN`。四条断言: -1. `minicamp.innoseed.club/` redirects to `/minicamp` + renders - subdomain chrome (4 tracks, no recruitment timeline). -2. `minicamp.innoseed.club/apply` still lands on Apply (not hijacked). -3. `127.0.0.1:8765/` lands on the main landing page (no redirect). -4. `127.0.0.1:8765/minicamp` renders the page with full Nav (NOT - subdomain chrome). +1. `minicamp.innoseed.club/` → 重定向到 `/minicamp` + 子域名 chrome(4 tracks,无招新时间线) +2. `minicamp.innoseed.club/apply` 仍落在 Apply(不被劫持) +3. `127.0.0.1:8765/` 落在主站 landing(无重定向) +4. `127.0.0.1:8765/minicamp` 用完整 Nav(**不是**子域名 chrome) -## Phase 2 — commit on a branch, merge to main, push +### Phase 2 — 分支提交,合并到 main 并推送 -Vercel auto-deploys production on every push to `main`. **The canonical -ship path is: branch → commit → merge to main locally → push main.** -Skip `gh pr create` for routine fixes; the merge itself is the review -boundary. (Use a PR only when you want external review — see -"PR-based review" below.) +Vercel 在每次推 `main` 时自动部署 Production。**常规上线路径:branch → commit → 本地 ff-only merge 到 main → push main。** 例行修复不必强制 `gh pr create`;需要外部 review(spec/设计大改、高风险重构)再用 PR。 ```bash -# Branch off main. Use codex/ or MciG-ggg/. git checkout main && git pull git checkout -b codex/ -# Stage deliberately — never `git add .`. Composite-emitted .d.ts/.js -# are gitignored, but verify with `git status --short` before commit. +# 刻意 stage —— 禁止 git add . +# composite 冒出的 .d.ts/.js 已 gitignore,提交前仍用 git status --short 确认干净。 git add git commit -F- <<'EOF' @@ -88,105 +79,79 @@ git commit -F- <<'EOF' EOF -# Ship it: fast-forward main and push. Vercel picks up the new HEAD. git checkout main git merge --ff-only codex/ git push origin main -# Delete the local branch (remote was never pushed, so nothing to prune). git branch -d codex/ ``` -**Push to `main` is the deploy.** There is no separate `vercel deploy` -step; CI on `main` builds and Vercel ships. If `git push` fails with -non-fast-forward, run `git fetch origin && git rebase origin/main` on -your branch before retrying. - -### PR-based review (optional) +**推 `main` = 部署。** 若 `git push` 非快进:`git fetch origin && git rebase origin/main` 后再试。 -For changes that need eyes on them before they hit production -(spec/design shifts, risky refactors), open a PR instead: +#### 可选:PR review ```bash git push -u origin HEAD gh pr create --title "" --body-file /tmp/pr-body.md -gh pr checks # wait for Vercel + CI to go green +gh pr checks gh pr merge --squash --delete-branch +git pull && git fetch --prune ``` -After the squash merge, pull `main` and clean up: `git pull && git -fetch --prune`. +Commit 类型示例:`feat` / `fix` / `perf` / `refactor` / `chore` / `docs`。 -### Commit message conventions +### Phase 3 — 让新子域名可达(每个新域名一次) -``` -feat(minicamp): add minicamp.innoseed.club subdomain -fix(apply): stack CTA buttons on tablet -perf(images): serve AVIF variants -chore(site): update copyright -docs(ag): document deploy workflow -``` - -## Phase 3 — make the subdomain reachable (one-time per new domain) - -`vercel.json` rewrites `/`, `/apply`, `/events`, `/recruit`, `/minicamp` -to `/index.html` — same SPA serves every host. To bind a new subdomain: +同一套 SPA rewrite 服务所有 host。绑定新子域名: ```bash -# A. Vercel side (browser or CLI): -# 1. Vercel dashboard → innoseed-landing → Settings → Domains, or: -# vercel domains add innoseed-landing -# 2. Run `vercel domains verify ` to get the exact target. -# The current minicamp target is: -# CNAME minicamp → 9293a6f6fcfa0256.vercel-dns-017.com -# 3. Wait for Vercel to issue the SSL cert after DNS resolves. - -# B. DNS side (provider-specific, e.g. Cloudflare): -# Use the exact CNAME target from `vercel domains verify`. -# Proxy: DNS only (off the orange cloud) — Cloudflare's proxy breaks -# Vercel's SSL issuance. Do not blindly use the generic -# cname.vercel-dns.com when Vercel returns a project-specific target. - -# C. Verify: -curl -I https://minicamp.innoseed.club/ # 200 from Vercel -open https://minicamp.innoseed.club/ # browser lands on Mini Camp +# A. Vercel +# vercel domains add innoseed-landing +# vercel domains verify → 拿精确 CNAME target +# 当前 minicamp 示例: +# CNAME minicamp → 9293a6f6fcfa0256.vercel-dns-017.com + +# B. DNS(如 Cloudflare) +# 用 verify 返回的精确 target;Proxy = DNS only(关掉橙云), +# 否则会打断 Vercel 签 SSL。不要盲用通用 cname.vercel-dns.com。 + +# C. 验证 +curl -I https://minicamp.innoseed.club/ +open https://minicamp.innoseed.club/ ``` -`vercel.json` does **not** need editing for new subdomains — same SPA -rewrite rules cover every host. Only `package.json`, `vite.config.ts`, -and `src/App.tsx` (hostname detection) need code changes when adding a -subdomain. +**不必**为新子域名改 `vercel.json`。代码侧通常改 `package.json` / `vite.config.ts` / `src/App.tsx`(hostname 检测);页面文案走 `innoseed-content`。 -## Branch cleanup - -Before cleanup, check what's safe to delete: +### 分支清理 ```bash git for-each-ref --format='%(refname:short)' refs/heads/ refs/remotes/ -# '+' marker on local branches = checked out in another worktree. -# Don't `git branch -D` those. - -# Remote branches: only safe to delete if merged into main. +# 本地分支带 '+' = 在别的 worktree 里 checkout,不要 git branch -D。 +# 远程:仅当已是 origin/main 祖先才可删(先 dry-run)。 for b in $(git branch -r | grep -v HEAD); do git merge-base --is-ancestor $b origin/main \ && echo "DELETE OK: $b" \ || echo "KEEP (not merged): $b" done - -# Confirm before deleting — output is dry-run only. ``` -`git fetch --prune` cleans up stale remote-tracking branches after -deletions on origin. +删完后 `git fetch --prune`。 + +## 本任务验证子集 + +- 必跑:`AGENTS.md` §4.1 全套(至少 typecheck + build + e2e) +- 触及 hostname / 新子域名 → 追加 `pnpm verify:subdomain` +- 触及 apply → 追加 `pnpm test:tag-codes` +- 上线后:Vercel dashboard → `innoseed-landing` → Deployments ## Troubleshooting -| Symptom | Cause | +| 症状 | 原因 | | --- | --- | -| `vite preview` returns 502 / 403 on `minicamp.innoseed.club` from curl | Vite 5 Host header check. Either use `vite.verify-subdomain.config.ts` (sets `preview.allowedHosts: true`) or set Host header to `127.0.0.1:8765` in curl. | -| `pnpm verify:subdomain` reports `subdomain_root.ok: false` but `pnpm preview` works | Missing `--proxy-server=direct://` flag. Both `--no-proxy-server` and `--proxy-server=direct://` are required. | -| `vite preview` says `Port 8765 in use` | A previous run didn't clean up. `pkill -9 -f vite` then retry. | -| `git push` rejected — non-fast-forward | `git fetch origin && git rebase origin/main`. | -| `gh pr create` fails with `No default branch` | `gh repo set-default` or pass `--base main`. | -| `pnpm exec tsc -p tsconfig.node.json` emits `src/content/site.d.ts` + `site.js` | Composite-mode cross-project ref artifact from `tsconfig.node.json`. Already in `.gitignore` — verify `git status` is clean before commit. | +| curl 子域名 502/403 | Vite 5 Host 检查;用 `vite.verify-subdomain.config.ts`(`preview.allowedHosts: true`)或把 Host 设成 `127.0.0.1:8765` | +| `verify:subdomain` 失败但 preview 正常 | 缺 `--proxy-server=direct://`(两个 proxy flag 都要) | +| Port 8765 in use | `pkill -9 -f vite` 后重试 | +| push 非快进 | `git fetch origin && git rebase origin/main` | +| `gh pr create` 无 default branch | `gh repo set-default` 或 `--base main` | +| `tsc -p tsconfig.node.json` 冒出 `site.d.ts` / `site.js` | composite 产物,已在 `.gitignore`;commit 前确认 `git status` 干净 | diff --git a/.agents/skills/innoseed-visual/SKILL.md b/.agents/skills/innoseed-visual/SKILL.md new file mode 100644 index 0000000..a7e1f2b --- /dev/null +++ b/.agents/skills/innoseed-visual/SKILL.md @@ -0,0 +1,73 @@ +--- +name: innoseed-visual +description: Change InnOSeed design tokens, globals.css typography/color/breakpoints, spec.md visual contract, pillar colors/icons, and brand image rebuild scripts. Use when editing visual design, motion, responsive tokens, public/imgs generation via optimize-images.py / build-og-cover.py, or syncing spec.md sections 5–8. +--- + +# InnOSeed Visual + +design token / `globals.css` / `spec.md` 视觉契约 / pillar 配色与图标 / 品牌图脚本的可执行流程。纯文案走 `innoseed-content`;上线走 `innoseed-deploy`。 + +## 何时触发 + +- 改颜色、字号、断点、动效(`globals.css` 与 `spec.md` §5–§8) +- 改 4 个 pillar:`--c-compete` / `--c-research` / `--c-startup` / `--c-bonds` 或 `PILLAR_ICONS` +- 重建 `banner-*` / `group-photo-*` / `og-cover.png` / favicon(**禁止手改导出图**) +- 改 `index.html` 的 preload `imagesrcset`(与 `public/imgs/` 强绑定) + +## 必读文件 + +| 文件 | 为何 | +| --- | --- | +| `spec.md` §5–§8 | 字号 / 颜色 / 断点 / 动效的**权威契约**;改视觉必须同步 | +| `src/styles/globals.css` | design token + 分区样式 + 移动断点(原生 CSS,无 Tailwind) | +| `src/components/Pillars.tsx` | 顶部 `PILLAR_ICONS` | +| `src/content/site.ts` / `apply.ts` | `pillarKey` 共享枚举 `compete | research | startup | bonds` | +| `scripts/optimize-images.py` / `scripts/build-og-cover.py` | 品牌图唯一合法生成路径 | +| `index.html` preload 列表 | 与 `public/imgs/` srcset 同步 | + +## 提醒(详见 AGENTS.md) + +- 不上 Tailwind / Sass / CSS-in-JS。 +- 图片进 `public/imgs/`,不要放 `src/`。 +- token 块与 `spec.md` 必须同 PR 对齐;标题加 `[spec]` 并让 owner 确认。 +- 主色约定:不要把蓝/紫当作 primary(见 `spec.md` §6)。 + +## 分步流程 + +### 改 typography / color / breakpoint / motion + +1. 先读 `spec.md` 对应节,写下目标值。 +2. 改 `globals.css` 顶部 token 与相关规则(保持三档移动断点语义,见 spec §7)。 +3. **立刻**回写 `spec.md` §5–§8,避免契约漂移。 +4. 必要时更新依赖这些 token 的组件 class(仍用扁平 `components/SectionName.tsx`)。 +5. 跑视觉相关验证子集;涉及 layout 脆点时盯 iPhone SE viewport。 + +### 改 pillar 配色 / 图标 + +1. `globals.css`:`--c-compete` / `--c-research` / `--c-startup` / `--c-bonds`。 +2. `Pillars.tsx`:`PILLAR_ICONS`。 +3. 确认 `site.ts` / `apply.ts` 的 `pillarKey` 仍是同一四元组;Apply 选项上的 `pillarKey` 颜色语义不要 silently 换义。 +4. 同步 `spec.md` 色板叙述。 + +### 重建品牌图 + +1. **不要**直接编辑 `public/imgs/banner-*` / `group-photo-*` / `og-cover.png` / `favicon.png`。 +2. 源图就位后跑: + - `scripts/optimize-images.py` — banner / group-photo 的 web 变体(含 AVIF 等) + - `scripts/build-og-cover.py` — `public/imgs/og-cover.png`(1200×630) +3. 若 srcset 文件名或宽度档变了:同步 `index.html` 的 ``。 +4. 提交前确认没有把巨大原图误加进仓库。 + +### 改 preload / 缓存相关图片引用 + +1. `public/imgs/` 与 `index.html` preload **成对改**。 +2. 若动到 `vercel.json` 静态缓存头,用 `vercel dev` 验,并考虑转 `innoseed-deploy` 做上线检查。 + +## 本任务验证子集 + +```bash +pnpm typecheck && pnpm build && pnpm test:e2e +``` + +- 改了 spec → PR 标题 `[spec]`,描述写明改了哪节。 +- 重点看 `iphone-se.spec.js`:该档最脆,禁止删 `_shared` 断言蒙混。 diff --git a/.pi/skills/innoseed-apply b/.pi/skills/innoseed-apply new file mode 120000 index 0000000..a99c404 --- /dev/null +++ b/.pi/skills/innoseed-apply @@ -0,0 +1 @@ +../../.agents/skills/innoseed-apply \ No newline at end of file diff --git a/.pi/skills/innoseed-content b/.pi/skills/innoseed-content new file mode 120000 index 0000000..abfe3e4 --- /dev/null +++ b/.pi/skills/innoseed-content @@ -0,0 +1 @@ +../../.agents/skills/innoseed-content \ No newline at end of file diff --git a/.pi/skills/innoseed-visual b/.pi/skills/innoseed-visual new file mode 120000 index 0000000..0cb2b19 --- /dev/null +++ b/.pi/skills/innoseed-visual @@ -0,0 +1 @@ +../../.agents/skills/innoseed-visual \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 40592b3..2ff3805 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,12 +1,21 @@ # AGENTS.md — 仓库操作约束 -> 给在这个仓库里干活的 agent 看的。人类 README 见 [README.md](./README.md);品牌 / 设计 / 演进史见 [spec.md](./spec.md)。本文件只写**仓库级操作契约**,不写历史叙事。 +> 给在这个仓库里干活的 agent 看的。人类 README 见 [README.md](./README.md);品牌 / 设计 / 演进史见 [spec.md](./spec.md)。本文件只写**仓库级操作契约**,不写历史叙事。可执行长流程见下方 skill,skill 可引用本文件,但**不复制**长段契约。 ## 1. 项目一句话 中南大学 InnOSeed Lab 品牌站。**v4 是 React 18 + TS 5.6 + Vite 5 + react-router-dom 7** 的纯静态 SPA,Vercel 部署。`api/apply.ts` 是唯一的 serverless function,`aliyun-fc/` 是配套的飞书 Bitable webhook。**所有"如何构建/部署/接入第三方"的事实源都在 README.md + spec.md,改之前先读**。 -共享 agent skill 放在 `.agents/skills/`;`.pi/skills/` 只作为本地 pi 兼容入口,不要在其中维护唯一版本。 +### Agent skills + +源只在 `.agents/skills//`;`.pi/skills/` **仅**为指向该目录的 symlink,不要在 `.pi` 维护唯一版本。 + +| Skill | 何时加载 | 路径 | +| --- | --- | --- | +| `innoseed-deploy` | 推 main / 加子域名 / DNS / `verify:subdomain` / 清分支 | `.agents/skills/innoseed-deploy/` | +| `innoseed-content` | 改文案 / SEO / NAV / section 锚点 / `MINICAMP` 内容块 | `.agents/skills/innoseed-content/` | +| `innoseed-apply` | 改 `/apply` 字段、encode/decode、面试官、tagcode 镜像 | `.agents/skills/innoseed-apply/` | +| `innoseed-visual` | 改 design token / `globals.css` / `spec.md` 视觉契约 / 品牌图脚本 | `.agents/skills/innoseed-visual/` | ## 2. 不要碰的东西 @@ -19,27 +28,24 @@ | `tsconfig.json` 的 `strict: true` | 全量迁移已完成,不要再放回宽松 | | `src/styles/globals.css` 顶部的 design token 块 | 颜色 / 字号 / 断点是 spec.md 锁定的;改前先同步更新 spec.md | | `index.html` 的 `` 列表 | 与 `public/imgs/` 的 srcset 强绑定,改一处要改另一处 | -| `api/apply.ts` 的 `// mirror of src/content/apply.ts#...` 注释链 | 改 `src/content/apply.ts` 的 encode/decode 后必须同步 `scripts/test-tag-codes.mjs` 镜像 | +| `api/apply.ts` 的 `// mirror of src/content/apply.ts#...` 注释链 | 改 encode/decode 后必须同步镜像 —— 走 `innoseed-apply` | | `.github/workflows/ci.yml` 的 Node 22 / pnpm 10 | 镜像 Vercel 生产环境,改 CI 时本地必跑通再 push | | `.omx/` | 运行时状态,agent 应当写入而非删除 | ## 3. 改之前必读 -| 任务 | 必读 | +| 任务 | 去哪 | | --- | --- | -| 改文案 | `src/content/site.ts` 是唯一文案源;Footer / Recruit 时间线 / 招新 FAQ 都在那里。改 Hero 同步看 `index.html` 的 `og:description` / `meta description` | -| 改视觉 / 排版 | `spec.md` §5–§8 是字号 / 颜色 / 断点 / 动效的契约;改完同步 spec.md | -| 改 4 个 pillar 配色 | `--c-compete` / `--c-research` / `--c-startup` / `--c-bonds` 在 `globals.css` 顶部;Pillar icons 在 `Pillars.tsx` 顶部 `PILLAR_ICONS`;`ApplyTag.pillarKey` 在 `site.ts` / `apply.ts` 共享 | -| 改 `/apply` 表单字段 | 同时改 `src/content/apply.ts` + `src/pages/Apply.tsx` + `api/apply.ts` 的 `ApplyRequest` 类型 + `scripts/test-tag-codes.mjs` 镜像 | -| 改招聘面试官 | `src/content/apply.ts` 的 `INTERVIEWERS` 数组;roster 改了同步 `INTERVIEWER_MAP` env | -| 改导航条目 | `src/content/site.ts` 的 `NAV_LINKS`;新加 anchor 必须对应 `
` 存在于某个组件 | -| 改 SEO meta | `index.html` (head) + `src/content/site.ts` 的 `META` 字段 | -| 改 Vercel 路由 / 缓存 | `vercel.json`;改完用 `vercel dev` 验一遍 (本地有 `.vercel/` 状态) | -| 接飞书 Bitable | 先看 `api/apply.ts` 顶部注释列出的 4 个目标 (Bitable / Airtable / Vercel KV / Notion),Bitable 是首选,签名 `applyHandle(request)` 不动 | +| 改文案 / SEO / 导航 / 锚点 / Mini Camp 文案 | 加载 `innoseed-content`(`site.ts` 是唯一文案源) | +| 改视觉 / 排版 / pillar 配色 / 品牌图 | 加载 `innoseed-visual`(`spec.md` §5–§8 是契约) | +| 改 `/apply` 字段 / encode/decode / 面试官 | 加载 `innoseed-apply`(三处镜像必须对齐) | +| 改 Vercel 路由 / 缓存 | `vercel.json`;改完用 `vercel dev` 验一遍 | +| 接飞书 Bitable | 先看 `api/apply.ts` 顶部注释列出的 4 个目标;Bitable 首选;签名 `applyHandle(request)` 不动;解码 webhook 细节见 `innoseed-apply` | +| 上线 / 子域名 / DNS | 加载 `innoseed-deploy` | ## 4. 工作流 (执行 / 验证 / 提交) -### 4.1 本地验证顺序 +### 4.1 本地验证顺序(权威清单) ```bash pnpm install @@ -47,96 +53,77 @@ pnpm typecheck # 严格 TS,必过 pnpm exec tsc -p tsconfig.node.json # vite.config.ts 的 Node 类型 pnpm build # tsc --noEmit && vite build pnpm test:tag-codes # 招新 tagcode 镜像测试 -pnpm test:e2e # 29 个烟雾测试 (~12s) +pnpm test:e2e # 烟雾测试 (~12s) ``` -> 改文案 / 视觉 → 至少跑 `pnpm typecheck && pnpm build && pnpm test:e2e` 三件套。 +> 改文案 / 视觉 → 至少 `pnpm typecheck && pnpm build && pnpm test:e2e`。 > 改表单 / API → 上面全套 + `pnpm test:tag-codes`。 +> 各 skill 只写本任务的验证子集,不另维护完整命令表。 -### 4.2 改 `src/content/apply.ts` 的 encode/decode - -这两个函数是**单源**,被三处消费: - -1. `src/content/apply.ts` (浏览器跑) -2. `api/apply.ts` (Vercel Function 跑,Vercel 不支持跨文件 import,所以部分是 inlined 副本) -3. `scripts/test-tag-codes.mjs` (Node 跑,无 TS,所以是手写镜像) - -改一处 → 同步另两处。改后跑 `pnpm test:tag-codes` 验镜像对齐。 - -### 4.3 提交 / 分支 +### 4.2 提交 / 分支 -- 分支前缀 `codex/` +- 分支前缀 `codex/`(或与现有 `MciG-ggg/` 风格一致) - commit 风格:简短动词开头 (例: `fix(nav): 修移动端 hamburger 收起时机`) - 不需要 lore trailer;若 PR 涉及 spec 改动,描述里点出 spec.md 改了哪节 -### 4.4 部署 +### 4.3 部署(摘要) -- 推 `main` → Vercel 自动部署 (Production) -- PR → Vercel 自动给 preview URL -- **不要**手动 `vercel deploy`;CI 是权威 -- 部署后看构建状态:Vercel dashboard → `innoseed-landing` → Deployments +- 推 `main` → Vercel 自动部署 Production;**不要**手动 `vercel deploy`;CI 是权威 +- 完整 ship / 子域名 / DNS / 分支清理 → `innoseed-deploy` ## 5. 路由 / 组件契约 | 入口 | 组件 / 渲染 | 关键依赖 | | --- | --- | --- | | `/` | `App.tsx` 默认分支 | 9 个 section 组件 + `Nav` + `Footer` | -| `/apply` | `pages/Apply.tsx` | 4 步表单,自身管 step state,**不**渲染 `Nav` / `Footer` (用户从 Hero CTA 进入) | -| `/events` `/recruit` | `pages/Events.tsx` / `pages/Recruit.tsx` | 渲染 `Nav` + 复用 `components/Events` / `components/Recruit` (传 `showHead={false}` 避免双 h2) + `Footer` | -| `/minicamp` | `pages/MiniCamp.tsx` | Mini Camp 活动介绍/回顾页。`minicamp.innoseed.club/` 根路径会被 `App.tsx` 同步 `` 重定向到这里。子域名下用 `SubdomainHeader` / `SubdomainFooter` 极简 chrome;主站下走完整 `Nav` + `Footer`。内容数据在 `src/content/site.ts` 的 `MINICAMP` 块 | -| `*` | `pages/NotFound.tsx` | 走 Vercel 404 兜底,前端 NotFound 兜客户端路由 | +| `/apply` | `pages/Apply.tsx` | 4 步表单,自身管 step state,**不**渲染 `Nav` / `Footer` | +| `/events` `/recruit` | `pages/Events.tsx` / `pages/Recruit.tsx` | `Nav` + 复用 section 组件(`showHead={false}`) + `Footer` | +| `/minicamp` | `pages/MiniCamp.tsx` | 子域名用 `SubdomainHeader` / `SubdomainFooter`;主站用完整 chrome;内容在 `site.ts` 的 `MINICAMP` | +| `*` | `pages/NotFound.tsx` | Vercel 404 兜底 + 客户端 NotFound | -**section id 锚点(供 nav 跳转)**:`#top` `#manifesto` `#pillars` `#members` `#events` `#recruit`。新增 section 必须:① 加 id ② 同步 `NAV_LINKS` ③ 跑 `pnpm test:e2e` 验 `checkNavSmoothScroll` 还过。 +**section 锚点**:`#top` `#manifesto` `#pillars` `#members` `#events` `#recruit`。新增步骤见 `innoseed-content`。子域名 host 行为见 `innoseed-deploy`。 ## 6. 可观测性 / Analytics -- `src/lib/observability.ts` 三层: 全局错误 / Web Vitals (LCP CLS INP) / 业务事件白名单 -- 业务事件名 (`EventName` union) 加新事件前先在 union 里登记,便于埋点审查 -- 上报策略: - - `VITE_ANALYTICS_ENDPOINT` 设了 → POST JSON (Plausible-style 自建) - - `VITE_ANALYTICS_URL` 设了 → 注入 script tag (Plausible / Umami) - - 都不设 → 只 `console.debug`,不联网 -- `vite.config.ts` 的 `inject-build-info` 把 `{ sha, time, mode, deploymentId, url }` 写到 `window.__BUILD__`,所有观测都带这个上下文 -- 永远不允许 observability 自己抛错 (`send` / `trackError` 都 swallow) +- `src/lib/observability.ts` 三层:全局错误 / Web Vitals (LCP CLS INP) / 业务事件白名单 +- 新业务事件名先在 `EventName` union 登记 +- `VITE_ANALYTICS_ENDPOINT` → POST JSON;`VITE_ANALYTICS_URL` → 注入 script;都不设 → 只 `console.debug` +- `window.__BUILD__` 由 vite `inject-build-info` 注入;observability **不得**自己抛错 ## 7. e2e 测试约定 -- 三档 viewport,各自一个 spec 文件 (避免 projects 配置导致断言跑错断点): - - `desktop.spec.js` → 1440×900 - - `mobile.spec.js` → 375×812 (iPhone 14) - - `iphone-se.spec.js` → 320×568 (iPhone SE 1) -- `apply.spec.js` → `/apply` 路由,`page.route` mock `/api/apply` (避免真打后端) -- 共享断言放 `e2e/_shared.js` -- 新加 section / 路由 → 加对应 viewport 的"渲染存在"断言;锚点改了 → `checkNavSmoothScroll` 自动覆盖 +- 三档 viewport 分文件:`desktop.spec.js`(1440×900) / `mobile.spec.js`(375×812) / `iphone-se.spec.js`(320×568) +- `apply.spec.js` mock `/api/apply`;共享断言在 `e2e/_shared.js`(**禁删**为跳过) +- 新 section / 路由 → 加对应 viewport「渲染存在」断言;锚点回归由 `checkNavSmoothScroll` 覆盖 ## 8. 安全护栏 -- **不发任何 secret 到前端**:`.env*` 已被 `.gitignore` 默认;构建产物 `dist/` 也别提交 -- `vercel.json` 的 `Permissions-Policy` 显式禁了 `camera` / `microphone` / `geolocation` / `interest-cohort`,新加嵌入功能时检查是否冲突 -- 飞书 `INTERVIEWER_MAP` 等 env 不要写进代码;Vercel dashboard 配 -- `apply.ts` 接收的 payload 视作不可信,server-side 必须 re-derive tag indices (当前 stub 已做) -- 第三方 CDN 仅限 Google Fonts;不引入新 CDN 依赖 +- **不发任何 secret 到前端**:`.env*` 已 gitignore;勿提交 `dist/` +- `vercel.json` 的 `Permissions-Policy` 禁了 `camera` / `microphone` / `geolocation` / `interest-cohort` +- 飞书 `INTERVIEWER_MAP` 等 env 只在 Vercel dashboard 配 +- `apply.ts` payload 不可信,server-side 必须 re-derive tag indices +- 第三方 CDN 仅限 Google Fonts ## 9. 不要做的常见错 -- ❌ 在 `globals.css` 用 Tailwind / Sass / CSS-in-JS — 仓库坚守原生 CSS + design token -- ❌ 把图片放 `src/` 而不是 `public/imgs/` — `src/` 走打包,`public/imgs/` 才走 srcset + 1 年缓存 -- ❌ 新建 `components/SectionName/index.tsx` 这种目录式组件 — 项目统一扁平 `components/SectionName.tsx` -- ❌ 改 `e2e/` viewport 不通过 `test.use({ viewport })` — 改全局 `playwright.config.js` 会让 mobile 断言跑在 1440 上 -- ❌ 删 `e2e/_shared.js` 的断言就为跳过 — 那些是回归守护,iPhone SE 1 那组尤其脆 -- ❌ 改 `vercel.json` 后不跑 `vercel dev` 验证 — 缓存策略错配要一周后才能在生产看出来 -- ❌ 在 PR 描述里写 "AI generated" 类元信息 — 仓库 owner 偏好自然语言 commit / PR 描述 +- ❌ 在 `globals.css` 用 Tailwind / Sass / CSS-in-JS — 原生 CSS + design token +- ❌ 把图片放 `src/` 而不是 `public/imgs/` +- ❌ 新建 `components/SectionName/index.tsx` — 统一扁平 `components/SectionName.tsx` +- ❌ 改 e2e viewport 不走 `test.use({ viewport })` +- ❌ 删 `e2e/_shared.js` 断言就为跳过 +- ❌ 改 `vercel.json` 后不跑 `vercel dev` +- ❌ PR 描述写 "AI generated" 类元信息 ## 10. 边界与外部依赖 - **Vercel** — 唯一正式部署目标 -- **Aliyun FC** — 飞书 Bitable 反向解码 webhook,**只**在 `aliyun-fc/` 子目录维护 -- **Google Fonts** — 唯一外链字体,font-family 链 system-first 兜底 +- **Aliyun FC** — 飞书 Bitable 反向解码 webhook,**只**在 `aliyun-fc/` 维护 +- **Google Fonts** — 唯一外链字体,system-first 兜底 - **GitHub** — 协作 + CI + Project 看板,见 `.github/PROJECT_SETUP.md` - **Sentry / DataDog / 第三方 APM** — 未引入,不要主动加 ## 11. 求助 / 上报 -- 仓库 owner 通过 `.github/ISSUE_TEMPLATE/` 的 Bug 报告 / 网站修改模板接收 -- 招新相关走 "招新待办" 模板 -- 涉及 spec.md 变更的 → PR 标题加 `[spec]`,review 时同步给 owner 确认 +- Bug / 网站修改 → `.github/ISSUE_TEMPLATE/` +- 招新相关 → 「招新待办」模板 +- 涉及 spec.md → PR 标题加 `[spec]`,review 时同步 owner diff --git a/src/styles/globals.css b/src/styles/globals.css index 5f2dee5..3b4756c 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -1099,13 +1099,14 @@ section { position: relative; } position: relative; border-radius: var(--radius-md); overflow: hidden; - aspect-ratio: 4/5; + aspect-ratio: 4/3; background: var(--bg-cream); box-shadow: 0 24px 60px -20px rgba(15,23,42,0.15); } .inside-image img { width: 100%; height: 100%; object-fit: cover; + object-position: center 30%; transition: transform 1.2s cubic-bezier(0.22, 1, 0.36, 1); } .inside-image:hover img { transform: scale(1.04); }