feat: centralize book download links in a new constants file for easier management feat: implement typed environment variable access for safer configuration fix: enhance image loading in use-cases section with responsive sizes docs: update deployment guide with security headers configuration for self-hosted setups
9.2 KiB
Code-Quality Follow-ups
This document tracks items that were identified during the code-quality pass but need design or infrastructure decisions before code can land. Each section spells out the gap, the proposed fix, and the artifacts the implementer needs.
i18n: single-locale today, multi-locale infra in place
Status
Configured for English only.
apps/web/i18n/routing.tsdeclareslocales: ['en'].- Only
apps/web/messages/en.jsonexists. packages/content/src/locales/registry.tsdefaults to['en']until the app callssetActiveLocaleswith more.apps/web/scripts/strip-default-locale-prefix.shruns afternext buildto strip the/enprefix from the static-export output (single-locale optimization).
When adding fr / ko / etc
- Add the locale code to
apps/web/i18n/routing.tslocalesarray. - Create
apps/web/messages/<locale>.jsonmirroring theen.jsonkeys. - Boot the registry at startup with the new list (the next-intl provider
already does this for the
routing.localesarray). - For each PageCopy-driven route, create
<locale>.jsonfiles alongside the existing English copy undercontent/pages/<route>/<locale>.json. - Re-run the build;
strip-default-locale-prefix.shcontinues to strip the default locale (en) prefix only.
Why not yet
No translated content shipped. Locking single-locale wiring keeps the codebase honest until that arrives.
knip dead-export audit
Status
Open. 102 export keywords appear under apps/web/components/. Likely some
are unused after the previous refactors (sub-component extractions,
re-exports from barrel files).
Run path
pnpm dlx knip --workspace apps/web
A typical first-run baseline:
- Unused exports flagged → review one batch per feature area; some are
intentional public API for sibling files in the same barrel and should be
marked with
// @publicor moved into a private file. - Unused dependencies flagged → confirm in
package.jsonbefore removing.
Why not yet
Running knip casually creates noise; the right pass is once the structural
refactors settle, then bake knip --no-progress into CI as a warn-only step.
Bundle analyzer
Status
Open. Heavy dependencies (motion, leaflet, react-leaflet-cluster) ship
to clients but actual cost is unmeasured.
Wiring (when ready)
pnpm add -D @next/bundle-analyzer --filter web
Then in next.config.mjs:
import withBundleAnalyzer from '@next/bundle-analyzer'
const analyzer = withBundleAnalyzer({ enabled: process.env.ANALYZE === 'true' })
export default withNextIntl(analyzer(nextConfig))
pnpm --filter web build with ANALYZE=true emits HTML reports under
.next/analyze/.
Why not yet
devDep install needs explicit user approval; once installed, also worth
adding a CI bundle-size budget (e.g. bundlewatch).
Typography token migration
Status
Open. Tokens exist in @repo/tokens; raw font-* + text-[Npx] patterns
appear ~171 times across the codebase. Migrating without a concurrent Figma
spec audit risks visible regressions on typography-heavy frames (press,
design-systems, blog).
What "done" looks like
-
Inventory the top-five token equivalences from raw classes:
Raw pattern Proposed token font-mono text-[10px] font-semibold leading-[1.35] uppercasetext-eyebrowfont-mono text-[10px] leading-[1.3]text-mono-sfont-display text-[36px] leading-none tracking-[-0.03em]text-h3-seriffont-sans text-[18px] leading-[1.15] tracking-[-0.01em]text-subhead-sansfont-sans text-[14px] leading-[1.2]text-body-sans -
Cross-check each pattern against the corresponding Figma frame; if a frame uses a one-off variant, keep the raw class and add an inline comment so the next reader doesn't "fix" it.
-
Replace the raw classes with the token name file by file. Keep one PR per feature area (press, builders-hub, circles) for safer Figma diffing.
-
After migration, add an ESLint rule (
no-restricted-syntax) that flagsfont-mono/font-sans/font-displayoutside the tokens file.
Why not yet
The work is mechanical only if the existing raw classes already match the token spec. A spot check found at least three near-matches that diverge from the token by 1px or 0.05em — those would silently change visuals when the token replaces them.
alt="" audit
Status
Open. 45 instances of alt="" across apps/web.
Required content-schema change
In packages/content/src/schemas/press.ts and circles.ts, the image field
currently allows alt: ''. Make alt required + non-empty for content
images so articlesToCards can drop its || article.title fallback.
// proposed
image: z.object({
src: z.string().min(1),
alt: z.string().min(1, 'image.alt must describe the image; use a decorative container if the image is purely visual'),
}),
Component-side rule
| Image purpose | alt value |
|---|---|
| Article thumbnail / podcast cover | {title} (or schema-supplied caption) |
| Decorative blur / pattern background | alt="" (intentional, leave a comment) |
| Hero foreground portrait | descriptive copy from translations |
Action items
- Tighten the schemas above.
- Re-run
getPageCopyintegration to surface any data files that violate. - Switch
articlesToCardsto readarticle.image.altdirectly (no fallback). - Audit all 45 sites; add a comment next to each intentional
alt="". - Enable
eslint-plugin-jsx-a11y/alt-textat error level.
E2E happy-path coverage
Status
Open. No test runner is configured in apps/web yet.
Bootstrap path
pnpm add -D playwright @playwright/test --filter web
pnpm exec playwright install --with-deps
Add to apps/web/package.json:
"scripts": {
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui"
}
First flows to cover (in order of incident risk)
/— home renders, hero loads, navigation overlay opens/closes./press— articles list renders ≥ one article from the API; cards link externally./circles— settings + circles + events all resolve; map renders./active-circles— Hasura fetch path works; stat cards show non-zero data./builders-hub/ideas/[slug]and/builders-hub/rfps/[slug]— both happy path andnotFound()path (use a known-bad slug).
Why not yet
Playwright bootstrap and CI integration is its own multi-hour task; bundling it into the quality pass would obscure the diff.
default → named export cleanup
Status
Mostly safe. Constraint: Next.js requires export default on page.tsx,
layout.tsx, loading.tsx, error.tsx, not-found.tsx, template.tsx,
route.ts, and middleware.ts.
Conversion targets (safe)
apps/web/components/locale/locale-switcher-select.tsx— defaultLocaleSwitcherSelect→ named export.apps/web/components/site-header/site-header-client.tsx—SiteHeaderClient.- All
components/sections/**/*.tsxfiles usingexport default— switch to named export, update barrel re-exports accordingly.
Conversion targets (must stay default)
apps/web/app/**/page.tsx,layout.tsx,loading.tsx,error.tsx,not-found.tsx.
Why not yet
Each conversion is mechanical but cascades through the per-section barrels. Doing it incrementally (one feature area per PR) avoids a 50-file diff.
Vitest infra
Already done
apps/web/vitest.config.tsand the first test file underapps/web/lib/__tests__/are in place.
Still required
- Add the
testscript toapps/web/package.json(vitest run). - Wire CI to run it (
turbo run test). - Author tests for
lib/reward.ts,lib/cn.ts,lib/page-sections.ts. - Configure coverage thresholds (suggested: 80% for
lib/).
cn() adoption sweep
Status
Open. lib/cn.ts exists; ~25 components still use template-literal
className composition.
Codemod
Many call sites match the pattern:
className={`base-classes ${dynamicClass} ${className ?? ''}`}
A safe rewriter is jscodeshift with a small transform that detects this
exact shape and replaces it with cn('base-classes', dynamicClass, className).
Hand-written variants will need manual review.
Why not yet
A naive sed across 25 files breaks template literals that interpolate non-className expressions; a real codemod is the right tool but requires setup.
resolveLocale + LocaleParams adoption
Status
Open. Helpers in lib/route-params.ts; 13 page.tsx files still inline the
isActiveLocale(locale) guard, 36 still inline params: Promise<{ locale }>.
Codemod
The transformation is uniform:
- export default async function FooPage({
- params,
- }: {
- params: Promise<{ locale: string }>
- }) {
- const { locale } = await params
- if (!isActiveLocale(locale)) {
- throw new Error(`FooPage received non-active locale "${locale}"`)
- }
+ export default async function FooPage({ params }: LocaleParams) {
+ const locale = await resolveLocale(params, 'FooPage')
A jscodeshift transform that recognises the function-name string in the error message keeps the page-name argument correct.