Skip to content

Bump dependencies + replace lint stack#737

Merged
RSkuma merged 18 commits into
hackforla:developfrom
Nickatak:next-rewrite-deps-bump
May 7, 2026
Merged

Bump dependencies + replace lint stack#737
RSkuma merged 18 commits into
hackforla:developfrom
Nickatak:next-rewrite-deps-bump

Conversation

@Nickatak
Copy link
Copy Markdown
Member

@Nickatak Nickatak commented May 2, 2026

PR: Bump dependencies + replace lint stack

Branch: next-rewrite-deps-bump stacked on next-rewrite-css-modules (#736), which is stacked on next-rewrite (#735), which is stacked on docs-rewrite (#734).
Retarget chain when each parent lands.


Summary

Modernizes the entire build, test, lint, and CI surface in a single pass. Three commits, each green, read in order:

  1. Bump runtime/framework/library deps. Python 3.13, Django 6.0, psycopg3, Postgres 18, Node 24, plus the frontend dep tree.
  2. Replace lint stack. Backend swaps black + flake8 + isort for ruff, adds mypy (gradual) and bandit. Frontend keeps ESLint, adds stylelint and knip. New lint.yml CI workflow runs the suite. Pre-commit and docs aligned.
  3. Fix lint workflow CI failures. Three small fixes for issues that only surfaced on a fresh CI checkout (SVG type declarations, next typegen, mypy env vars).

Why this shape

  • Modernize while the codebase is small. The rewrite chain (Rewrite docs/ for future-state architecture #734, [1/2] Port frontend to Next.js 16 (App Router) #735, [2/2] Tailwind + SCSS to CSS Modules #736) was a deliberately cheap moment to do major bumps; the next forced upgrade in 12-18 months will hit a project with new feature work piled on top.
  • One PR, three commits, "tooling-shaped." Every change is build / test / lint / CI / dep work; none of it touches business logic. Splitting (e.g. bumps PR + lint-swap PR) was considered but the splits aren't clean: commit 2 deletes black/flake8/isort which commit 1 deliberately skipped bumping; knip's dead-code findings entangle with the lint swap; doc alignment for the new tooling can't be decoupled from the tooling itself.
  • Read commit-by-commit. The unified diff is large (mostly lockfiles, configs, doc updates) but each commit's message tells its own self-contained story.

Tooling choices

Each new or replaced tool, with the load-bearing alternative we ruled out.

Backend

  • ruff (replaces black + flake8 + isort). Single Rust binary that does linting, formatting, and import sorting in one pass. Black-compatible formatter plus a rule surface that covers everything the legacy chain did (pyflakes, pycodestyle, isort) plus families the legacy chain needed plugins for (B bugbear, SIM simplify, C4 comprehensions, DJ Django-specific, UP pyupgrade). One config, one CLI, one version pin instead of three. Alternative considered: keep the chain but bump versions. Rejected because the chain itself is the maintenance cost; ruff isn't a marginal upgrade, it's a category change.
  • mypy (new) in gradual mode (disallow_untyped_defs = false). The codebase has effectively zero annotations today; strict-from-day-one would force every PR to fight the type checker. Gradual mode runs mypy with django-stubs + drf-stubs so ORM call sites get checked even when surrounding functions are untyped, while the bar to land a typed function stays low. Documented in CONTRIBUTING.md. Ratchets stricter as coverage grows. Alternative considered: pyright. Rejected because the Django ecosystem stubs are mypy-first.
  • bandit (new). Security linter targeting the OWASP-shaped issue classes (hardcoded secrets, SQL injection, weak crypto, unsafe deserialization). Low-friction to add, complements ruff's correctness rules. Configured to skip migrations (auto-generated, lots of false positives) and the test suite (assertions are not security issues in tests).
  • django-stubs + djangorestframework-stubs (new). The mypy plugins that make Django ORM and DRF call sites type-aware. Without them, mypy treats every Model.objects.filter(...) return as Any and the type checker is roughly useless on a Django codebase.

Frontend

  • stylelint (new) with stylelint-config-standard. ESLint can't see CSS files; the post-[2/2] Tailwind + SCSS to CSS Modules #736 CSS Modules surface was previously unlinted. Project-specific overrides: camelCase selector-class-pattern and keyframes-name-pattern (matches styles.signupForm JS-import shape), ignoreProperties for composes (CSS Modules directive that looks like an unknown property to vanilla parsers) and clip (canonical sr-only recipe).
  • knip (new) with fail-on-find for every category. Detects unused files, unused exports, unused dependencies, and unlisted dependencies. Auto-discovers entry points (App Router pages, configs) and resolves plugin references through their config schemas. Real findings on the first run, all fixed in this PR: 6 unused component files deleted, 3 unused exports removed, 4 unused devDeps removed. Alternatives considered: depcheck (only does dependencies), ts-prune (only does exports). Knip covers both with one config. Warn-mode was considered and rejected; warnings would accumulate and the whole point of running knip is preventing that.
  • ESLint 9.x (pinned; defers ESLint 10). eslint-plugin-jsx-a11y and eslint-plugin-react cap their published peer ranges at ^9; the maintainers haven't shipped 10-compatible releases. --legacy-peer-deps produces a circular-structure JSON error during config loading. Re-evaluation trigger: both plugins shipping eslint: "^10.0.0" peers.
  • prettier (existing, kept). Project default. No project-specific config; defaults are fine. Wired through ESLint via eslint-plugin-prettier so formatting failures are lint errors, which means "ready to lint" and "ready to commit" mean the same thing.
  • react-hooks 7.x React-Compiler rules downgraded to warn. The eslint-plugin-react-hooks 5 to 7 bump activated three new rules (set-state-in-effect, refs, static-components). The legacy components in this repo violate them in ~28 places (mount-then-render gates, refs read in render, locally-declared subcomponents). Fixing all 29 is a real refactor and out of scope for a "set up the lint suite" PR. Downgraded to warn so the signal is visible without blocking merge; intent is to address in a follow-up React-Compiler-prep PR.

Test runner

  • vitest 3.x (pinned; defers vitest 4). Vitest 4 requires Vite 8, which requires @vitejs/plugin-react@6, which requires babel-plugin-react-compiler as a hard peer. We're not using the React Compiler; pulling in a compiler dep just to satisfy a peer range is the wrong trade. Vitest 3.2.4 + plugin-react 5 + Vite 7 is the cleanest tree that supports all our existing test setup. Re-evaluation trigger: @vitejs/plugin-react removes the babel-plugin-react-compiler peer requirement, or we opt into the compiler.

Commits

# Subject
1 Bump runtime, framework, and library dependencies
2 Replace lint stack: ruff + mypy + bandit (backend) + stylelint + knip (frontend), CI workflow
3 Fix lint workflow CI failures

What's in this PR

Commit 1: Dependency bumps

Backend (Python 3.12 to 3.13, Django 5.1 to 6.0):

  • Django 5.1.2 to 6.0.4
  • DRF 3.15.2 to 3.17.1
  • daphne 4.1.2 to 4.2.1
  • whitenoise 6.8.2 to 6.12.0
  • psycopg2-binary 2.9.9 to psycopg[binary] 3.3.4 (driver swap; settings.py needs no change since django.db.backends.postgresql auto-detects psycopg3 when installed)
  • Python base image 3.12-alpine to 3.13-alpine in both dev/django.dockerfile and stage/django.dockerfile

Infra:

  • Postgres 16 to 18 in both compose files (dev volumes need docker compose down -v on first pickup)
  • Node 22-alpine to 24-alpine in dev/next.dockerfile and all three stages of stage/next.dockerfile

Frontend:

  • next 16.2.4 (current), react 19.2.4 to 19.2.5 (patch)
  • typescript 5 to 6
  • jsdom 25 to 29, prettier 3.4 to 3.8, globals 15 to 17, @types/node 20 to 24
  • eslint 9.17 to 9.39 (latest 9.x; ESLint 10 deferred, see Tooling choices)
  • eslint-plugin-react-hooks 5.1 to 7.1
  • @vitejs/plugin-react 4 to 5
  • vitest 2 to 3 (vitest 4 deferred, see Tooling choices)
  • vitest.config.ts renamed to vitest.config.mts since @vitejs/plugin-react@5 is ESM-only

Lint tooling (black, flake8, isort) intentionally left at current versions; those are replaced wholesale in commit 2.

Commit 2: Lint stack replacement

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).
  • Three pre-existing ruff violations addressed:
    • E501 in settings.py (line over 88 chars). Split.
    • DJ008 on Opportunity model (no __str__). Added f"{role.title} @ {project.name}".
    • DJ001 on min_experience_required (CharField with null=True). noqa'd with TODO comment. The fix drops null=True which generates a schema migration; deferred to a focused follow-up PR rather than expanding this PR's scope.

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 pre-rewrite eslint.config.mjs referenced eslint-plugin-tailwindcss even though Tailwind was deleted in [2/2] Tailwind + SCSS to CSS Modules #736. Removed.
  • The legacy next lint script no longer exists in Next 16; lint script now runs eslint . directly.
  • eslint-plugin-react-hooks 5 to 7 brings the React Compiler rule set; three new rules downgraded to warn (see Tooling choices).
  • stylelint added with stylelint-config-standard plus camelCase patterns and ignore lists (see Tooling choices).
  • 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 removed (SearchButton, sampleCopData, combineClasses)
    • 1 unused type re-export removed (AssetDatum from creditsIconData.ts)
    • 4 unused devDependencies removed (@eslint/js, globals, @typescript-eslint/eslint-plugin, @typescript-eslint/parser, typescript-eslint; the latter three were transitive via eslint-config-next)
  • @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. The previous file was stale: pinned to Python 3.9, referenced paths that no longer exist (app/setup.cfg, config.settings), ran old versions of the tools we're now deleting. New config: 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 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 with a tool/surface table.
  • docs/developer/installation.md: required Node 22 to 24, Python 3.12 to 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.

Commit 3: CI workflow fixes

Three issues surfaced when commit 2's Lint workflow ran on a fresh CI checkout (none reproducible locally because of cached .next/ and Docker env vars):

  • tsc --noEmit couldn't resolve @/shared/icons/*.svg and @/shared/images/*.svg imports. SVGR transforms .svg into React components at runtime but doesn't emit .d.ts files. Added frontend/src/types/svg.d.ts with the ambient module declaration.
  • tsc also failed on next-env.d.ts's static import of ./.next/types/routes.d.ts, which doesn't exist on a fresh checkout. The lint:types script now runs next typegen && tsc --noEmit.
  • mypy crashed inside django-stubs because backend.settings reads required env vars via python-decouple at import time. Added dummy env vars to the mypy step in lint.yml.

Test plan

  • ruff check, ruff format --check, mypy, bandit all clean
  • eslint, stylelint, knip, tsc --noEmit, prettier --check all clean
  • Backend python manage.py test: 9 tests pass on Django 6 / Python 3.13 / psycopg3 / Postgres 18
  • Frontend vitest run: 11 tests pass, 3 skipped (matches pre-PR baseline)
  • Frontend next build succeeds
  • docker compose up --watch brings up Django 6 + Daphne 4.2 cleanly on Postgres 18
  • CI: Lint and Vitest workflows both green

Follow-ups (not blocking this PR)

  • Schema migration: drop null=True on Opportunity.min_experience_required (DJ001 violation noqa'd here). Generates a migration; deserves its own focused PR.
  • React-Compiler prep: address the 28 react-hooks rule warnings. Real refactor, restructuring mount-then-render gates, redesigning Calendar's drag tracking, hoisting locally-declared subcomponents.
  • GitHub Actions deprecation: actions/checkout@v4 and actions/setup-python@v5 show a non-blocking deprecation warning about Node 20 (forced to Node 24 in June 2026). Bump when convenient.
  • Re-evaluate ESLint 10 once eslint-plugin-jsx-a11y and eslint-plugin-react ship 10-compatible releases.
  • Re-evaluate vitest 4 once @vitejs/plugin-react no longer requires babel-plugin-react-compiler as a hard peer (or the project opts into the compiler).

Nickatak and others added 17 commits April 26, 2026 16:16
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>
Backend (Python 3.12 → 3.13, Django 5.1 → 6.0):
- Django 5.1.2 → 6.0.4
- djangorestframework 3.15.2 → 3.17.1
- daphne 4.1.2 → 4.2.1
- whitenoise 6.8.2 → 6.12.0
- psycopg2-binary 2.9.9 → psycopg[binary] 3.3.4
  (Driver swap. settings.py needs no change since
  django.db.backends.postgresql auto-detects psycopg3 when
  installed; envvars stay as-is.)
- Python base image 3.12-alpine → 3.13-alpine in both
  dev/django.dockerfile and stage/django.dockerfile

Infra:
- Postgres 16 → 18 in both compose files. Major bump means
  dev volumes need `docker compose down -v` on first pickup.
- Node 22-alpine → 24-alpine in dev/next.dockerfile and all
  three stages of stage/next.dockerfile (Node 24 LTS).

Frontend:
- next 16.2.4, react 19.2.4 → 19.2.5 (patch)
- typescript 5 → 6
- jsdom 25 → 29
- prettier 3.4 → 3.8
- globals 15 → 17
- @types/node 20 → 24 (tracking the runtime)
- eslint 9.17 → 9.39 (latest 9.x; ESLint 10 deferred —
  jsx-a11y and eslint-plugin-react have not yet shipped
  ESLint 10 peer ranges)
- eslint-plugin-react-hooks 5.1 → 7.1
- @vitejs/plugin-react 4 → 5
- vitest 2 → 3 (vitest 4 requires vite 8 + plugin-react 6
  which adds babel-plugin-react-compiler as a hard peer;
  not worth the extra dep at this point)
- vitest.config.ts → vitest.config.mts since plugin-react 5
  is ESM-only and the project does not have type:module set

Lint tooling (black, flake8, isort) intentionally left at
current versions — those are being replaced wholesale in the
next commit, so bumping them is wasted churn.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… (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 hackforla#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>
Three issues surfaced when the new Lint workflow ran on a fresh CI
checkout:

1. tsc could not resolve `@/shared/icons/*.svg` and
   `@/shared/images/*.svg` imports. SVGR transforms .svg into React
   components at runtime but doesn't emit .d.ts files. Locally the
   missing declarations slipped past because `.next/` from prior
   builds had partial types in scope. Add `frontend/src/types/svg.d.ts`
   with the ambient module declaration.

2. tsc also failed on `next-env.d.ts`'s static import of
   `./.next/types/routes.d.ts`, which doesn't exist on a fresh
   checkout. Update the `lint:types` script to run `next typegen`
   first - the Next 16 CLI command that generates route types
   without doing a full build.

3. mypy crashed with "Error constructing plugin instance of
   NewSemanalDjangoPlugin" because the django-stubs plugin imports
   `backend.settings`, and settings.py reads required env vars via
   python-decouple at import time. Add dummy env vars to the mypy
   step in the lint workflow - they're never used for I/O, just
   needed so the import doesn't raise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The initial bump in this branch caught the framework deps (django,
drf, daphne, whitenoise, psycopg) but the lock didn't refresh
uritemplate (4.1.1 -> 4.2.0) or pyyaml (6.0.2 -> 6.0.3) even though
both were within the existing caret ranges. Likely a stale poetry
cache during the original lock run; explicit `poetry update` on
just those two refreshes the lock cleanly.

No pyproject.toml change since the caret pins already permit these
versions. Backend tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Nickatak Nickatak marked this pull request as ready for review May 7, 2026 21:05
Copy link
Copy Markdown
Member

@RSkuma RSkuma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved after discussion

@RSkuma RSkuma merged commit 5e1bbed into hackforla:develop May 7, 2026
Nickatak added a commit to Nickatak/CivicTechJobs that referenced this pull request May 19, 2026
vitest 3 narrowed the default `MockInstance` returned by a bare,
unparameterized `vi.spyOn` reference. `ReturnType<typeof vi.spyOn>`
now resolves to `MockInstance<(this: unknown, ...args: unknown[])
=> unknown>`, which the spy actually produced by `vi.spyOn(authApi,
"login")` etc. can't assign into - function parameters are
contravariant, and `LoginPayload` isn't assignable to `unknown` in
the contained call signature. vitest 2 was looser, so this only
surfaced after the runtime/dep bump in hackforla#737.

Type the spy variables directly as `MockInstance<typeof
target.method>`, sidestepping the `vi.spyOn` generic constraints
entirely. Three spies affected (login, signup, fetch); the no-arg
spies (csrf, me, logout) had no contravariance problem and are left
unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants