From 0ca7ec65b6da3f34cd36a19a50a157cc9879e450 Mon Sep 17 00:00:00 2001
From: Heyoub
Date: Mon, 24 Aug 2026 11:47:14 -0400
Subject: [PATCH 1/9] chore(plans): add site-check-153 plan
Issue #153 walks the live site on desktop and mobile and collects five
findings. Four are shippable together because they all land in the app
shell or in one screen's outbound links; the fifth (replace the Home hero
CTA with a mailing-list invite) has no mechanism to build against, so the
plan records the block up front rather than inventing one.
Co-Authored-By: Claude Fable 5
---
plans/site-check-153.md | 151 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 151 insertions(+)
create mode 100644 plans/site-check-153.md
diff --git a/plans/site-check-153.md b/plans/site-check-153.md
new file mode 100644
index 0000000..d288c9a
--- /dev/null
+++ b/plans/site-check-153.md
@@ -0,0 +1,151 @@
+---
+status: in-progress
+depends: []
+specs:
+ - specs/behaviors/app-shell.md
+ - specs/screens/volunteer.md
+issues: [153]
+---
+
+# Plan: site check — header order, mobile sheet padding, dead outbound links
+
+## Scope
+
+Issue [#153](https://github.com/CodeForPhilly/codeforphilly-ng/issues/153)
+("Site check for desktop & mobile") collects a walkthrough of the live site on
+both breakpoints. Four of its items are shippable now; one is blocked (see
+Follow-ups).
+
+What ships:
+
+- **Desktop header reorder** (spec-governed). The Volunteer CTA leaves the
+ content nav and becomes the rightmost element of the header, after the auth
+ control; About joins the left cluster's text links; a GitHub icon link is
+ added to the right cluster.
+- **Mobile sheet padding + accessible name.** The sheet's nav and search sat
+ flush against the panel edge. Fixed with the intended shadcn structure
+ (`SheetHeader` + `SheetTitle`) plus explicit horizontal padding — which also
+ gives the underlying Radix dialog the accessible name it was missing.
+- **Header ARIA cleanups.** Three defects surfaced by an accessibility pass over
+ the header, done here because this plan rewrites the same file.
+- **Dead outbound links** (spec-governed). The whole `codeforphilly.gitbook.io`
+ space returns 404 "Content owner not found"; `Volunteer.tsx`'s two remaining
+ GitBook links are repointed at live equivalents. Same class of defect as
+ [`home-start-project-cta`](home-start-project-cta.md) (PR #128), which fixed
+ the Home screen's copy of the same dead URL.
+- **Footer repo URL.** The "view this site on GitHub" link still pointed at
+ `codeforphilly-rewrite`; the repo is `codeforphilly-ng` and the old URL only
+ resolves through GitHub's rename redirect.
+
+Explicitly out of scope:
+
+- **Replacing the Home hero's Volunteer CTA with a mailing-list invite** (also
+ recommended by #153) — blocked, see Follow-ups. `Home.tsx` is untouched.
+- Any other visual restyle of the header. The Volunteer button keeps its
+ existing green treatment; only its position changes.
+
+## Implements
+
+- [app-shell.md](../specs/behaviors/app-shell.md) — "Center / right at ≥ md"
+ split into a left content cluster and a right utility cluster, with the new
+ item order; "Auth controls" repositioned second-from-right; GitHub link added
+ to the right cluster and to the mobile sheet; the sheet's accessible name
+ added under Accessibility.
+- [volunteer.md](../specs/screens/volunteer.md) — "Show up to meetups" card
+ links to the Meetup group; "Start your own project" band links to the
+ `CodeForPhilly/partnerships` first-steps guide. Both replace dead GitBook
+ URLs.
+
+## Approach
+
+### 1. Spec changes first (specops — source of truth leads)
+
+`specs/behaviors/app-shell.md` and `specs/screens/volunteer.md` both prescribed
+the current (wrong) state, so they lead. Header spacing is deliberately *not*
+specced — [specs/README.md:49](../specs/README.md) puts spacing outside spec
+scope — so the mobile-sheet padding fix carries no spec change.
+
+### 2. `apps/web/src/components/AppHeader.tsx`
+
+- Left `
-
+
When we meet →
diff --git a/apps/web/tests/AppFooter.test.tsx b/apps/web/tests/AppFooter.test.tsx
index 6b0eb49..8924baf 100644
--- a/apps/web/tests/AppFooter.test.tsx
+++ b/apps/web/tests/AppFooter.test.tsx
@@ -17,7 +17,7 @@ describe('AppFooter', () => {
expect(link).toBeInTheDocument();
expect(link).toHaveAttribute(
'href',
- 'https://github.com/CodeForPhilly/codeforphilly-rewrite',
+ 'https://github.com/CodeForPhilly/codeforphilly-ng',
);
});
diff --git a/apps/web/tests/Volunteer.test.tsx b/apps/web/tests/Volunteer.test.tsx
new file mode 100644
index 0000000..3a45b0c
--- /dev/null
+++ b/apps/web/tests/Volunteer.test.tsx
@@ -0,0 +1,86 @@
+import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
+import { screen, waitFor } from '@testing-library/react';
+import { renderScreen, mockPaginated } from './test-utils.js';
+import { Volunteer } from '../src/screens/Volunteer.js';
+import { AuthProvider } from '../src/hooks/useAuth.js';
+
+const MEETUP_URL = 'https://www.meetup.com/Code-for-Philly/';
+const START_PROJECT_URL =
+ 'https://github.com/CodeForPhilly/partnerships/blob/master/creating-new-partnerships/first-steps.md';
+
+describe('Volunteer', () => {
+ beforeEach(() => {
+ vi.spyOn(globalThis, 'fetch').mockImplementation(((input: string) => {
+ if (input.startsWith('/api/auth/me')) {
+ return Promise.resolve(new Response(null, { status: 404 }));
+ }
+ if (input.startsWith('/api/projects')) {
+ return Promise.resolve(
+ new Response(JSON.stringify(mockPaginated([], { totalItems: 268 })), {
+ status: 200,
+ headers: { 'content-type': 'application/json' },
+ }),
+ );
+ }
+ return Promise.resolve(
+ new Response(JSON.stringify(mockPaginated([])), {
+ status: 200,
+ headers: { 'content-type': 'application/json' },
+ }),
+ );
+ }) as typeof fetch);
+ });
+
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
+ function renderVolunteer() {
+ return renderScreen(
+
+
+ ,
+ );
+ }
+
+ it('renders the hero headline', () => {
+ renderVolunteer();
+ expect(
+ screen.getByRole('heading', {
+ name: /volunteer with code for philly/i,
+ level: 1,
+ }),
+ ).toBeInTheDocument();
+ });
+
+ it('points "When we meet →" at the live Meetup group, not the dead GitBook page', () => {
+ renderVolunteer();
+ const link = screen.getByRole('link', { name: /when we meet/i });
+ expect(link).toHaveAttribute('href', MEETUP_URL);
+ expect(link).toHaveAttribute('target', '_blank');
+ expect(link).toHaveAttribute('rel', 'noopener noreferrer');
+ });
+
+ it('points "Read the guide →" at the partnerships repo, not the dead GitBook page', () => {
+ renderVolunteer();
+ const link = screen.getByRole('link', { name: /read the guide/i });
+ expect(link).toHaveAttribute('href', START_PROJECT_URL);
+ expect(link).toHaveAttribute('target', '_blank');
+ expect(link).toHaveAttribute('rel', 'noopener noreferrer');
+ });
+
+ it('has no codeforphilly.gitbook.io links anywhere on the screen', async () => {
+ const { container } = renderVolunteer();
+
+ // Wait for the live project count so the fully-settled DOM is asserted on.
+ await waitFor(() => {
+ expect(screen.getByText(/browse 268 active projects/i)).toBeInTheDocument();
+ });
+
+ const hrefs = Array.from(container.querySelectorAll('a')).map(
+ (a) => a.getAttribute('href') ?? '',
+ );
+ expect(hrefs.filter((h) => h.includes('gitbook.io'))).toHaveLength(0);
+ expect(container.innerHTML).not.toContain('codeforphilly.gitbook.io');
+ });
+});
From 4c2e392bff3473dc84224af85c9fa23ad57481c4 Mon Sep 17 00:00:00 2001
From: Heyoub
Date: Mon, 24 Aug 2026 12:04:30 -0400
Subject: [PATCH 5/9] fix(web): let the mobile sheet nav scroll when it
overflows
The sheet is a full-height flex column with no scroll container, so a nav
list taller than the viewport was simply unreachable below the fold. That
was already latent; adding the GitHub row makes it one row likelier on
short phones. min-h-0 lets the flex child shrink at all, and overflow-y-auto
gives it somewhere to put the excess.
Co-Authored-By: Claude Fable 5
---
apps/web/src/components/AppHeader.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/apps/web/src/components/AppHeader.tsx b/apps/web/src/components/AppHeader.tsx
index dd414de..7991907 100644
--- a/apps/web/src/components/AppHeader.tsx
+++ b/apps/web/src/components/AppHeader.tsx
@@ -288,9 +288,11 @@ export function AppHeader() {
Menu
+ {/* min-h-0 + overflow-y-auto so the list stays reachable on
+ short viewports instead of overflowing the panel. */}
Date: Mon, 24 Aug 2026 12:24:58 -0400
Subject: [PATCH 6/9] chore(plans): record site-check-153 validation results
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Ticks the criteria verified during implementation and, deliberately,
leaves two unticked rather than rewriting them to match what was
achievable: the browser pass belongs to whoever does UI QA, and the
all-workspaces test gate cannot close on this Windows dev box.
Notes record why. apps/api fails ten tests here on a tree whose API code
is byte-identical to develop, because the fixtures assume POSIX —
store.test.ts injects a write failure via /dev/null/impossible-path,
which Windows will happily create, so the expected rejection never comes.
Reproduces with the files run alone, so it is not runner contention.
Filed as a follow-up rather than fixed: cross-platform fixtures are their
own scope, and silently ticking a gate that did not run is worse than
leaving the box open.
Also flags HomeStub.tsx, which carries the same stale repo URL the footer
had but is imported by nothing.
Co-Authored-By: Claude Fable 5
---
plans/site-check-153.md | 53 +++++++++++++++++++++++++++++++----------
1 file changed, 41 insertions(+), 12 deletions(-)
diff --git a/plans/site-check-153.md b/plans/site-check-153.md
index d288c9a..5a18097 100644
--- a/plans/site-check-153.md
+++ b/plans/site-check-153.md
@@ -106,17 +106,20 @@ One-line repo URL swap to `codeforphilly-ng`.
## Validation
-- [ ] Specs updated before code: app-shell header clusters + volunteer link targets.
-- [ ] Desktop header order is Projects · Help Wanted · Members · About ▾ … GitHub · Search · Sign in · Volunteer, with Volunteer rightmost and still green.
-- [ ] No `ml-1` spacing hacks remain among the header nav's children.
-- [ ] GitHub link is icon-only, labelled "Code for Philly on GitHub", and opens `https://github.com/CodeForPhilly` in a new tab with `rel="noopener noreferrer"`.
-- [ ] Mobile sheet has a "Menu" title, horizontal padding on nav + search, and no `pt-8`; the title does not collide with the close button.
-- [ ] The sheet dialog exposes an accessible name; Radix still supplies `aria-expanded` on the trigger.
-- [ ] Loading skeleton uses `aria-hidden`; About trigger's accessible name is its visible text; account-menu label retained.
-- [ ] Every mobile sheet item closes the sheet on click, including Contact.
-- [ ] No `codeforphilly.gitbook.io` URL remains in `apps/web/src`.
-- [ ] Footer "view this site on GitHub" points at `codeforphilly-ng`.
-- [ ] `npm run -w packages/shared build && npm run type-check && npm run lint && npm test` clean.
+- [x] Specs updated before code: app-shell header clusters + volunteer link targets.
+- [x] Desktop header order is Projects · Help Wanted · Members · About ▾ … GitHub · Search · Sign in · Volunteer, with Volunteer rightmost and still green.
+- [x] No `ml-1` spacing hacks remain among the header nav's children.
+- [x] GitHub link is icon-only, labelled "Code for Philly on GitHub", and opens `https://github.com/CodeForPhilly` in a new tab with `rel="noopener noreferrer"`.
+- [x] Mobile sheet has a "Menu" title, horizontal padding on nav + search, and no `pt-8`; the title does not collide with the close button.
+- [x] The sheet dialog exposes an accessible name; Radix still supplies `aria-expanded` on the trigger.
+- [x] Loading skeleton uses `aria-hidden`; About trigger's accessible name is its visible text; account-menu label retained.
+- [x] Every mobile sheet item closes the sheet on click, including Contact.
+- [x] No `codeforphilly.gitbook.io` URL remains in `apps/web/src`.
+- [x] Footer "view this site on GitHub" points at `codeforphilly-ng`.
+- [x] Both replacement URLs return 200 and carry the expected content.
+- [x] `npm run -w packages/shared build`, `npm run type-check`, and `npm run lint` clean.
+- [x] `npm test` clean for the workspaces this plan touches: web 96/96, shared 75/75.
+- [ ] `npm test` clean for **all** workspaces — `apps/api` cannot pass on the Windows dev box used here (see Notes); needs a Linux run or CI to close.
- [ ] Browser test: desktop header order + mobile sheet padding at < md, both breakpoints.
## Risks
@@ -131,7 +134,20 @@ One-line repo URL swap to `codeforphilly-ng`.
## Notes
-(To be populated at closeout.)
+(To be populated at closeout. Recorded during implementation:)
+
+- **`apps/api` tests do not pass on Windows, independent of this plan.** Ten
+ failures across `scrub-data.test.ts` (4), `internal-reload.test.ts` (4), and
+ `store.test.ts` (2), on a tree where `git diff develop..HEAD -- apps/api
+ packages/` is empty — this branch touches no API code. The mechanism is
+ POSIX-isms in the test fixtures: `store.test.ts` injects a write failure by
+ pointing the private store at `/dev/null/impossible-path` and asserting the
+ transaction rejects, but on Windows that is an ordinary creatable directory,
+ so the write succeeds and the expected throw never happens. They reproduce
+ with the files run alone, so it is not test-runner contention. CI runs the
+ same gate on Linux, where the fixture behaves as intended. Worth a
+ cross-platform fixture cleanup if Windows dev boxes are to be supported;
+ filed under Follow-ups.
## Follow-ups
@@ -149,3 +165,16 @@ One-line repo URL swap to `codeforphilly-ng`.
worker, unsubscribe tokens). Building an anonymous-capture CTA ahead of that
spec would invent unspecified behavior. `Home.tsx` is deliberately untouched
here; the CTA swap should follow the newsletter spec work, not precede it.
+
+- **Tracked as: dead file, not fixed here — `apps/web/src/pages/HomeStub.tsx`.**
+ It carries the same stale `codeforphilly-rewrite` URL the footer had, but
+ nothing imports or routes it (`App.tsx` imports only `LoginPlaceholder` from
+ `src/pages/`; every live screen lives in `src/screens/`). Left alone because
+ the right fix is deleting the file, not patching a URL nobody renders — and
+ that deletion wants its own scope. Flagging so a future grep for the old repo
+ name doesn't read as an unfixed live link.
+
+- **Issue — make the `apps/api` test fixtures cross-platform.** The `/dev/null`
+ failure-injection idiom (and whatever the other seven failures share) makes
+ the API suite unrunnable on a Windows dev box, so the documented validation
+ gate can only be completed on Linux or in CI. See Notes for the mechanism.
From 848bbf0c4b207678afb0f508f2cd7692de725d3f Mon Sep 17 00:00:00 2001
From: Heyoub
Date: Mon, 24 Aug 2026 12:48:12 -0400
Subject: [PATCH 7/9] chore(plans): record exact api test counts in
site-check-153 notes
Pins the numbers behind the unticked all-workspaces gate so a future
reader can tell a known Windows baseline from a real regression, and
notes the develop re-run that confirms it.
Co-Authored-By: Claude Fable 5
---
plans/site-check-153.md | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/plans/site-check-153.md b/plans/site-check-153.md
index 5a18097..1f0fe6d 100644
--- a/plans/site-check-153.md
+++ b/plans/site-check-153.md
@@ -136,10 +136,13 @@ One-line repo URL swap to `codeforphilly-ng`.
(To be populated at closeout. Recorded during implementation:)
-- **`apps/api` tests do not pass on Windows, independent of this plan.** Ten
- failures across `scrub-data.test.ts` (4), `internal-reload.test.ts` (4), and
- `store.test.ts` (2), on a tree where `git diff develop..HEAD -- apps/api
- packages/` is empty — this branch touches no API code. The mechanism is
+- **`apps/api` tests do not pass on Windows, independent of this plan.**
+ `apps/api` finishes 3 failed | 30 passed (33 files), 10 failed | 413 passed
+ (423 tests) — the ten spread across `scrub-data.test.ts` (4),
+ `internal-reload.test.ts` (4), and `store.test.ts` (2), on a tree where
+ `git diff develop..HEAD -- apps/api packages/` is empty — this branch touches
+ no API code. Checking out `develop` and re-running `store.test.ts` there
+ reproduces its 2 failed | 11 passed exactly. The mechanism is
POSIX-isms in the test fixtures: `store.test.ts` injects a write failure by
pointing the private store at `/dev/null/impossible-path` and asserting the
transaction rejects, but on Windows that is an ordinary creatable directory,
From b91aaef24696a1a4c736a13e0553b135a2926bf1 Mon Sep 17 00:00:00 2001
From: Heyoub
Date: Mon, 24 Aug 2026 12:52:56 -0400
Subject: [PATCH 8/9] chore(plans): record browser validation for
site-check-153
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Desktop header verified in headed Chrome at 1400px; the sheet verified via
its portal at desktop width because the automation harness could not shrink
a maximized window below md — same limitation web-shell.md's plan recorded.
Co-Authored-By: Claude Fable 5
---
plans/site-check-153.md | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/plans/site-check-153.md b/plans/site-check-153.md
index 1f0fe6d..9178493 100644
--- a/plans/site-check-153.md
+++ b/plans/site-check-153.md
@@ -120,7 +120,15 @@ One-line repo URL swap to `codeforphilly-ng`.
- [x] `npm run -w packages/shared build`, `npm run type-check`, and `npm run lint` clean.
- [x] `npm test` clean for the workspaces this plan touches: web 96/96, shared 75/75.
- [ ] `npm test` clean for **all** workspaces — `apps/api` cannot pass on the Windows dev box used here (see Notes); needs a Linux run or CI to close.
-- [ ] Browser test: desktop header order + mobile sheet padding at < md, both breakpoints.
+- [x] Browser test (headed Chrome, Vite dev server): desktop header order,
+ spacing, GitHub icon, and Volunteer-rightmost verified at 1400px; the
+ sheet verified open — "Menu" title, padded nav/search, no title/close
+ collision — and Escape closes it. Caveat: the harness could not shrink
+ the (maximized) window below md, so the sheet was opened via its
+ CSS-hidden trigger at desktop width. The sheet is a fixed `w-72`
+ portal, so its rendering is identical at < md; the < md *header bar*
+ (logo + auth + hamburger row) still rides on the jsdom tests, same
+ limitation `web-shell.md:109` recorded.
## Risks
From 8c94dc83e5127b70a0fe067254e134507f48f2bf Mon Sep 17 00:00:00 2001
From: Heyoub
Date: Mon, 24 Aug 2026 12:54:45 -0400
Subject: [PATCH 9/9] chore(plans): mark site-check-153 done (PR #154)
Co-Authored-By: Claude Fable 5
---
plans/site-check-153.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/plans/site-check-153.md b/plans/site-check-153.md
index 9178493..021ed1a 100644
--- a/plans/site-check-153.md
+++ b/plans/site-check-153.md
@@ -1,10 +1,11 @@
---
-status: in-progress
+status: done
depends: []
specs:
- specs/behaviors/app-shell.md
- specs/screens/volunteer.md
issues: [153]
+pr: 154
---
# Plan: site check — header order, mobile sheet padding, dead outbound links