[PR #1945] feat(staffml): make the app an installable PWA #34669

Open
opened 2026-07-14 19:28:22 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/harvard-edge/cs249r_book/pull/1945
Author: @farhan523
Created: 7/7/2026
Status: 🔄 Open

Base: devHead: feat/staffml-pwa


📝 Commits (1)

  • 54cd9cc feat(staffml): make the app an installable PWA

📊 Changes

10 files changed (+306 additions, -10 deletions)

View changed files

interviews/staffml/public/icons/apple-touch-icon.png (+0 -0)
interviews/staffml/public/icons/icon-192.png (+0 -0)
interviews/staffml/public/icons/icon-512.png (+0 -0)
interviews/staffml/public/icons/icon-maskable-192.png (+0 -0)
interviews/staffml/public/icons/icon-maskable-512.png (+0 -0)
📝 interviews/staffml/public/sw.js (+171 -0)
📝 interviews/staffml/src/app/layout.tsx (+4 -0)
interviews/staffml/src/app/manifest.ts (+44 -0)
interviews/staffml/src/lib/__tests__/pwa-manifest.test.ts (+62 -0)
📝 interviews/staffml/src/lib/corpus-provider.tsx (+25 -10)

📄 Description

Summary

Makes StaffML installable as a Progressive Web App with offline support — a natural fit for an app that already runs 100% client-side.

  • src/app/manifest.ts — Next.js manifest route (force-static so it works with output: export). start_url/scope/id/icon URLs carry NEXT_PUBLIC_BASE_PATH explicitly, following the same convention as the og-image URLs in layout.tsx. Theme/background match the light default from theme-bootstrap.js.
  • public/icons/any + maskable icons (192/512) plus a 180×180 apple-touch-icon, rasterized from the roofline mark in favicon.svg. Maskable/apple variants are full-bleed so Android's circle/squircle masks and iOS's corner radius never clip the mark. layout.tsx metadata now links the apple icon (iOS ignores SVG favicons).
  • public/sw.js — new, clearly-delimited app-shell section appended below the existing vault-API cache. The two sections never compete for a request: the vault handler exits early unless the URL starts with the vault API origin (cross-origin); the app-shell handler exits early unless the request is same-origin and inside the worker's scope (so the surrounding Quarto book's assets are left alone). Strategy:
    • Navigations: network-first — a deploy is picked up immediately; cache only serves offline revisits, with an inline branded offline page as last resort.
    • script/style/image/font: stale-while-revalidate, 300-entry cap (_next/static/* is content-hashed so staleness is impossible there).
    • Everything else passes through — corpus JSON, XHR/fetch data freshness stays owned by the vault section and VersionDriftToast.
    • Cache prefix staffml-app- is disjoint from staffml-vault-, so the vault section's release-keyed pruning and the app section's version pruning can never delete each other's caches. No changes to any existing vault logic.
  • corpus-provider.tsx — the service worker now registers unconditionally instead of only after a successful vault probe: offline support must not depend on the vault API being reachable, since offline is exactly when the probe fails. The SET_VAULT_API_ORIGIN message stays gated on probe success, so the vault section stays inert until then (preserving the Chip R4-C-2 message-hardening path unchanged).

Regression test

src/lib/__tests__/pwa-manifest.test.ts guards the manifest↔icons contract: the manifest declares the fields installability requires (name, short_name, display: standalone, scope containing start_url, both any and maskable at 192+512), and every icon it references exists in public/ with exactly the pixel dimensions it advertises (PNG IHDR check) — a mismatch there doesn't fail the build, it just silently breaks installability in the field.

Testing

  • vitest: 133/133 passing (7 new).
  • next build clean both with and without NEXT_PUBLIC_BASE_PATH=/staffml; verified exported HTML links the manifest and apple-touch-icon with the correct prefix in both configurations, and the manifest's start_url/scope/icon URLs all carry the base path.
  • Served the export locally and verified in Chrome: install prompt appears, worker activates, visited pages load offline, unvisited pages get the branded offline fallback, and the vault section still receives SET_VAULT_API_ORIGIN after a successful probe.
  • Note: npm run lint currently crashes on dev as well (ESLint 10 / eslint-plugin-react incompatibility inside eslint-config-next, triggered on analytics-worker/worker.js) — pre-existing and unrelated; scoped lint can't run either since the crash happens at rule-load time.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/harvard-edge/cs249r_book/pull/1945 **Author:** [@farhan523](https://github.com/farhan523) **Created:** 7/7/2026 **Status:** 🔄 Open **Base:** `dev` ← **Head:** `feat/staffml-pwa` --- ### 📝 Commits (1) - [`54cd9cc`](https://github.com/harvard-edge/cs249r_book/commit/54cd9cc7fb97308dfda70fe5216addea1712a0e1) feat(staffml): make the app an installable PWA ### 📊 Changes **10 files changed** (+306 additions, -10 deletions) <details> <summary>View changed files</summary> ➕ `interviews/staffml/public/icons/apple-touch-icon.png` (+0 -0) ➕ `interviews/staffml/public/icons/icon-192.png` (+0 -0) ➕ `interviews/staffml/public/icons/icon-512.png` (+0 -0) ➕ `interviews/staffml/public/icons/icon-maskable-192.png` (+0 -0) ➕ `interviews/staffml/public/icons/icon-maskable-512.png` (+0 -0) 📝 `interviews/staffml/public/sw.js` (+171 -0) 📝 `interviews/staffml/src/app/layout.tsx` (+4 -0) ➕ `interviews/staffml/src/app/manifest.ts` (+44 -0) ➕ `interviews/staffml/src/lib/__tests__/pwa-manifest.test.ts` (+62 -0) 📝 `interviews/staffml/src/lib/corpus-provider.tsx` (+25 -10) </details> ### 📄 Description ## Summary Makes StaffML installable as a Progressive Web App with offline support — a natural fit for an app that already runs 100% client-side. - **`src/app/manifest.ts`** — Next.js manifest route (`force-static` so it works with `output: export`). `start_url`/`scope`/`id`/icon URLs carry `NEXT_PUBLIC_BASE_PATH` explicitly, following the same convention as the og-image URLs in `layout.tsx`. Theme/background match the light default from `theme-bootstrap.js`. - **`public/icons/`** — `any` + `maskable` icons (192/512) plus a 180×180 `apple-touch-icon`, rasterized from the roofline mark in `favicon.svg`. Maskable/apple variants are full-bleed so Android's circle/squircle masks and iOS's corner radius never clip the mark. `layout.tsx` metadata now links the apple icon (iOS ignores SVG favicons). - **`public/sw.js`** — new, clearly-delimited **app-shell section** appended below the existing vault-API cache. The two sections never compete for a request: the vault handler exits early unless the URL starts with the vault API origin (cross-origin); the app-shell handler exits early unless the request is same-origin *and inside the worker's scope* (so the surrounding Quarto book's assets are left alone). Strategy: - **Navigations: network-first** — a deploy is picked up immediately; cache only serves offline revisits, with an inline branded offline page as last resort. - **script/style/image/font: stale-while-revalidate**, 300-entry cap (`_next/static/*` is content-hashed so staleness is impossible there). - **Everything else passes through** — corpus JSON, XHR/fetch data freshness stays owned by the vault section and `VersionDriftToast`. - Cache prefix `staffml-app-` is disjoint from `staffml-vault-`, so the vault section's release-keyed pruning and the app section's version pruning can never delete each other's caches. No changes to any existing vault logic. - **`corpus-provider.tsx`** — the service worker now registers **unconditionally** instead of only after a successful vault probe: offline support must not depend on the vault API being reachable, since offline is exactly when the probe fails. The `SET_VAULT_API_ORIGIN` message stays gated on probe success, so the vault section stays inert until then (preserving the Chip R4-C-2 message-hardening path unchanged). ## Regression test `src/lib/__tests__/pwa-manifest.test.ts` guards the manifest↔icons contract: the manifest declares the fields installability requires (`name`, `short_name`, `display: standalone`, scope containing start_url, both `any` and `maskable` at 192+512), and every icon it references exists in `public/` with exactly the pixel dimensions it advertises (PNG IHDR check) — a mismatch there doesn't fail the build, it just silently breaks installability in the field. ## Testing - `vitest`: 133/133 passing (7 new). - `next build` clean **both with and without** `NEXT_PUBLIC_BASE_PATH=/staffml`; verified exported HTML links the manifest and apple-touch-icon with the correct prefix in both configurations, and the manifest's `start_url`/`scope`/icon URLs all carry the base path. - Served the export locally and verified in Chrome: install prompt appears, worker activates, visited pages load offline, unvisited pages get the branded offline fallback, and the vault section still receives `SET_VAULT_API_ORIGIN` after a successful probe. - Note: `npm run lint` currently crashes on `dev` as well (ESLint 10 / `eslint-plugin-react` incompatibility inside `eslint-config-next`, triggered on `analytics-worker/worker.js`) — pre-existing and unrelated; scoped lint can't run either since the crash happens at rule-load time. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-07-14 19:28:23 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/cs249r_book#34669