Rename the lowercase bash tool struct and its bounded output
accumulator, plus the file read/write helper symbols and their
tests, to contract-based names. Tool names, schemas, and behavior
are unchanged; doc and test references updated to match; the
model-visible catalog text and budget files are untouched.
Verified: cargo test -p codewhale-tui --lib (shell/registry/file/
file_tool/tool_catalog filters) 308 passed; web
public-surface-contract 13/13 passed.
bf6def00d added todo_write to the model-facing toolbox
(read/write/edit/bash/agent/todo_write/tool_search) and updated
docs/TOOL_SURFACE.md to 'exactly seven model-facing names', but
web/lib/public-surface-contract.test.ts still asserted six. The
release-candidate and Web Frontend proof runs fail on that stale
assertion at the frozen SHA; align the contract test with the
documented surface. Verified: vitest public-surface-contract (13/13).
Document Work and To-do as the two product concepts, synchronize every shipped locale, publish the six-tool and typed-image contracts, and keep static web generation offline while live GitHub chrome refreshes after deployment.
Replace duplicated implementation-heavy notes with a complete user-facing release ledger, sync the embedded changelog, and align compaction math documentation with the shipped trigger.\n\nExpose the existing Skills and Plugins guides in the website documentation index while retaining v0.9.5 as the latest published release until 0.9.6 is public.
- FAQ: replace the two-binary codewhale/codewhale-tui answer with the
one-runtime contract — codew is a byte-identical short name, the updater
refreshes legacy codewhale-tui paths from the same bytes, and the tui
crate now compiles into codewhale-cli; drop the separate codewhale-tui
cargo install line (EN+ZH).
- FAQ: add Mistral AI to the provider sample list (EN+ZH).
- FAQ: codewhale doctor prints its report to stdout — the claimed
~/.codewhale/doctor.log file does not exist (EN+ZH).
- FAQ: drop the audit.log sentence; no runtime writer for it exists (EN+ZH).
- FAQ: OpenRouter setup uses the documented route form — --provider
openrouter plus OpenRouter's own model slugs; no provider/model prefix
parsing exists (EN+ZH).
- getting-started: step 4 becomes 'Set up your ideal fleet' — add every
provider, then /fleet setup, the documented authoring wizard; drop
codewhale fleet init, which only prints the ledger path.
- fleet docs: drop codewhale fleet init from the CLI verb block.
- home dictionaries + guide: align the four-step lede with the new step 4
(EN+ZH).
Bump the workspace, every published crate, the npm CLI package and its
codewhaleBinaryVersion, the runtime SDK, and the VS Code extension to 0.9.6 —
the four version sources the release workflow cross-checks before it will
tag.
The changelog entry describes v0.9.6 as what it is: a subtractive release.
The guards that interrupted live work, the per-mode prompt doctrine, and the
deterministic second compaction system are gone; a truncated provider
response can no longer be recorded as a finished answer. Most of these were
found by running v0.9.5 against Terminal-Bench 2.1 beside Pi 0.8.41 on the
same model, effort, endpoint, and task digests, then reading the trials
Codewhale lost — so the entry names those trials rather than describing the
fixes in the abstract.
Mistral AI ships in this release; Xavier Pestel (@xavierpestel-ai) is
credited in the contributors section for #5295.
Scope Mistral's polymorphic reasoning and replay behavior to exact first-party HTTPS routes, preserve stored thinking across real prompt construction, and keep DeepSeek's sanitizer from injecting a second dialect into tool-call history.
Align the current model registry, provider-scoped model override, generated facts, docs, and focused route-isolation tests. Split the large stream decoder test module so the source-structure gate remains below budget.
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Advance the separately modeled public-release record only after the immutable GitHub release and 34-asset gate are live. Regenerate the checked-in web facts so install pages and deployed receipts resolve v0.9.5 instead of the prior release.
Record GitHub v0.9.4 as the latest published release while keeping the workspace and website source candidate at v0.9.5. Regenerate the build-time facts fallback so the manual Cloudflare deploy receipt compares against the same published tag already served from KV.
Verified with the focused facts and deploy contracts, the complete 256-test web suite, ESLint, TypeScript, facts/docs drift checks, and a 288-page production build.
Keep provider-neutral auto selection in the provider-aware TUI and launch workflow lanes from the exact running executable. Repair CNB and installer alias contracts, refresh legacy website-installed TUI bytes during upgrades, and make account pull reject an unimplemented local import truthfully.\n\nVerified with focused CLI/config tests, strict Clippy, workflow contracts, shell syntax checks, and hermetic web installer tests.
Preserve the exact Codewhale-first identity sentence in metadata and social previews. Render it as the Open Graph heading itself so accessible and visual surfaces say the brand once without the previous duplicated prefix.
Move translated layouts onto the compact navigation until xl, reserve wide companion labels for 2xl, and keep every masthead control inside the viewport.
Portal the compact menu to a true viewport modal with inert background roots, contained keyboard focus, an in-dialog close control, and immediate cleanup when a resize crosses the desktop breakpoint.
Verified with all 254 web tests, ESLint, TypeScript, responsive width probes, and live English, Chinese, and Spanish pointer, keyboard, focus, resize, and navigation checks.
Record the four human contributors whose work is present in the 0.9.5 candidate, update the public credit matrix and website snapshot, and add the missing canonical identity for PR #5257. The candidate heading and compare links remain explicitly pre-tag until publication approval.
Bump every tagged package and internal dependency pin to 0.9.5, refresh Cargo and npm lock records, regenerate the packaged changelog and web facts, and record the user-visible candidate contract. The changelog deliberately remains marked Unreleased candidate until the rebuilt binary is dogfooded and publication is explicitly approved.
Derive SoftwareApplication softwareVersion only from the published-release receipt backing the install URL. Omit the field when no published release is known so a source candidate is never presented as downloadable.
Verified with focused schema tests, ESLint, TypeScript, and diff checking.
Build once through the OpenNext adapter before preview or deploy, and remove Wrangler custom-build recursion so cache population and upload use the same bundle. Keep the manual main-only exact-SHA workflow and post-deploy receipt gate intact.
Verified with deploy-preflight tests, ESLint, a complete 288-page OpenNext build, Wrangler 4.113.0 deploy --dry-run, and diff checking.
Keep the identity phrase brand-free, derive one shared Open Graph alt string, and render the visual brand from the same SITE_NAME constant. Add a regression against repeated branding and use the supported neutral es-419 date locale.
Verified with metadata and dictionary tests, locale checks, ESLint, and diff checking.
Three fixes on the newspaper-ocean site:
- Nav: baseline-align the Han secondary labels with the Latin primary
(inline-flex items-baseline + matching line-height), and give the
masthead real brand buttons — GitHub mark before the star count,
Discord logo icon-only — with a .site-discord-link rule matching
.site-github-link and a shared .brand-mark size.
- EN copy: hero/meta/footer sharpened around "Codewhale dives into the
deep so you don't have to"; every "local-first" claim dropped in
favor of "any model, on your machine". heroIntro keeps the {brand}
token the lede split and dictionary tests require.
- Locales: home/chrome rewritten natively in all nine non-English
packs (zh, ja, ko, vi, ru, uk, es, id, pt-BR) instead of machine
translation. zh hero uses the 一入码门深似海 allusion per community
feedback. Key parity, template tokens, and ru/uk script purity all
hold; check:locales and dictionaries.test.ts pass.
Verified: npm ci && prebuild && check:facts && check:docs &&
check:locales && vitest (250/250) && eslint && tsc --noEmit && build.
Agent-assisted (Kimi Code); copy reviewed against the en reference.
Advertise codewhale and the release/npm codew convenience name without exposing the retired codewhale-tui install surface. Keep Cargo truthful: codewhale-cli installs only codewhale unless the user defines an alias.
Verified with focused public-surface tests, facts/docs/locale checks, ESLint, and diff checking.
Build codewhale once, expose the verified bytes as codew across release channels, and retain seven TUI-named release aliases solely so shipped v0.9.4 clients can discover and cross the transition. Current installers and containers expose only codewhale and codew.\n\nVerified with the npm asset suite, exact 34-asset assembly, workflow contract, Homebrew renderer, release-body, dogfood installer, and shell syntax tests.\n\nRefs #5259
Resolutions:
- crates/tui/src/main.rs: kept the release shim (codewhale_tui::run, #5259).
Main's signal-arming repair c07f00c28 was aimed at the old monolith, so it is
ported here into crates/tui/src/lib.rs: TerminatingSignals::register() now
installs SIGINT/SIGTERM/SIGHUP handlers synchronously before the spawn, and
spawn_signal_cleanup_task() moves ahead of telemetry arming/notice. Without
this port the branch would silently keep the Ctrl-C kill-window #1583/#5282
fixed on main.
- scripts/source-structure-budget.json: kept branch ceilings (both sides
rebaselined); single full rebaseline lands at the end of the 0.9.5 stack.
- web docs tools page: took main's public-surface contract fix (update_plan is
not a default-active tool).
Brings in: builder shared-shell liveness fix (b596b6bfa), fetch-cache test
serialization, nanoid GHSA pin, runtime-contract/source-structure rebaselines,
README refresh. Verified: cargo check -p codewhale-tui green post-merge.
`npm audit` in web/ reported one high-severity advisory: nanoid <3.3.17
loops indefinitely when a custom generator is given size zero. It reaches
us transitively through postcss, which is already an override entry, so
the fix follows the pattern the file established for exactly this case
rather than waiting on an upstream postcss bump.
nanoid now resolves to 3.3.18 and `npm audit` reports 0 vulnerabilities,
restoring the release contract's zero-advisory requirement.
Verified: 0 vulnerabilities, 250 web tests, eslint, tsc --noEmit, and
next build all pass against the reinstalled tree.
Assisted by Claude Code.
21ed173cf made `todo_write` the canonical work-progress tool and moved
`work_update` to a hidden compat alias, updating
docs/public-surface-facts.json and docs/RUNTIME_SIMPLIFICATION_DESIGN.md
but not the two places that still asserted the old name. The Web Frontend
lane has been red since that commit.
- public-surface-contract.test.ts pinned `work_update` in the nine-name
default-active list and in the design-doc sentence it greps for. Both
now read `todo_write`, matching the files they are checking.
- The docs tools page listed `update_plan · work_update` as coordination
tools. Neither is model-visible — `update_plan` replays older Plan
artifacts only and `work_update` is a hidden alias — so the page named
two tools a reader cannot call and omitted the one they can. It now
lists `todo_write`, in both the English and Chinese copy.
Verified: 250 web tests, eslint, tsc --noEmit, and next build all pass.
Assisted by Claude Code.
Entrance reveals on scroll, considered hover/focus states, restrained depth
on the terminal and card surfaces, and feedback on state changes — sized to
a print object coming to life rather than a landing page.
Every effect has a reduced-motion path written alongside it, not bolted on:
all four components check `prefers-reduced-motion` and the CSS carries its
own block. That block also avoids the usual reduced-motion bug — freezing
the ticker would strand every entry past the fold, so the track stops
moving and becomes scrollable instead of simply halting.
No animation library. `package.json` is untouched; this is CSS transitions,
the Web Animations API, and IntersectionObserver.
Nothing animated triggers layout. The properties in play are transform,
color, border-color, and background-size (underline draws) — all paint, so
cumulative layout shift stays zero. The brief asked for transform/opacity
only; the paint-only additions keep that intent.
The accessibility work from the previous pass is intact and re-checked:
the white/55 contrast value, the roving tabindex and aria-controls wiring,
and the mobile-menu focus return all survive.
Known, judged acceptable: focus stays inside the menu during its 170ms exit
fade and returns to the toggle on unmount; pointer interaction is disabled
for that window.
Drafted by Kimi K3 in Codewhale exec on an isolated worktree. Gates re-run
here: 250 tests, eslint clean.
Defect fixes ahead of relaunch — no redesign, no new copy, no media.
Accessibility:
- The mobile menu declares `aria-modal`, which promises the dialog owns
interaction, but focus never entered it and never came back. Focus now
moves inside on open and returns to the toggle on close. The toggle node
is captured before cleanup rather than read from the ref inside it, which
would race React clearing it.
- The terminal player had `role="tablist"` with none of the pattern behind
it. Adds arrow-key/Home/End movement, a roving tabindex, and
aria-controls/aria-labelledby wiring between each tab and the panel.
- Context text ran at white/45 on ink — 4.32:1, below the 4.5:1 WCAG AA
floor for normal text. Now white/55, 5.78:1.
Metadata:
- SoftwareApplication JSON-LD on the home page. Every field traces to a
repo-sourced fact already rendered on the page; `softwareVersion` is
omitted rather than guessed when facts carry none.
- `id` was missing from the OpenGraph locale map, so Indonesian pages
emitted no og:locale.
Drafted by Kimi K3 in Codewhale exec, reviewed here. The claimed contrast
ratio was recomputed independently (5.78:1 vs its stated 5.75:1 — a
rounding difference against the exact ink token, and the before value does
genuinely fail AA). Gates re-run here: 250 tests, eslint clean.
Note for follow-up, not introduced by this change: `npm audit` in web/ now
reports 1 high-severity js-yaml advisory. The release contract requires 0.
- source-structure budget: fold the test-fix commit's 3 executor.rs lines
(673293 -> 673296).
- docs/public-surface-facts.json: sourceCandidate toolCount 67 -> 69 to
match the regenerated web facts (MCP-discovery surface).
- web/lib/release-credits.ts + docs/CONTRIBUTORS.md: add @bistack (#5238)
and extend @SparkofSpike's v0.9.4 entries (#5242/#5240/#5234) — the
changelog/credits/contributors parity test requires exact handle parity.
- core_command_surfaces.feature: the /rlm dispatch copy changed to
'Loading that into a persistent working context...' in a50b6532b
(pre-existing on main, hidden by the cancelled CI); align the step.
CI's stable toolchain moved past local 1.97: private_interfaces now denies
the 13 tui::ui seam types (DispatchRecovery, UserDispatchPrepare,
TerminalInputPump, ProviderKeyVerifier, etc.) that the ui split's
pub(crate) fns legitimately expose, failing Lint, Test (macos/windows),
Mobile runtime smoke, and npm wrapper smoke. Make the types pub(crate) —
they were already crate-visible in practice via those signatures — and fix
the one clippy 'matching on Some with ok() is redundant' in effort.rs.
Also regenerate web/lib/facts.generated.ts: the harvested MCP-discovery
work raised the public tool surface 67 -> 69.
Contributor tier is a distinct Meta wire model, not an alias to the
standard tier — stop collapsing it in effective_muse_wire_id so the
API actually receives muse-spark-1.2-contributor. Pricing/display
already treated it as distinct; wire now matches.
Bump web facts toolCount 66 -> 67 (harness tool).
Once 0.9.4 is dated, the notes are no longer a "source candidate" and the
version compare link points at the tag range. Keep the old assertions for
an Unreleased-candidate heading.
Shorten dense hero, proof, decides, meta, and start lede across all ten
locale dictionaries; tighten EN/ZH getting-started step bodies and thinking
trace context while preserving key and token parity.
Both #5135 fast gates were red on the new tip for generated-file drift, not
code:
- web/lib/facts.generated.ts still claimed 67 model-facing tools; the train
removed one, and the fresh derive counts 66. Regenerated with
`npm run prebuild` (only generatedAt and toolCount change).
- crates/tui/CHANGELOG.md is the packaged slice of the root CHANGELOG and
had not been re-synced after the root gained the work-bar standing-register
entry. Regenerated with scripts/sync-changelog.sh.
Verified: scripts/release/check-versions.sh passes locally (workspace=0.9.4,
npm=0.9.4, lockfile in sync) and web check:facts drift is clean. Authored
with agent assistance (Claude).
The roadmap listed under Underway: "SQLite + FTS5 backend with
graph-structured agent memory and multi-signal recall". Every clause was
false against the code. `native_memory.rs:3` says Markdown is the durable
source of truth and SQLite is a rebuildable index that may be deleted at
any time; there is no graph code in the repo; recall is single-signal.
Says what shipped and keeps the graph/multi-signal work as still ahead,
in both EN and ZH.
The wire exists to put the people behind the repository on the front page. dependabot[bot] dependency bumps and github-actions[bot] close sweeps were spending slots that belong to human contributors — the same crowding-out the draft filter already addressed.
Decision handed to me by the ticker work: filter by GitHub's own [bot] login suffix (its verdict, not our inference). Bot-authored issues and pulls stay off entirely; a published release keeps its slot no matter who pushed the button, but a bot publisher's byline is dropped instead of named.
Two new tests pin the filter and the byline-less release.
The ticker printed `ISS #1234 title · 3h` for whatever the issues and pulls
endpoints last touched. It proved the repo was awake and nothing else: no
merges, no releases, no names, and English chrome on nine translated pages.
It now reports events, in the reader's language, with the person who did the
work named:
- Merged pull requests carry the contributor's handle. `merged_at`,
`author_association`, and the login all arrive in the list payload we
already fetch, so naming @bistack or @shenjackyuanjie beside their merge
costs nothing extra.
- Opened and closed issues carry who filed them.
- Releases appear. That is the one added endpoint — `/releases?per_page=5`,
cached an hour, ~1 request/hour. Three cached calls total, ~13 req/h
unauthenticated against GitHub's 60/h/IP, and no per-item follow-ups.
- GitHub's own FIRST_TIME_CONTRIBUTOR verdict marks a newcomer's
contribution. Copied verbatim, never inferred from the size of the window
we happened to fetch, and it stops on its own as GitHub recomputes it.
Two honesty fixes the live render forced:
Each verb is now dated by its own event — `merged_at`, `closed_at`,
`published_at` — not by `updatedAt`. An issue opened in March and commented
on this morning was reading as "opened, 12 minutes ago".
Drafts are gone from the strip. A draft is its author's own not-ready
marker, and on this repo agents open them in batches: five of the twenty
items in the first live check were draft PRs from one bot, crowding out the
merges. They return the moment they are opened or merged.
A busy week could also bury the newest release entirely, so a release
published in the last sixty days keeps a slot — carrying its real date, so a
quiet quarter still reads as a quiet quarter rather than pinning an old tag
beside today's merges.
Verbs, the by-line, the first-contribution mark, and the strip's aria-label
are dictionary keys in all ten locales; titles, tags, and handles are the
repository's own record and stay verbatim. Ages format through CLDR off the
locale's existing `dateLocale`, the same way the masthead already sets its
date, with the compact English form as fallback where Intl data is missing.
Empty stays empty: an unreachable or fully-filtered feed removes the strip
rather than rendering a skeleton. Reduced motion now lets the frozen track
scroll instead of clipping every entry past the fold.
Verified: tsc, check:locales, check:facts, check:docs, 248 tests, lint, and a
288-page production build all clean. Live `next dev` against the real repo
rendered 15 entries on /en and /zh — merges by @Inference1 and @h3c-hexin,
issues by @vFONGv, @bistack, @shenjackyuanjie, @rafaelcavalheri, the v0.9.3
release, Chinese verbs and relative times throughout. Pointed at an
unreachable repo, both locales render fully with no ticker and no leaked
dictionary tokens.
`docs/public-surface-facts.json` listed `update_plan` in
`toolSurface.defaultActive`, and `public-surface-contract.test.ts` pinned that
same ten-name list in two places plus its own title. The code has never agreed:
`DEFAULT_ACTIVE_NATIVE_TOOLS` (crates/tui/src/core/engine/tool_catalog.rs:44)
holds eight names, `tool_search` is synthetic and always active for nine total,
and `update_plan` does not appear in tool_catalog.rs at all. It is a real tool,
just not a default-active one.
Found because a docs pass corrected `docs/TOOL_SURFACE.md` from "ten" to "nine"
against the code, which turned the contract test red. The doc was right and the
facts file was wrong, so the fix is to the facts file and the assertions rather
than to the doc — the test caught a genuine drift in our published surface
description, which is what it is for.
Receipt: cd web && npm test -> 28 files, 235 tests passed, exit=0;
npm run check:facts -> OK.
Every "ships with no endpoint configured" claim in this repo was written when
it was true. It stopped being true when the ingest Worker was deployed and the
client default was wired, so each of those sentences is now a false statement
about a live service. Rewrite them from what the service actually does.
Named plainly, everywhere: batches from an enabled session go to
https://telemetry.codewhale.net/v1/telemetry, a Cloudflare Worker whose
complete source is in this repo under telemetry-ingest/. Storage is Workers
Analytics Engine, whose row is exactly `_sample_interval`, `blob1`-`blob20`,
`dataset`, `double1`-`double20`, `index1`, `timestamp` — there is no IP,
country, or geo column, so storing one is structurally impossible rather than
switched off. The handler reads two headers, never touches the request's geo
properties, logs nothing, and rate-limits on `install_id` rather than an
address. Retention is Cloudflare's fixed three months. `docs/TELEMETRY.md`'s
shipping-gate section becomes a description instead of a promise, and the
retention line it left blank is filled in.
What did NOT change, and is stated at least as loudly as before: telemetry is
opt-in and off by default; nothing is collected, and the telemetry directory is
not created, until the first-run notice is answered with Enable; the endpoint
default decides only where an already-enabled session sends, never whether one
collects; `CODEWHALE_TELEMETRY=0`, `telemetry = false`, and a persisted off
remain hard floors; and the red-line list is untouched. The local dry-run sink
is documented as what it now is — an explicit `telemetry_endpoint = ""` — so a
user can still read their own payloads instead of taking this on trust.
Also stated rather than left implied: what turning telemetry off does and does
not reach. It erases the install id, buffer, and dry-run records locally and
stops everything further; rows already accepted are keyed only by a rotating
random id and age out with the retention window. There is no deletion API and
this does not claim one.
Both roadmap entries are updated in English and Chinese — the opt-in item and
the "Always-on or silent product telemetry" ruled-out item, which now says what
remains ruled out now that an endpoint exists. The public-surface trust string
and the contract-test assertion on it move together; the assertion gains the
consent half and the endpoint name, because a trust claim that says "an
endpoint" without saying which one is not a trust claim.
Gates: cargo test -p codewhale-config, cargo test -p codewhale-telemetry,
telemetry-ingest npm test (87, including the doc weld that parses
docs/TELEMETRY.md), and web check:facts / check:locales / check:docs / lint all
pass. web npm test has one pre-existing unrelated failure in the tool-surface
assertion against docs/TOOL_SURFACE.md, which this change does not touch and
which fails identically at HEAD.
Shortcut facts (this is why the contract test moved):
docs/public-surface-facts.json declared both `Tab` and `Shift+Tab` as
`when: "composer idle"`, and web/lib/public-surface-contract.test.ts pinned
that string into README.md and docs/KEYBINDINGS.md. Both halves are false —
crates/tui/src/tui/ui.rs:6978 gates Tab on `!app.input.is_empty()`, and
ui.rs:6363-6367 gates Shift+Tab only on the modal stack. The matrix now says
"composer empty" / "always (suppressed only under a non-Config modal)" and the
test asserts the corrected wording plus a negative guard so the idle claim
cannot come back. docs/MODES.md:24 already said "when the composer is empty";
it was the matrix and the test that were stale.
/runtime rendered the Chinese-primary H1 for every locale:
web/app/[locale]/runtime/page.tsx:84-88 had two byte-identical ternary
branches, both `Runtime & 集成 Integrations`. That contradicts
web/lib/i18n/dictionaries/types.ts:33-46 ("Never hardcode Han characters at a
call site") and docs/LOCALIZATION.md:80-82 ("no locale renders another
language's script by accident"). Every other page follows the correct pattern
(constitution/page.tsx:60-65). Fixed both branches; same class, smaller blast
radius, in feed/page.tsx:114 ("Section 03 · 动态" hardcoded in the EN branch).
FAQ provider list was 25 hand-maintained names against 40 real routes:
web/lib/facts.generated.ts derives 40 providers from `pub enum ApiProvider`,
and models/page.tsx already renders `facts.providers.length`. The FAQ omitted
15, including xAI, Baidu Qianfan, Meta Model API, and all four Model Studio
routes. Because ja/ko/ru/es/pt-BR/uk/vi/id fall back to English page bodies,
9 of 10 locales saw the stale list. The count is now derived from FACTS and
the enumeration is explicitly a sample ("including …") pointing at /models.
Other corrected claims:
- docs/subagents/page.tsx omitted the `consultant` role. It is one of the eight
in FLEET_ROLE_SCHEMA_VALUES (crates/tui/src/tools/subagent/mod.rs:376-385),
advertised to the model, and already in web/lib/content/vocabulary.ts:183-186.
- lib/media-manifest.ts:99-100 promised a recording "from the v0.9.2 release
candidate" on a live page (/docs/guide via components/session-media.tsx:47).
v0.9.2 and v0.9.3 both shipped; restated against 0.9.4.
- lib/docs-map.ts:39-40 advertised Nix and Scoop as contents of /install. Both
are real (docs/INSTALL.md:356, :507) but the site page has neither; the
description now names what the page actually contains.
- web/README.md:99/101/122/127 — "en / zh, every page is bilingual" (10 locales
route and non-zh get English bodies), a home-page section list naming four
sections that do not exist, and "EN ↔ ZH toggle" for what
components/locale-switcher.tsx:13-14 builds as an N-locale dropdown.
- docs/LOCALIZATION.md stated three different TUI key counts (1248 in the table,
1,153 at :234). `python3 scripts/check-tui-locale-parity.py` prints 1299 for
every pack. The zh-Hant row claimed 499/1248 "missing keys fall back to
English at runtime" — it is 1299/1299, so nothing falls back; the row now says
so and flags the PARTIAL_PACKS promotion as an open decision. The web
reference shape said 51/60 keys twenty-three lines above its own correct
52/62; check:locales prints 52/62.
Deleted (verified zero importers, no barrel, no glob loader, no CI reference):
- components/stat-grid.tsx — pulled from community/page.tsx in c268cc375, which
added the guard at lib/public-copy.test.ts:141 asserting the page must NOT
use it. That guard reads page source as a string and is kept.
- components/mermaid-diagram.tsx — its only consumer was removed in 5abe250f3e
("Cut: … the Mermaid architecture diagram"). Removed with it: the dead
`.mermaid-frame` rules in app/globals.css and the `mermaid` production
dependency, via `npm uninstall mermaid` so package-lock.json stays consistent
with package.json (CI runs `npm ci`, which hard-fails on drift).
Verified in web/: npm test exit=0 (28 files, 235 tests), check:facts exit=0,
check:locales exit=0, check:docs exit=0, lint exit=0.
Adds https://discord.gg/37gfS3ksug in the places someone actually looks for
a community: a badge in the first badge row and an entry on the language/link
line of all ten READMEs, a Discord action in the site nav beside the star
badge, an entry in the footer Project column, and a link in the homepage
community section.
"Discord" is a brand name, so it is a literal rather than a dictionary key —
it needs no translation and every routed locale gets the identical link shape.
The one exception is the zh README's nav line, which reads "Discord 社区" so
the word is a noun phrase in context rather than a bare English token.
docs-ia.test.ts pins the footer project routes in exact locale-swap parity, so
its expectation is extended rather than relaxed — a new footer link still has
to appear in every locale or the test fails.
Receipts: cd web && npx tsc --noEmit exit=0; npm test -> 28 files, 235 tests
passed; npm run lint exit=0.
`html[lang="zh"] h1/h2/h3` relaxed `overflow-wrap: anywhere` so Chinese
headings stop stranding punctuation on a line of its own. ja needs exactly the
same rule and never had it — capping the ocean headings made it visible, with
`コマンド 1 つで始める。` breaking between `1` and `つ`. ko wants it too: it has
real word boundaries and should break on them rather than anywhere.
Extended rather than duplicated, per the rule that CJK overrides are extended
and never routed around.
Verified by eye at /ja and /ko, 1440px and 390px; no horizontal page scroll in
en/zh/ja/ko at 390px. npm test (235), lint, check:locales, check:docs.
The TUI's ambient life — fish, jellyfish, bubbles, the whale cameo — stays out
of scope. Two orders of magnitude separate web-native "ocean motion" from what
this product has ever shipped, and this page quotes the product's restraint
doctrine in its own copy. The whole site now has four keyframes; these are two
of them.
M1, the column breath: one overlay on `.ocean-column` animating OPACITY ONLY
between 0 and 0.045 on a 90s cycle — the TUI's authored 0.018–0.055 phase bias
as the same fraction of a mix. Opacity is the only property that cannot cause
layout work on an element four bands tall. No position change, no hue shift,
no filter.
M2, the caustic: ~1.3s of ice light crossing the footer whale, parked
off-canvas for the remaining ~2.7s, peak 0.33, sine-in-out — `ambient_life.rs`
at its literal amplitude and cadence. One whale only; two caustics is chrome.
It is clipped to the mark, so it lands on the whale and never on a rectangle
of footer behind it, and `Whale` takes it as an opt-in prop.
The clip sits on a static <g> with the highlight moving inside it. Clipping the
moving rect instead slides the whale-shaped window along with the light, which
is a very quiet way to render nothing at all — caught by screenshotting a
frozen frame rather than by reading the markup.
Both effects are gated behind `prefers-reduced-motion: no-preference` rather
than overridden under `reduce`: an override can be defeated by specificity, a
gate cannot. Verified under emulated `reduce` — the breath overlay does not
exist, the caustic has no animation and stays parked, and the column's field
is still there. Reduced motion freezes the field; it never removes it.
No new strings. Verified: npx tsc --noEmit, npm test (235), npm run lint,
by eye at /en 1440px including a frozen mid-sweep frame.