9.0 KiB
AGENTS.md
Guidance for AI coding agents working in this repository.
Project
Next.js frontend for a Paradox save file parser. It uploads a game save file to a backend service, polls until parsing finishes, and displays the result in a grid.
- Next.js 16 (App Router, Turbopack) with React 19
- TypeScript 6 and 7 side by side (see "Things that will bite you"), ESLint 9 (flat config), Prettier 3
- ag-grid for the data grid, Bootstrap 5 for styling
Source files live in src/:
app/for routes,components/for shared UIservices/for backend callslib/for helpersconfig/for app configtypes/for .d.ts type declaration files
Architecture
The parsing backend is a separate service in its own repository, not part
of this project. Start it manually before running the frontend; it is expected
at http://127.0.0.1:5226.
The browser never calls that backend directly. Requests go through a Next route handler that forwards them:
SaveParserBackendService -> /api/proxy/saveParserBackend/* -> backend
(client, fetch) (server, route handler)
- src/services/SaveParserBackendService.ts
is the only place that talks to the backend. It exposes
uploadSave,getSaveStatusandgetParsedSave— upload, poll until parsing finishes, then fetch the result. - src/app/api/proxy/saveParserBackend/[[...path]]/route.ts forwards every method to the configured backend URL. It exists only to keep requests same-origin, and is meant to be deleted once the backend handles CORS itself.
- Route paths live in src/app/api/proxy/paths.ts rather than as string literals, so both sides of the proxy stay in sync.
Configuration comes from src/config/AppConfig.ts
(defaults) merged with runtimeConfig.json at the repo root, which holds the
backend URL. That file is gitignored, so a fresh clone starts without one and
falls back to the defaults.
Current status
Much of the app is still scaffold. Do not assume a feature works because the UI for it exists:
- The grid is not wired up. src/app/browse/page.tsx renders two hardcoded rows and never calls the backend. Nothing connects the upload flow to it yet.
- Auth does not work.
AuthServiceposts to/api/auth/loginand/api/auth/register, which do not exist as routes. The service is slated for removal in favour of Discord OAuth. hashPasswordis a no-op that returns the password unchanged. Treat any current auth code as a placeholder, not as something to build on.
Terminal
On Windows, prefer bash.exe — but only a real MSYS2, Cygwin, or
MinGW/Git Bash installation, for example D:\msys2\usr\bin\bash.exe.
Never use C:\Windows\System32\bash.exe. Despite its name it is not a
POSIX bash; it is the launcher stub for WSL. Do not start WSL to run ordinary
commands.
If no MSYS/Cygwin/MinGW bash is installed, use PowerShell as the terminal. PowerShell is the fallback, never WSL.
If bash reports command not found for node or npm, that is a PATH
problem rather than a missing install: MSYS2 defaults MSYS2_PATH_TYPE to
minimal, which discards the Windows PATH. Setting it to inherit fixes it.
Commands
npm run dev # dev server
npm run build # production build
npm run lint # eslint
npm run format # prettier, rewrites everything except .gitignore/.prettierignore
npx tsc --noEmit # typecheck
Checking your work
Always run npm run lint and npm run build after changing code. They are
the primary way to find errors in this project, and they catch different
things:
npm run lintreports code errors and style violations. It must finish with zero problems — no errors and no warnings.npm run buildcatches type errors and build-time problems that lint cannot see, such as Node APIs leaking into the edge runtime. It must exit cleanly and print no warnings.
npx tsc --noEmit is a faster way to check types alone while iterating, but it
does not replace a full build.
Do not report a change as finished until both commands pass.
Version control
This project is managed with git.
Read-only git commands are fine to use without asking.
git status, git log, git diff, git show, git blame and similar inspection commands
may be run freely.
Never run a git command that changes the repository without the user's
explicit permission.
This is a strict ban, and it covers everything that writes to the history, the index,
or the working tree including add, commit, checkout, restore, reset,
stash, branch, merge, rebase, push, rm, etc.
Ask first and wait for a clear yes before running such git command.
Conventions
Prettier settings live in .prettierrc and are not negotiable: 4 spaces, no
semicolons, single quotes, 80 column width. Run npm run format after
editing, or match the surrounding style exactly.
Import from within src/ using the @/ alias, for example
import { getMsgFromError } from '@/lib/errors'.
Write doc comments as JSDoc (/** ... */), which editors show on hover.
Plain // and /// comments do not appear in tooltips.
In .ts files put only prose in the JSDoc and let the signature carry the types;
do not duplicate types in {braces}.
Catch blocks should not annotate the error. Let it be unknown and convert it
with the shared helper:
} catch (err) {
const errMsg = getMsgFromError(err)
...
}
Avoid any; no-explicit-any is enforced. Prefer unknown plus narrowing.
Things that will bite you
Both TypeScript 6 and 7 are installed on purpose, under swapped names.
TypeScript 7 is the native Go compiler and type-checks much faster, but
typescript-eslint refuses to run under it, which would break npm run lint
entirely. So each tool gets the version it can use:
| package.json entry | resolves to | used by |
|---|---|---|
typescript |
@typescript/typescript6 (TS 6 API, bin tsc6) |
typescript-eslint |
@typescript/native |
typescript@7 (Go compiler) |
next build |
The alias is deliberately reversed: typescript-eslint looks the compiler up by
the package name typescript, so that name must stay on TS 6. Do not "fix"
this by renaming the entries.
next.config.ts sets experimental.useTypeScriptCli, which makes Next spawn a
tsc binary instead of using the compiler API. Next resolves that binary from
the package named typescript too, so it would also get TS 6. The prebuild
script scripts/replace-tsc6-with-tsc7.mjs
works around this by rewriting node_modules/typescript/bin/tsc6 into a
one-line stub that imports TS 7's lib/tsc.js.
Consequences to keep in mind:
- The patch lives in
node_modules, sonpm installundoes it.prebuildreapplies it, sonpm run buildis always correct; nothing else needs doing. npx tsc --noEmitis unaffected either way.node_modules/.bin/tsclinks straight to@typescript/native, so the standalone typecheck is always TS 7.- Do not drop the
prebuildscript while leavinguseTypeScriptClienabled. Nothing breaks visibly — the build just silently goes back to type-checking with TS 6.
All of this is temporary. The only reason TS 6 is still here is that
TypeScript 7.0 ships no stable programmatic JS API, so typescript-eslint cannot
run on it. That API arrives in TypeScript 7.1, planned for 2026-11-24. Once
it is out and typescript-eslint supports it, tear the whole arrangement down:
drop @typescript/native and the @typescript/typescript6 alias for a plain
"typescript": "^7.1", delete scripts/replace-tsc6-with-tsc7.mjs and the
prebuild script, and re-check whether useTypeScriptCli is still worth
keeping.
Env vars in npm scripts must go through cross-env. The dev and build
scripts set NEXT_TELEMETRY_DISABLED=1, which stops Next from collecting
anonymous usage data and saves the ~0.5s the telemetry flush adds to every
build. The FOO=1 cmd prefix is POSIX shell syntax, and on Windows npm runs
scripts through cmd, where it is a syntax error — so the prefix is written as
cross-env NEXT_TELEMETRY_DISABLED=1 next ... instead. Keep cross-env on any
new script that needs an env var.
Setting the variable from prebuild would not work: prebuild and build are
sibling processes, so an env var exported by one is not visible to the other.
Each script that needs it sets it itself. prebuild has no cross-env prefix
because it only rewrites the tsc bin and never talks to telemetry.
instrumentation.ts is compiled for the edge runtime too and it can't be disabled.
Anything using fs, path, or other Node APIs must be imported behind the
process.env.NEXT_RUNTIME === 'nodejs' guard, or the build warns and pulls
Node-only code into the edge bundle.