返回 CodeWhale
README.md
根目录 / web / README.md
1 # codewhale-web
2
3 Documentation and community site for [Codewhale](https://github.com/Hmbown/CodeWhale) — lives at **codewhale.net**.
4
5 Next.js 15 (App Router) + Tailwind, deployed to Cloudflare Workers via [`@opennextjs/cloudflare`](https://opennext.js.org/cloudflare). Curated "Today's Dispatch" content is regenerated every 6 hours by a Cloudflare Cron Trigger that calls `deepseek-v4-flash` to summarise recent repo activity, and stored in Workers KV.
6
7 ## Local dev
8
9 ```bash
10 cd web
11 npm install
12 cp .env.example .env.local # fill in the keys you have
13 npm run dev # http://localhost:3000
14 ```
15
16 Env (mirrors `.env.example`):
17
18 | Variable | What | Required? |
19 | --------------------------- | ---------------------------------------------------------------- | -------------------- |
20 | `DEEPSEEK_API_KEY` | DeepSeek platform key (`sk-...`) | only for the `/api/cron` tasks (summarization + community agent) |
21 | `GITHUB_TOKEN` | Fine-grained PAT, public-repo read scope | optional (raises rate limit 60 → 5000 req/h) |
22 | `GITHUB_REPO` | Defaults to `Hmbown/CodeWhale` | optional |
23 | `CRON_SECRET` | Shared secret for manual `/api/cron` invocation | optional (Cloudflare cron triggers don't need it) |
24 | `DEEPSEEK_MODEL` | Defaults to `deepseek-v4-flash` | optional |
25 | `DEEPSEEK_BASE_URL` | Defaults to `https://api.deepseek.com` | optional |
26 | `MAINTAINER_TOKEN` | Admin panel auth; access `/admin?token=<value>` | only for `/admin` |
27 | `MAINTAINER_GITHUB_PAT` | PAT with `issues:write`, for posting comments via `/admin` | only for `/admin` posting |
28 | `NEXT_PUBLIC_GITEE_ENABLED` | Set to `1` once the Gitee mirror exists; blank hides Gitee links | optional |
29
30 The site renders fine without any of them — `Today's Dispatch` falls back to a static editorial; the GitHub feed shows "feed not yet loaded".
31
32 ## Deploy to Cloudflare
33
34 Ordinary pushes and pull requests run the web checks and production build, but
35 they do **not** deploy. The `deploy` job in `.github/workflows/web.yml` runs
36 only for a maintainer-triggered `workflow_dispatch` on `main`. Before approval,
37 record the exact 40-character `origin/main` SHA and trigger that ref:
38
39 ```bash
40 git fetch origin main
41 git rev-parse origin/main
42 gh workflow run web.yml --repo Hmbown/CodeWhale --ref main
43 ```
44
45 Every green push to `main` also emits a `Deployment approval needed` workflow
46 notice with that command. The reminder does not receive Cloudflare credentials
47 and cannot deploy; it keeps the manual production approval boundary visible.
48
49 The manual job records the pre-deploy source drift, builds the OpenNext bundle,
50 deploys only after the protected Cloudflare inputs pass, and then requires the
51 public `/api/facts` receipt to report the exact workflow SHA. A credential-free
52 local comparison is available without starting a deployment:
53
54 ```bash
55 npm run compare:deployed-facts -- --expected-revision <exact-40-character-sha>
56 ```
57
58 You already own `codewhale.net` on Cloudflare and have a Workers Paid plan. The deploy is two steps:
59
60 1. **Provision KV namespaces once:**
61
62 ```bash
63 npx wrangler kv namespace create CURATED_KV
64 npx wrangler kv namespace create NEXT_INC_CACHE_KV
65 ```
66
67 Copy the printed `id` values into the matching `wrangler.jsonc` bindings
68 (replace each `REPLACE_WITH_KV_ID`).
69
70 2. **Set secrets and deploy:**
71
72 ```bash
73 npx wrangler secret put DEEPSEEK_API_KEY
74 npx wrangler secret put GITHUB_TOKEN # optional
75 npx wrangler secret put CRON_SECRET # optional, for manual /api/cron?task=curate hits
76
77 npm run deploy # builds with OpenNext + uploads
78 ```
79
80 3. **Point the domain:** in the Cloudflare dashboard, add a Worker route for `codewhale.net/*` → the deployed Worker, named `codewhale-web` (see `wrangler.jsonc`).
81
82 The first cron run happens within 6 hours; you can also kick it manually:
83
84 ```bash
85 curl -H "x-cron-secret: $CRON_SECRET" "https://codewhale.net/api/cron?task=curate"
86 ```
87
88 ## What's where
89
90 Pages are bilingual by default: each `app/[locale]/` page renders both
91 English and Chinese from the same file, keyed by the `[locale]` segment
92 (see `lib/i18n/config.ts`). Copy changes must update both locales. The
93 v0.9.2 wave adds routed **partial** locales (ja, vi, ko, ru, uk, es, pt-BR):
94 their shared chrome (nav/footer/switcher) and home-page copy live in
95 `lib/i18n/dictionaries/<code>/` (checked by `npm run check:locales`), and
96 everything else falls back to the English copy. Routing, middleware
97 detection (`lib/i18n/detect.ts`), sitemap, and hreflang all derive from the
98 one registry.
99
100 ```
101 web/
102 ├── app/
103 │ ├── globals.css ocean portal, docs layout, type, and shared surfaces
104 │ ├── [locale]/ 10 routed locales; zh has native page bodies,
105 │ │ the rest fall back to the English body
106 │ │ ├── layout.tsx root + locale layout: html shell, fonts, nav, footer
107 │ │ ├── page.tsx home — hero, ticker, proof, decides, workflow,
108 │ │ │ start, boundaries, surfaces, install band, community
109 │ │ ├── install/page.tsx per-OS install with auto-detection
110 │ │ ├── docs/page.tsx modes / tools / approval / config / mcp / providers
111 │ │ ├── faq/page.tsx frequently asked questions
112 │ │ ├── feed/page.tsx live mirror of issues + PRs
113 │ │ ├── roadmap/page.tsx shipped / underway / considered / ruled out
114 │ │ ├── contribute/page.tsx how to PR + house rules + dev loop
115 │ │ └── admin/ maintainer panel (page.tsx + admin-client.tsx)
116 │ └── api/
117 │ ├── cron/route.ts cron tasks: curate, triage, facts-drift, …
118 │ ├── facts/route.ts public source/deployment receipt
119 │ ├── github/feed/route.ts cached JSON endpoint
120 │ └── admin/ login, logout, post (MAINTAINER_TOKEN-gated)
121 ├── data/
122 │ └── latest-published-release.json manually advanced only after publication
123 ├── components/
124 │ ├── nav.tsx sticky header w/ date strip + CJK accents
125 │ ├── footer.tsx dense 5-column footer
126 │ ├── whale.tsx shared Codewhale mark
127 │ ├── ticker.tsx live wire: merges, issues, releases + handles
128 │ ├── feed-card.tsx one issue/PR card
129 │ ├── locale-switcher.tsx N-locale dropdown with partial badges
130 │ └── install-*.tsx install page blocks (binary, code block, tiles)
131 ├── lib/
132 │ ├── types.ts shared types
133 │ ├── i18n/ locale config, en/zh dictionaries
134 │ ├── github.ts REST client + relative-time formatter
135 │ ├── deepseek.ts v4-flash chat client + curate() prompt
136 │ ├── facts.ts getFacts(): KV value, else build-time FACTS
137 │ ├── facts.generated.ts GENERATED — do not edit by hand
138 │ ├── facts-drift.ts runtime re-derivation for the drift cron
139 │ ├── community-agent.ts triage / pr-review / digest cron tasks
140 │ └── kv.ts Cloudflare KV access via OpenNext bindings
141 ├── scripts/
142 │ ├── derive-facts.mjs prebuild: repo sources → lib/facts.generated.ts
143 │ ├── compare-deployed-facts.mjs credential-free exact-SHA receipt check
144 │ └── check-kv-id.mjs predeploy guard for KV namespace ids
145 ├── wrangler.jsonc CF Worker config + cron + KV binding
146 ├── open-next.config.ts OpenNext adapter config
147 └── tailwind.config.ts design tokens
148 ```
149
150 ## Facts pipeline
151
152 Mechanical facts (version, provider list, sandbox backends, crate names,
153 default model, Node engines) are never hand-written into pages:
154
155 1. **Build time** — `scripts/derive-facts.mjs` runs as `prebuild` (and before
156 `npm run dev`), parses the parent repo (`Cargo.toml`, `crates/tui/src/config.rs`,
157 `crates/tui/src/sandbox/mod.rs`, `npm/codewhale/package.json`) and writes
158 `lib/facts.generated.ts`. Never edit that file by hand.
159 2. **Published release** — `data/latest-published-release.json` records the
160 latest GitHub Release separately from the source candidate. Install commands
161 use this published tag; they never turn the workspace version into a release
162 before publication. The credential-free deployed-facts comparison checks the
163 record against the public receipt.
164 3. **Runtime** — the `/api/cron?task=facts-drift` cron (`lib/facts-drift.ts`)
165 resolves an exact `main` revision, derives every source fact from that SHA,
166 and writes changes to `CURATED_KV` under `facts:current`. Pages accept that
167 snapshot only when its source provenance is the same as or newer than the
168 deployed build. Legacy, malformed, or older KV data cannot replace newer
169 build facts; published-release metadata is resolved independently. Public
170 fact pages revalidate their cached HTML every five minutes.
171
172 `/api/facts` exposes only public provenance and counts: deployed/resolved source
173 revision, version, provider count, tool count, selection reason, and latest
174 published release. It contains no environment values, tokens, or KV contents.
175
176 When a new `ApiProvider` variant lands in `crates/tui/src/config.rs`, it must
177 be added to the `labelMap` in **both** `scripts/derive-facts.mjs` and
178 `lib/facts-drift.ts` (or to the `EXCLUDED` set if deliberately hidden). Both
179 fail loudly on unmapped variants, so the build / cron will tell you.
180
181 ## Visual direction
182
183 The public site is a documentation portal with a restrained underwater atmosphere. Content and navigation come first; ocean depth, currents, and the whale mark provide identity without turning every section into a themed card.
184
185 - **Palette**: cool paper and mist for reading surfaces, deep navy for terminal and community sections, muted current blue for links, and small gold/coral signals where status needs contrast.
186 - **Type**: Space Grotesk for headings, IBM Plex Sans for body copy, and JetBrains Mono for commands and compact interface labels.
187 - **Structure**: compact documentation rows, quiet hairline dividers, generous but bounded reading widths, and responsive layouts that remove chrome before content.
188
189 If you want to retune the palette, edit `:root` in `app/globals.css` and the `colors` block in `tailwind.config.ts`.
190
190 lines MARKDOWN