Hoverboard is a conference website template. Organizers fork it, configure it and deploy it to their own Firebase project. The web app is Lit components that Astro renders to static pages at build time, from the site's content in Firestore. The pages then hydrate in the browser and keep the content live. Cloud Functions send notifications.
| Path | What it is |
|---|---|
packages/web |
The web app: Astro pages, Lit components, Redux Toolkit store, Workbox service worker |
packages/server/functions |
Cloud Functions (v2 API). Must stay self-contained, because firebase.json deploys it alone |
packages/cli |
The hb CLI (./hb <command>), run with tsx, no build step |
packages/storage |
Firestore and Storage security rules, indexes, the Firestore schema, the collection registry and the rules tests |
packages/translations |
UI translations (XLIFF) from upstream's Crowdin project, read by packages/web. No dependencies |
packages/config |
The site's own config and content, which upstream only has a README in. ./hb init creates it from the demo |
packages/demo-config |
The demo site: site.json, content/, images in public/. Used when packages/config has no site.json, and by tests |
packages/web/defaults |
Upstream site.json defaults that the site's config overrides. Objects merge, and arrays and other values replace |
docs/ |
Tutorials and the release policy |
Each package with dependencies has its own package.json and package-lock.json. The root is a thin orchestrator: its scripts delegate with npm --prefix ./packages/<name>.
- Node.js and npm versions come from
enginesin the rootpackage.json. CI uses the same file. npm ciat the root also installs every package throughpostinstall.- Java is needed for the Firestore emulator, which the rules tests and
npm startuse. - Chromium is needed for the
A11ytests:npx --prefix packages/web playwright install chromium. - Production Firestore commands sign in with the Firebase CLI login (
npx firebase login). There are no service account key files.
Run from the repo root.
| Command | Does |
|---|---|
npm test |
All Vitest projects: Web, Server, Stages, Hydration, Functions, CLI, Storage, Firestore and Sample data (both start the emulator), Translations, Smoke and A11y |
npx vitest run --project Web |
One project. Add a path to run one file |
npm run lint |
ESLint, Prettier, syncpack, lit-analyzer, site config, astro check and type checks for web, server and storage |
npm run fix |
ESLint and Prettier autofix |
npm run build |
Production build of every page to packages/web/dist. Reads content from the Firestore emulator, see FIRESTORE_TARGET below |
npm start |
Emulators, functions and the Astro dev server at http://localhost:4321 |
npm run serve |
Builds from the emulator data, then serves dist on the Hosting emulator at http://localhost:5000 |
./hb doctor |
Checks the local setup, Firebase login, project, billing plan, deployed functions, deploy roles, service account keys and browser API keys |
./hb init |
Sets up a site: Firebase project and web app, event details, text and images in packages/config, the Firestore database, billing, starting content, backups, first deploy and GitHub repository. Changes production, so only run it when asked |
./hb status |
Shows the project, versions, features, last deploy, the fork's changes, document counts and the site's stages (emulator, or --production) |
Before finishing a change, run npm run lint and npm test, or at least the affected Vitest project and type check.
- TypeScript is strict, with
verbatimModuleSyntax. Useimport typefor type-only imports. Custom elements register as a side effect, so import them withimport './my-element'where they are used. - Components use
@customElement, extendThemedComponent(which adds the shared theme styles), and read store state with the@fromStoredecorator. Use the color tokens fromsrc/themes/tokens.tsas CSS variables, never hex colors. A test fails on hex colors outsidesrc/themes/. - Pages are Astro files in
src/routes/.build/routes.tsadds the pages of enabled features, andsrc/pages/has the pages every site has. Each page reads its content withloadContent(), passes the collections its components read toseedPage()in its frontmatter, and renders its view withclient:load. Components render on the server first, so their first render in the browser must match: nowindow,document, randomness or viewport checks beforefirstUpdated(). Use CSS media queries for layout by screen size. - Build content comes from
FIRESTORE_TARGET: unset reads the emulator,productionreads production Firestore (deploys only),nonebuilds without content (CI checks and smoke tests), andsamplereadsdocs/sample-datawithout Firestore (src/data/sample.ts). TheSample dataproject checks thatsamplereads the same content as the emulator seeded with it. - CSP.
build/csp.tsadds the same Content-Security-Policy to every built page, with the hashes of the inline scripts. Inline scripts must be in the<head>(<Fragment slot="head">in a page), or the build fails. A new origin the site loads from goes incspPolicy().npm startruns without the policy, so check withnpm run serve. - Site config. Client code reads site config only through
src/config/site.ts, never from the data files directly. Build-time config reads live inpackages/web/build/. Upstream's site is the demo inpackages/demo-config: edit it there, never inpackages/config, which belongs to each fork. The build,npm startand the CLI usepackages/configonce it has asite.json, and tests always use the demo. Images go in the config'spublic/, which the build lays overpackages/web/public. When you add or rename a key inpackages/demo-configorpackages/web/defaults, update the schema inpackages/web/schemas/too../hb config validatechecks it. A change that breaks an existingsite.jsonneeds a config migration, see docs/releases.md. - Firestore data. Every collection Hoverboard uses is in
packages/storage/collections.ts, with its features and its definition inpackages/storage/schemas/firestore.schema.json. A new collection or field goes in both, and infirestore.ruleswhen the site reads or writes it. TheFirestoretests fail when they disagree. - UI text uses
msg()from@lit/localizewith an explicitidsuch asfooter.locale-picker.label. After adding or changing one, runnpm --prefix packages/web run localize:extractand commitpackages/translations/source/en.xlf.npm run lintfails when it is out of date. - Tests sit next to the code as
*.test.ts. Web tests run in jsdom: render withfixturefrompackages/web/__tests__/helpers/fixtures.ts, set state withsetStoreState, turn features off withsetFeaturesfromhelpers/features.ts, switch locale withuseLocalefromhelpers/locale.ts(only theSmoke (fake locale)project has a second locale), and assert with the jest-dom matchers. Every bug fix or feature needs a test. - Accessibility. Find elements by role and name in tests. A new component calls
expectNoAxeViolations()fromhelpers/axe.tsin its test, in each state, and a new page goes inhydration-pages.ts, whose pages are checked with axe after they hydrate. UI changes keep theA11yproject passing. docs/ACCESSIBILITY.md has the target and what to keep in mind. - Server tests are
*.server.test.tsfiles in theServerproject. They render components with Lit SSR in Node, as the build does: fill the store withseedPage()fromsrc/data/page.ts, then render withrender()from@lit-labs/ssr. A component that reads content the page did not seed fails with "db is not available on the server". - Hydration tests are in the
Hydrationproject. Its global setup renders each page inpackages/web/__tests__/fixtures/hydration-pages.tswith Lit SSR, andpages.hydration.test.tshydrates them in jsdom. It fails when the first client render does not match the server HTML. Add a page there when a component renders differently in the browser. - Stage tests are in a
Stages (<fixture>)project per fixture inpackages/web/__tests__/fixtures/stages.ts: a site in one or more event stages, such asfirst-callorreturning-live, with itssite.jsonchanges, part of the sample data and a moment in time.pages.stages.test.tsrenders the header, footer, home, speakers and schedule pages with Lit SSR at that moment, and checks the fixture's stages, the hero's and header's next steps, the home bands with content, links only to pages with content, and the "coming soon" pages. Run them withnpx vitest run --project 'Stages*'. A change to what a stage shows updates a fixture'ssteps. - Smoke tests are
*.smoke.test.tsfiles in their own Vitest projects.features.smoke.test.tsbuilds the site once per feature with that feature off (--project 'Smoke (blog off)'), and checks its pages, links and chunks are gone.build.smoke.test.tsbuilds the minimal site inpackages/web/__tests__/fixtures/minimal-site. Run them all withnpx vitest run --project 'Smoke*'. - A11y tests are in the
A11yproject. Its global setup builds the demo site with the sample data, the minimal site, and an empty first edition (the demo's config without content, with the dev bar's clock at thefirst-announcedfixture's time), and serves them on the Hosting emulator.pages.a11y.test.tsruns axe in Chromium on each page template in every theme and scheme (the empty first edition's home page only), and fails on any WCAG 2.2 AA violation. Best-practice rules are only reported. axe'sregionrule flags the minimal site's skip link, because it can't compare link order across shadow roots.checks.a11y.test.tschecks what axe can't, on each template: oneh1, the landmarks and a unique title, a Tab walk to the footer where every stop is visible, changes when focused and isn't entirely covered, no sideways scrolling at 320px, and no animations with reduced motion. It reports, without failing, text that WCAG 1.4.12 text spacing clips. Shared page setup is inbrowser.ts. Results and screenshots go topackages/web/test-results/a11y. - Features are gated with
__HB_FEATURES__.<name>where the code of a disabled feature should be left out of the build. The build replaces it withtrueorfalse. UseisFeatureEnabled(name)only for names known at runtime. - Vitest is configured once in the root
vitest.config.ts. Do not addvitestto a package'spackage.json, because a second copy breaksexpect.extendfrom setup files. - Dependencies shared by several packages must use the same version range.
npm run lint:syncpackchecks this. - Paths. Astro and Vite run with
packages/webas the working directory, so their relative paths resolve from there. The site's config is../configor../demo-config: useCONFIG_PATHSfrombuild/resolve-config.ts, orsiteConfigPath()in the CLI. - Local development always uses the
demo-hoverboardproject on the emulators, whateverfirebase useselects. The web app connects to the emulators and skips Analytics and Performance Monitoring when the project ID starts withdemo-. - Data commands (
./hb data …,./hb backup …,./hb notifyand./hb upgrade --migrations-only) use the emulator by default. Only target production with--productionwhen explicitly asked../hb backup scheduleand./hb data pushalways change production, so only run them when asked../hb edition new,switchandrebuildcreate, change and archive Firebase projects, so only run them when asked../hb upgradewithout--migrations-onlymerges a Hoverboard release into a fork, so don't run it in this repository. The CLI no longer readsFIRESTORE_TARGET, which only the build uses. - Sample data is
docs/sample-data, an export in the formathb data exportwrites (src/lib/data-format.ts): a manifest and one JSON file per collection, by document path.hb data seedimports it. Tests read its files directly. Keep it valid: a CLI test imports it with no problems and no pending migration. - CLI commands are grouped by topic, as
hb <topic> <action>(hb data check), and load their modules inside their action, after the target is set. A command that can change production takes--dry-runand--yes, and asks withconfirmChange(). Commands return aCommandResultor a boolean, and throwUsageErrorfor a usage error.src/program.tsturns them into exit codes (0, 1 for a problem to fix, 2 for a usage error) and--jsonoutput. - Formatting. Prettier formats everything, including Markdown, JSON and YAML. Run
npx prettier --write <files>after editing them. - Docs use short, plain sentences. Mark claims that were not tested with "verify".
- Pull request titles follow Conventional Commits, for example
fix(web): show session times in the event time zone. They are squash merged, and the title becomes the release note. - Mark breaking changes with
!and explain what organizers must do in aBREAKING CHANGE:paragraph. docs/releases.md defines what counts as breaking. - release-please opens the release pull request and updates CHANGELOG.md. Do not edit the changelog by hand.
serviceAccount.json,*-adminsdk-*.json,.firebaserc, or any other credentials.- Emulator debug logs (
*-debug.log). NEXT.mdand the planning specs indocs/that it lists. They are local working documents. Do not link to them from committed files.