Set up SvelteKit project with Tailwind and design tokens #1
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Task 1 — Plan reference:
docs/superpowers/plans/2026-05-05-erbstuecke-wannsee.mdUser story: As a developer, I need a working SvelteKit project scaffold with the correct dependencies and design system configured so that all subsequent tasks have a consistent foundation.
Acceptance criteria
npx sv create erbstuecke-wannseescaffolds a SvelteKit 2 + Svelte 5 + TypeScript project@sveltejs/adapter-nodeis configured insvelte.config.jscanvas,surface,line,primary,primary-dark,accent,ink,ink-muted,status-free,status-taken,admin-bg)@fontsourcepackages (no CDN dependency)src/app.d.tsdeclaresApp.LocalswithfamilyCodeandadminfieldssrc/lib/types.tsexportsKATEGORIEN(8 fixed categories) and shared row typesnpm run devstarts without TypeScript errorsFiles to create
svelte.config.js,tailwind.config.js,postcss.config.js,vite.config.tssrc/app.html,src/app.css,src/app.d.tssrc/lib/types.tsDesign tokens
canvas#EAE5DCsurface#FFFFFFline#E0D8CCprimary#5B7A66primary-dark#4A6855accent#C4874Aink#1C2820ink-muted#6B6050status-free#4A7C5Cstatus-taken#9B6060admin-bg#2A3B30Dependencies to install
Size: S | Spec: system-design §1 (Stack)
👤 Markus Keller — Application Architect
Observations
App.Localsshape in the plan (familyCode: { id, code, displayName } | null) matches the hooks design exactly. Good.KATEGORIENis defined as aconsttuple withas const— this correctly enforces the 8-category closed set at the type level. No runtime branching needed.src/lib/types.tsand all DB logic insrc/lib/db.ts. This matches the single-responsibility-per-module rule.tailwind.config.jstoken structure nestsprimaryandinkas objects withDEFAULT— this means Tailwind classes liketext-primary,text-primary-dark,text-ink,text-ink-mutedall resolve correctly. Correct approach.app.cssuses@layer basewith@apply font-sans text-ink bg-canvasonbody— this is the right place to set document-level defaults. Consistent with not hardcoding hex values in components.Recommendations
App.Localsinterface usesdisplayName(camelCase) but the DB schema storesdisplay_name(snake_case). Align on one convention at the boundary: map to camelCase inhooks.server.tswhen populatinglocals, never expose snake_case into Svelte templates.app.htmldoes not include a<meta name="robots" content="noindex">tag. For a private family app, add this to prevent accidental indexing if TLS is ever misconfigured and the URL leaks.npm run buildcompleting without errors — onlynpm run dev. Add a build-time check as a final criterion, becauseadapter-nodetype mismatches surface only at build time.@sveltejs/adapter-nodeas a runtime dependency (npm install). It should be a dev dependency (npm install --save-dev) since it is only used during build; the build output does not import it at runtime.Open Decisions (omit if none)
canvascolor discrepancy — The issue's design token table listscanvasas#EAE5DC, but theui_expert.mdbrand system listscanvasas#F2EDE4. One of these is wrong. The plan and implementation must use the same hex value. Decide which is authoritative before Step 4 is executed.👤 Felix Brandt — Fullstack Developer
Observations
src/lib/types.tsexportsKATEGORIENas aconsttuple —as constensurestypeof KATEGORIEN[number]is a proper union type.ArtikelRow,ArtikelFotoRow, andReservierungRoware plain object types with no inheritance: correct for a flat SQLite result shape.app.d.tsdeclaresfamilyCodeandadminas nullable (| null) — this forces all downstream load functions and form actions to guard againstnullexplicitly. Good defensive typing.fontFamilyoverride replaces the defaultserifandsansstacks — this meansfont-serifandfont-sansclasses will use Lora and Inter throughout, with Georgia and system-ui as fallbacks. Correct.app.cssimports specific weight files from@fontsource(/400.css,/600.css, etc.) — this is correct; importing the bare package would load all weights and bloat the bundle.Recommendations
@fontsource/interimport list includes weight800. Check whether Inter 800 (ExtraBold) is actually specified anywhere in the design system. If it is only used in one place, preferfont-extraboldwith the700weight loaded — unnecessary font weights add ~30–40 KB each to the CSS bundle.types.ts) does not include aCodeRowtype. Thehooks.server.ts(Task 3 in the full plan) will need{ id: number; code: string; display_name: string }from the DB query result. Define it intypes.tsnow to avoid a futureas anycast in the hooks layer.postcss.config.jsuses object shorthand ({ tailwindcss: {}, autoprefixer: {} }) — verify the installed Tailwind version is 3.x. Tailwind 4 changed the PostCSS integration to a single@tailwindcss/postcssplugin. The issue specifies Tailwind 3, so this config is correct, but pintailwindcssto^3inpackage.jsonto prevent accidental upgrades.svelte-check, not the Vite dev server. Addnpm run check(which runssvelte-check) as an explicit verification step.Open Decisions (omit if none)
@sveltejs/adapter-nodeas runtime vs dev dependency — The plan installs it as a runtime dep. It is only needed at build time. If it ends up in the productionnode_modules(because it was not--save-dev), the Docker image runner stage (npm ci --omit=dev) will correctly exclude it only if it is listed underdevDependencies. Clarify intent before the scaffold step to avoid a bloated production image.👤 Nora "NullX" Steiner — Application Security Engineer
Observations
app.d.tsshape is security-relevant: declaringfamilyCodeandadminas| nullmeans the type system will force explicit null-checks at every auth boundary downstream. This is the correct foundation.App.Localsinterface names the fieldfamilyCode(notfamilyCodeId) — the full DB row is stored in locals. This means downstream code can readlocals.familyCode.idwithout a second DB lookup. Correct and consistent with the system design.SESSION_SECRETreference appears in this task. That is appropriate — it will be needed in Task 3 (hooks) and Task 5 (admin auth), not the scaffold.Recommendations
src/app.htmldoes not set aContent-Security-Policymeta tag. For this project, Caddy will deliver the CSP header — but document this expectation explicitly in a comment inapp.htmlor in the Caddyfile task (Task 8). Without documentation, a future developer may wonder why there is no CSP in the HTML.tailwind.config.jscontentglob includes./src/**/*.{html,js,svelte,ts}— this is correct and does not accidentally include test fixture files or spec HTML fromdocs/. Confirmdocs/is outside thesrc/tree (it is, per the file map), so no design system HTML tokens will leak into the Tailwind purge output..envis listed in.gitignorebefore the first commit. The plan's Step 11 runsgit add -A && git commit. If.gitignoredoes not exclude.env, a developer who creates.envlocally before committing will accidentally commit it. Add a.gitignorestep to the scaffold task.app.htmlusesdata-sveltekit-preload-data="hover"— this is safe for a private app, but note that preloading on hover will fire load functions before the user clicks. For the gate screen (which validates the?code=param), this is fine. No security concern at this stage.Open Decisions (omit if none)
.gitignorecontents not specified in this task — Thenpx sv createscaffold will generate a default.gitignore. Confirm it includes.env,node_modules/, and.svelte-kit/. If usingsv createwith the official template, this is likely covered — but it should be an explicit acceptance criterion, not an assumption.👤 Sara Holt — QA Engineer & Test Strategist
Observations
npx sv create— this meansvitest.config.ts(or the Vite config integration) will be generated by the scaffold, not hand-written. This is correct for the scaffold task; Felix's TDD tests in Task 2 build on this foundation.KATEGORIENarray is a fixed-at-compile-time list. A unit test that assertsKATEGORIEN.length === 8and that each member is a non-empty string is inexpensive and catches future accidental edits. This is worth adding to the Task 2 test suite.Recommendations
npm run devloads the server, (2)npx svelte-check --tsconfig ./tsconfig.jsonexits 0. The dev server swallows some type errors silently;svelte-checkcatches them all.vitest.config.tsverification step: runnpx vitest runimmediately after scaffold to confirm the baseline test suite (any tests thesv createtemplate generates) passes before any code is added. This establishes a known-green baseline.App.Localsinterface inapp.d.tsshould have an integration test in Task 3 (hooks) that assertslocals.familyCodeisnullwhen no cookie is present and has the correct shape when a valid cookie is present. Flag this now so it is not forgotten when implementing the hooks layer.KATEGORIENconst is declared intypes.tsbut has no corresponding test file listed in this task's scope. Confirm thatsrc/lib/types.test.tsis planned for Task 2 or that type-level coverage is considered sufficient for aconstexport.Open Decisions (omit if none)
sv createmay generate Vitest config inline invite.config.tsrather than a standalonevitest.config.ts. The plan's file map listsvite.config.tsbut notvitest.config.ts. Decide which approach is used so downstream tasks reference the correct config file when adding test scripts.👤 Leonie Voss — UI/UX Design Lead
Observations
canvas,surface,line,primary,primary-dark,accent,ink,ink-muted,status-free,status-taken,admin-bgare all present. No hex values are hardcoded in component files at this stage.@fontsource(no CDN dependency) — correct per the spec. ThefontFamilyconfig overridesserifandsansstacks with correct fallbacks (Georgia, system-ui).app.csssetsbody { @apply font-sans text-ink bg-canvas; }— this means every page inherits Inter, the dark ink text color, and the warm canvas background by default. Components that need Lora usefont-serif. This is the right layering.app.htmlsetslang="de"— correct for a German-only app. Screen readers use this to select the correct pronunciation and hyphenation rules.Recommendations
tailwind.config.jsdoes not configurefontFamilyweights explicitly — Tailwind's font utilities do not control which weights are loaded, only the font-family stack. The@fontsourceimports inapp.cssare the actual weight gate. Verify that the weights imported (400, 600, 700for Lora;400, 500, 600, 700, 800for Inter) cover every usage in the design spec before later tasks start usingfont-bold(700) orfont-semibold(600) on Inter elements.canvastoken in the issue is#EAE5DC, but theui_expert.mdbrand system lists#F2EDE4. This will cause visible color inconsistency between the spec HTML mockups and the implemented app. Resolve before any UI task begins — the design system HTML files are the visual ground truth.app.htmlviewport meta usesinitial-scale=1withoutuser-scalable=no. Do not adduser-scalable=no— WCAG 1.4.4 (Resize Text) requires that users can zoom. The currentinitial-scale=1without zoom lock is correct.app.cssimports@fontsource/inter/800.css(ExtraBold). Verify a concrete usage in the design spec that requires weight 800. If only weights up to 700 are used, remove the 800 import to save ~35 KB of CSS.Open Decisions (omit if none)
canvascolor authoritative value —#EAE5DC(issue + plan) vs#F2EDE4(ui_expert.md brand table + design system HTML). Both are warm off-whites but visually distinguishable side-by-side. This must be resolved before any CSS is written. (Raised by: Leonie Voss, also flagged by Markus Keller)👤 Tobias Wendt (@tobiwendt) — DevOps & Platform Engineer
Observations
@sveltejs/adapter-nodeis listed as a runtime dependency in the plan (npm install @sveltejs/adapter-node). The adapter is used only duringnpm run build— it should be--save-dev. The multi-stage Dockerfile runner stage runsnpm ci --omit=dev, so a miscategorized adapter will bloat the production image with a build-only package.better-sqlite3as a runtime dependency — correct. The native module is needed at runtime by the running Node process.sharpis correctly a runtime dependency — it does image processing at request time inside the running container, not only at build time.Recommendations
@sveltejs/adapter-nodetodevDependencies. In the plan's Step 2, change:npm install @sveltejs/adapter-node→npm install --save-dev @sveltejs/adapter-node. This keeps the production image lean and matches how every SvelteKit project handles adapters..env.examplefile to the scaffold task output. Even though no environment variables are consumed yet, establishing the file at Step 1 sets the convention and ensures it is committed before anyone creates a.env. Minimal initial content:git add -A) will stage everything in the scaffolded directory. Verifynpx sv creategenerates a.gitignorethat excludes.env,.svelte-kit/, andnode_modules/before running the commit step.node:22-alpine— consistent with the LTS version used during development. Note it here so the scaffold'spackage.jsonenginesfield can be set to"node": ">=22"from the start.Open Decisions (omit if none)
@sveltejs/adapter-nodedependency category — Runtime (dependencies) vs build-time (devDependencies). Installing as runtime is technically harmless for local dev but produces a larger Docker image. RecommenddevDependencies; confirm before Step 2 executes. (Raised by: Tobias Wendt, also flagged by Felix Brandt)👤 Elicit — Requirements Engineer
Observations
docs/superpowers/plans/2026-05-05-erbstuecke-wannsee.md) and the spec section (system-design §1) — good traceability.Recommendations
npm run dev starts without TypeScript errorsis partially testable but imprecise. TypeScript errors in.sveltefiles requiresvelte-checkto surface, not just the Vite dev server. Rewrite as: "npm run devstarts andnpm run checkexits 0 with no errors." This is fully testable and unambiguous.src/lib/types.ts exports KATEGORIEN (8 fixed categories) and shared row typesdoes not specify which row types are required. The plan listsArtikelRow,ArtikelFotoRow,ReservierungRow— addCodeRowto the list (it is needed by hooks in Task 3 but absent from the issue's acceptance criteria).featureorchore) and area label (infrastructure). The milestone (v1.0 — MVP) is correctly set.adapter-nodeis active insvelte.config.js— it just says "is configured." Add a testable condition: "npm run buildproduces abuild/directory containingindex.js" — this proves the adapter is wired correctly, not just imported.Open Decisions (omit if none)
CodeRowtype in acceptance criteria — The issue lists shared row types as an acceptance criterion but does not enumerate them. IfCodeRowis omitted fromtypes.tsin Task 1, the hooks implementation in Task 3 will introduce anas anycast or inline the type. Decide whetherCodeRowbelongs in this task's scope or is deferred to Task 3. (Raised by: Elicit)🗳️ Decision Queue — Action Required
5 decisions need your input before implementation starts.
Design Token
canvascolor authoritative value — The issue and implementation plan use#EAE5DC, whileui_expert.mdand the design system persona use#F2EDE4. Both are warm off-whites but visually distinguishable. One source must be declared authoritative; all others updated to match before any CSS is written. (Raised by: Leonie Voss, Markus Keller)Dependency Classification
@sveltejs/adapter-nodeshould bedevDependency— The plan installs it as a runtime dependency. It is only consumed duringnpm run build, not at runtime. The Docker multi-stage runner stage usesnpm ci --omit=dev, so this only matters if the runner stage is used — but correct classification is cheap now and prevents a bloated production image. Move to--save-dev. (Raised by: Tobias Wendt, Felix Brandt, Markus Keller)Acceptance Criteria Gaps
npm run checkvsnpm run devas the TypeScript verification step — The current criterion says "npm run dev starts without TypeScript errors." The dev server does not surface all TypeScript errors in.sveltefiles. Replace withnpm run check(runssvelte-check) as the explicit verification command. (Raised by: Sara Holt, Felix Brandt, Elicit)CodeRowtype scope — Task 1 or Task 3? —CodeRow({ id, code, display_name }) is needed byhooks.server.ts(Task 3). If it is not defined intypes.tsduring this task, the hooks implementation will either inline the type or useas any. Decide: addCodeRowto this task's acceptance criteria, or explicitly defer it to Task 3 with a note. (Raised by: Felix Brandt, Elicit)Infrastructure Convention
.env.exampleand.gitignoreas explicit acceptance criteria — The scaffold commits withgit add -A. If.gitignoredoes not exclude.env(or if.env.exampleis not created first), a developer who creates.envlocally before the first commit risks committing secrets. Thesv createtemplate likely handles this, but it should be a named acceptance criterion, not an assumption. (Raised by: Tobias Wendt, Nora Steiner)Design Token: We will use the token from the spec
Dependency Classification: move to --save-dev
CodeRow will be done here