From 1aa2b439323dc5e339c3bcdc58e92c441eeaef09 Mon Sep 17 00:00:00 2001 From: Lukasz Raczylo Date: Fri, 21 Aug 2026 09:54:48 +0100 Subject: [PATCH] fix(webui): defuse scaffold config traps, tighten inline guards Micro-fix from re-review: 1. webui/components.json: deleted the font/iconLibrary keys instead of picking a value. Re-review checked shadcn-vue's actual parse schema (node_modules/shadcn-vue/dist/schema/index.js's rawConfigSchema): both fields are z.string().optional(), not an enum - the five-choice enum lives only on the init CLI's --font/--icon-library flags, not on the components.json schema. The previous commit's "schema forbids a fix" rationale was wrong; corrected here. Verified empirically, not just by reading the schema: ran a real `npx shadcn-vue add badge -o -y` against components.json with both keys removed - src/assets/ main.css came back byte-for-byte unchanged (previously this exact class of call re-added a Google Fonts import and the .dark-class toggle). That same run did still re-add @lucide/vue to package.json's dependencies (some components' registry entries list an icon package regardless of iconLibrary) - reverted before committing, and webui/README.md's warning section rewritten to explain the omission (so nobody "helpfully" restores the keys), fix the schema claim, and note the narrower remaining package.json trap. 2. admin.go:27-28: adminCSP's doc comment now names all three set-sites (GET /admin, every GET /admin/assets/*, the three JSON routes) - previously only mentioned the page and JSON routes, dropped after the asset route gained the same header in the prior fix round. 3. Both inline-script guards (webui/generate.mjs's assertNoInlineAssets, admin_test.go's hasInlineScript) tightened from a bare \bsrc\s*= pattern to (^|\s)src\s*= - \b alone matches inside "data-src=" too (the "-" before "src" is a non-word character, so \b sits right before "src"), which would wrongly treat a data-src/nosrc attribute as the real src= that makes a `) + if len(got) != 1 { + t.Fatalf("hasInlineScript = %v, want exactly 1 offender (data-src is not src=)", got) + } +} + func truncateForTest(b []byte, n int) string { if len(b) <= n { return string(b) diff --git a/webui/README.md b/webui/README.md index 72da937..bdeaf3b 100644 --- a/webui/README.md +++ b/webui/README.md @@ -12,33 +12,47 @@ built, baked-in output, never `webui/` itself. ## Warning: `shadcn-vue add`/`init` regressions -`components.json`'s `iconLibrary` and `font` fields are locked to -`"lucide"` and `"inter"` — **not this project's real choices** (this -project uses FontAwesome only, per `vue-web-development` house skill, and -no external font at all). The shadcn-vue CLI's schema does not accept -`"none"`/`"fontawesome"`/`"system"` for either field (checked directly: -`node_modules/shadcn-vue/dist/preset-*.js`'s `PRESET_ICON_LIBRARIES` and -`PRESET_FONTS` arrays — five fixed choices each, no escape hatch), and -JSON supports no comment syntax to warn inline (the repo's own -pre-commit JSON validator rejects JSONC comments — see `tsconfig.*.json` -history). This warning is the only place the trap could be recorded. +`components.json` has no `iconLibrary` or `font` key. **Do not add them +back.** Their omission is the fix, not an oversight — read on before you +"helpfully" restore either one. -Both `npx shadcn-vue init` and `npx shadcn-vue add ` read -these fields and **silently rewrite `src/assets/main.css`** — hit twice -in this project's own history: `init` first, then a follow-up `add` -call, both re-added a `@import url('https://fonts.googleapis.com/...')` -line and switched the dark-mode `@custom-variant` back to a `.dark`-class -toggle. If you ever run either command again: +Earlier revisions of this file set `"iconLibrary": "lucide"` and +`"font": "inter"`, both wrong for this project (FontAwesome only, per +the `vue-web-development` house skill; no external font at all). Both +`npx shadcn-vue init` and `npx shadcn-vue add ` read those two +fields and, when set, rewrite `src/assets/main.css` — hit twice in this +project's own history, `init` then a later `add`, both re-adding a +`@import url('https://fonts.googleapis.com/...')` line and switching the +dark-mode `@custom-variant` back to a `.dark`-class toggle. -1. **Diff `src/assets/main.css` before committing.** Remove any - re-added `@import url('https://fonts.googleapis...')` line (external - font, blocked by the admin CSP's `style-src 'self'` anyway) and - restore the `@custom-variant dark` removal (see the file's own header - comment for why: `prefers-color-scheme`, not a class toggle). -2. **Check the new component for a `lucide-vue-next`/`@lucide/vue` - import** and swap it for the FontAwesome equivalent (see - `src/components/ui/select/Select*.vue` for the pattern already - applied there). +The actual shadcn-vue config schema +(`node_modules/shadcn-vue/dist/schema/index.js`'s `rawConfigSchema`) +declares both fields as `z.string().optional()` — not an enum, contrary +to an earlier, incorrect claim in this project's own review notes that +the CLI schema forced one of five fixed values. The CLI's `init --font`/ +`--icon-library` flags do offer five fixed choices each, but that is a +flag-parsing constraint, not a `components.json` schema constraint. +Deleting both keys leaves them `undefined` after parse, a valid state: +verified empirically, running `npx shadcn-vue add badge -o -y` against a +`components.json` with both keys removed left `src/assets/main.css` +byte-for-byte unchanged. + +That verification also surfaced a narrower, remaining trap: the same run +still added `@lucide/vue` back to `package.json`'s dependencies (Badge +itself does not use it — some components' registry entries list an icon +package regardless of `iconLibrary`). So after any future `shadcn-vue +add`/`init` call: + +1. **Check `git diff webui/package.json`** for a re-added + `lucide-vue-next`/`@lucide/vue` dependency, and remove it + (`npm uninstall`) if nothing in `src/` imports from it. +2. **Check the new component's own file** for a + `lucide-vue-next`/`@lucide/vue` import and swap it for the + FontAwesome equivalent (see `src/components/ui/select/Select*.vue` + for the pattern already applied there). +3. **Diff `src/assets/main.css` anyway** before committing, in case a + future shadcn-vue release starts reading a different config key for + the same font-injection step. ## Local development diff --git a/webui/components.json b/webui/components.json index 4282fc1..8203674 100644 --- a/webui/components.json +++ b/webui/components.json @@ -1,7 +1,6 @@ { "$schema": "https://shadcn-vue.com/schema.json", "style": "reka-nova", - "font": "inter", "typescript": true, "tailwind": { "config": "", @@ -10,7 +9,6 @@ "cssVariables": true, "prefix": "" }, - "iconLibrary": "lucide", "rtl": false, "pointer": false, "aliases": { diff --git a/webui/generate.mjs b/webui/generate.mjs index ba3d444..37d9091 100644 --- a/webui/generate.mjs +++ b/webui/generate.mjs @@ -80,7 +80,13 @@ function assertNoInlineAssets(html, sourceLabel) { const scriptTagRe = /]*)>/gi let match while ((match = scriptTagRe.exec(html)) !== null) { - if (!/\bsrc\s*=/i.test(match[1])) { + // Anchored to attribute-start (start-of-string or preceding + // whitespace), not a bare \b word boundary: \b alone matches inside + // "data-src=" too (the "-" before "src" is itself a non-word + // character, so \b sits right before "src"), which would wrongly + // treat a data-src/nosrc/whatever-src attribute as the real src= that + // makes a