`next lint` is deprecated and disappears in Next 16. apps/web now runs
`eslint .` against eslint.config.mjs on ESLint 9, and the shared preset moves
to packages/config/eslint-preset.mjs. eslint-config-next 15 still ships only
an eslintrc-style config, so FlatCompat translates it; that keeps
next/core-web-vitals as the single source of rules.
The migration was done by hand rather than via @next/codemod, because the
codemod does not know about the shared @repo/config preset.
Two things worth knowing.
ESLint 9 only walks file extensions that some config names in `files`. The
first working version of this config linted three loose .js files, skipped
every .tsx in src/, and still exited 0 -- a green run that checked nothing.
The preset now names the extensions explicitly. Coverage went from 3 files to
82, and a deliberately broken canary component confirms rules still fire.
`@eslint/eslintrc` also needed a second scoped override: it does a default
import of minimatch and declares ^3.1.2, which the global ^9.0.7 audit
override broke. Same shape as the ajv fix in the previous commit.
The linter had two faults stacked on top of each other.
First, the global `ajv: ^8.17.1` override starved @eslint/eslintrc, which
needs ajv 6 and passes it `missingRefs`; ajv 8 rejects that option, so ESLint
died before loading a single rule. Scoped overrides give eslint and
@eslint/eslintrc ajv 6.12.6 and leave the audit override in place everywhere
else.
Second, underneath that, `extends: ["@repo/config/eslint-preset.js"]` could
never resolve: ESLint's shareable-config naming convention rewrites it to
`@repo/eslint-config-config/...`. require.resolve hands ESLint a real path.
With the linter running it reported 35 react/no-unescaped-entities errors
across 7 files. Those are escaped rather than silenced, using ESLint's own
coordinates so only flagged text nodes were touched. Lint now exits 0. Two
@next/next/no-img-element warnings remain on the splash logos, which are
deliberate: theme-paired and fetchPriority="high".
Em dashes are also gone from the six blog posts and the README (105
occurrences), same rules as the site copy: colons for definition labels,
commas for appositives, semicolons for independent clauses.
- Cap the two wildly oversized bento source PNGs so Next can't serve giant variants:
occupy-wallets 1200x837/393K -> 700x488/167K, neroswap 1584x672/1.8M -> 700x297/404K.
(bauhaus/vegan are already 512px and self-cap; privacy-labs was already small.)
- Fix the bento <Image> sizes: object-contain height-constrains the image so it renders
~360px wide in either span; 66vw/100vw made Next serve up to ~750-1200w. Now
'(max-width:768px) 90vw, 33vw'.
- Bump turbo 2.7.4 -> 2.9.16 (GHSA-hcf7-66rw-9f5r, GHSA-3qcw-2rhx-2726). pnpm audit
now clean.
The data-URI logo still had LCP ~5s with zero network request, proving the
bottleneck is render delay (JS), not the image. So:
- page.tsx is now a Server Component. The hero, tagline, buttons, ServicesPreview,
PortfolioGrid, and FounderSignature render as static server HTML (they had no
client features, so they now hydrate to nothing).
- Splash state machine + header/nav/background gating + nebula position + scroll
restoration moved into a small client island (home-shell.tsx) that takes the
static content as children. Nebula reads the title via getElementById.
- Dedupe @types/react to 19 across the workspace (pnpm override) so the
server->client children boundary type-checks.
Build was failing because the new MDX post tried to `import` the
SuchMoonLaunchStoreButtons component — next-mdx-remote/rsc doesn't
support imports inside .mdx; components must be passed via the
`components` prop. Wire the helper into [slug]/page.tsx and drop the
import line from the .mdx.
Dependency bumps to clear the 14 outstanding Dependabot advisories
(9 high, 4 moderate, 1 low):
- apps/web/next: ^15.5.15 → ^15.5.18 (covers 12 next.js advisories)
- pnpm overrides: pin postcss ^8.5.14 (clears 1117015 XSS via </style>)
- pnpm overrides: pin fast-uri ^3.1.2 (clears 1117870 + 1117884 path
traversal / host confusion)
pnpm audit: 14 vulnerabilities → 0.
Forces flatted, minimatch, picomatch, ajv, and brace-expansion to
patched versions across the transitive graph (ESLint, Tailwind, and
Playwright toolchains). Both prod and full audits now report no
known vulnerabilities.
- Brand Motion card rewritten with concrete capability language
- Revenue-share card clarified: no upfront fees, no retainers, low % after launch
- Recent Work section added with Occupy Wallets as the first case study
- Bumped next to ^15.5.15 to patch 2 high and 3 moderate advisories
- Added pnpm override for yaml ^2.8.3 to resolve the transitive next-mdx-remote
stack-overflow advisory. Production audit now reports no known vulnerabilities.
- Add gray-matter + next-mdx-remote for MDX rendering
- Create content/blog/ with .mdx files and frontmatter
- Add dynamic [slug] route and blog utility (src/lib/blog.ts)
- Blog index auto-populates from MDX files, sorted newest first
- Upgrade React to 19 for next-mdx-remote v6 compatibility
- Convert existing posts: website-relaunch, smirk-wallet, bauhaus-echo, crypto-checkout
- Add Playwright tests with axe-core (42 tests across 3 browsers)
- Add prefers-reduced-motion support to NebulaField
- Add JSON-LD structured data (Organization schema)
- Add canonical URLs to all pages
- Consolidate TODO files, update README