Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ updates:
patterns: ["@types/*"]
eslint:
patterns: ["eslint", "@eslint/*"]
# eslint-config-next 16's bundled React/import/a11y plugins still declare
# ESLint 9 peer ranges and fail at runtime on ESLint 10. Remove this when
# the follow-up compatibility issue is closed.
ignore:
- dependency-name: eslint
update-types: ["version-update:semver-major"]

- package-ecosystem: github-actions
directory: /
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ apps/headless/build/
apps/headless/.build/
apps/web/.next/
apps/web/build/
apps/web/next-env.d.ts
*.tsbuildinfo

# Local QA evidence is generated on demand; reviewed evidence lives in docs/qa.
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ Cutting that release is tracked in
- QA evidence now includes a recorded progressive-pruning scenario, and the
committed bundle is checksum-verified in CI.
- Repository moved to the `LockInTime` organisation; site links updated.
- The website now runs on Next.js 16 with its native flat ESLint configuration;
the legacy `FlatCompat` and `@eslint/eslintrc` path has been removed.

### Known gaps

Expand Down
9 changes: 9 additions & 0 deletions apps/web/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!-- BEGIN:nextjs-agent-rules -->

# This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.

This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.

<!-- END:nextjs-agent-rules -->
1 change: 1 addition & 0 deletions apps/web/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
15 changes: 5 additions & 10 deletions apps/web/components/scan-frame.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import Image from "next/image";
import { useEffect, useMemo, useState } from "react";
import { useEffect, useState } from "react";
import github from "@/public/scan-github.png";

/** Viewport for the capture below — must match scan-github.png pixel size. */
Expand Down Expand Up @@ -63,23 +63,18 @@ function toPercent(box: PixelBox) {
};
}

const resolvedSteps = steps.map((step) => ({ ...step, box: toPercent(step.bounds) }));

/** Hero demo: viewport screenshot + inspect bounds from the same Headless session. */
export function ScanFrame() {
const [active, setActive] = useState(0);
const [ready, setReady] = useState(false);

const resolvedSteps = useMemo(
() => steps.map((step) => ({ ...step, box: toPercent(step.bounds) })),
[],
);

useEffect(() => {
setReady(true);
const timer = window.setInterval(() => {
setActive((index) => (index + 1) % resolvedSteps.length);
}, STEP_MS);
return () => window.clearInterval(timer);
}, [resolvedSteps.length]);
}, []);

const step = resolvedSteps[active];

Expand All @@ -100,7 +95,7 @@ export function ScanFrame() {
</span>
</div>

<div className={`scan-canvas${ready ? " scan-canvas-ready" : ""}`}>
<div className="scan-canvas scan-canvas-ready">
<Image
src={github}
alt=""
Expand Down
22 changes: 15 additions & 7 deletions apps/web/components/theme-toggle.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
"use client";

import { useEffect, useState } from "react";
import { useSyncExternalStore } from "react";

type Theme = "light" | "dark";

function readTheme(): Theme {
return document.documentElement.getAttribute("data-theme") === "light" ? "light" : "dark";
}

function readServerTheme(): Theme {
return "dark";
}

function subscribeToTheme(onStoreChange: () => void) {
const observer = new MutationObserver(onStoreChange);
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["data-theme"],
});
return () => observer.disconnect();
}

/** Light/dark switcher. Reflects and writes `data-theme` on <html>, set first by the no-flash inline script in the root layout. */
export function ThemeToggle({ className }: { className?: string }) {
const [theme, setTheme] = useState<Theme>("dark");

useEffect(() => {
setTheme(readTheme());
}, []);
const theme = useSyncExternalStore(subscribeToTheme, readTheme, readServerTheme);

function toggle() {
const next: Theme = theme === "light" ? "dark" : "light";
document.documentElement.setAttribute("data-theme", next);
window.localStorage.setItem("theme", next);
setTheme(next);
}

return (
Expand Down
19 changes: 8 additions & 11 deletions apps/web/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { FlatCompat } from "@eslint/eslintrc";
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { defineConfig, globalIgnores } from "eslint/config";
import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
import nextTypeScript from "eslint-config-next/typescript";

const compat = new FlatCompat({
baseDirectory: dirname(fileURLToPath(import.meta.url)),
});

const config = [
...compat.config({ extends: ["next/core-web-vitals", "next/typescript"] }),
{ ignores: [".next/**", "next-env.d.ts"] },
];
const config = defineConfig([
...nextCoreWebVitals,
...nextTypeScript,
globalIgnores([".next/**", "next-env.d.ts"]),
]);

export default config;
6 changes: 0 additions & 6 deletions apps/web/next-env.d.ts

This file was deleted.

5 changes: 2 additions & 3 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^1.25.0",
"next": "^15.4.1",
"next": "^16.3.0",
"ogl": "^1.0.11",
"postcss": "^8.5.10",
"postprocessing": "^6.39.3",
Expand All @@ -27,13 +27,12 @@
"three": "^0.185.1"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
"@tailwindcss/postcss": "^4.1.11",
"@types/node": "^24.0.14",
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4",
"eslint": "^9.36.0",
"eslint-config-next": "^15.5.20",
"eslint-config-next": "^16.3.0",
"tailwindcss": "^4.1.11",
"typescript": "^5.8.3"
}
Expand Down
30 changes: 24 additions & 6 deletions apps/web/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": false,
"skipLibCheck": true,
"strict": true,
Expand All @@ -11,13 +15,27 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"jsx": "react-jsx",
"incremental": true,
"plugins": [{ "name": "next" }],
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
"@/*": [
"./*"
]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}
Loading
Loading