learning.nyuchi.com — the Nyuchi Learning surface, and the home of the classroom tools Nyuchi Web Services builds for schools.
Version: the latest git tag (see CHANGELOG.md) | Live: learning.nyuchi.com | Work lands on: staging | Production: main | Deploy: Vercel
A small static Astro site:
| Route | What it does |
|---|---|
/ |
Points visitors at the three surfaces the learning content lives on, and at the tool they can use today. |
/toddle-enhancement-extension |
The product page for the Toddle Enhancement Extension. |
/toddle-enhancement-extension/releases |
The extension's release notes, for teachers, from src/data/releases.ts. |
/feedback, /feedback/thanks |
The feedback form, posted to Formspree. |
/legal/*, /accessibility |
Privacy, cookies, terms, data handling, student privacy, security and the rest, from src/data/legal.ts. |
The redirect job has not gone away — the content split described in CHANGELOG.md still holds — but this domain is now also where the classroom software lives, so the site is no longer only a signpost.
Nothing here defines a colour, a font or a component class. All of it comes from
@bundu/ui, Nyuchi's implementation
of the Mzizi design system:
/* src/styles/global.css */
@import "@bundu/ui/styles/globals.css"; /* seven minerals, semantic tokens, components */
@import "@bundu/ui/styles/brand-nyuchi.css"; /* gold primary + ring, as on nyuchi.com *//* tailwind.config.mjs */
presets: [preset] /* @bundu/ui/tailwind-preset */This is deliberate, and it is the second thing this repo got wrong before. The
previous version carried a hand-copied subset of the tokens with a comment
claiming they were verbatim; they were not, and the site drifted away from
nyuchi.com without anyone noticing. tests/branding.test.ts now fails the
build if a token is redefined locally.
If a colour looks wrong, it is wrong in @bundu/ui — fixing it there fixes
every Nyuchi surface at once.
Vercel. The site is fully static — every page is known at build time, so nothing renders per request.
Work goes to staging by pull request, and each merge there is tagged as the
next patch (.github/workflows/staging-version.yml). A release is a pull
request from staging to main; production deploys from main, which is
tagged as the next minor with a GitHub release
(.github/workflows/main-version.yml). See CHANGELOG.md.
Response headers, including the CSP, are declared in vercel.json.
npm install
npm run dev # http://localhost:4321 — hot reload
npm run check # astro check (TypeScript across .astro files)
npm test # build, then the tests (vp test), then the CSP hash check
npm run lint # vp lint (oxlint, type-aware)
npm run format # prettier --write; format:check is what CI runs
npm run fmt # vp fmt; `vp fmt --check` is the org's vite-plus gate
npm run qa # drive a real browser: accessibility + layout, 18 combinations
npm run consent:probe # drive a real browser through the cookie banner
npm run csp:probe # check the Intercom messenger against the CSP in a browser
npm run build # produces .vercel/output for the Vercel adapter
npm run clean # remove build output, caches and finished branchesvercel.json carries a real Content-Security-Policy, not just the framing and
sniffing headers. script-src is 'self', the hosts of the two third parties
(Google's analytics tag, after consent, and the Intercom messenger, when asked
for), and a hash for each inline script, so nothing inline runs unless it was
in the build that was reviewed.
The inline scripts are the theme bootstrap, which has to run before the first
paint or the page flashes light before going dark; the analytics consent check
in Analytics.astro, which loads Google's tag only after the visitor agrees;
and the small modules Astro inlines. A hash allowlist is only worth something
if it cannot silently fall out of date, so npm test verifies it:
npm run csp # fail if a built inline script is not in the CSP
npm run csp:fix # rewrite vercel.json with the current hashes, then reviewEdit an inline script without running csp:fix and CI fails with the missing
hash, rather than the script quietly breaking in production.
npm, Astro 7, Tailwind 4, Vite+ (vp test, vp lint, vp fmt), Prettier.
More in docs/toolchain.md.
- Astro 7 with Tailwind 4.
@bundu/uiships a Tailwind 3 style preset, which Tailwind 4 consumes through@config— that is what makestext-h2,ease-soft,max-w-narrowand the mineral colours resolve, both as utilities here and inside the kit's own@applyrules. overridespinspath-to-regexpto^6.3.0.@astrojs/vercelpulls in a version with a high-severity advisory and there is no fixed release upstream; the override is within the same major, so it is a patch rather than a behaviour change.- Two formatters, one set of settings. The org runs two required gates:
a lint gate that runs Prettier, and
vite-plus / check, which runsvp fmt --check..prettierrcand thefmtblock invite.config.tshold the same settings, andtests/toolchain.test.tsfails if they drift. *.astrois in.prettierignore. The org lint gate runs Prettier fromnyuchi/.github, which has noprettier-plugin-astro, so it cannot parse those files. They are still type-checked bynpm run check.
npm run qa builds the site, serves it, and drives Chromium over every page at
phone, tablet and desktop width in both colour schemes. It runs axe-core
against WCAG 2.1 AA and checks for horizontal overflow, which is the classic
small-screen bug and is invisible in a desktop screenshot.
It is not in npm test because it needs a browser; run it before shipping a
visual change. A sandboxed browser that does not trust the local TLS proxy
fails the Google Fonts request — console errors are reported but do not fail
the run, for that reason. Overflow and accessibility violations do.
CI splits the audit in two:
| Step | Scope | Blocking |
|---|---|---|
| Audit what ships | --omit=dev, high and above |
yes — expected to stay at zero |
| Report advisories in build and test tooling | everything, moderate and above | no |
The blocking step is scripts/audit.mjs: npm audit --audit-level=high --omit=dev, with a dated list of advisories set aside by ID, each with the
reason it cannot reach a visitor and a date to recheck. Past that date the step
fails again. The second step keeps advisories in the tooling visible in the log
without pinning the job red over something that never ships.
src/
data/site.ts nav, footer, wordmark, extension links, analytics id
data/legal.ts the facts the legal pages are built from
data/consent.ts consent categories, storage key, version
data/releases.ts the extension's release notes
layouts/BaseLayout.astro head, theme bootstrap, header + footer
components/ Wordmark, SiteHeader, SiteFooter, Analytics,
CookieBanner, SupportWidget
pages/ home, the extension and its releases,
feedback, accessibility, legal/*
styles/global.css @bundu/ui imports and nothing else
public/llms.txt the site, summarised for language models
scripts/ CSP check and fix, audit, browser QA and
probes, clean
tests/ links, branding, security, consent, SEO,
toolchain
docs/toolchain.md how the tools fit together
Proprietary. See LICENSE.