A buddy build of a Stacks app prints:
[STX] DOM API violation in default.stx:
✗ window.localStorage is prohibited (line: 8)
→ Use useLocalStorage() from composablesdefault.stx does not contain localStorage. Neither does any other default.stx in
the project — I checked all nine, including the framework defaults:
resources/layouts/default.stx 0 matches
storage/framework/defaults/resources/layouts/default.stx 0
storage/framework/defaults/resources/layouts/{system-tray,dashboard}/default.stx 0
… (9 files, all 0)The code is in resources/views/login.stx:14, and resources/layouts/default.stx is
just the layout that view extends (login.stx:1 → @extends('layouts/default')).
Cause
script-validation.ts:215 reports whatever it is handed:
const fileName = filePath.split('/').pop() || filePathand process.ts:1781 hands it the file being processed at that point:
for (const { match, attrs, content } of clientScriptMatches) {
validateClientScript(content, filePath, options.strict)When a view's <script> lives inside @section('content') and the view is composed into
a layout, that filePath is the layout's. So the diagnostic names a file that does not
contain the offending code, and the reported line number indexes neither file — line: 8
against login.stx:14, and default.stx is 5 lines long in total.
For a project driving a standards cleanup off these warnings, this costs real time: I
went looking through every default.stx on disk before finding the actual source.
Suggested fix
Thread the authoring file (and its line offset) through to validateClientScript rather
than the file currently being processed, so the message points at login.stx:14. If the
origin isn't available at that call site, saying in <layout> (script from a composed
view) would still beat naming a file that provably doesn't contain the match.
Second question, possibly by design
The block it flagged is a plain <script>, not <script client> — a deliberate
pre-paint theme bootstrap:
<script>
// Login uses a different layout, so it carries its own theme bootstrap.
// Stays a plain pre-paint script on purpose: it must run before first paint,
// and a hydrated directive would flash the wrong theme. See
// stacksjs/stx#1788 for why useColorMode cannot express this yet.
;(() => {
try {
const s = localStorage.getItem('theme') || 'system'Pre-paint is the one place useLocalStorage() cannot be the answer — the runtime does
not exist yet, which is the whole reason the block is written this way. Suggesting it
there is advice that cannot be taken.
If bare <script> is meant to be server/pre-paint (as script-classifier.js treats a
signal-free bare block), should the DOM validator run on it at all? If it should, the
suggestion text could acknowledge the pre-paint case rather than pointing at a composable
that isn't loaded yet.