Files
2026-08-05 20:27:01 +01:00

11 KiB
Raw Permalink Blame History

AGENTS.md

What this is

SvelteKit 5 (runes mode) static site that assembles Steam launch commands from composable "program classes" (gamescope, MangoHud, Proton, obs-gamecapture, gamemode). User flips toggles in a tabbed UI; the app renders one flat launch command string like:

gamescope -f -- %command% mangohud waitforexecandrun %command%

Rendered by adapter-static (prerender = true in src/routes/+page.ts).

Core model (src/lib/)

  • meow.ts — the engine. ArgEvaluatable interface (enabled(), envs(), binary(), prefix(), suffix(), priority(), arguments()); ArgEval evaluates components: filter enabled(), sort by priority() (lowest = outermost wrapper), envs as VAR=VAL space-joined, each program rendered prefix binary args suffix (empty parts dropped), args sorted by prio and rendered arg val (bare arg when val empty), then %command% appended.
  • programs.ts — data types + Program class implementing ArgEvaluatable from a ProgramConfig. Key fields: enabled, name, binary, prefix, suffix, priority, envs, args, options (toggleable sub-flags), envSeparators (per-var merge separators), option conflicts (see below).
  • presets.ts — re-export index only; real factories live per-program in src/lib/preset/ (see file map).
  • preset/helpers.ts — builders shared by presets: envToggle(label, env, val, category?), group(program, labels) (pairwise conflict keys). (Mutually-exclusive flag choices like -F filter use ONE option with an args[].values dropdown — do NOT reintroduce one-option-per-value.)

Semantics to keep in mind

  • Env vars with the same name merge into ONE var: values dedupe, joined by the var's separator from envSeparators (default ,).
  • conflicts keys are 'programname' (program-level) or 'program:Option label' (option-level). Only enabled vs enabled conflict; conflicts are displayed as a non-blocking warning banner.
  • Env merge destructuring: var is a reserved word in TS — use [varName, vals] in destructures.
  • Args are space-separated (-W 1920), never arg=val.

File map

file role
src/lib/meow.ts evaluation engine (see above)
src/lib/programs.ts types + Program adapter + findConflicts()
src/lib/presets.ts re-exports the eight preset factories
src/lib/preset/helpers.ts shared option builders
src/lib/preset/gamescope.ts gamescope args (fullscreen, filter/scale dropdowns, HDR, VR, …)
src/lib/preset/proton-ge.ts GloriousEggroll Proton env toggles
src/lib/preset/proton-cachyos.ts CachyOS Proton env toggles (largest set)
src/lib/preset/obs.ts obs-gamecapture CLI
src/lib/preset/gamemode.ts gamemoderun wrapper (priority 5 = outermost)
src/lib/preset/mangohud.ts MANGOHUD env options + MANGOHUD_CONFIG separator merge
src/lib/preset/mesa.ts Mesa Vulkan layers env toggles (anti-lag, overlay layer, fps-monitor)
src/lib/preset/prime.ts prime-run wrapper for NVIDIA PRIME offload
src/lib/ProgramForm.svelte per-program editor: enabled checkbox, options list, Advanced <details> (name/binary/prefix/suffix/priority, env vars, args, env separators)
src/lib/decode.ts applyCommand(cmd, programs) — enable options/programs a pasted command references (only ever enables, never disables)
src/routes/+page.svelte tabs + pre.result command preview + conflict banner; localStorage persistence; decode textarea
launch_args_env_vars.md reference: gamescope args, Proton env-var tables, OBS, gamemode
steam-tinker-launcher.md reference: STL v14 docs (gamescope CLI catalog §3.13.6, mangohud, proton, dxvk, wine)

Conventions

  • New programs are preset factory functions in src/lib/preset/<name>.ts returning a ProgramConfig, re-exported from presets.ts, imported into +page.svelte's programs array.
  • Every option carries a category: string; ProgramForm groups options under category headings (keep categories consistent within a preset).
  • Every option should also carry a desc: string; it appears as a hover tooltip (1 s delay, CSS-only) next to the option row.
  • Default new presets to enabled: false so the default output stays stable.
  • Preset files ≤ ~140 lines; split if they grow (that's why the directory exists). Shared builders go in helpers.ts.
  • +page.svelte uses PURE TAB indentation — never build edit oldText with spaces.
  • Mutual exclusivity is expressed via conflicts (options) or group() (helpers), never via extra app state.
  • user-select: none on tabs/labels to stop double-click selection; inputs and pre.result stay selectable.

Adding options & programs (agent playbook)

Every task in this repo ends with the verification loop below; a change is not done until all three pass.

Add an option to an existing program

  1. Edit src/lib/preset/<name>.ts — the program's factory, inside options: [...]. Mine the flag/env catalogs from launch_args_env_vars.md and steam-tinker-launcher.md.

  2. Pick the builder (from ./helpers):

    • Env toggle (checkbox sets VAR=1): envToggle('Label', 'ENV_VAR', '1', 'Category') — pass a custom value as the 3rd arg for non-1 values.

    • Flag with a value (gets an editable input in the UI while enabled):

      { label: 'Mouse sensitivity', enabled: false, args: [{ arg: '-s', val: '1.0', prio: 0 }], category: 'Window' }
      
    • Flag with a fixed value set (gets a <select> dropdown instead of a text input): add values: ['auto', 'drm', 'wayland'] to the arg.

    • Flag whose value must match a regex (input turns red + banner error while invalid): add pattern: NUM or pattern: NUM_DEC (exported from ./helpers) or a custom regex source string.

    • Bare flag: same shape with val: '' (checkbox only, no input).

    • Mutually exclusive flag with a fixed value set (e.g. -F fsr vs -F nis): ONE option with values on the arg: { label: 'Filter', enabled: false, args: [{ arg: '-F', val: 'fsr', prio: 0, values: ['none', 'linear', 'fsr', 'nis'] }], category: 'Filter' }. Do NOT create one option per value.

  3. category is required — reuse an existing one from that preset or add a new one (it becomes a tab). Keep categories consistent within a preset.

  4. Mutual exclusivity: conflicts: ['other-program'] or conflicts: ['same-program:Other option label']; group() generates these automatically.

  5. Presets only supply defaults — the form binds arg val to an input while the option is enabled, so users can override values.

Add a new program

  1. Create src/lib/preset/<name>.ts exporting export function <name>(): ProgramConfig (model on src/lib/preset/obs.ts — the smallest real example).
  2. Required config: name, enabled (default false so output stays stable), binary, prefix (wrapper like 'gamemoderun'), suffix ('waitforexecandrun' for proton, '--' for gamescope-style), priority, envs: [], args: [], options: [...].
  3. Re-export: add export { <name> } from './preset/<name>'; to src/lib/presets.ts.
  4. Import it in src/routes/+page.svelte and append to the programs = $state([...]) array.
  5. Keep the file ≤ ~140 lines; use envToggle()/group() for builders and repetitive options.

Priority semantics: components sort ascending by priority() — the outermost wrapper has the LOWEST number (gamemoderun=5, gamescope=10, proton=20, mangohud=30).

Verification (mandatory before yielding)

pnpm exec prettier --write <edited files>   # lint runs prettier --check
pnpm check                                  # svelte-check — authoritative type check
pnpm lint                                   # prettier --check + eslint
pnpm build                                  # adapter-static build

All four must pass. pnpm check is the authority — see the known quirk below.

Known traps for agents:

  • pi-lens/LSP diagnostics on .svelte files are stale all session — they falsely report enabled / category / envSeparators as missing from ProgramConfig on every edit. They are noise: the fields verifiably exist in programs.ts. Do NOT chase them; rely on pnpm check (returns 0 errors every time). The .ts files' diagnostics are accurate.
  • +page.svelte uses pure tabs — edit oldText must use tabs, not spaces. If the edit tool reports content-drift, re-read the file verbatim before retrying.
  • This file (AGENTS.md) is auto-reformatted by prettier/markdownlint after edits — always re-read it before further edits; it also uses real em-dashes ().
  • var is a reserved word in TS destructuring — use [varName, vals].
  • A stale vite dev server can serve corrupted modules (missing exports). Fix: kill all vite processes and restart — never pkill -f with a pattern that also appears in your own command line (it kills your shell).
  • Do not add options the docs mark as per-user paths or STL-internal settings (Vortex/MO2/winetricks/Conty…); README.md is sv boilerplate, ignore it.

Reference docs to mine for new options: launch_args_env_vars.md and steam-tinker-launcher.md (README.md is sv boilerplate, ignore it).