From 517afcb88d9489668e334665e59ba08871a13878 Mon Sep 17 00:00:00 2001 From: Jose Cruz Date: Wed, 22 Jul 2026 02:42:59 -0400 Subject: [PATCH 1/6] docs(web): drop the deleted repo-assets route, document the real pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `app/repo-assets/[...path]/route.ts` was removed by the fix for #43, but the README still described it in three places. A contributor following it went looking for a file that does not exist. Assets are copied by `scripts/copy-repo-assets.mjs` into `public/repo-assets/` and served statically. Both `predev` and `build` run that script, which is why it works without anyone invoking it — worth saying out loud, since the directory is generated and not committed. Co-Authored-By: Claude Opus 4.8 (1M context) --- web/README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/web/README.md b/web/README.md index 2809970..f0f4da7 100644 --- a/web/README.md +++ b/web/README.md @@ -76,11 +76,16 @@ files require a new deploy/build to appear on the site. Images referenced in the README (e.g. `assets/hackathons-banner.svg`) are resolved by `resolveAssetSrc()` in `lib/parse-readme.ts`: -1. **Local first** — if the file exists under `../assets/`, it's served by the - route handler at `app/repo-assets/[...path]/route.ts`. +1. **Local first** — if the file exists under `../assets/`, it's served as a + static file from `public/repo-assets/`. 2. **Remote fallback** — otherwise it falls back to the file on `main` via `raw.githubusercontent.com`. +`public/repo-assets/` is generated, not committed. `scripts/copy-repo-assets.mjs` +copies `../assets/` into it, and both `npm run dev` (via `predev`) and +`npm run build` run that script first — so the files are in place before Next.js +starts. Run it on its own with `npm run copy-assets`. + ## Project structure ```text @@ -92,8 +97,7 @@ web/ │ ├── my/page.tsx # Protected member tracker hub │ ├── auth/[[...auth]]/page.tsx # Clerk sign-in/sign-up │ ├── hackathons/page.tsx # Legacy README browser -│ ├── layout.tsx # Root layout, fonts, optional ClerkProvider -│ └── repo-assets/[...path]/route.ts # Serves files from ../assets +│ └── layout.tsx # Root layout, fonts, optional ClerkProvider ├── components/ │ ├── hq/ # Current HackHQ UI (globe, deck, nav, …) │ │ ├── nav.tsx # Nav pill; inline links at sm and up From 55921f51d26375175d404a0bfb8a86b88d6e9fdf Mon Sep 17 00:00:00 2001 From: Jose Cruz Date: Wed, 22 Jul 2026 02:43:27 -0400 Subject: [PATCH 2/6] docs(web): describe the actual render model (hourly ISR, not build-only) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The README asserted the opposite of what the code does: that data is baked in at build time and "changes to those files require a new deploy/build to appear on the site". Every data-backed page has exported `revalidate = 3600` since the #47 fix, precisely so deadline-derived state recomputes without a rebuild. Replaces the flat "Static (prerendered)" row with what `next build` actually reports: five ISR routes at 1h, /resources prerendered with no revalidation because its content is compiled-in constants, and /auth dynamic. Also records *why* the hour exists, so the next person does not "simplify" it away — #47's rationale lived only in next.config.ts comments. Co-Authored-By: Claude Opus 4.8 (1M context) --- web/README.md | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/web/README.md b/web/README.md index f0f4da7..e0817d2 100644 --- a/web/README.md +++ b/web/README.md @@ -56,20 +56,29 @@ listings are excluded from the map on purpose and never trip the check. ### Render model -Production pages are **statically generated at build time** (`next build`). -Both loaders use `fs.readFileSync` during that build step, so listing data, -deadline-derived status, and counts are baked into the HTML/JSON payload when -the site is built — not on each visitor request. +Pages are prerendered, then **revalidated hourly** (ISR). Both loaders use +`fs.readFileSync` at build time, so listing data and counts are baked into the +payload — but every data-backed page exports `revalidate = 3600`, so the server +re-runs the loader in the background at most once an hour and serves the fresh +result from then on. **A data change does not need a rebuild to appear**; it +needs a deploy of the changed file plus up to an hour. + +That hour is deliberate: deadline-derived state ("closing soon", day counts) +depends on the current date, so a page built last week would otherwise keep +serving last week's countdown (#47). | Route | Production render mode | | ----- | ---------------------- | -| `/`, `/deck`, `/globe`, `/my`, `/hackathons` | Static (prerendered) | -| `/repo-assets/[...path]` | Dynamic (serves files from `../assets/` on demand) | +| `/`, `/deck`, `/globe`, `/my`, `/hackathons` | Prerendered, ISR — `revalidate = 3600` | +| `/resources` | Prerendered, no revalidation — content is compiled-in constants, not repo data | +| `/auth/[[...auth]]` | Dynamic — rendered per request | + +`next build` prints this: the ISR routes carry a `Revalidate` value of `1h`, +`/resources` carries none, and `/auth/[[...auth]]` is marked `ƒ (Dynamic)`. -**Development (`npm run dev`)** differs: Next.js re-executes server components -when you refresh or when files change, so edits to `listings.json` or -`README.md` show up without a full rebuild. In production, changes to those -files require a new deploy/build to appear on the site. +**Development (`npm run dev`)** is more immediate: Next.js re-executes server +components when you refresh or when files change, so edits to `listings.json` +or `README.md` show up right away rather than on the next revalidation. ### Assets From bec661bf76c92f65fa801ae631c613c11a480d10 Mon Sep 17 00:00:00 2001 From: Jose Cruz Date: Wed, 22 Jul 2026 02:44:10 -0400 Subject: [PATCH 3/6] docs(web): add /resources and correct the nav breakpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /resources shipped in #154 and the README never mentioned it — not in the page list, the data-source table, or the tree. It is the one page backed by neither loader: its content is constants in lib/resources.ts. The nav notes were also a breakpoint behind. #154 moved the inline links from `sm` to `md` because adding RESOURCES made the pill wrap between 640 and 673px, so "inline links at sm and up" and "below 640px" both describe the old layout. Co-Authored-By: Claude Opus 4.8 (1M context) --- web/README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/web/README.md b/web/README.md index e0817d2..a485ed2 100644 --- a/web/README.md +++ b/web/README.md @@ -12,6 +12,7 @@ deck, and member tracker, plus a legacy searchable directory at `/hackathons`. - **Globe (`/globe`)** — 3D Mapbox map with status-colored markers. - **Deck (`/deck`)** — flip through hackathons as tactile cards or a dense list. - **My HackHQ (`/my`)** — protected personal tracker pipeline (optional Clerk sign-in). +- **Resources (`/resources`)** — a stage-by-stage field guide with curated links. - **All hackathons (`/hackathons`)** — legacy README-driven search and filters. ## How it works @@ -25,6 +26,7 @@ disk when pages are generated. | -------- | ------ | ----------- | | `/`, `/deck`, `/globe`, `/my` | `loadHackathons()` in `lib/listings.ts` | `../.github/scripts/listings.json` | | `/hackathons` | `loadSiteData()` in `lib/parse-readme.ts` | `../README.md` (table + stats banner) | +| `/resources` | none — imported directly | `lib/resources.ts` (stages, links, teaser copy) | `listings.json` is the source of truth for the main HackHQ experience. `parse-readme.ts` still powers the legacy `/hackathons` page, which parses the @@ -104,18 +106,23 @@ web/ │ ├── globe/page.tsx # 3D globe │ ├── deck/page.tsx # Card deck │ ├── my/page.tsx # Protected member tracker hub +│ ├── resources/page.tsx # Hackathon field guide │ ├── auth/[[...auth]]/page.tsx # Clerk sign-in/sign-up │ ├── hackathons/page.tsx # Legacy README browser │ └── layout.tsx # Root layout, fonts, optional ClerkProvider ├── components/ │ ├── hq/ # Current HackHQ UI (globe, deck, nav, …) -│ │ ├── nav.tsx # Nav pill; inline links at sm and up -│ │ └── mobile-menu.tsx # The same sections below 640px +│ │ ├── nav.tsx # Nav pill; inline links at md and up +│ │ ├── mobile-menu.tsx # The same sections below 768px +│ │ ├── resources.tsx # /resources page sections +│ │ ├── stage-jump-nav.tsx # Sticky stage rail; publishes its clearance +│ │ └── resources-teaser.tsx # Home-page 2×2 teaser + resource-tile-card │ └── legacy/ # README-driven browser, gallery, cards ├── lib/ │ ├── listings.ts # Reads listings.json, enriches for frontend │ ├── nav.ts # Nav sections + active-route matching │ ├── parse-readme.ts # Parses ../README.md (legacy /hackathons) +│ ├── resources.ts # Field-guide stages, links, teaser tiles │ ├── types-hq.ts # Hackathon types and display helpers │ └── types.ts # Legacy opportunity types └── proxy.ts # Clerk middleware (when keys are configured) From ada4f5b30771df6c059ec3104a3f5cf7b39cd0bb Mon Sep 17 00:00:00 2001 From: Jose Cruz Date: Wed, 22 Jul 2026 02:44:40 -0400 Subject: [PATCH 4/6] docs(web): fix the leftover rebuild claim in Production build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same defect as the Render model section, one heading down: it told readers a rebuild is required for data changes. It is not — the pages revalidate hourly. Calls out /resources as the exception, since it has no revalidate. Co-Authored-By: Claude Opus 4.8 (1M context) --- web/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/README.md b/web/README.md index a485ed2..fab5750 100644 --- a/web/README.md +++ b/web/README.md @@ -184,8 +184,10 @@ npm run build npm run start ``` -After changing `listings.json` or `README.md`, run a new build (or redeploy) -for production to pick up the updates. +After changing `listings.json` or `README.md`, deploy the changed file. A +rebuild is not required for the data-backed pages — they revalidate within the +hour (see [Render model](#render-model)). Rebuild when you want the change live +immediately, or when you have changed `/resources`, which does not revalidate. ## Tech stack From f9abad7a589a415f3c521500bfae99ded6563d55 Mon Sep 17 00:00:00 2001 From: Jose Cruz Date: Wed, 22 Jul 2026 02:45:00 -0400 Subject: [PATCH 5/6] docs(web): list the test and copy-assets scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit package.json defines six scripts; the table listed four. `npm test` is the notable omission — it is the 94-test Vitest suite CI gates on, so a contributor reading only this file had no way to run it before pushing. Co-Authored-By: Claude Opus 4.8 (1M context) --- web/README.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/web/README.md b/web/README.md index fab5750..42e55f4 100644 --- a/web/README.md +++ b/web/README.md @@ -170,12 +170,17 @@ connections, and enable email/password under email authentication. ## Scripts -| Script | Description | -| --------------- | ------------------------------------ | -| `npm run dev` | Start the development server | -| `npm run build` | Create a production build | -| `npm run start` | Serve the production build | -| `npm run lint` | Run ESLint | +| Script | Description | +| ---------------------- | ---------------------------------------------------- | +| `npm run dev` | Start the development server | +| `npm run build` | Create a production build | +| `npm run start` | Serve the production build | +| `npm run lint` | Run ESLint | +| `npm test` | Run the Vitest suite (what CI runs) | +| `npm run copy-assets` | Refresh `public/repo-assets/` from `../assets/` | + +`dev` and `build` run `copy-assets` for you; you only need it directly after +changing something under `../assets/` while a dev server is already running. ## Production build From c2b2bf8c3a30b1668bfe50f04593ade28e669560 Mon Sep 17 00:00:00 2001 From: Jose Cruz Date: Wed, 22 Jul 2026 02:49:24 -0400 Subject: [PATCH 6/6] =?UTF-8?q?docs(web):=20say=20what=20ISR=20actually=20?= =?UTF-8?q?refreshes=20=E2=80=94=20dates,=20not=20content?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correcting my own over-correction earlier in this branch. The README used to claim every data change needs a rebuild; I replaced that with "a data change does not need a rebuild to appear", which is wrong in the other direction. next.config.ts bundles README.md, listings.json and geocodes.json into the deployment via outputFileTracingIncludes, so a revalidation re-reads the *deployed* copy. Editing those files still requires a new build and deploy. What the hour genuinely buys is what #47 asked for and what the page comments say: deadline-derived state — closing-soon flags and day counts, all computed from the current date — stays correct between deploys. Co-Authored-By: Claude Opus 4.8 (1M context) --- web/README.md | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/web/README.md b/web/README.md index 42e55f4..22d5282 100644 --- a/web/README.md +++ b/web/README.md @@ -58,16 +58,22 @@ listings are excluded from the map on purpose and never trip the check. ### Render model -Pages are prerendered, then **revalidated hourly** (ISR). Both loaders use -`fs.readFileSync` at build time, so listing data and counts are baked into the -payload — but every data-backed page exports `revalidate = 3600`, so the server -re-runs the loader in the background at most once an hour and serves the fresh -result from then on. **A data change does not need a rebuild to appear**; it -needs a deploy of the changed file plus up to an hour. +Pages are prerendered, then **revalidated hourly** (ISR): every data-backed page +exports `revalidate = 3600`, so the server re-runs its loader in the background +at most once an hour. -That hour is deliberate: deadline-derived state ("closing soon", day counts) -depends on the current date, so a page built last week would otherwise keep -serving last week's countdown (#47). +Be precise about what that refreshes. The loaders `fs.readFileSync` the repo +files, and those files are **bundled into the deployment** by +`outputFileTracingIncludes` in `next.config.ts` — so a revalidation re-reads the +*deployed* copy, not whatever is on `main` now. + +| Changes without a rebuild | Needs a new build + deploy | +| ------------------------- | -------------------------- | +| Deadline-derived state — "closing soon" flags, day counts, anything computed from the current date | The listings themselves — editing `listings.json`, `README.md`, or `geocodes.json` | + +That is exactly what the hour is for (#47): those flags are derived from *today*, +so a page prerendered last week would otherwise keep serving last week's +countdown until someone redeployed. | Route | Production render mode | | ----- | ---------------------- | @@ -189,10 +195,10 @@ npm run build npm run start ``` -After changing `listings.json` or `README.md`, deploy the changed file. A -rebuild is not required for the data-backed pages — they revalidate within the -hour (see [Render model](#render-model)). Rebuild when you want the change live -immediately, or when you have changed `/resources`, which does not revalidate. +After changing `listings.json` or `README.md`, run a new build and deploy — the +data files are bundled into the deployment, so hourly revalidation alone will +not pick up an edit. Revalidation keeps *date-derived* state fresh between +deploys; it does not fetch new content. See [Render model](#render-model). ## Tech stack