Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
123 commits
Select commit Hold shift + click to select a range
37888db
feat: scaffold Astro + Vue migration (phases 0-1)
sinduri-g Jul 21, 2026
b619d94
config: keep CI green for the nested astro/ migration app
sinduri-g Jul 21, 2026
71f7bcb
feat(astro): content collection with verified markdown pipeline (phas…
sinduri-g Jul 21, 2026
e7a3ba0
feat(astro): meta-description synthesis and services step injection (…
sinduri-g Jul 21, 2026
0eb3c2f
feat(astro): SEO component and adventure routes (phase 3 increment 1)
sinduri-g Jul 21, 2026
4d0b700
feat(astro): level detail route with static discussion and leaderboar…
sinduri-g Jul 21, 2026
eed19d8
feat(astro): challenges filter with first Vue island (phase 3 increme…
sinduri-g Jul 21, 2026
d3aa11d
feat(astro): site chrome, theme-toggle island, and home page (phase 3…
sinduri-g Jul 21, 2026
f38bcd1
feat(astro): consent banner island with GA4 gated load (phase 3 incre…
sinduri-g Jul 21, 2026
09573d4
feat(astro): design pass — real Hero and adventure cards (phase 3 inc…
sinduri-g Jul 21, 2026
b57b6e7
feat(astro): design pass — challenge card layout in the filter island…
sinduri-g Jul 21, 2026
75fb237
feat(astro): adventure detail page design (phase 3 increment 8)
sinduri-g Jul 21, 2026
6090a32
feat(astro): 404 page (phase 3)
sinduri-g Jul 21, 2026
72da682
feat(astro): static pages — privacy, accessibility, contribute, handb…
sinduri-g Jul 22, 2026
8e3b336
feat(astro): remaining static pages and redirects (phase 3)
sinduri-g Jul 22, 2026
676b8ea
feat(astro): challenge detail page design with architecture diagrams …
sinduri-g Jul 24, 2026
2d13df8
feat(astro): solution walkthrough route via alias bridge (phase 3)
sinduri-g Jul 24, 2026
ae01085
test(astro): accessibility e2e suite (axe + a11y checks) and fixes (p…
sinduri-g Jul 24, 2026
9e85ea0
test(astro): SEO + smoke + hydration e2e (phase 5)
sinduri-g Jul 24, 2026
d3e499f
ci(astro): rewrite deploy and preview workflows for astro-at-root (cu…
sinduri-g Jul 24, 2026
5c70dd9
refactor(astro): self-contain the app's data ahead of the cutover mov…
sinduri-g Jul 24, 2026
0cf49e7
feat(astro)!: cut over — Astro + Vue app becomes the root project (st…
sinduri-g Jul 24, 2026
6373f70
ci(astro): rewrite validate-adventures for the content collection (cu…
sinduri-g Jul 24, 2026
47cedc6
ci(astro): decouple sync-adventure and add-discussion-url from the ge…
sinduri-g Jul 24, 2026
b31d372
docs(claude): rewrite CLAUDE.md for the Astro + Vue stack (cutover fo…
sinduri-g Jul 24, 2026
e9acf45
docs(astro): rewrite README and prune stale REUSE entries (cutover fo…
sinduri-g Jul 24, 2026
1261fbc
docs(astro): update ADVENTURES, PERFORMANCE, styleguide for the migra…
sinduri-g Jul 24, 2026
df575e7
chore(astro): add ESLint for astro + vue + ts, wire into CI (cutover …
sinduri-g Jul 24, 2026
9812da1
feat(astro): generate sitemap.xml from the content collection (cutove…
sinduri-g Jul 24, 2026
86d6c15
docs(claude): mark lint + sitemap endpoint done, add lint to checks
sinduri-g Jul 24, 2026
4b2d201
test(astro): consent state-machine runtime regression tests (cutover …
sinduri-g Jul 24, 2026
505a37f
docs(claude): mark consent runtime tests done
sinduri-g Jul 24, 2026
0a91f2d
feat(astro): gated GA4 click-event tracking (cutover follow-up)
sinduri-g Jul 24, 2026
229d7dc
docs(claude): mark click-event tracking done
sinduri-g Jul 24, 2026
23482ca
feat(astro): absorb dead-reckoning expert level + abbr expansions fro…
sinduri-g Jul 24, 2026
59f99ce
feat(astro): restore navbar + footer chrome to production parity
sinduri-g Jul 24, 2026
058da35
feat(astro): rebuild home page sections to production parity
sinduri-g Jul 24, 2026
2354b5b
feat(astro): rebuild Adventures index page to production parity
sinduri-g Jul 24, 2026
c788bed
feat(astro): rebuild challenges filter UI + interactive home/challeng…
sinduri-g Jul 24, 2026
a69be42
fix(astro): restore adventure-detail + level + 404 regressions
sinduri-g Jul 24, 2026
8f986a8
feat(astro): restore code-block language-label header + Copy button
sinduri-g Jul 24, 2026
c11ad14
docs: add current Astro/Vue component inventory to styleguide
sinduri-g Jul 24, 2026
29f637b
fix(astro): address code-review findings on the parity work
sinduri-g Jul 24, 2026
d96dab6
refactor(astro): extract shared adventure sort + live check to lib/ch…
sinduri-g Jul 24, 2026
18a1407
Merge origin/main into feat/astro-migration
sinduri-g Jul 25, 2026
17941dc
fix(a11y): JS abbreviation tooltips (fix mobile overflow + cutoff)
sinduri-g Jul 25, 2026
2034610
docs(claude): mark abbr JS tooltip + parity restoration done in follo…
sinduri-g Jul 25, 2026
4eb56df
chore(reuse): restore CC0-1.0 annotation for scraped community data
sinduri-g Jul 25, 2026
18ec147
perf(astro): defer/scope island hydration + trim filter payload
sinduri-g Jul 27, 2026
52a2279
refactor(astro): render code-block header + Copy button at build time
sinduri-g Jul 27, 2026
6577bbe
fix(astro): address 28 review issues across stability, perf, a11y, SE…
sinduri-g Jul 27, 2026
ba3f0ba
fix(astro): audit fixes — bugs, types, a11y, dead code, CI
sinduri-g Jul 27, 2026
e98551f
fix(astro): apply full branch audit — CI, a11y, bugs, cleanup
sinduri-g Jul 27, 2026
9a52c6b
chore(merge): merge origin/main into feat/astro-migration
sinduri-g Jul 28, 2026
58c385e
fix: restore ConsentBanner to prod parity and fix all audit findings
sinduri-g Jul 28, 2026
9bb92e4
test: add Vitest unit test suite covering lib, stores, and Vue compon…
sinduri-g Jul 28, 2026
248ea8a
chore(icons): replace astro-icon and lucide-vue-next with unplugin-icons
sinduri-g Jul 28, 2026
a9066ac
chore(icons): migrate all component and page icon references
sinduri-g Jul 28, 2026
11a533c
fix(community): restore challenge-rockstars section on about and cont…
sinduri-g Jul 28, 2026
c7ef475
fix(test): update challenges filter smoke test for radio role
sinduri-g Jul 28, 2026
f859999
refactor: post-migration audit fixes and test improvements
sinduri-g Jul 28, 2026
0911bcd
fix(a11y): add focus ring to nudge link and raise dismiss button to 32px
sinduri-g Jul 28, 2026
2dd1a32
fix(a11y): prevent verification command pre from overflowing at 200% …
sinduri-g Jul 28, 2026
89f2129
docs(claude): fix stale Reka UI reference and Phase 1 comment
sinduri-g Jul 28, 2026
2fe229e
feat(markdown): add Shiki dual-theme syntax highlighting for code blocks
sinduri-g Jul 28, 2026
1680563
docs(claude): mark Shiki highlighting done in known follow-ups
sinduri-g Jul 28, 2026
6b7d5a9
docs(styleguide): rewrite for Astro + Vue — full component API reference
sinduri-g Jul 28, 2026
bcac121
fix(test): add missing dead-reckoning detail route; drop stale comments
sinduri-g Jul 28, 2026
744563f
chore: exhaustive audit fixes across 20 files
sinduri-g Jul 28, 2026
abd48c5
fix(audit): apply 25-item code-quality audit across the codebase
sinduri-g Aug 24, 2026
bbd4ad3
fix(security): tighten CSP img-src and fix unsafe index accesses
sinduri-g Aug 24, 2026
ec76c3e
test(lib): add edge-case tests for sortAdventuresByMonthDesc and cano…
sinduri-g Aug 24, 2026
2690497
chore(deps): bump all safe deps; fix 4 audit vulnerabilities
sinduri-g Aug 24, 2026
6c45c7c
fix(migration): fix e2e webServer daemon, AdventureCard contributor U…
sinduri-g Aug 24, 2026
9606d12
fix(styleguide): revert AdventureCard contributor url; document inten…
sinduri-g Aug 24, 2026
bba0737
fix(security): allow avatars.discourse-cdn.com in CSP img-src
sinduri-g Aug 24, 2026
e07d96b
fix(P2): widen CSP img-src to all discourse-cdn subdomains
sinduri-g Aug 24, 2026
82caed6
fix(P6): stop the consent banner stealing focus on hydration
sinduri-g Aug 24, 2026
41d1110
fix(P9): make the handbook Q&A abbreviation keyboard-reachable
sinduri-g Aug 24, 2026
c0d353b
fix(P11): restore consent banner reflow guard and give Decline equal …
sinduri-g Aug 24, 2026
7e6ecd0
fix(P12): close filter dropdowns when focus leaves them
sinduri-g Aug 24, 2026
57d63ee
feat(P1): restore per-page JSON-LD on adventure, level and solution p…
sinduri-g Aug 24, 2026
47cb635
fix(P3): restore the Community Leaders sidebar on /handbook/
sinduri-g Aug 24, 2026
931bf25
fix(P4): show every adventure leaderboard row, not just the top 5
sinduri-g Aug 24, 2026
e7a8d0f
fix(P7): restore the GA4 click_event payload contract
sinduri-g Aug 24, 2026
80aaa89
fix(P8): restore the indexed titles and description on /adventures/ a…
sinduri-g Aug 24, 2026
8c4a391
fix(P10): guarantee unique abbreviation IDs in the rendered HTML
sinduri-g Aug 24, 2026
8a167c0
fix(P14): restore the /brand/ table-of-contents scrollspy
sinduri-g Aug 24, 2026
ec808fb
docs: drop migration archaeology from code comments
sinduri-g Aug 24, 2026
8f0dfd7
fix(P10): enforce the beginAbbrScope() contract
sinduri-g Aug 24, 2026
9923a79
ci(P18): run unit tests in CI again
sinduri-g Aug 24, 2026
367d53b
docs: drop migration archaeology from code comments
sinduri-g Aug 24, 2026
52f05cf
fix(P20): declare the build's real runtime dependencies
sinduri-g Aug 24, 2026
351797a
refactor(P21): de-fork deadline.mjs and level-constants.mjs
sinduri-g Aug 24, 2026
cc64b98
fix(P19): restore jszip so the reveal template generator runs
sinduri-g Aug 24, 2026
69a375f
fix(a11y): raise Shiki comment contrast to WCAG AA
sinduri-g Aug 24, 2026
610f6ed
test(P22): restore the deleted e2e guards and gate route coverage
sinduri-g Aug 24, 2026
edc490d
chore(P22): lint accessibility in .astro and .vue, add an astro check…
sinduri-g Aug 24, 2026
1251ff7
refactor(a11y): bind the radiogroup arrow keys to the radios, drop th…
sinduri-g Aug 24, 2026
031df93
fix(P8): keep the page heading stable on challenge tag routes
sinduri-g Aug 24, 2026
168c8cb
fix(build): make Shiki failures fail the build instead of dropping hi…
sinduri-g Aug 24, 2026
910b172
refactor(P19): ThemeToggle from Vue island to static .astro
sinduri-g Aug 24, 2026
b3f626c
refactor(P20): MobileMenu from Vue island to static .astro
sinduri-g Aug 24, 2026
bb88c98
refactor(P21): ConsentBanner from Vue island to static .astro
sinduri-g Aug 24, 2026
1435330
refactor(P22): StarterNudge from Vue island to static .astro
sinduri-g Aug 24, 2026
c0d01d1
feat(nudge): point the starter nudge at the latest adventure, not a l…
sinduri-g Aug 24, 2026
7b6f07a
fix(a11y): restore the spaces around inline links in the banner and n…
sinduri-g Aug 24, 2026
235fbc1
feat(nudge): prefer a live adventure, fall back to the most recent
sinduri-g Aug 24, 2026
9a47e2b
fix: point the hero CTA back at the in-page adventure grid
sinduri-g Aug 24, 2026
53037b2
refactor: replace the ChallengesFilter island with .astro + a script
sinduri-g Aug 24, 2026
61f66bc
docs: record the framework policy and the Astro whitespace requirement
sinduri-g Aug 24, 2026
c8c6e88
fix(types): clear the type errors the new .astro components introduced
sinduri-g Aug 24, 2026
3ea9f52
fix(types): clear all astro check errors and gate it in CI (#217)
sinduri-g Aug 24, 2026
72f44f3
fix(a11y): give the primary button a visible boundary in light mode (…
sinduri-g Aug 24, 2026
0d213b3
test: correct a stale live-region assertion on the tag-route heading …
sinduri-g Aug 24, 2026
9e9cbd5
fix(a11y): raise avatar initials above the very-small-text threshold
sinduri-g Aug 24, 2026
ecba05c
fix: resolve all pre-merge blockers from the Astro migration audit
sinduri-g Aug 24, 2026
5c150f3
test: add regression tests for solution.astro hashchange listener lif…
sinduri-g Aug 24, 2026
0a6d718
docs: clarify listener lifecycle rule with two legitimate exceptions
sinduri-g Aug 24, 2026
b233203
docs: Phase 2 — clear stale React Router references from all docs
sinduri-g Aug 24, 2026
936d4fa
docs: clear stale React Router / pre-migration references (Phase 2 ex…
sinduri-g Aug 25, 2026
82b494d
security: Phase 3 hardening — SHA pins, CSP, Discourse sync, tests
sinduri-g Aug 25, 2026
a6bb052
fix(csp): remove frame-ancestors from meta CSP — it is header-only
sinduri-g Aug 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
7 changes: 6 additions & 1 deletion .ai/templates/generate-reveal-zip.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@
*
* Run: node .ai/templates/generate-reveal-zip.mjs
*
* Requires jszip (devDependency). The reveal assets are read from the committed
* public/reveal/, not from node_modules, so reveal.js itself is not needed to run this.
*
* Re-vendor note: public/reveal/ is a hand-vendored four-file subset of reveal.js@6.0.1.
* reveal.js is deliberately NOT a dependency; install it ad hoc when updating
* (npm install reveal.js@6, same pattern as pptxgenjs for the PPTX generator).
* The full dist/ tree is NOT copied. When updating Reveal, copy only these four files from
* node_modules/reveal.js/dist/ to public/reveal/ do not copy the whole directory, as
* node_modules/reveal.js/dist/ to public/reveal/ - do not copy the whole directory, as
* that would restore deleted themes and plugins along with their OFL and BSD attribution
* obligations. The four required files are: reset.css, reveal.css, reveal.js,
* plugin/notes.js.
Expand Down
4 changes: 2 additions & 2 deletions .claude/commands/add-solution.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,13 @@ If lint or build fail, fix the issues before reporting done.
Check the built HTML contains the solution title:

```bash
grep -c "<solution-title-fragment>" dist/client/adventures/<adventure-id>/levels/<level-id>/solution/index.html
grep -c "<solution-title-fragment>" dist/adventures/<adventure-id>/levels/<level-id>/solution/index.html
```

Use a unique word from the solution title as the fragment. If a contributor was provided, also confirm their name appears:

```bash
grep -c "<contributor-name>" dist/client/adventures/<adventure-id>/levels/<level-id>/solution/index.html
grep -c "<contributor-name>" dist/adventures/<adventure-id>/levels/<level-id>/solution/index.html
```

Report success with the path if both pass.
Expand Down
6 changes: 3 additions & 3 deletions .claude/commands/create-presentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Generate a presentation that matches the OffOn design system, in the format of y
2. Reads the appropriate template file.
3. Generates slide content and fills in the placeholders.
4. Writes the output file to `public/`.
5. Does not touch `public/sitemap.xml`, `react-router.config.ts`, or `src/routes.ts`.
5. Does not touch `src/pages/sitemap.xml.ts`, `astro.config.mjs` redirects, or test route lists — presentations go in `public/` and are not Astro pages.

---

Expand Down Expand Up @@ -517,7 +517,7 @@ When creating a deck for a challenge walkthrough:
- Slides 3–N: One slide per major step. Use `.sh` header with step number as overline label.
- Final slide: What the learner accomplished + link to the challenge on offon.dev.

For scenario and architecture text, pull from the adventure's generated TypeScript in `src/data/adventures/<id>/<id>.generated.ts`. The `scenario`, `architecture`, `backstory`, and `objective` fields are pre-rendered HTML; strip tags or paraphrase for slide copy; do not paste raw HTML into the deck.
For scenario and architecture text, pull from the adventure YAML at `src/data/adventures/<id>/adventure.yaml`. The `scenario`, `architecture`, `backstory`, and `objective` fields contain markdown; paraphrase for slide copy. Do not use or look for `*.generated.ts` files — they no longer exist.

---

Expand All @@ -529,7 +529,7 @@ For scenario and architecture text, pull from the adventure's generated TypeScri
4. Update the title in `<title>` (Reveal.js).
5. Write to:
- Reveal.js: `public/<event-slug>/index.html` (create the subfolder if it does not exist)
6. Do not add any of these files to `src/routes.ts`, `public/sitemap.xml`, or `react-router.config.ts`.
6. Do not add any of these files to `src/pages/sitemap.xml.ts` or the test route lists in `e2e/` — presentations live under `public/` and are not Astro pages.
7. Confirm: `ls -lh <output-path>`

**For PowerPoint (`pptx`):** edit `.ai/templates/generate-pptx.mjs` with the presentation content, then run:
Expand Down
10 changes: 5 additions & 5 deletions .claude/commands/navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,12 @@ Dragon NaturallySpeaking and iOS Voice Control navigate by speaking visible link
If `aria-label` differs from visible text, the user cannot activate the link by speaking
what they see. **The accessible name must contain the visible text.**

## SPA/React Router Note
## SPA Navigation Note

After route changes, screen reader users hear nothing unless focus is managed. When React
Router navigates, move focus to the new page's `<h1>` or the skip link target, and ensure
the page title updates. This is already handled by React Router v7's framework mode —
verify it is not broken when adding new routes.
After route changes, screen reader users hear nothing unless focus is managed. Astro's
`<ClientRouter />` emits a route announcer and moves focus to `#main-content` after each
client-side navigation — this is wired in `Layout.astro`. Verify it is not broken when
adding new routes.

## Definition of Done Checklist

Expand Down
6 changes: 3 additions & 3 deletions .claude/commands/progressive-enhancement.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ if ('fetch' in window && 'querySelector' in document) {

## Critical: Core Content Must Not Require JavaScript

This site uses React Router v7 with `ssr: false` (static prerendering). Pages are
prerendered at build time so content is in the HTML. Verify that:
This site uses Astro with `output: 'static'`. Pages are prerendered at build time
so content is in the HTML. Verify that:

- Every page's core content is in the prerendered HTML output in `dist/client/`
- Every page's core content is in the prerendered HTML output in `dist/`
- No critical information is rendered exclusively client-side after hydration
- Filter/search UI degrades gracefully (content visible even when JS filtering is unavailable)

Expand Down
14 changes: 7 additions & 7 deletions .claude/commands/user-personalization.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ in `src/index.css`. Check every new animation or color change against these quer
}
}

@media (prefers-color-scheme: dark) { /* handled by useTheme hook */ }
@media (prefers-color-scheme: dark) { /* handled by the inline pre-paint script in Layout.astro */ }

@media (prefers-contrast: more) {
:root {
Expand All @@ -70,14 +70,14 @@ in `src/index.css`. Check every new animation or color change against these quer

This site manages two user preferences:

**Theme (light/dark):** Handled by `useTheme` hook in `src/hooks/useTheme.tsx`.
- Stored in `localStorage` under the `theme` key (see `THEME_STORAGE_KEY` in `src/data/constants.ts`)
- Initialized to `dark` on first render, updated in `useEffect` from stored value
- Never read `localStorage` during render — hydration safety rule applies
**Theme (light/dark):** Handled by `ThemeToggle.astro` and an inline pre-paint script in `Layout.astro`.
- Stored in `localStorage` under the `theme` key
- The pre-paint script reads it before first paint to avoid FOUC; `ThemeToggle.astro` toggles it via a delegated `click` listener on `document`
- Never read `localStorage` during server-side render — only in the inline pre-paint script or in an `astro:page-load` handler

**Analytics consent:** Handled by `useConsent` hook in `src/hooks/useConsent.tsx`.
**Analytics consent:** State lives in the `$consent` nanostore (`src/stores/consent.ts`).
- Stored in `localStorage` under `analytics_consent` key
- The consent banner is the personalization UI for this preference
- `ConsentBanner.astro` is the personalization UI; its script re-subscribes to `$consent` on every `astro:page-load`
- All `localStorage` access must be in `try/catch` blocks

## Moderate: Safe localStorage Pattern
Expand Down
2 changes: 1 addition & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
## Manual checks

- [ ] Screen reader tested _(UI changes only)_
- [ ] New routes added to sitemap.xml, prerender array, README _(routes only)_
- [ ] New routes added to e2e/a11y.spec.ts, e2e/smoke.spec.ts, sitemap.xml.ts, and README _(routes only; sitemap is auto-generated from getStaticPaths, so only static pages need manual entries)_
- [ ] UI verified at 375px, 768px, and 1280px against the production build (`npm run build && npm run preview`) _(UI changes only)_
- [ ] Re-read every changed file; checked all call sites of any modified exports _(all changes)_
- [ ] Per-level discussion JSON exists with correct `discussionUrl` _(adventure/level changes only)_
2 changes: 2 additions & 0 deletions .github/workflows/a11y-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ jobs:

- run: npm ci

- run: npm run sync

- run: npm run build

- uses: actions/cache@v6
Expand Down
13 changes: 5 additions & 8 deletions .github/workflows/add-discussion-url.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
pull-requests: write

steps:
- uses: actions/create-github-app-token@v3
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
id: app-token
with:
client-id: ${{ vars.APP_ID }}
Expand Down Expand Up @@ -102,8 +102,8 @@ jobs:
DISCUSSION_URL: ${{ inputs.discussion_url }}
run: node scripts/set-discussion-url.mjs

- name: Regenerate TypeScript
run: node scripts/generate-adventures.mjs
- name: Validate adventure YAML (Zod content schema)
run: npm run sync

- name: Create pull request
env:
Expand All @@ -125,9 +125,6 @@ jobs:
git add \
"src/data/adventures/${ADVENTURE_ID}/adventure.yaml" \
"src/data/adventures/${ADVENTURE_ID}/${LEVEL_ID}-posts.json" \
"src/data/adventures/${ADVENTURE_ID}.generated.ts" \
"src/data/adventures/index.ts" \
"src/data/adventures/summaries.ts" \
"scripts/refresh-leaderboard.mjs"

HAS_CHANGES=false
Expand All @@ -154,14 +151,14 @@ jobs:

- \`adventure.yaml\` — \`community_url\` set on the \`${LEVEL_ID}\` level; \`community_category_id\` set at adventure root if it was missing
- \`${LEVEL_ID}-posts.json\` — \`discussionUrl\` set and initial posts fetched from Discourse
- Generated TypeScript updated (\`${ADVENTURE_ID}.generated.ts\`, \`index.ts\`, \`summaries.ts\`)
- \`scripts/refresh-leaderboard.mjs\` — \`ADVENTURE_CATEGORIES\` patched with the resolved category ID
- (Astro reads the YAML directly — no generated TypeScript to update)

### Before merging

- [ ] Verify the discussion URL is correct and the thread is publicly visible
- [ ] Check that the posts JSON contains the expected content (or is an empty array if the thread is new)
- [ ] Run \`npm run lint && npm test && npm run build && npm run test:e2e\` locally
- [ ] Run \`npm run lint && npm run test:unit && npm run build && npm run test:e2e\` locally

### After merging

Expand Down
26 changes: 20 additions & 6 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
- cron: "0 2 * * *"

permissions:
contents: write
contents: read # All jobs read-only by default; deploy job overrides to write

concurrency:
group: pages
Expand All @@ -21,6 +21,8 @@ env:
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: write # Required by JamesIves/github-pages-deploy-action to push gh-pages
steps:
- uses: actions/checkout@v7

Expand All @@ -29,17 +31,29 @@ jobs:
node-version-file: .nvmrc
cache: npm

- run: npm ci
- uses: actions/cache@v6
id: npm-cache
with:
path: node_modules
key: node-modules-${{ runner.os }}-${{ hashFiles('package-lock.json') }}

- run: npm run build
- if: steps.npm-cache.outputs.cache-hit != 'true'
run: npm ci

- run: cp dist/client/404/index.html dist/client/404.html
- run: npm run sync
- run: npm run lint
- run: npm run check
- run: npm run test:unit:coverage

# Astro static build → dist/ (not dist/client/). Astro emits dist/404.html
# natively, so the old `cp .../404/index.html 404.html` step is gone.
- run: npm run build

- name: Deploy to gh-pages
uses: JamesIves/github-pages-deploy-action@v4
uses: JamesIves/github-pages-deploy-action@fa24774553152dd7873cd16ebd8d959b010c5445 # v4
with:
branch: gh-pages
folder: dist/client
folder: dist
force: false
clean: true
clean-exclude: |
Expand Down
56 changes: 25 additions & 31 deletions .github/workflows/preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
lint:
check:
if: github.event.action != 'closed'
runs-on: ubuntu-latest
steps:
Expand All @@ -32,7 +32,12 @@ jobs:
- if: steps.node-cache.outputs.cache-hit != 'true'
run: npm ci

# Content validation gate: astro sync runs the Zod collection schema and
# fails on invalid adventure YAML.
- run: npm run sync
- run: npm run lint
- run: npm run check
- run: npm run test:unit:coverage

build:
if: github.event.action != 'closed'
Expand All @@ -54,14 +59,13 @@ jobs:
- if: steps.node-cache.outputs.cache-hit != 'true'
run: npm ci

- run: npm run sync
- run: npm run build

- run: npm test

- uses: actions/upload-artifact@v7
with:
name: dist-client
path: dist/client/
name: dist
path: dist/
retention-days: 1

e2e:
Expand Down Expand Up @@ -91,8 +95,8 @@ jobs:

- uses: actions/download-artifact@v7
with:
name: dist-client
path: dist/client/
name: dist
path: dist/

- uses: actions/cache@v6
id: playwright-cache
Expand All @@ -106,13 +110,12 @@ jobs:
- if: steps.playwright-cache.outputs.cache-hit == 'true'
run: npx playwright install-deps chromium

# playwright.config webServer runs `astro preview` (serves dist/) itself.
- run: npm run test:e2e -- --shard=${{ matrix.shard }}/3

preview:
needs: [lint, e2e]
# Run when both passed (normal PR update), or when build was skipped (PR closed, cleanup).
# Never run when tests failed.
if: always() && ((needs.lint.result == 'success' && needs.e2e.result == 'success') || needs.lint.result == 'skipped')
needs: [check, e2e]
if: always() && ((needs.check.result == 'success' && needs.e2e.result == 'success') || needs.check.result == 'skipped')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
Expand All @@ -132,27 +135,18 @@ jobs:
- if: github.event.action != 'closed' && steps.node-cache.outputs.cache-hit != 'true'
run: npm ci

# Astro's `base` (from VITE_BASE_PATH) prefixes every asset/link, and public/
# assets are copied into dist/ automatically — so the whole dist/ deploys as
# the preview with no per-directory asset-copy step. Layout.astro marks
# /pr-preview/ builds noindex.
- if: github.event.action != 'closed'
run: VITE_BASE_PATH=/pr-preview/pr-${{ github.event.number }}/ npm run build
run: npm run sync

- if: github.event.action != 'closed'
run: |
PREVIEW_DIR="dist/client/pr-preview/pr-${{ github.event.number }}"
cp -r dist/client/assets "${PREVIEW_DIR}/"
cp -r dist/client/fonts "${PREVIEW_DIR}/"
cp -r dist/client/reveal "${PREVIEW_DIR}/"
cp -r dist/client/team "${PREVIEW_DIR}/"
cp -r dist/client/speakers "${PREVIEW_DIR}/"
cp -r dist/client/brand "${PREVIEW_DIR}/"
cp -r dist/client/solutions "${PREVIEW_DIR}/"
cp -r dist/client/qr "${PREVIEW_DIR}/"
cp -r dist/client/downloads "${PREVIEW_DIR}/"
cp -r dist/client/screenshots "${PREVIEW_DIR}/"
cp -r dist/client/deck "${PREVIEW_DIR}/"
cp -r dist/client/deck-template "${PREVIEW_DIR}/"
find dist/client -maxdepth 1 -type f \( -name "*.svg" -o -name "*.png" -o -name "*.ico" -o -name "*.webmanifest" -o -name "*.webp" \) -exec cp {} "${PREVIEW_DIR}/" \;
cp "${PREVIEW_DIR}/index.html" "${PREVIEW_DIR}/404.html"

- uses: rossjrw/pr-preview-action@v1
env:
PR_NUMBER: ${{ github.event.number }}
run: VITE_BASE_PATH=/pr-preview/pr-${PR_NUMBER}/ npm run build

- uses: rossjrw/pr-preview-action@ffa7509e91a3ec8dfc2e5536c4d5c1acdf7a6de9 # v1
with:
source-dir: dist/client/pr-preview/pr-${{ github.event.number }}/
source-dir: dist/
18 changes: 13 additions & 5 deletions .github/workflows/refresh-community-data.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
refresh:
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v3
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
id: app-token
with:
client-id: ${{ vars.APP_ID }}
Expand Down Expand Up @@ -96,14 +96,22 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh issue create \
--title "Refresh community data: JSON validation failed" \
--label "bug" \
--body "The scheduled \`refresh-community-data\` workflow failed JSON structure validation and did not commit new data to \`main\`.
TITLE="Refresh community data: JSON validation failed"
OPEN=$(gh issue list \
--state open \
--search "\"${TITLE}\" in:title" \
--json number \
--jq 'length' 2>/dev/null || echo "0")
if [ "$OPEN" -eq 0 ]; then
gh issue create \
--title "${TITLE}" \
--label "bug" \
--body "The scheduled \`refresh-community-data\` workflow failed JSON structure validation and did not commit new data to \`main\`.

**Run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}

Check the run log for which file failed and which field was missing or malformed. Fix the upstream script that writes that file, then re-run the workflow manually via workflow_dispatch."
fi

- name: Commit if changed
id: commit
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/refresh-community-sitemap.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v3
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
id: app-token
with:
client-id: ${{ vars.APP_ID }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/reuse.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ jobs:
steps:
- uses: actions/checkout@v7
- name: REUSE lint
uses: fsfe/reuse-action@v6
uses: fsfe/reuse-action@676e2d560c9a403aa252096d99fcab3e1132b0f5 # v6
Loading
Loading