[2/2] Tailwind + SCSS to CSS Modules#736
Merged
RSkuma merged 14 commits intohackforla:developfrom May 7, 2026
Merged
Conversation
Removes 10 docs that were either committed-as-drafts and never filled in (containing literal [INSERT-...] placeholders or marked **_DRAFT NOT YET FILLED OUT_**), or self-flagged # OUTDATED while describing a previous project layout that no longer exists. Also cleans up direct references to the deleted files: - 10 corresponding nav entries removed from mkdocs/mkdocs.yml - 3 broken role links removed from joining-the-team/intro.md - 2 broken bullets removed from resources.md (renumbered) - 2 broken Additional Resources entries removed from developer/installation.md - 1 sentence pointing at the deleted Frontend Architecture guide removed from developer/backend.md Verified with `mkdocs build --strict`. Remaining info-level link warnings are pre-existing issues unrelated to this PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aligns the dev docs with the engineering-assessment direction: Next.js + existing Django backend, PeopleDepot integration deferred to Stage 2, Cognito ID-token JWT, CSS Modules, three-container ECS task with Postgres-in-container. Flattens mkdocs/docs/ to docs/ at repo root and drops mkdocs entirely: no more mkdocs.yml, mkdocs-build workflow, docker-compose.docs.yml, or the three mkdocs-*.md docs about the doc system itself. GitHub renders the markdown directly. Folds in audit corrections from doc-by-doc review: Stage 1 / Stage 2 phasing, em-dash sweep, WHY-notes pattern, /demo route dropped, development-culture.md merged into CONTRIBUTING.md, the-team.md and index.md dropped (content promoted to README.md). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the legacy Vite + React 18 frontend toolchain with a Next.js 16 App Router scaffold, and split the monolithic stage image into separate Next.js and Django containers. Frontend toolchain: - Next 16 + React 19 + TypeScript 5 - Tailwind 3.4 + SCSS preserved from legacy (PR2 will fold into CSS Modules) - Vitest + jsdom + Testing Library replacing Jest - SVGR for both Webpack and Turbopack with svgo: false to preserve clip-path id references - vite-plugin-svgr in vitest config for test-time SVG imports Infra: - dev/next.dockerfile replaces dev/vite.Dockerfile (port 3000) - stage/next.dockerfile + stage/django.dockerfile replace the combined stage/Dockerfile that baked the Vite build into Django - docker-compose.stage.yml runs three services (pgdb on 5433, django on 8000, next on 3000) with BACKEND_INTERNAL_URL= http://django:8000 driving Next's /api/* and /admin/* rewrites Cleanup: - Drop .github/workflows/build-deploy-stage.yml and aws/task-definition.json - the deploy pipeline now lives in hackforla/incubator - Anchor the legacy 'data' and 'media' gitignore rules to the repo root so they don't shadow per-feature data/ directories the port introduces - README + docs/developer/design-system.md updated to reflect the Next.js direction Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move all of the React/TS source, tests, and assets from the
Vite + react-router-dom layout into the Next.js 16 App Router,
restructured around features/, shared/, and route shims under
src/app/.
Routing:
- Route groups (with-nav)/ and (auth)/ replace the legacy
HomeLayout / DefaultNavLayout / AuthNav split. Group layouts
hold the navbar wiring; page.tsx files are thin shims that
delegate to the matching feature.
- /privacypolicy renamed to /privacy-policy (hyphenated).
- /demo and /demo-tailwind dropped.
Features:
- features/landing/, credits/, qualifier/, session/,
privacy-policy/, not-found/ each own their components, data,
and a FEATURE_MAP.md describing the entry point and layer
dependencies.
- shared/components/ deduplicates the legacy components/ and
tw-components/ split (Tailwind variants kept where both
existed).
- shared/icons/ and shared/images/ are SVGR-imported; PNGs in
public/ for unprocessed cases.
Library swaps for React 19 compatibility:
- react-popper -> @floating-ui/react (Dropdown)
- react-transition-group -> mount/unmount (TransitionWrapper);
PR2 will reintroduce CSS-driven fade transitions
- TextField generic over FieldValues (was pinned to
{ password: string } in legacy)
Tests:
- 12 ported to Vitest + Testing Library
- TextField.test.tsx and LandingPage.test.tsx skipped with
inline notes (component shape changed; selectors stale)
Hydration / SSR fixes from smoke testing:
- CookieBanner reads cookies in useEffect after mount-then-
render to avoid a server/client tree mismatch
- QualifiersContext hydrates localStorage in useEffect
- Logo SVG sizing uses h-{N} w-auto to override SVGR's baked-in
width/height attributes (same fix applied to the credits
high-five illustration)
- LandingPageCop modal styles converted from a dead
_LandingPageCop.scss tree to inline Tailwind utilities
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Remove tailwindcss, sass, autoprefixer, postcss-import, postcss, eslint-plugin-tailwindcss, and tailwind-merge from package.json. Delete tailwind.config.ts and postcss.config.mjs (Next's defaults handle the remaining work). Replace src/app/globals.scss with a pure-CSS globals.css containing: - Tailwind-preflight-equivalent reset (box-sizing, zeroed margins, neutralized buttons/anchors, block-level media) - Design tokens (colors, spacing, radius, weights, z-index) as CSS custom properties on :root - Document baseline using --font-roboto from next/font Drop src/shared/styles/ (the legacy SCSS token + utility-class partials); their values now live as :root custom properties. Simplify shared/lib/utils.ts: cn() drops twMerge and is just clsx; combineClasses aliases cn (replaces the SCSS-era duplicate). Components and features still reference Tailwind class names; they will go unstyled until the per-feature commits in this chain swap each one for a co-located *.module.css. The build is green and tests still pass at this commit, which is the runnable boundary that matters for the PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Buttons, StandardCard, CircleCard, Typography, Dialog now ship a co-located *.module.css and reference it via clsx-composed style maps instead of Tailwind utility strings. - Drop the legacy `dark:` variants from Buttons; the app has no dark-mode toggle wired up, so the classes were dead weight. - Dialog's slide-in/out animations move from Tailwind keyframes in tailwind.config.ts to CSS @Keyframes inside Dialog.module.css. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TextField, Checkbox, and the Inputs/* family (Calendar, Chip, Dropdown, ProtoInput) now ship co-located *.module.css. ProtoInput.module.css exposes an `inputBase` class that Dropdown composes from, mirroring the legacy SCSS @mixin pattern in pure CSS Modules. Calendar's table grid (border styles, sticky header, hover/select highlight) was the most involved port; the 36px cell size and 24px tick column carry over verbatim. Tests adjusted: Calendar drag-select test now reads aria-checked on the inner checkbox div instead of querying for a hashed ".calendar-cell" class. Checkbox labelHidden test skipped with a note; the hiding is a pure CSS Modules class effect that jsdom does not apply via getComputedStyle, so there is nothing meaningful to assert in the JS layer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
HeaderNav, FooterNav, AuthNav, CookieBanner, and AccordionFaq each ship a co-located *.module.css. Tailwind responsive prefixes (`md:`, `lg:`, `max-md:`) become explicit `@media (min-width)` / `@media (max-width)` blocks using the legacy breakpoint pixel values (xs 480, sm 577, md 769, lg 1025, xl 1201). Tailwind's `space-x-*` and `space-y-*` (margin-on-children) are replaced with `gap` on the flex parent, which is the modern equivalent. Drop the legacy `mg:` typo on HeaderNav's login link (was silently a no-op since `mg:` matches no breakpoint). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Notification, TransitionWrapper, ClickCarousel, ScrollCarousel, and ChevronScroll all ship co-located *.module.css and drop the last of the SCSS files in src/shared/components/. TransitionWrapper had a dead _TransitionWrapper.scss with fade-* classes that the React 19 mount/unmount rewrite no longer references; that file is removed without a replacement module. Notification close-button test asserts on the aria-hidden attribute instead of a hashed module class — same rationale as the Calendar drag-select test from the previous commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LandingPageIntro, LandingPageCop, and LandingPageCopCards now each ship co-located *.module.css. Drops the last consumers of the legacy SCSS utility classes (`flex-container`, `col-3`, `paragraph-1`, `title-3`, etc.) for the landing route. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CreditsPage and Card move to co-located *.module.css. Tailwind responsive breakpoint utilities (xs, sm, md, lg, xl) become explicit @media (min-width) blocks at the same px values. The CreditsPage.module.css media-query stack is unusually chunky because the page resizes typography, padding, grid columns, and the hero illustration at every breakpoint - the density mirrors the original Tailwind class density per element. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
QualifierConsole, Stepper, QualifierNav, QualifierPage1, QualifierPage2, QualifierPageCalendar, RadioButtonForm, ProgressIndicator, and ChipsSelection each ship a co-located *.module.css. Stepper's connector lines now flow through `position: "first" | "last"` props on the Step component to control which sides hide the connecting bar (replacing the legacy `group-first:`/`group-last:` Tailwind variants which depended on the parent `.group` class). QualifierConsole's `<h1 className="sr-only">` becomes an explicit visually-hidden module class; the CSS rules match Tailwind's sr-only output. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LoginForm and SignupForm both reference a shared SessionForm.module.css since their layouts (heading, submit button, alt-link footer) are identical. Signup adds a nameGrid class for the side-by-side first/last name pair on md+ screens. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PrivacyPolicyPage moves its inline `styleClass` map of Tailwind strings into a co-located *.module.css. The hero illustration keeps its lg: visibility gate via @media (min-width: 1025px). NotFoundPage rebuilds the layout that the deleted _NotFoundPage.scss used to provide, now in a small co-located module. The component had been rendering unstyled since commit 1 of this chain. This is the last per-feature commit in the Tailwind/SCSS to CSS Modules sweep. No Tailwind class names or sass partials remain in the tree. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Member
Author
|
This is a faithful recreation against the staging site: The 404 page has a bug (navbar bottom is floating). |
This was referenced May 2, 2026
RSkuma
pushed a commit
that referenced
this pull request
May 7, 2026
… (frontend), CI workflow
Backend - swap black + flake8 + isort for ruff:
- pyproject.toml: replaces three dev deps with ruff under
[tool.ruff]/[tool.ruff.lint] (selecting E/W/F/I/B/UP/DJ/SIM/C4),
[tool.mypy] (gradual mode with django-stubs + drf-stubs plugins),
[tool.django-stubs] (settings module pointer),
[tool.bandit] (excludes migrations + tests).
- backend/.flake8: deleted; ruff config supersedes it.
- ruff format pass produced trivial reshape on manage.py and
settings.py (line-length-driven splits). E501 in settings.py
was a pre-existing line over 88 chars; split into a tuple
string. DJ008 (Opportunity has no __str__) addressed by
adding `f"{role.title} @ {project.name}"`. DJ001 on
min_experience_required noqa'd with TODO comment - the fix
drops null=True which generates a schema migration; deferred
to a focused follow-up PR.
Frontend - extend the lint surface beyond ESLint:
- ESLint stays at 9.x; eslint-config-next 16's flat configs are
loaded directly (no more FlatCompat shim). The eslint-plugin-
tailwindcss reference from the pre-rewrite eslint.config.mjs
was removed (tailwind was deleted in PR #736 but the lint
config still referenced the plugin). The legacy `next lint`
script is gone in Next 16; lint script now runs `eslint .`.
- eslint-plugin-react-hooks 5 -> 7 brings the React Compiler
rule set; the three new rules (set-state-in-effect, refs,
static-components) are downgraded to warn since the legacy
components in the tree violate them in many places. TODO
comment in the config; intent is to address in a follow-up
React-Compiler-prep PR.
- stylelint added with stylelint-config-standard plus camelCase
selector/keyframes patterns and ignoreProperties for `composes`
(CSS Modules directive) and `clip` (sr-only pattern).
- knip added with fail-on-find for every category. Real findings
in this PR: 6 unused component files deleted (AccordionFaq,
ClickCarousel, ScrollCarousel, ChevronScroll, ChipsSelection,
Chip), 3 unused exports (SearchButton, sampleCopData,
combineClasses), 1 unused type re-export (AssetDatum from
creditsIconData.ts), 4 unused devDependencies (@eslint/js,
globals, @typescript-eslint/eslint-plugin, @typescript-eslint/
parser, typescript-eslint - the latter three were transitive
via eslint-config-next anyway).
- @svgr/webpack stays in package.json since next.config.ts
references it as a string loader name; knip can't see that
through configs, so it's the sole entry in
ignoreDependencies.
Pre-commit:
- .pre-commit-config.yaml rewritten. Was stale: pinned to
python 3.9, referenced paths that no longer exist, ran old
versions of the deleted tools. Now: ruff-pre-commit (check
+ format passes), mirrors-prettier, local hooks for eslint
and stylelint that delegate to npx so the project's pinned
versions are used. Python pinned to 3.13, Node to 24.
CI:
- .github/workflows/lint.yml added. Two parallel jobs
(backend / frontend) each running their respective full lint
suites. Authoritative gate; PRs cannot merge while red.
- .github/workflows/jest-react-test.yml renamed to
vitest-test.yml; Node bumped from 18 to 24.
- .github/workflows/linter.yml (Super-Linter) deleted -
superseded.
Cleanup:
- dev/linter.dockerfile + dev/linter.env.example deleted -
the dev linter container was the legacy enforcement path
for the old toolchain; CI workflow + pre-commit replace it.
Docs (single source of truth alignment):
- docs/developer/eslint-guide.md renamed to
frontend-lint-guide.md and rewritten to cover the four-
layer frontend lint stack (ESLint + Stylelint + Knip +
Prettier) plus tsc.
- docs/developer/backend.md: lint section updated;
directory tree no longer lists .flake8.
- docs/developer/quickstart-guide.md: backend + frontend
lint command sections updated.
- docs/developer/devops.md: linting section rewritten;
references the new tools in a tool/surface table.
- docs/developer/installation.md: required Node 22 -> 24,
Python 3.12 -> 3.13, pre-commit-Python-hooks list
updated to ruff. Editor extension recommendations
expanded (ESLint, Stylelint, Prettier).
- docs/developer/design-system.md, docs/resources.md,
README.md: link updates from eslint-guide to
frontend-lint-guide.
- CONTRIBUTING.md: typing-policy section added describing
the gradual mypy posture - new code annotates
signatures, existing code is annotation-welcome but not
required, don't annotate just to silence the type
checker.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR: Tailwind + SCSS → CSS Modules
Branch:
next-rewrite-css-modulesstacked onnext-rewrite(#735), which is itself stacked ondocs-rewrite(#734).Retarget chain when each parent lands.
Summary
Removes Tailwind, SCSS, and every utility-class system from the frontend. Components now ship co-located
*.module.cssfiles; design tokens live as CSS custom properties on:root; breakpoints are inlined@mediaqueries. PR1 preserved Tailwind + SCSS through the Next.js port so the visual surface stayed put while the framework changed underneath; this is the styling-system swap that PR1 made room for.This is PR2 of the 2-PR chain opened by #735.
Why this shape
sassdep, the_partials.scsschain, and the global@useplumbing are all gone.flex-container,col-3,paragraph-1,mr-4, etc.) are not preserved as global CSS. Each component's CSS Module owns its layout.Commits
What's in this PR
Toolchain
tailwindcss,tailwind-merge,eslint-plugin-tailwindcss,sass,autoprefixer,postcss,postcss-import.tailwind.config.ts,postcss.config.mjs,src/shared/styles/(the entire SCSS token + utility partials directory), every_*.scssundersrc/shared/components/andsrc/features/.globals.css(replacesglobals.scss) holds: a Tailwind-preflight-equivalent reset (box-sizing, margin reset, neutralized buttons/anchors, block-level media), all design tokens as:rootcustom properties, and the bodyfont-familywired to the existing--font-robotovariable fromnext/font/local.Design tokens
All values mirror the legacy SCSS variables and Tailwind theme. Names follow the legacy naming so token-search-and-replace stays mechanical:
--color-blue-dark,--color-blue-dark-hover,--color-tan,--color-grey-light, etc. (17 tokens)--space-1through--space-10(8px increments, mirrors the legacyp1–p10Tailwind extension)--radius-sm,--radius-default(20px),--radius-large(60px),--radius-xlarge(100px)--weight-thinthrough--weight-black--z-base,--z-overlay,--z-dropdown,--z-modalBreakpoints are documented as a comment in
globals.cssand inlined as literal pixel values in module-level@mediaqueries, since CSS still doesn't allow custom properties inside media-feature lists. Values match the legacy Tailwind config: xs 480, sm 577, md 769, lg 1025, xl 1201.Notable per-component changes
cn()simplified. No more Tailwind class-collision merging; the utility is now aclsxpassthrough.combineClassesaliasescnso existing call sites keep working.dark:*Tailwind variants inButtons.tsxwere dead — there's no theme-switching wired up. Removed rather than carried forward.tailwind.config.tsintoDialog.module.cssas plain@keyframes.composes:for shared input baselines.Dropdown.module.csscomposesinputBasefromProtoInput.module.css;Chip.module.cssdoes the same for itsmultiandsinglevariants. This is the CSS Modules native equivalent to the legacy@mixin inputSCSS pattern.group-first:/group-last:Tailwind variants depended on a parent.groupmarker class; replaced with aposition: "first" | "last"prop onStepthat gates the relevant connector via a.lineHiddenmodule class.sr-onlyequivalents. Visually-hidden labels (CheckboxlabelHidden, QualifierConsole<h1>) inline theclip: rect(0,0,0,0)etc. recipe directly in their modules.Tests
Three tests had to change because they asserted on Tailwind class names that became hashed CSS Modules class names:
aria-checkedon the inner checkbox div instead oftoHaveClass(".calendar-cell .selected").toHaveAttribute("aria-hidden", "true")instead oftoHaveClass("hidden").labelHiddenisdescribe.skipwith an inline note. The hiding is purely a CSS Modules class effect that jsdom doesn't apply viagetComputedStyle, so there's nothing meaningful to assert in JS. Verified by hand in the browser.Full suite: 11 passing, 3 skipped (the two from PR1 plus the new Checkbox skip).
What's deferred
lint.yml+ deletelinter.yml) are still pending post-merge of the parent chain.jest-react-test.ymlworkflow rename (still labeled "Jest" but runs Vitest) — same status as in [1/2] Port frontend to Next.js 16 (App Router) #735, not addressed here.Test plan
Programmatic:
npm install— clean from updated lockfile.npm run build— production build green, all 7 routes compile.npm test— 11 pass, 3 skipped.*.scssfiles anywhere infrontend/src. Notailwind.config.*, nopostcss.config.*. Notailwind-mergeorsassinpackage.json.Page walk (visual smoke test, no behavior changes expected):
/landing/credits/privacy-policyh2/h3/bullets/links styled/login/signup/qualifier/1/qualifier/2/qualifier/3/foo(any unknown URL) — 404 with HeaderNav, FooterNav, imageStacking note
This PR sits two-deep in the chain:
When each parent merges, retarget the next PR's base. Squash-merging into
developis fine; per-commit history here is preserved on the branch but does not need to land as separate commits.