diff --git a/README.md b/README.md index 3a195a435..c0116127b 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,17 @@ pnpm dev This will start your the app at [localhost:3000](http://localhost:3000) or the next available port. +### Generated reference pages + +Two scripts write reference pages from a local checkout of [solidjs/solid](https://github.com/solidjs/solid) (branch `next`); the pages they write carry a comment naming the script, and edits belong upstream, not in the page. + +```sh +pnpm sync:ref --source ../solid --skip-fetch # solid-js, @solidjs/web, @solidjs/signals from the JSDoc +pnpm sync:diagnostics --source ../solid # one page per diagnostic code from the repair skill +``` + +Without `--source`, `sync:ref` makes a sparse checkout under `/tmp/solid-v2-source` that `sync:diagnostics` reads too. + ### Framework and Theme The docs are built on [SolidBase](https://solidbase.dev) and uses the [Osmium Theme](./osmium). diff --git a/package.json b/package.json index 4accbc58a..64554688f 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "check:lint": "eslint --flag unstable_native_nodejs_ts_config .", "check:types": "tsc --noEmit", "sync:ref": "node scripts/extract-solid-ref.mjs", + "sync:diagnostics": "node scripts/extract-diagnostics.mjs", "check:examples": "node scripts/check-examples.mjs", "lint:tone": "node scripts/lint-tone.mjs" }, diff --git a/scripts/build-llms.mjs b/scripts/build-llms.mjs index e7b89542a..36019c4a5 100644 --- a/scripts/build-llms.mjs +++ b/scripts/build-llms.mjs @@ -177,6 +177,7 @@ async function buildSmallCorpus(title) { "", `Index: ${new URL("/llms.txt", SITE_ORIGIN)}`, `Full corpus: ${new URL("/llms-full.txt", SITE_ORIGIN)}`, + `Diagnostic codes: ${new URL("/reference/solid-js/diagnostics.md", SITE_ORIGIN)} (one page per code, linked from each console report)`, "", primerBody, "", diff --git a/scripts/extract-diagnostics.mjs b/scripts/extract-diagnostics.mjs new file mode 100644 index 000000000..be26d4d92 --- /dev/null +++ b/scripts/extract-diagnostics.mjs @@ -0,0 +1,656 @@ +#!/usr/bin/env node +// Generates the diagnostics reference: one page per diagnostic code under +// src/routes/reference/(1)solid-js/(8)diagnostics, plus its index. The pages +// are what the console footer links to (`DEV.guideUrl(code)` is +// `/reference/solid-js/diagnostics/`, slug = the code lowercased with +// `-` for `_`), written for a human reading a console report: what they +// saw, why it fires, the fix, and the test assertion that guards it. +// +// Sources, in the solidjs/solid checkout: +// - packages/solid/diagnostics.json: code, kind, severity, category, and a +// one-line trigger per code (generated upstream from the emit sites). +// - packages/solid/skills/reactivity-diagnostics/SKILL.md: the repair skill, +// one `### CODE` section per code, written for an agent. The section is +// split into "why it fires" and "how to fix it" at the first sentence that +// gives an instruction (see FIX_STARTS for the sections where that split +// needs a hand). +// +// The output is deterministic: the same checkout produces the same files, +// and a regeneration writes nothing new. Pages carry GENERATED_MARKER and +// are removed before each run, so a code dropped upstream drops here too. +// +// Usage: +// node scripts/extract-diagnostics.mjs --source [--dry-run] +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import * as prettier from "prettier"; + +const DEFAULT_SOURCE = "/tmp/solid-v2-source"; +const DEFAULT_OUT = "src/routes/reference/(1)solid-js/(8)diagnostics"; +const SKILL_PATH = "packages/solid/skills/reactivity-diagnostics/SKILL.md"; +const TABLE_PATH = "packages/solid/diagnostics.json"; +const SOURCE_REF = "next"; +const ROUTE_BASE = "/reference/solid-js/diagnostics"; +const SKILL_INSTALLED = + "node_modules/solid-js/skills/reactivity-diagnostics/SKILL.md"; +const SKILL_URL = + "https://github.com/solidjs/solid/blob/next/packages/solid/skills/reactivity-diagnostics/SKILL.md"; + +// Distinct from the extract-solid-ref marker on purpose: the tone lint +// downgrades that marker's pages to warnings because their text is upstream +// JSDoc, while these pages are prose for humans and must pass as written. +// Anything the skill says that the lint rejects is rewritten here +// (TONE_REWRITES), never waved through. +const GENERATED_MARKER = + "Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate."; + +// Phrases the skill uses that WRITING.md's tone lint rejects on a +// hand-written page, with the plain form. Each entry must match at least +// once, so a rewrite that stops matching (the skill changed) is noticed +// rather than silently kept. +const TONE_REWRITES = [ + [/\bwhat actually changes\b/g, "what changes"], + [/\bjust call the function\b/g, "call the function"], +]; + +// Wording that only works inside the skill's running order, replaced for a +// page read on its own. Same rule: every entry must match. +const STANDALONE_REWRITES = [ + [ + /^Same shape as above but/m, + "The same shape as `STRICT_READ_UNTRACKED`, but", + ], +]; + +// A section whose repair does not open with an instruction (the sentence +// starts with "The message names…", "Actions are…"), or whose only sentence +// carries both halves. The value is text copied from the section; the page's +// "How to fix it" starts there. A value the section no longer contains is an +// error, so the map tracks the skill. +const FIX_STARTS = { + ACTION_CALLED_IN_OWNED_SCOPE: "Actions are imperative entry points", + BINDING_SLOT_POSITION: "The rule: a slot property is a JSX attribute value", + CLEANUP_IN_FORBIDDEN_SCOPE: "return a cleanup function from the callback", + EFFECT_WRITES_OWN_SOURCE: + "The written value is a function of what the effect reads", + HEAD_TAG_INVALID: "`non-head-tag`/`invalid-attribute`: only head elements", + HOT_SCOPE_RERUNS: "The message names the latest cause", + LAZY_ASSET_UNMAPPED: "This is bundler-plugin configuration", + PENDING_ASYNC_UNTRACKED_READ: "Async values must be read where tracking", + RUN_WITH_DISPOSED_OWNER: "This usually means a stale owner", + SSR_BOUNDARY_WATERFALL: "the same repairs apply, in the same order", + SSR_RENDER_ERROR_CONTAINED: "In every case the repair is", + SSR_STREAM_ABANDONED: "At volume it is the cost", + UNRECOGNIZED_INSERT_VALUE: "Usually a component function inserted", + WIDE_SCOPE_DEPS: "The message lists up to 12 of them", +}; + +// A sentence that opens with one of these is an instruction: the first such +// sentence in a section starts its "How to fix it". +const INSTRUCTION_START = + /^(?:Add|Attach|Avoid|Bound|Call|Create|Declare|Delete|Derive|Do not|Do NOT|Drop|Either|Find|Fix|Give|Guess|Hoist|If the|If you|Instead|Invert|Keep|Key|Lift|Look for|Make|Move|Mutate|Narrow|Never|Pass|Preload|Profile|Put|Read|Register|Remove|Repair|Repairs|Replace|Report|Return|Same fix|Serve|Set|Show|Split|The fix|The repair|Then|To see|To _report_|Treat|Two repairs|Use|Usually the right fix|Wrap)\b/; + +// What `@solidjs/diagnostics` offers against each code, beyond +// `expectNoDiagnostics`, where the skill names it ("Verifying a fix"). +const BUDGET_NOTES = { + perf: "`expectRerunBudget(artifact, max, { scope })` bounds how often a named scope may re-run, and `expectNoWaste(artifact)` fails on re-runs whose result was unchanged.\nBoth read the attribution records, which `captureArtifact` collects unless `attribution: false` is passed.", + graph: + "`expectRerunBudget(artifact, max, { scope })` bounds how often a named scope may re-run after a write.\nIt reads the attribution records, which `captureArtifact` collects unless `attribution: false` is passed.", +}; +const CODE_BUDGET_NOTES = { + SILENT_HOLD: + "`expectNoSilentHolds(artifact)` fails on every hold the screen never acknowledged, at any length, not only the ones long enough to warn.\nIn a budget file the same check is `maxSilentHoldMs: 0`.", +}; + +const SEVERITY_GLOSS = { + error: "broken behavior", + warn: "code that works but is structurally wrong or expensive", + info: "advisory, delivered on the diagnostics channel and never printed", +}; + +const scriptPath = fileURLToPath(import.meta.url); +const repoRoot = path.resolve(path.dirname(scriptPath), ".."); + +const args = parseArgs(process.argv.slice(2)); +if (args.help) { + printHelp(); + process.exit(0); +} +const sourceDir = path.resolve(args.source ?? DEFAULT_SOURCE); +const outDir = path.resolve(repoRoot, args.out ?? DEFAULT_OUT); +const dryRun = Boolean(args["dry-run"]); + +const skillFile = path.join(sourceDir, SKILL_PATH); +const tableFile = path.join(sourceDir, TABLE_PATH); +for (const file of [skillFile, tableFile]) { + if (!fs.existsSync(file)) { + throw new Error( + `${file} not found. Pass --source (a sparse checkout from scripts/extract-solid-ref.mjs works).` + ); + } +} + +const table = JSON.parse(fs.readFileSync(tableFile, "utf8")); +const codes = table.map((entry) => entry.code).sort(); +const codeSet = new Set(codes); +const skill = parseSkill(fs.readFileSync(skillFile, "utf8")); + +const missing = codes.filter((code) => !skill.sections.has(code)); +if (missing.length) { + throw new Error(`No skill section for: ${missing.join(", ")}`); +} +const unlisted = [...skill.sections.keys()].filter( + (code) => !codeSet.has(code) +); +if (unlisted.length) { + // The skill also covers a code that is thrown as a plain error rather than + // emitted on the channel (FLUSH_IN_ACTION), which the generated table does + // not list; it gets no page, and the table stays the list of pages. + console.warn( + `Skill section without a diagnostics.json entry (no page): ${unlisted.join(", ")}` + ); +} + +const rewriteHits = new Map(); +const problems = []; +const files = [ + { + fullPath: path.join(outDir, "(0)index.mdx"), + content: renderIndex(table), + }, + ...table + .slice() + .sort((a, b) => a.code.localeCompare(b.code)) + .map((entry) => { + try { + return { + fullPath: path.join(outDir, `${slugOf(entry.code)}.mdx`), + content: renderCodePage(entry, skill.sections.get(entry.code)), + }; + } catch (error) { + problems.push(error.message); + return null; + } + }), +]; +if (problems.length) throw new Error(problems.join("\n")); +for (const [pattern] of [...TONE_REWRITES, ...STANDALONE_REWRITES]) { + if (!rewriteHits.has(pattern.source)) { + throw new Error( + `Rewrite /${pattern.source}/ matched nothing; the skill changed, so drop or update it.` + ); + } +} + +if (dryRun) { + for (const file of files) console.log(path.relative(repoRoot, file.fullPath)); + console.log(`Files: ${files.length}`); +} else { + await writeFiles(files); + console.log( + `Wrote ${files.length} diagnostics page(s) to ${path.relative(repoRoot, outDir)}` + ); +} + +function parseArgs(rawArgs) { + const parsed = {}; + for (let i = 0; i < rawArgs.length; i++) { + const arg = rawArgs[i]; + if (!arg.startsWith("--")) throw new Error(`Unexpected argument: ${arg}`); + const key = arg.slice(2); + if (["dry-run", "help"].includes(key)) { + parsed[key] = true; + continue; + } + const next = rawArgs[++i]; + if (!next || next.startsWith("--")) { + throw new Error(`Missing value for --${key}`); + } + parsed[key] = next; + } + return parsed; +} + +function printHelp() { + console.log(`Usage: +node scripts/extract-diagnostics.mjs [options] + +Options: + --source Local solidjs/solid checkout. Defaults to ${DEFAULT_SOURCE}. + --out Output directory. Defaults to ${DEFAULT_OUT}. + --dry-run Print planned files without writing MDX. + --help Show this help. +`); +} + +// --- Skill parsing --------------------------------------------------------- + +// Splits SKILL.md into one section per code: the lines under `### CODE` (or +// `#### CODE` inside a combined `### A / B` heading) up to the next heading +// of the same or a higher level, plus the group it sits in (the enclosing +// `##` heading and the paragraphs between it and its first `###`). +function parseSkill(markdown) { + const lines = markdown.replace(/^---[\s\S]*?\n---\n/, "").split("\n"); + const sections = new Map(); + let group = null; + let current = null; + let inFence = false; + + for (const line of lines) { + if (/^\s*```/.test(line)) inFence = !inFence; + const heading = inFence ? null : /^(#{2,4})\s+(.+?)\s*$/.exec(line); + if (heading) { + const level = heading[1].length; + const title = heading[2]; + if (current && level <= current.level) current = null; + if (level === 2) { + group = { title, preamble: [], open: true }; + continue; + } + if (group) group.open = false; + if (/^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+$/.test(title)) { + current = { code: title, level, group, lines: [] }; + sections.set(title, current); + } + continue; + } + if (current) current.lines.push(line); + else if (group?.open) group.preamble.push(line); + } + return { sections }; +} + +// Turns section lines into blocks: a paragraph becomes one sentence per +// line (the house style), a list item is joined into one line, a fence is +// kept verbatim. Blocks are separated by a single empty string. +function toBlocks(lines) { + const blocks = []; + let paragraph = []; + let list = []; + let fence = null; + const flush = () => { + if (paragraph.length) { + blocks.push({ + type: "paragraph", + lines: splitSentences(paragraph.join(" ")), + }); + paragraph = []; + } + if (list.length) { + blocks.push({ type: "list", lines: list }); + list = []; + } + }; + for (const raw of lines) { + const line = raw.replace(/\s+$/, ""); + if (fence) { + fence.lines.push(line); + if (/^\s*```/.test(line)) { + blocks.push(fence); + fence = null; + } + continue; + } + if (/^\s*```/.test(line)) { + flush(); + fence = { type: "fence", lines: [line] }; + continue; + } + if (!line.trim()) { + flush(); + continue; + } + if (/^\s*(?:[-*]|\d+\.)\s+/.test(line)) { + if (paragraph.length) flush(); + list.push(line.trim()); + continue; + } + if (list.length) { + // A wrapped list item continues on an indented line. + list[list.length - 1] += ` ${line.trim()}`; + continue; + } + paragraph.push(line.trim()); + } + flush(); + return blocks; +} + +// One sentence per line. Code spans are protected first so a `.` inside +// `draft.user.name` or `attribution.history("hold")` never ends a sentence. +function splitSentences(text) { + const spans = []; + const protectedText = text.replace(/`[^`]*`/g, (span) => { + spans.push(span); + return `\u0000${spans.length - 1}\u0000`; + }); + return protectedText + .split(/(?<=[.!?])\s+(?=[A-Z(\u0000"[_*])/) + .map((sentence) => + sentence.replace(/\u0000(\d+)\u0000/g, (_m, i) => spans[Number(i)]) + ) + .map((sentence) => sentence.trim()) + .filter(Boolean); +} + +function applyRewrites(text, rewrites) { + let result = text; + for (const [pattern, replacement] of rewrites) { + pattern.lastIndex = 0; + if (pattern.test(result)) rewriteHits.set(pattern.source, true); + result = result.replace(pattern, replacement); + } + return result; +} + +// Splits a section's blocks into the "why" and the "fix" at the first +// instruction (see INSTRUCTION_START), or at FIX_STARTS[code] when the +// skill's wording needs that. Without either, the first paragraph is the +// why and everything after it the fix. +function splitSection(code, blocks) { + const marker = FIX_STARTS[code]; + if (marker) { + for (let b = 0; b < blocks.length; b++) { + const block = blocks[b]; + if (block.type === "fence") continue; + for (let l = 0; l < block.lines.length; l++) { + const at = block.lines[l].indexOf(marker); + if (at === -1) continue; + const before = block.lines[l].slice(0, at).replace(/[\s,;:—–-]+$/, ""); + const after = block.lines[l].slice(at); + const head = [...block.lines.slice(0, l)]; + if (before) head.push(/[.!?]$/.test(before) ? before : `${before}.`); + const tail = [ + after[0].toUpperCase() + after.slice(1), + ...block.lines.slice(l + 1), + ]; + return { + why: [ + ...blocks.slice(0, b), + ...(head.length ? [{ ...block, lines: head }] : []), + ], + fix: [{ ...block, lines: tail }, ...blocks.slice(b + 1)], + }; + } + } + throw new Error( + `FIX_STARTS.${code} (${JSON.stringify(marker)}) is not in the section` + ); + } + for (let b = 0; b < blocks.length; b++) { + const block = blocks[b]; + if (block.type !== "paragraph") continue; + const l = block.lines.findIndex((line) => INSTRUCTION_START.test(line)); + if (l === -1) continue; + const head = block.lines.slice(0, l); + return { + why: [ + ...blocks.slice(0, b), + ...(head.length ? [{ ...block, lines: head }] : []), + ], + fix: [{ ...block, lines: block.lines.slice(l) }, ...blocks.slice(b + 1)], + }; + } + if (blocks.length < 2) { + throw new Error( + `${code}: no instruction found and nothing after the first paragraph; add FIX_STARTS.${code}` + ); + } + return { why: blocks.slice(0, 1), fix: blocks.slice(1) }; +} + +function renderBlocks(blocks, ownCode) { + return blocks + .map((block) => + block.type === "fence" + ? block.lines.join("\n") + : block.lines.map((line) => renderProse(line, ownCode)).join("\n") + ) + .join("\n\n"); +} + +// Prose outside fences: the skill's in-file anchors become page links, every +// other code it names (bare or in backticks) links to its page, and the +// characters MDX would read as JSX are escaped outside code spans. +function renderProse(line, ownCode) { + const linked = line.replace( + /\[([A-Z0-9_]+)\]\(#([a-z0-9_]+)\)/g, + (match, label, anchor) => { + const code = anchor.toUpperCase(); + return codeSet.has(code) ? codeLink(code, ownCode) : match; + } + ); + return linked + .split(/(\[[^\]]*\]\([^)]*\)|`[^`]*`)/g) + .map((part) => { + if (part.startsWith("[")) return part; + if (part.startsWith("`")) { + const code = part.slice(1, -1); + return codeSet.has(code) ? codeLink(code, ownCode) : part; + } + return part + .replace(/\b([A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+)\b/g, (match) => + codeSet.has(match) ? codeLink(match, ownCode) : match + ) + .replace(/<([A-Za-z][^>\n]*)>/g, (_m, inner) => `<${inner}>`) + .replace(/[{}]/g, (brace) => `\\${brace}`); + }) + .join(""); +} + +function codeLink(code, ownCode) { + return code === ownCode ? `\`${code}\`` : `[\`${code}\`](${routeOf(code)})`; +} + +function slugOf(code) { + return code.toLowerCase().replace(/_/g, "-"); +} + +function routeOf(code) { + return `${ROUTE_BASE}/${slugOf(code)}`; +} + +function yamlString(value) { + return JSON.stringify(value); +} + +function listOf(values) { + return values.map((value) => `\`${value}\``).join(", "); +} + +// --- Pages ----------------------------------------------------------------- + +function renderCodePage(entry, section) { + const { code } = entry; + const text = applyRewrites( + applyRewrites(section.lines.join("\n"), TONE_REWRITES), + STANDALONE_REWRITES + ); + const blocks = toBlocks(text.split("\n")); + const { why, fix } = splitSection(code, blocks); + // The group's preamble ("These fire only while attribution is enabled…") + // applies to every code under it; it closes "Why it fires" as a note. + const preamble = toBlocks(section.group?.preamble ?? []).filter( + (block) => block.type !== "fence" + ); + const groupNote = preamble.length + ? `\n\n:::note[${section.group.title}]\n${renderBlocks(preamble, code)}\n:::` + : ""; + + const frontmatter = [ + "---", + `title: ${yamlString(code)}`, + `category: "Diagnostics"`, + `use_cases: ${yamlString(`${code.toLowerCase()} diagnostic, console warning repair, diagnostics assertion`)}`, + "tags:", + ...["diagnostics", ...entry.kind, ...entry.severity, "reference", "v2"].map( + (tag) => ` - ${yamlString(tag)}` + ), + 'version: "2.0"', + `description: ${yamlString(`${code}: ${entry.trigger}.`)}`, + `source_repo: "solidjs/solid"`, + `source_ref: ${yamlString(SOURCE_REF)}`, + `source_path: ${yamlString(SKILL_PATH)}`, + "---", + ].join("\n"); + + const severities = entry.severity + .map((severity) => `\`${severity}\` (${SEVERITY_GLOSS[severity]})`) + .join(" or "); + + const tests = [ + "`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw.", + `\`expectNoDiagnostics(artifact)\` fails while \`${code}\` is among them, with the events as evidence; after the fix, the code is gone from \`artifact.diagnostics\`.`, + ...(entry.severity.every((severity) => severity === "info") + ? [ + "The assertion counts an `info` event like any other, so a scenario that accepts this one lists the code in its `allow` option.", + ] + : []), + ...(CODE_BUDGET_NOTES[code] ? [CODE_BUDGET_NOTES[code]] : []), + ...(!CODE_BUDGET_NOTES[code] && BUDGET_NOTES[entry.category] + ? [BUDGET_NOTES[entry.category]] + : []), + "[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form.", + ].join("\n"); + + const learn = [ + `- [Diagnostics](${ROUTE_BASE}): every code, with its kind, severity, and trigger.`, + "- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at.", + ...(entry.kind.some((kind) => + ["perf", "graph", "responsiveness"].includes(kind) + ) + ? [ + "- [Attribution](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution): the engine whose records explain a re-run, a hold, or a waterfall.", + ] + : []), + ].join("\n"); + + return `${frontmatter} + +{/* ${GENERATED_MARKER} */} + +${renderProse(`${entry.trigger}.`, code)} + +## What you saw + +- **Kind:** ${listOf(entry.kind)} +- **Severity:** ${severities} + +The console report opens with \`[${code}]\` and the message, then an \`in\` line naming the owners that enclose the subject, root first, and an \`at\` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with \`code: "${code}"\`, \`ownerPath\`, and \`loc\`. +[Diagnostics](${ROUTE_BASE}#read-a-report) explains each line. + +## Why it fires + +${renderBlocks(why, code)}${groupNote} + +## How to fix it + +${renderBlocks(fix, code)} + +## In tests + +${tests} + +## Learn more + +${learn} +`; +} + +function renderIndex(entries) { + const frontmatter = [ + "---", + 'title: "Diagnostics"', + 'titleTemplate: ":title"', + 'version: "2.0"', + `description: ${yamlString("Every diagnostic code a development build of Solid reports: what it means, how to read the console report, and one page per code with the repair.")}`, + `source_repo: "solidjs/solid"`, + `source_ref: ${yamlString(SOURCE_REF)}`, + `source_path: ${yamlString(TABLE_PATH)}`, + "---", + ].join("\n"); + + const rows = entries + .slice() + .sort((a, b) => a.code.localeCompare(b.code)) + .map( + (entry) => + `| [\`${entry.code}\`](${routeOf(entry.code)}) | ${listOf(entry.kind)} | ${listOf(entry.severity)} | ${renderProse(entry.trigger, null).replace(/\|/g, "\\|")} |` + ); + + return `${frontmatter} + +{/* ${GENERATED_MARKER} */} + +A diagnostic is a finding the runtime makes about your code while it runs: a reactive value read where nothing tracks it, a write from inside a computation, an effect that re-runs because of its own write, a wait the screen never acknowledged. +Each finding carries a stable code, and every code has a page here that says what you saw, why it fires, and how to fix it. + +A development build prints each finding to the console. +An observe build delivers the same findings as events on [\`OBSERVE.diagnostics\`](/reference/solid-js/advanced/diagnostics-dev-hooks/observe) without printing them, and a test captures them with \`captureArtifact\` from \`@solidjs/diagnostics\` so that \`expectNoDiagnostics(artifact)\` fails on a regression. +A production build reports nothing. + +Severity says what kind of finding it is: \`error\` is ${SEVERITY_GLOSS.error}, \`warn\` is ${SEVERITY_GLOSS.warn}, and \`info\` is ${SEVERITY_GLOSS.info}. +Some codes fire only while the [attribution engine](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution) is enabled, because they measure cost or waiting rather than correctness; their pages say so. + +## Read a report + +One console entry per finding: + +\`\`\`text +[STRICT_READ_UNTRACKED] Reactive value "title" read directly in will not update. Move it into a tracking scope (JSX, a memo, or an effect's compute function). + in › › + at http://localhost:5173/src/components/TodoRow.tsx:18:3 +[STRICT_READ_UNTRACKED] what it means and how to fix it: https://v2.solidjs.com${ROUTE_BASE}/strict-read-untracked +[STRICT_READ_UNTRACKED] agent repair guide: ${SKILL_INSTALLED}#strict_read_untracked — ${SKILL_URL}#strict_read_untracked +\`\`\` + +- The first line is the code in brackets and the message: what the runtime observed and, for most codes, what to change. +- The \`in\` line is the chain of owners from the root down to the scope that produced the finding: components as \`\`, computations by the \`name\` option you gave them or \`effect\`/\`computed\` by default. + No \`in\` line means the subject has no named owner: a top-level scope, or an unowned primitive, which for the \`NO_OWNER_*\` codes is the finding itself. + In a captured artifact the same chain is \`event.ownerPath\`. +- The \`at\` line is the source location of the innermost located node on that chain, \`file:line:col\` relative to the project root, as written by the compiler's \`sourceNames\` \`locations\` kind (on in development through [\`@solidjs/vite-plugin\`](/reference/vite-plugin-solid/options#sourcenames)). + In a browser the path is printed under the dev server's origin, so the console links it to the Sources panel; on the server it is the bare path. + In a captured artifact it is \`event.loc\`. +- When the finding is about a compiled JSX binding (an attribute, a class, a style, an inserted value), the entry also carries the element it writes as a second console argument: hover it to highlight the element on the page, click it to jump to the Elements panel. +- The first report of each code ends with a footer: the link to that code's page here, then the path of the repair skill installed with \`solid-js\` for an agent working in the repository. + +Naming your memos and effects (\`{ name }\`) turns the trailing \`effect\` in the \`in\` line into something you can search for. +[Debugging reactivity](/guides/debugging-reactivity) walks through the common cases. + +## Codes + +| Code | Kind | Severity | Trigger | +| --- | --- | --- | --- | +${rows.join("\n")} +`; +} + +// --- Output ---------------------------------------------------------------- + +// Written in the repository's Prettier style, like the reference pages, so a +// regeneration never fights the formatter. +async function writeFiles(files) { + cleanGeneratedFiles(outDir); + const config = (await prettier.resolveConfig(outDir)) ?? {}; + for (const file of files) { + fs.mkdirSync(path.dirname(file.fullPath), { recursive: true }); + const content = await prettier.format(file.content, { + ...config, + filepath: file.fullPath, + }); + fs.writeFileSync(file.fullPath, content); + } +} + +function cleanGeneratedFiles(dir) { + if (!fs.existsSync(dir)) return; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const fullPath = path.join(dir, entry.name); + if (entry.isFile() && fullPath.endsWith(".mdx")) { + const content = fs.readFileSync(fullPath, "utf8"); + if (content.includes(GENERATED_MARKER)) fs.unlinkSync(fullPath); + } + } +} diff --git a/src/routes/(5)guides/(10)debugging-reactivity.mdx b/src/routes/(5)guides/(10)debugging-reactivity.mdx index 3d9492ca5..cd787877b 100644 --- a/src/routes/(5)guides/(10)debugging-reactivity.mdx +++ b/src/routes/(5)guides/(10)debugging-reactivity.mdx @@ -23,13 +23,15 @@ Development builds print one console entry per finding, with a code in square br [STRICT_READ_UNTRACKED] Reactive value read directly in will not update. Move it into a tracking scope (JSX, a memo, or an effect's compute function). in › › + at http://localhost:5173/src/components/LineItem.tsx:12:5 ``` The code is stable across releases; the sentence after it says what the runtime observed and what to change. The `in` line is the chain of owners from the root down to the scope that produced the finding: components as ``, computations by the `name` option you gave them or `effect`/`computed` by default. +The `at` line is the source position of the nearest node on that chain the compiler recorded, `file:line:col` relative to the project root; in a browser it is printed under the dev server's origin, so clicking it opens the file in the Sources panel. When the finding is about a JSX binding (an attribute, class, style, or inserted text), the console entry also carries the DOM element it writes as a second argument, so hovering it in the browser's console highlights the element on the page and clicking it jumps to the Elements panel. -The first time each code appears, Solid adds a footer pointing to the repair guide that ships inside the `solid-js` package under `skills/reactivity-diagnostics/SKILL.md`, and the same file on GitHub anchored to that code. +The first time each code appears, Solid adds a footer: a link to the code's page in the [diagnostics reference](/reference/solid-js/diagnostics), then the repair guide that ships inside the `solid-js` package under `skills/reactivity-diagnostics/SKILL.md` and the same file on GitHub, anchored to that code, for an assistant working in the repository. Codes fall into a few groups: diff --git a/src/routes/(5)guides/(15)observability.mdx b/src/routes/(5)guides/(15)observability.mdx index e2faa6b9c..702da0f1a 100644 --- a/src/routes/(5)guides/(15)observability.mdx +++ b/src/routes/(5)guides/(15)observability.mdx @@ -38,6 +38,7 @@ export default defineConfig({ ``` `observe: true` adds the `observe` condition to every environment and turns on source names ([`solid.sourceNames`](/reference/vite-plugin-solid/options#sourcenames)), so component labels such as ``, binding names such as `span.textContent`, and primitive names such as `count` survive minification and the paths below name components rather than `computed`. +Source locations (the `locations` kind, the `at` line of a dev console report) stay off in an observe build unless you set `sourceNames: { locations: true }`. Under `vite dev` the `development` condition still wins; the dev build is a superset, so what you observe there holds in production. Without the plugin, set `resolve.conditions` in your bundler (or `node --conditions=observe` for an unbundled server) and pass `sourceNames: true` to the compiler yourself; in a dev build the option is on by default. diff --git a/src/routes/(5)guides/(17)ai-tools.mdx b/src/routes/(5)guides/(17)ai-tools.mdx index 97e97a850..1bb4e9e5b 100644 --- a/src/routes/(5)guides/(17)ai-tools.mdx +++ b/src/routes/(5)guides/(17)ai-tools.mdx @@ -46,15 +46,16 @@ An assistant that works from evidence rather than from reading the code makes fe ### Coded warnings with a repair-guide footer In development, the runtime reports a structural mistake as a console diagnostic: a reactive value read outside a tracking scope, a write from inside a memo, an effect that re-runs because of its own write. -Each report carries a stable code in brackets and an `in` line naming the owners that enclose the subject. -The first report of each code ends with a footer that names the installed repair guide and the same file on GitHub, anchored to that code: +Each report carries a stable code in brackets, an `in` line naming the owners that enclose the subject, and an `at` line with the `file:line:col` the compiler recorded for it. +The first report of each code ends with a footer: a line for a person, linking the code's page in the [diagnostics reference](/reference/solid-js/diagnostics), then a line for an assistant, naming the installed repair guide and the same file on GitHub, anchored to that code: ``` -[REACTIVE_WRITE_IN_OWNED_SCOPE] repair guide: node_modules/solid-js/skills/reactivity-diagnostics/SKILL.md — https://github.com/solidjs/solid/blob/next/packages/solid/skills/reactivity-diagnostics/SKILL.md#reactive_write_in_owned_scope +[REACTIVE_WRITE_IN_OWNED_SCOPE] what it means and how to fix it: https://v2.solidjs.com/reference/solid-js/diagnostics/reactive-write-in-owned-scope +[REACTIVE_WRITE_IN_OWNED_SCOPE] agent repair guide: node_modules/solid-js/skills/reactivity-diagnostics/SKILL.md#reactive_write_in_owned_scope — https://github.com/solidjs/solid/blob/next/packages/solid/skills/reactivity-diagnostics/SKILL.md#reactive_write_in_owned_scope ``` Tell the assistant to treat every code as a defect to look up in the guide, not as noise to suppress. -[Debugging reactivity](/guides/debugging-reactivity) explains how to read a report. +[Debugging reactivity](/guides/debugging-reactivity) explains how to read a report, and the [diagnostics reference](/reference/solid-js/diagnostics) has a page per code with the same repair written for a person. ### `DEV` and `OBSERVE` diff --git a/src/routes/reference/(1)solid-js/(0)index.mdx b/src/routes/reference/(1)solid-js/(0)index.mdx index 01f6c603a..279bf0066 100644 --- a/src/routes/reference/(1)solid-js/(0)index.mdx +++ b/src/routes/reference/(1)solid-js/(0)index.mdx @@ -78,6 +78,12 @@ These pages support libraries and tooling; application code rarely needs them. - [`isHydrating`](/reference/solid-js/advanced/manual-hydration/is-hydrating) says whether the running code is claiming server-rendered DOM; [`isHydratable`](/reference/solid-js/advanced/manual-hydration/is-hydratable) whether the owner sits where hydration applies. - [`DEV`](/reference/solid-js/advanced/diagnostics-dev-hooks/dev) exposes the development diagnostics that [Debugging reactivity](/guides/debugging-reactivity) reads. +## Diagnostics + +The codes a development build prints to the console, one page each. + +- [Diagnostics](/reference/solid-js/diagnostics): how to read a report, then every code with its kind, severity, and trigger; each code's page says what you saw, why it fires, and how to fix it. + ## Types - [Reactive types](/reference/solid-js/types/reactive-types): `Accessor`, `Setter`, `Signal` diff --git a/src/routes/reference/(1)solid-js/(6)advanced/(7)diagnostics-dev-hooks/dev.mdx b/src/routes/reference/(1)solid-js/(6)advanced/(7)diagnostics-dev-hooks/dev.mdx index 7c43466c0..1d48b6afa 100644 --- a/src/routes/reference/(1)solid-js/(6)advanced/(7)diagnostics-dev-hooks/dev.mdx +++ b/src/routes/reference/(1)solid-js/(6)advanced/(7)diagnostics-dev-hooks/dev.mdx @@ -85,18 +85,27 @@ interface Dev { - **Type:** `void` -Console face of an emitted event — see `reportDiagnostic`. +Console face of an emitted event — see `reportDiagnostic`: one entry +per finding — the message, the `in` line built from `ownerPath` +(`in › › effect`), the `at` line built from `loc` +(`at src/App.tsx:18:3`; in a browser `at http://localhost:5173/src/App.tsx:18:3`, +which Chrome and Firefox link to the Sources panel — a path that leaves +the root prints as-is), the once-per-code footer, and, for a binding +effect the rendering runtime tagged, the element it writes as a second +console argument. #### `guideUrl` - **Type:** `string` -The stable URL of `code`'s section in the repair guide — the -`reactivity-diagnostics` skill shipped with `solid-js`, one section per -code. The one place the URL is built: the console footer prints it and -the performance tracks' Insights link (`learnMoreUrl`) reads it, so both -name the same section. Dev-tier: it is guidance for a developer, and a -URL string on a retained object is a cost every observe build would pay. +The URL of `code`'s page in the docs — what the finding means and how +to fix it, one page per code +([`/reference/solid-js/diagnostics/`](/reference/solid-js/diagnostics), +slug = the code lowercased with `-` for `_`). The one place the URL is +built: the console footer prints it and the performance tracks' Insights +link (`learnMoreUrl`) reads it, so both name the same page. Dev-tier: it +is guidance for a developer, and a URL string on a retained object is a +cost every observe build would pay. ### `DevHooks` @@ -239,6 +248,7 @@ interface DiagnosticEvent { ownerName?: string; nodeName?: string; ownerPath?: string[]; + loc?: string; data?: Record; } ``` @@ -286,6 +296,17 @@ Unnamed owners (plain roots) are skipped. Absent when the subject has no named owner at all (a top-level scope, or an unowned primitive — which is usually the finding itself). +#### `loc` + +- **Type:** `string` + +Source location of the innermost located node enclosing the subject +(`file:line:col`, relative to the project root — the compiler's +`sourceNames` `locations` kind): the subject's own `loc`, else the +nearest owner's up the chain (a component root carries its JSX site). +Absent when nothing on the chain was compiled with locations. A host +event that sets it keeps it. + #### `data` - **Type:** `Record` diff --git a/src/routes/reference/(1)solid-js/(6)advanced/(7)diagnostics-dev-hooks/observe.mdx b/src/routes/reference/(1)solid-js/(6)advanced/(7)diagnostics-dev-hooks/observe.mdx index df1466292..4e2571647 100644 --- a/src/routes/reference/(1)solid-js/(6)advanced/(7)diagnostics-dev-hooks/observe.mdx +++ b/src/routes/reference/(1)solid-js/(6)advanced/(7)diagnostics-dev-hooks/observe.mdx @@ -622,6 +622,7 @@ interface DiagnosticEvent { ownerName?: string; nodeName?: string; ownerPath?: string[]; + loc?: string; data?: Record; } ``` @@ -669,6 +670,17 @@ Unnamed owners (plain roots) are skipped. Absent when the subject has no named owner at all (a top-level scope, or an unowned primitive — which is usually the finding itself). +#### `loc` + +- **Type:** `string` + +Source location of the innermost located node enclosing the subject +(`file:line:col`, relative to the project root — the compiler's +`sourceNames` `locations` kind): the subject's own `loc`, else the +nearest owner's up the chain (a component root carries its JSX site). +Absent when nothing on the chain was compiled with locations. A host +event that sets it keeps it. + #### `data` - **Type:** `Record` @@ -736,7 +748,7 @@ Records an event on the channel from outside the reactive core — a host runtime reporting its own findings (hydration mismatches, server render faults) so consumers see one stream. `subject` locates it like the internal sites do; a host whose owners are not signals' owners passes -`ownerPath` on the event instead and it is used as-is. +`ownerPath` (and `loc`) on the event instead and they are used as-is. ### `DiagnosticSeverity` diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/(0)index.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/(0)index.mdx new file mode 100644 index 000000000..ebf4bd555 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/(0)index.mdx @@ -0,0 +1,117 @@ +--- +title: "Diagnostics" +titleTemplate: ":title" +version: "2.0" +description: "Every diagnostic code a development build of Solid reports: what it means, how to read the console report, and one page per code with the repair." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/diagnostics.json" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +A diagnostic is a finding the runtime makes about your code while it runs: a reactive value read where nothing tracks it, a write from inside a computation, an effect that re-runs because of its own write, a wait the screen never acknowledged. +Each finding carries a stable code, and every code has a page here that says what you saw, why it fires, and how to fix it. + +A development build prints each finding to the console. +An observe build delivers the same findings as events on [`OBSERVE.diagnostics`](/reference/solid-js/advanced/diagnostics-dev-hooks/observe) without printing them, and a test captures them with `captureArtifact` from `@solidjs/diagnostics` so that `expectNoDiagnostics(artifact)` fails on a regression. +A production build reports nothing. + +Severity says what kind of finding it is: `error` is broken behavior, `warn` is code that works but is structurally wrong or expensive, and `info` is advisory, delivered on the diagnostics channel and never printed. +Some codes fire only while the [attribution engine](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution) is enabled, because they measure cost or waiting rather than correctness; their pages say so. + +## Read a report + +One console entry per finding: + +```text +[STRICT_READ_UNTRACKED] Reactive value "title" read directly in will not update. Move it into a tracking scope (JSX, a memo, or an effect's compute function). + in › › + at http://localhost:5173/src/components/TodoRow.tsx:18:3 +[STRICT_READ_UNTRACKED] what it means and how to fix it: https://v2.solidjs.com/reference/solid-js/diagnostics/strict-read-untracked +[STRICT_READ_UNTRACKED] agent repair guide: node_modules/solid-js/skills/reactivity-diagnostics/SKILL.md#strict_read_untracked — https://github.com/solidjs/solid/blob/next/packages/solid/skills/reactivity-diagnostics/SKILL.md#strict_read_untracked +``` + +- The first line is the code in brackets and the message: what the runtime observed and, for most codes, what to change. +- The `in` line is the chain of owners from the root down to the scope that produced the finding: components as ``, computations by the `name` option you gave them or `effect`/`computed` by default. + No `in` line means the subject has no named owner: a top-level scope, or an unowned primitive, which for the `NO_OWNER_*` codes is the finding itself. + In a captured artifact the same chain is `event.ownerPath`. +- The `at` line is the source location of the innermost located node on that chain, `file:line:col` relative to the project root, as written by the compiler's `sourceNames` `locations` kind (on in development through [`@solidjs/vite-plugin`](/reference/vite-plugin-solid/options#sourcenames)). + In a browser the path is printed under the dev server's origin, so the console links it to the Sources panel; on the server it is the bare path. + In a captured artifact it is `event.loc`. +- When the finding is about a compiled JSX binding (an attribute, a class, a style, an inserted value), the entry also carries the element it writes as a second console argument: hover it to highlight the element on the page, click it to jump to the Elements panel. +- The first report of each code ends with a footer: the link to that code's page here, then the path of the repair skill installed with `solid-js` for an agent working in the repository. + +Naming your memos and effects (`{ name }`) turns the trailing `effect` in the `in` line into something you can search for. +[Debugging reactivity](/guides/debugging-reactivity) walks through the common cases. + +## Codes + +| Code | Kind | Severity | Trigger | +| ------------------------------------------------------------------------------------------------------ | ---------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [`ABANDONED_FLIGHTS`](/reference/solid-js/diagnostics/abandoned-flights) | `responsiveness` | `warn` | One async source abandoned 3+ flights in 1s, each superseded before landing — the request-per-keystroke signature (attribution enabled) | +| [`ACTION_CALLED_IN_OWNED_SCOPE`](/reference/solid-js/diagnostics/action-called-in-owned-scope) | `write` | `error` | `action()` invoked from a component body or computation | +| [`ASYNC_OUTSIDE_LOADING_BOUNDARY`](/reference/solid-js/diagnostics/async-outside-loading-boundary) | `async` | `warn` | Async computation outside Loading boundary (non-halting; root mount is deferred) | +| [`ASYNC_STORE_SETTER`](/reference/solid-js/diagnostics/async-store-setter) | `write` | `error` | Store setter callback returned a Promise (setters are synchronous transactions) | +| [`ASYNC_WATERFALL`](/reference/solid-js/diagnostics/async-waterfall) | `perf` | `info`, `warn` | 2+/3+ sequential async flights, origin-proven (attribution enabled) | +| [`BINDING_SLOT_POSITION`](/reference/solid-js/diagnostics/binding-slot-position) | `ssr`, `render` | `warn`, `error` | Binding-slot value where it cannot bind; `data.reason` names the position (spread is an error and throws), client reasons `orphan`/`fill-shape` | +| [`CLEANUP_IN_FORBIDDEN_SCOPE`](/reference/solid-js/diagnostics/cleanup-in-forbidden-scope) | `lifecycle` | `error` | `onCleanup` inside trackedEffect/onSettled | +| [`DYNAMIC_ASYNC_COMPONENT`](/reference/solid-js/diagnostics/dynamic-async-component) | `ssr` | `error` | An async `dynamic()` source landed on a client component function, which cannot serialize for adoption; the memo rejects (observe + dev; rejects in every tier) | +| [`EFFECT_RELAY_TEAR`](/reference/solid-js/diagnostics/effect-relay-tear) | `perf` | `info`, `warn` | Reader ran twice for one root change because an effect relayed it; `warn` when derivable or repeated (attribution enabled) | +| [`EFFECT_WRITES_OWN_SOURCE`](/reference/solid-js/diagnostics/effect-writes-own-source) | `perf` | `info`, `warn` | Effect's write provably feeds back into its own inputs; `info` for multi-effect rings (attribution enabled) | +| [`FALLBACK_FLASH`](/reference/solid-js/diagnostics/fallback-flash) | `responsiveness` | `info` | A `Loading` fallback showed for under 150ms — feedback for a wait too short to need it (attribution enabled) | +| [`FLUSH_IN_EFFECT_CALLBACK`](/reference/solid-js/diagnostics/flush-in-effect-callback) | `lifecycle` | `warn` | `flush()` from an effect callback (no-op; the drain is already running) | +| [`FRAME_MARKER_CORRUPTED`](/reference/solid-js/diagnostics/frame-marker-corrupted) | `ssr` | `error` | Frame slot range missing its end marker on the client — nesting or an HTML rewriter (observe + dev) | +| [`GRAPH_GROWTH`](/reference/solid-js/diagnostics/graph-growth) | `perf` | `warn` | The live graph and what outlived each navigation climbed at a route's settle on 3 visits to 1.25× — something each visit leaves behind (attribution enabled) | +| [`HEAD_TAG_INVALID`](/reference/solid-js/diagnostics/head-tag-invalid) | `head` | `warn` | `useHead` registration the render could not honor; `data.reason` names the rule (dev) | +| [`HOT_SCOPE_FANOUT`](/reference/solid-js/diagnostics/hot-scope-fanout) | `perf` | `warn` | 5+/50+/500+ scopes hot from one root cause (attribution enabled) | +| [`HOT_SCOPE_RERUNS`](/reference/solid-js/diagnostics/hot-scope-reruns) | `perf` | `warn` | 120+ re-runs of one scope in 1s (attribution enabled) | +| [`HOT_SCOPE_TIME`](/reference/solid-js/diagnostics/hot-scope-time) | `perf` | `warn` | 8ms+ self-time in one scope in 1s (attribution enabled) | +| [`HUGE_FAN_IN`](/reference/solid-js/diagnostics/huge-fan-in) | `graph` | `warn` | One recompute tracked 2000 sources (always on) | +| [`HUGE_FAN_OUT`](/reference/solid-js/diagnostics/huge-fan-out) | `graph` | `warn` | One change reached 2000 live subscribers (always on), or 250 — `fanOut` — on a stamped root write (attribution enabled) | +| [`IMMUTABLE_UPDATE_IN_STORE`](/reference/solid-js/diagnostics/immutable-update-in-store) | `perf` | `warn` | Store container replaced by a mostly-identical copy (attribution enabled) | +| [`INVALID_AFFECTS_TARGET`](/reference/solid-js/diagnostics/invalid-affects-target) | `write` | `error` | `affects()` given a key path, or a key on an accessor | +| [`INVALID_REFRESH_TARGET`](/reference/solid-js/diagnostics/invalid-refresh-target) | `write` | `error` | `refresh()` target is not a source accessor or refreshable store | +| [`INVARIANT_VIOLATION`](/reference/solid-js/diagnostics/invariant-violation) | `error` | `error` | Internal consistency check failed (throws under `__TEST__`, reported in dev) | +| [`LATE_HEADER_WRITE`](/reference/solid-js/diagnostics/late-header-write) | `head` | `error` | Response header written after the head was sent; dropped (observe + dev; dev throws) | +| [`LAZY_ASSET_UNMAPPED`](/reference/solid-js/diagnostics/lazy-asset-unmapped) | `ssr` | `warn` | `lazy()` component's client assets could not be resolved for the page (dev) | +| [`LOADING_ON_OUTSIDE_HOLD`](/reference/solid-js/diagnostics/loading-on-outside-hold) | `async` | `warn` | `` changed but its data is also read outside the boundary and holds the frame: the fallback can never be seen | +| [`LONG_HOLD`](/reference/solid-js/diagnostics/long-hold) | `responsiveness` | `info`, `warn` | Acknowledged hold whose tail (last input → commit) ran 500ms+/1000ms+ (attribution enabled) | +| [`LOWERCASE_EVENT_ATTRIBUTE`](/reference/solid-js/diagnostics/lowercase-event-attribute) | `render` | `warn` | A function was set on a lowercase `on*` (or `on:`) attribute, which is not an event handler in 2.0; use `onXxx` (dev; client) | +| [`MISSING_EFFECT_FN`](/reference/solid-js/diagnostics/missing-effect-fn) | `lifecycle` | `error` | `createEffect` called without the effect function | +| [`NO_OWNER_BOUNDARY`](/reference/solid-js/diagnostics/no-owner-boundary) | `lifecycle` | `warn` | Boundary created without owner | +| [`NO_OWNER_CLEANUP`](/reference/solid-js/diagnostics/no-owner-cleanup) | `lifecycle` | `warn` | `onCleanup` called without owner | +| [`NO_OWNER_EFFECT`](/reference/solid-js/diagnostics/no-owner-effect) | `lifecycle` | `warn` | Effect created without reactive owner | +| [`OPTIMISTIC_REVERTED`](/reference/solid-js/diagnostics/optimistic-reverted) | `responsiveness` | `info` | An optimistic value the screen showed was replaced by a different one — reverted at settle, or superseded by the truth (attribution enabled) | +| [`PENDING_ASYNC_FORBIDDEN_SCOPE`](/reference/solid-js/diagnostics/pending-async-forbidden-scope) | `async` | `warn` | Pending async read in trackedEffect/onSettled | +| [`PENDING_ASYNC_UNTRACKED_READ`](/reference/solid-js/diagnostics/pending-async-untracked-read) | `async` | `error` | Reading pending async outside tracking scope | +| [`PRELOAD_DESCRIPTOR_INVALID`](/reference/solid-js/diagnostics/preload-descriptor-invalid) | `head` | `warn` | `registerAsset("preload")` descriptor broke a field rule; link dropped or field ignored (dev) | +| [`PRIMITIVE_IN_EFFECT_CALLBACK`](/reference/solid-js/diagnostics/primitive-in-effect-callback) | `lifecycle` | `error` | Memo, effect, or root created inside an effect callback or its cleanup, where there is no ambient owner (throws) | +| [`PRIMITIVE_IN_FORBIDDEN_SCOPE`](/reference/solid-js/diagnostics/primitive-in-forbidden-scope) | `lifecycle` | `error` | Reactive primitive created inside trackedEffect/onSettled | +| [`REACTIVE_WRITE_IN_OWNED_SCOPE`](/reference/solid-js/diagnostics/reactive-write-in-owned-scope) | `write` | `error` | Reactive write/invalidation inside component/computation | +| [`REACTIVITY_HALTED`](/reference/solid-js/diagnostics/reactivity-halted) | `error` | `error` | Uncaught error escaped every boundary; scheduling stopped (reported, cause to `reportError`) | +| [`REVEAL_IN_RENDER_TO_STRING`](/reference/solid-js/diagnostics/reveal-in-render-to-string) | `ssr` | `warn` | Nested `` with collapsed/together under `renderToString` (dev) | +| [`RUN_WITH_DISPOSED_OWNER`](/reference/solid-js/diagnostics/run-with-disposed-owner) | `owner` | `warn` | `runWithOwner` with disposed owner | +| [`SERVER_ERROR_SANITIZED`](/reference/solid-js/diagnostics/server-error-sanitized) | `ssr` | `error`, `info` | Error replaced with the generic Error on the wire — `data.source` `"server-function"` (`error`) or `"ssr"` (`info`); `data.error` is the original (observe + dev) | +| [`SERVER_WRITE`](/reference/solid-js/diagnostics/server-write) | `write` | `warn` | Signal/store/optimistic setter ran during a server render; inert, will throw (dev; once per category) | +| [`SETTLE_WALK_UNINITIALIZED_SOURCE`](/reference/solid-js/diagnostics/settle-walk-uninitialized-source) | `lifecycle` | `error` | Internal: settle walk reached a source that never produced a value (reported) | +| [`SETTLED_CLEANUP_UNOWNED`](/reference/solid-js/diagnostics/settled-cleanup-unowned) | `lifecycle` | `error` | `onSettled` returned a cleanup in an unowned (out-of-band) scope | +| [`SILENT_HOLD`](/reference/solid-js/diagnostics/silent-hold) | `responsiveness` | `info`, `warn` | Write held 100ms+/200ms+ by pending async with no on-screen acknowledgement (attribution enabled) | +| [`SSR_BOUNDARY_WATERFALL`](/reference/solid-js/diagnostics/ssr-boundary-waterfall) | `ssr` | `info`, `warn` | A `` boundary needed 3+/4+ render passes — 2+/3+ sequential async waits (dev) | +| [`SSR_CLIENT_CONTENT_MASKED`](/reference/solid-js/diagnostics/ssr-client-content-masked) | `ssr` | `warn` | Client-only content in a `` surfaced only after a server wait; the server's work was discarded (dev) | +| [`SSR_LIVE_HOLE_LATCHED`](/reference/solid-js/diagnostics/ssr-live-hole-latched) | `ssr` | `warn` | A live hole that shipped its markers was closed by the impurity gate on re-run and stopped updating (dev) | +| [`SSR_RENDER_ERROR_CONTAINED`](/reference/solid-js/diagnostics/ssr-render-error-contained) | `ssr` | `error` | Server render error routed by a boundary: `data.handling` fallback / client / failed (observe + dev) | +| [`SSR_STREAM_ABANDONED`](/reference/solid-js/diagnostics/ssr-stream-abandoned) | `ssr` | `warn` | Response stream cancelled or sink failed with fragments pending (observe + dev) | +| [`SSR_SUBTREE_ABANDONED`](/reference/solid-js/diagnostics/ssr-subtree-abandoned) | `ssr` | `warn` | A failed fragment's pending descendants were discarded (observe + dev) | +| [`SSR_UNDECLARED_LIVE_SOURCE`](/reference/solid-js/diagnostics/ssr-undeclared-live-source) | `ssr` | `warn` | A document render is still pumping an async iterable in server-component scope 5s in — declare the function `live(...)` or bound the source (dev) | +| [`STACKED_HOLDS`](/reference/solid-js/diagnostics/stacked-holds) | `responsiveness` | `warn` | 3+ interactions were waiting in one hold when it committed — repeats piled behind the same source (attribution enabled) | +| [`STRICT_READ_UNTRACKED`](/reference/solid-js/diagnostics/strict-read-untracked) | `strict-read` | `warn` | Untracked reactive read in component/effect body | +| [`SYNC_NODE_RECEIVED_ASYNC`](/reference/solid-js/diagnostics/sync-node-received-async) | `lifecycle` | `error` | `sync: true` computation returned a Promise / AsyncIterable | +| [`UNRECOGNIZED_INSERT_VALUE`](/reference/solid-js/diagnostics/unrecognized-insert-value) | `render` | `warn` | Value at an insert position the renderer cannot render; skipped (dev; server and client) | +| [`UNSCOPED_HOLE_ALLOCATED_IDS`](/reference/solid-js/diagnostics/unscoped-hole-allocated-ids) | `render` | `warn` | Unscoped hole was handed a function whose content took ids at a position the other side does not share; keys permute (dev) | +| [`UNSTABLE_LIST_IDENTITY`](/reference/solid-js/diagnostics/unstable-list-identity) | `perf` | `warn` | `mapArray`/`For` recreated rows for equivalent items (attribution enabled) | +| [`UNSTABLE_MEMO_OUTPUT`](/reference/solid-js/diagnostics/unstable-memo-output) | `perf` | `warn` | Memo returned a new-but-equivalent container 4+ runs running (attribution enabled) | +| [`UNTRACKED_ASYNC_HANDLER`](/reference/solid-js/diagnostics/untracked-async-handler) | `responsiveness` | `info`, `warn` | Handler awaited 100ms+/200ms+ past its frame with no write before the `await` and no `action()`: a dead click no hold could judge (attribution enabled) | +| [`UNTRACKED_READ_AFTER_AWAIT`](/reference/solid-js/diagnostics/untracked-read-after-await) | `async` | `warn` | Async computation first read a signal/memo/store key after an `await`; never a dependency (dev; V8 engines only) | +| [`WASTED_RECOMPUTE`](/reference/solid-js/diagnostics/wasted-recompute) | `perf` | `warn` | 80%+ of a scope's 5+ runs in a second produced an unchanged value for 2ms+ of compute — inputs change, result does not (attribution enabled) | +| [`WIDE_SCOPE_DEPS`](/reference/solid-js/diagnostics/wide-scope-deps) | `perf` | `warn` | Scope subscribed to 200+ sources (attribution cost checks on) | diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/abandoned-flights.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/abandoned-flights.mdx new file mode 100644 index 000000000..c82828934 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/abandoned-flights.mdx @@ -0,0 +1,61 @@ +--- +title: "ABANDONED_FLIGHTS" +category: "Diagnostics" +use_cases: "abandoned_flights diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "responsiveness" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "ABANDONED_FLIGHTS: One async source abandoned 3+ flights in 1s, each superseded before landing — the request-per-keystroke signature (attribution enabled)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +One async source abandoned 3+ flights in 1s, each superseded before landing — the request-per-keystroke signature (attribution enabled). + +## What you saw + +- **Kind:** `responsiveness` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[ABANDONED_FLIGHTS]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "ABANDONED_FLIGHTS"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +One async source started flight after flight and threw most of the answers away: `data.abandoned` flights in `data.windowMs` were each superseded by the next before landing. +This is the search-as-you-type shape — every keystroke re-asks, and only the last answer is used — and it costs a request per input plus a hold that keeps extending. + +:::note[Responsiveness (from the attribution engine)] +The runtime did the correct thing; the user saw nothing while it did. +These fire only while attribution is enabled. +::: + +## How to fix it + +Repairs, in order: + +- Put a debounced or equality-gated derivation between the input and the fetch: the memo the fetch reads changes only when the query has settled (a `createMemo` over a debounced signal, or one that returns the previous value when the trimmed query is equal), so the flight starts once. +- Key the fetch on what changes (the parsed filter, not the raw text) so equal inputs do not re-ask. +- If several readers legitimately start the same request, `markFlight` the shared preload so the engine sees one flight rather than N. + +Do NOT "fix" it by caching the promise in a signal outside the graph; the hold and the acknowledgement machinery stop seeing the wait. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `ABANDONED_FLIGHTS` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. +- [Attribution](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution): the engine whose records explain a re-run, a hold, or a waterfall. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/action-called-in-owned-scope.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/action-called-in-owned-scope.mdx new file mode 100644 index 000000000..dec35f68f --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/action-called-in-owned-scope.mdx @@ -0,0 +1,48 @@ +--- +title: "ACTION_CALLED_IN_OWNED_SCOPE" +category: "Diagnostics" +use_cases: "action_called_in_owned_scope diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "write" + - "error" + - "reference" + - "v2" +version: "2.0" +description: "ACTION_CALLED_IN_OWNED_SCOPE: `action()` invoked from a component body or computation." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +`action()` invoked from a component body or computation. + +## What you saw + +- **Kind:** `write` +- **Severity:** `error` (broken behavior) + +The console report opens with `[ACTION_CALLED_IN_OWNED_SCOPE]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "ACTION_CALLED_IN_OWNED_SCOPE"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +An action was invoked during a component body or computation. + +## How to fix it + +Actions are imperative entry points — call them from event handlers or other imperative code. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `ACTION_CALLED_IN_OWNED_SCOPE` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/async-outside-loading-boundary.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/async-outside-loading-boundary.mdx new file mode 100644 index 000000000..091e950af --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/async-outside-loading-boundary.mdx @@ -0,0 +1,52 @@ +--- +title: "ASYNC_OUTSIDE_LOADING_BOUNDARY" +category: "Diagnostics" +use_cases: "async_outside_loading_boundary diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "async" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "ASYNC_OUTSIDE_LOADING_BOUNDARY: Async computation outside Loading boundary (non-halting; root mount is deferred)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Async computation outside Loading boundary (non-halting; root mount is deferred). + +## What you saw + +- **Kind:** `async` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[ASYNC_OUTSIDE_LOADING_BOUNDARY]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "ASYNC_OUTSIDE_LOADING_BOUNDARY"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +On the client, an async value was read outside a Loading boundary during mounting. +The root mount waits until the pending async work settles. + +## How to fix it + +Put the pending content inside `` and provide a fallback if the page should show something while it waits. + +During SSR, reading a source declared with `ssrSource: "client"` outside `` throws an error because the server cannot run that source. +Wrap the read in `` to provide a fallback, or declare `loadingValue`/`seedLoadingValue` to render a provisional value. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `ASYNC_OUTSIDE_LOADING_BOUNDARY` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/async-store-setter.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/async-store-setter.mdx new file mode 100644 index 000000000..501d1ed8a --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/async-store-setter.mdx @@ -0,0 +1,50 @@ +--- +title: "ASYNC_STORE_SETTER" +category: "Diagnostics" +use_cases: "async_store_setter diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "write" + - "error" + - "reference" + - "v2" +version: "2.0" +description: "ASYNC_STORE_SETTER: Store setter callback returned a Promise (setters are synchronous transactions)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Store setter callback returned a Promise (setters are synchronous transactions). + +## What you saw + +- **Kind:** `write` +- **Severity:** `error` (broken behavior) + +The console report opens with `[ASYNC_STORE_SETTER]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "ASYNC_STORE_SETTER"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A store setter callback returned a Promise — `setStore(async d => …)`, or a sync arrow whose helper is async. +A store setter is a synchronous transaction: the draft closes when the callback returns, so only the writes before the first `await` were in it; the rest are lost. + +## How to fix it + +Move the async orchestration into an `action()` and call the setter synchronously from inside it (`yield` to re-enter the transaction after an `await`). +Signals are not subject to this rule — a signal may hold a promise as its value. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `ASYNC_STORE_SETTER` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/async-waterfall.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/async-waterfall.mdx new file mode 100644 index 000000000..c6f32aa2d --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/async-waterfall.mdx @@ -0,0 +1,62 @@ +--- +title: "ASYNC_WATERFALL" +category: "Diagnostics" +use_cases: "async_waterfall diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "perf" + - "info" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "ASYNC_WATERFALL: 2+/3+ sequential async flights, origin-proven (attribution enabled)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +2+/3+ sequential async flights, origin-proven (attribution enabled). + +## What you saw + +- **Kind:** `perf` +- **Severity:** `info` (advisory, delivered on the diagnostics channel and never printed) or `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[ASYNC_WATERFALL]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "ASYNC_WATERFALL"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +Async flights ran in sequence when they might have run in parallel: each named flight provably could not start until the previous one resolved, and each took real time (the per-link durations are in the message/data). + +:::note[Performance pathologies (from the attribution engine)] +These fire only while attribution is enabled and describe cost, not incorrect behavior. +The numbers in the message are measurements, not guesses. +::: + +## How to fix it + +Read the chain from `attribution.history("waterfall")` if you need more than the warning shows. +(The server's `` boundary counterpart is [`SSR_BOUNDARY_WATERFALL`](/reference/solid-js/diagnostics/ssr-boundary-waterfall), below.) Repairs, in order of preference: + +1. If a later request does not need the earlier response, derive both from the same inputs so they start together (in one scope, read ALL async sources before using any — a not-ready throw at the first read stops the later ones from even starting). +2. If the dependency is intrinsic, preload the dependent data (route preloaders, hover preloads) or join the requests server-side. +3. False positive only if the work was ALREADY started by a layer the graph cannot see (a preloader or request cache handing out wrapper promises): that layer should call `attribution.markFlight(promise, startedAt)` (from `solid-js/attribution`) on what it hands out; the chain then breaks on origin proof. Depth-2 chains are `info` severity for exactly this reason — treat them as leads, not verdicts. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `ASYNC_WATERFALL` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +`expectRerunBudget(artifact, max, { scope })` bounds how often a named scope may re-run, and `expectNoWaste(artifact)` fails on re-runs whose result was unchanged. +Both read the attribution records, which `captureArtifact` collects unless `attribution: false` is passed. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. +- [Attribution](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution): the engine whose records explain a re-run, a hold, or a waterfall. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/binding-slot-position.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/binding-slot-position.mdx new file mode 100644 index 000000000..21c67ee4c --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/binding-slot-position.mdx @@ -0,0 +1,62 @@ +--- +title: "BINDING_SLOT_POSITION" +category: "Diagnostics" +use_cases: "binding_slot_position diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "ssr" + - "render" + - "warn" + - "error" + - "reference" + - "v2" +version: "2.0" +description: "BINDING_SLOT_POSITION: Binding-slot value where it cannot bind; `data.reason` names the position (spread is an error and throws), client reasons `orphan`/`fill-shape`." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Binding-slot value where it cannot bind; `data.reason` names the position (spread is an error and throws), client reasons `orphan`/`fill-shape`. + +## What you saw + +- **Kind:** `ssr`, `render` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) or `error` (broken behavior) + +The console report opens with `[BINDING_SLOT_POSITION]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "BINDING_SLOT_POSITION"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A binding slot's property (`const row = props.row(args); row.done`) landed where the server template cannot bind it. + +:::note[Server rendering] +The server runtime reports on the same channel, with the same `in › ` line. +Two groups. +**Findings** ([`SSR_RENDER_ERROR_CONTAINED`](/reference/solid-js/diagnostics/ssr-render-error-contained), [`SSR_SUBTREE_ABANDONED`](/reference/solid-js/diagnostics/ssr-subtree-abandoned), [`SSR_STREAM_ABANDONED`](/reference/solid-js/diagnostics/ssr-stream-abandoned), [`LATE_HEADER_WRITE`](/reference/solid-js/diagnostics/late-header-write), [`DYNAMIC_ASYNC_COMPONENT`](/reference/solid-js/diagnostics/dynamic-async-component), [`SERVER_ERROR_SANITIZED`](/reference/solid-js/diagnostics/server-error-sanitized), [`FRAME_MARKER_CORRUPTED`](/reference/solid-js/diagnostics/frame-marker-corrupted)) are facts about a render that exist in observe builds too — an APM sees them in production; in dev they print. +**Checks** (the rest, [`SSR_BOUNDARY_WATERFALL`](/reference/solid-js/diagnostics/ssr-boundary-waterfall) and [`SSR_CLIENT_CONTENT_MASKED`](/reference/solid-js/diagnostics/ssr-client-content-masked) included) are dev-only guidance. +A captured artifact from a server render carries both. +::: + +## How to fix it + +The rule: a slot property is a JSX attribute value, whole, and nothing else. +`data.reason`: `"spread"` (throws — the slot's whole return spread onto an element; name each position instead), `"stringified"` (coerced into a string — a template literal, a concatenation), `"coerced"` (used in an expression — a comparison, arithmetic, a branch on its result; the server has no value to compute with, so decide in the client fill and return the decided value), `"inline"` (reached `class`/`style` inside template quotes — the element was compiled without the `serverComponents` compiler option), `"text"` (placed as text, not a bindable position yet), `"markup"` (read off a slot whose client fill returned content, not an object), `"server-local"` (a `ref`/`on*` position got a plain server function — bind a slot property or an `action=`), `"tuple"` (an `on*` position got an array — return the `[handler, data]` tuple from the fill instead), `"reserved-key"` (the fill's object used a key the slot's range occupies), `"orphan"` (client, kind `render`: an element carries markers for an occurrence that can never bind — `data.why` `"fill"`, no client fill for the prop; `"record"`, a called occurrence with no args record once none can arrive, which is the protocol out of step — client and server from different builds — not a fill mistake). +For `stringified`/`coerced`/`inline`/`text` NOTHING renders at the position on either face, so the misuse shows on the first render, not the first refetch. +Truthiness (`if (row.done)`) has no hook and is the one misuse only the rule catches — a stand-in is always truthy. +The fuller guide is `@solidjs/web`'s `skills/server-components/SKILL.md`. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `BINDING_SLOT_POSITION` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/cleanup-in-forbidden-scope.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/cleanup-in-forbidden-scope.mdx new file mode 100644 index 000000000..aa7e024bc --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/cleanup-in-forbidden-scope.mdx @@ -0,0 +1,48 @@ +--- +title: "CLEANUP_IN_FORBIDDEN_SCOPE" +category: "Diagnostics" +use_cases: "cleanup_in_forbidden_scope diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "lifecycle" + - "error" + - "reference" + - "v2" +version: "2.0" +description: "CLEANUP_IN_FORBIDDEN_SCOPE: `onCleanup` inside trackedEffect/onSettled." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +`onCleanup` inside trackedEffect/onSettled. + +## What you saw + +- **Kind:** `lifecycle` +- **Severity:** `error` (broken behavior) + +The console report opens with `[CLEANUP_IN_FORBIDDEN_SCOPE]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "CLEANUP_IN_FORBIDDEN_SCOPE"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +`onCleanup` inside `createTrackedEffect` or `onSettled` is not supported. + +## How to fix it + +Return a cleanup function from the callback instead. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `CLEANUP_IN_FORBIDDEN_SCOPE` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/dynamic-async-component.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/dynamic-async-component.mdx new file mode 100644 index 000000000..c98cd0665 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/dynamic-async-component.mdx @@ -0,0 +1,58 @@ +--- +title: "DYNAMIC_ASYNC_COMPONENT" +category: "Diagnostics" +use_cases: "dynamic_async_component diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "ssr" + - "error" + - "reference" + - "v2" +version: "2.0" +description: "DYNAMIC_ASYNC_COMPONENT: An async `dynamic()` source landed on a client component function, which cannot serialize for adoption; the memo rejects (observe + dev; rejects in every tier)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +An async `dynamic()` source landed on a client component function, which cannot serialize for adoption; the memo rejects (observe + dev; rejects in every tier). + +## What you saw + +- **Kind:** `ssr` +- **Severity:** `error` (broken behavior) + +The console report opens with `[DYNAMIC_ASYNC_COMPONENT]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "DYNAMIC_ASYNC_COMPONENT"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +An async `dynamic()` source resolved to a client component function. +The instance memo serializes its landing for the client to adopt (#3666) and a function has no encoding, so the memo rejects with this message in every tier (the nearest `` / `onError` contains it) instead of leaving the client pending or re-running the source under the boundary — the phantom-fallback bug the adoption fixed. +`data.component` names the function. + +:::note[Server rendering] +The server runtime reports on the same channel, with the same `in › ` line. +Two groups. +**Findings** ([`SSR_RENDER_ERROR_CONTAINED`](/reference/solid-js/diagnostics/ssr-render-error-contained), [`SSR_SUBTREE_ABANDONED`](/reference/solid-js/diagnostics/ssr-subtree-abandoned), [`SSR_STREAM_ABANDONED`](/reference/solid-js/diagnostics/ssr-stream-abandoned), [`LATE_HEADER_WRITE`](/reference/solid-js/diagnostics/late-header-write), `DYNAMIC_ASYNC_COMPONENT`, [`SERVER_ERROR_SANITIZED`](/reference/solid-js/diagnostics/server-error-sanitized), [`FRAME_MARKER_CORRUPTED`](/reference/solid-js/diagnostics/frame-marker-corrupted)) are facts about a render that exist in observe builds too — an APM sees them in production; in dev they print. +**Checks** (the rest, [`SSR_BOUNDARY_WATERFALL`](/reference/solid-js/diagnostics/ssr-boundary-waterfall) and [`SSR_CLIENT_CONTENT_MASKED`](/reference/solid-js/diagnostics/ssr-client-content-masked) included) are dev-only guidance. +A captured artifact from a server render carries both. +::: + +## How to fix it + +Move the async upstream (a `createAsync`/`createMemo` the source reads synchronously) or use `lazy()` for code; a server component or a tag name may stay async. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `DYNAMIC_ASYNC_COMPONENT` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/effect-relay-tear.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/effect-relay-tear.mdx new file mode 100644 index 000000000..5308a9a5f --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/effect-relay-tear.mdx @@ -0,0 +1,63 @@ +--- +title: "EFFECT_RELAY_TEAR" +category: "Diagnostics" +use_cases: "effect_relay_tear diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "perf" + - "info" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "EFFECT_RELAY_TEAR: Reader ran twice for one root change because an effect relayed it; `warn` when derivable or repeated (attribution enabled)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Reader ran twice for one root change because an effect relayed it; `warn` when derivable or repeated (attribution enabled). + +## What you saw + +- **Kind:** `perf` +- **Severity:** `info` (advisory, delivered on the diagnostics channel and never printed) or `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[EFFECT_RELAY_TEAR]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "EFFECT_RELAY_TEAR"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +Derived state kept in sync by an effect: `createEffect(() => f(a()), v => setS(v))`. +The message names a reader that ran twice for ONE write of `a` — once in the flush where `a` changed (against the stale `S`), once after the effect's write landed — so its first frame was inconsistent. +That double run is proven from the cause chain; the rest of the message is confidence: + +:::note[Performance pathologies (from the attribution engine)] +These fire only while attribution is enabled and describe cost, not incorrect behavior. +The numbers in the message are measurements, not guesses. +::: + +## How to fix it + +- "The written value is the effect's compute output" — by Solid's contract the compute half is a pure function of its tracked reads, so the value is derivable. Repair: `const s = createMemo(() => f(a()))`, delete the effect and the signal. This form warns on its own once it has repeated (`data.copy: true`), even with no double-running reader — everything reading the copy paints a flush behind everything reading the source. +- "The written value is `a` itself" (`data.passthrough`) — the prop-to-state port. Repair: read `a` where `S` was read; a memo only if a stable derivation is genuinely needed. +- "Nothing else writes `S`" (`data.soleWriter`) — derived state without the identity proof; same memo repair. +- "`S` has other writers — editable state reset from a source" (`copy` true, `soleWriter` false; `info` until it repeats) — the controlled-input shape. If resetting local editable state when the source changes is the intent, the tear is its cost; if `S` only ever mirrors the source, drop the copy. +- Neither (`info` until it repeats) — the written value is not the compute output and the signal has other writers. If the effect reads something outside the graph (layout, `Date.now()`, a ref), the tear is the cost of measuring and the finding is a fact to accept; if the value is computed from what the effect reads, it is still a memo. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `EFFECT_RELAY_TEAR` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +`expectRerunBudget(artifact, max, { scope })` bounds how often a named scope may re-run, and `expectNoWaste(artifact)` fails on re-runs whose result was unchanged. +Both read the attribution records, which `captureArtifact` collects unless `attribution: false` is passed. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. +- [Attribution](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution): the engine whose records explain a re-run, a hold, or a waterfall. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/effect-writes-own-source.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/effect-writes-own-source.mdx new file mode 100644 index 000000000..f3f622862 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/effect-writes-own-source.mdx @@ -0,0 +1,61 @@ +--- +title: "EFFECT_WRITES_OWN_SOURCE" +category: "Diagnostics" +use_cases: "effect_writes_own_source diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "perf" + - "info" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "EFFECT_WRITES_OWN_SOURCE: Effect's write provably feeds back into its own inputs; `info` for multi-effect rings (attribution enabled)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Effect's write provably feeds back into its own inputs; `info` for multi-effect rings (attribution enabled). + +## What you saw + +- **Kind:** `perf` +- **Severity:** `info` (advisory, delivered on the diagnostics channel and never printed) or `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[EFFECT_WRITES_OWN_SOURCE]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "EFFECT_WRITES_OWN_SOURCE"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +An effect re-ran because of a write it made itself: its callback wrote a value that its own inputs depend on (directly, or through the memos the message names), so the flush settled in two passes and the screen rendered the pre-write value in between. +The effect converged — an infinite loop throws on its own — which is exactly why nothing else reports it. +Typical shapes: clamping (`if (page() > max()) setPage(max())`), resetting one signal when another changes, filling a default, normalizing input. + +:::note[Performance pathologies (from the attribution engine)] +These fire only while attribution is enabled and describe cost, not incorrect behavior. +The numbers in the message are measurements, not guesses. +::: + +## How to fix it + +The written value is a function of what the effect reads, so it is a memo: `const page = createMemo(() => Math.min(rawPage(), max()))`, or normalize where the source is _written_ (in the setter/handler) instead of correcting it afterwards. +The `info` form names a cycle relayed across several effects (`A` writes `x`, `B` reads `x` and writes `y`, `A` reads `y`) — same repair: derive every relayed value from the original inputs and drop the writes. +The walk follows graph edges only; a write fed back through untracked indirection is not claimed. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `EFFECT_WRITES_OWN_SOURCE` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +`expectRerunBudget(artifact, max, { scope })` bounds how often a named scope may re-run, and `expectNoWaste(artifact)` fails on re-runs whose result was unchanged. +Both read the attribution records, which `captureArtifact` collects unless `attribution: false` is passed. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. +- [Attribution](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution): the engine whose records explain a re-run, a hold, or a waterfall. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/fallback-flash.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/fallback-flash.mdx new file mode 100644 index 000000000..95bfa29b6 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/fallback-flash.mdx @@ -0,0 +1,62 @@ +--- +title: "FALLBACK_FLASH" +category: "Diagnostics" +use_cases: "fallback_flash diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "responsiveness" + - "info" + - "reference" + - "v2" +version: "2.0" +description: "FALLBACK_FLASH: A `Loading` fallback showed for under 150ms — feedback for a wait too short to need it (attribution enabled)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +A `Loading` fallback showed for under 150ms — feedback for a wait too short to need it (attribution enabled). + +## What you saw + +- **Kind:** `responsiveness` +- **Severity:** `info` (advisory, delivered on the diagnostics channel and never printed) + +The console report opens with `[FALLBACK_FLASH]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "FALLBACK_FLASH"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A `Loading` fallback appeared and vanished inside 150ms (`data.shownMs`): the data was nearly there, and the spinner read as a flicker. +This is the other end of [`SILENT_HOLD`](/reference/solid-js/diagnostics/silent-hold) — too much feedback for too little wait — and it is `info`, a count to read (`feedback().fallbacks[] .flashes` has the total per boundary). + +:::note[Responsiveness (from the attribution engine)] +The runtime did the correct thing; the user saw nothing while it did. +These fire only while attribution is enabled. +::: + +## How to fix it + +Repairs: + +- Preload or cache the data so it is present before the boundary asks (`markFlight` a preload started on hover or route match). +- Lift the read above the boundary when the parent already waits, so the child boundary never shows. +- For a boundary that has revealed once, let a later update hold instead of re-showing the fallback — that is the default; a flash on update means `on` is keyed to something that changes more often than the data. + +Do NOT add a minimum-display timer to the fallback; that trades a flicker for a wait. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `FALLBACK_FLASH` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +The assertion counts an `info` event like any other, so a scenario that accepts this one lists the code in its `allow` option. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. +- [Attribution](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution): the engine whose records explain a re-run, a hold, or a waterfall. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/flush-in-effect-callback.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/flush-in-effect-callback.mdx new file mode 100644 index 000000000..097468e12 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/flush-in-effect-callback.mdx @@ -0,0 +1,50 @@ +--- +title: "FLUSH_IN_EFFECT_CALLBACK" +category: "Diagnostics" +use_cases: "flush_in_effect_callback diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "lifecycle" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "FLUSH_IN_EFFECT_CALLBACK: `flush()` from an effect callback (no-op; the drain is already running)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +`flush()` from an effect callback (no-op; the drain is already running). + +## What you saw + +- **Kind:** `lifecycle` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[FLUSH_IN_EFFECT_CALLBACK]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "FLUSH_IN_EFFECT_CALLBACK"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +`flush()` was called inside an effect callback, where it is a no-op (the flush running effects is already in progress). +Writes made there are handled in the same flush's continuation. + +## How to fix it + +If you truly need a drain afterwards, defer it: `queueMicrotask(() => flush())`. +Usually the right fix is deleting the call. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `FLUSH_IN_EFFECT_CALLBACK` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/frame-marker-corrupted.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/frame-marker-corrupted.mdx new file mode 100644 index 000000000..f8b07e62e --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/frame-marker-corrupted.mdx @@ -0,0 +1,56 @@ +--- +title: "FRAME_MARKER_CORRUPTED" +category: "Diagnostics" +use_cases: "frame_marker_corrupted diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "ssr" + - "error" + - "reference" + - "v2" +version: "2.0" +description: "FRAME_MARKER_CORRUPTED: Frame slot range missing its end marker on the client — nesting or an HTML rewriter (observe + dev)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Frame slot range missing its end marker on the client — nesting or an HTML rewriter (observe + dev). + +## What you saw + +- **Kind:** `ssr` +- **Severity:** `error` (broken behavior) + +The console report opens with `[FRAME_MARKER_CORRUPTED]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "FRAME_MARKER_CORRUPTED"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +(Client.) A frame's slot start marker has no end marker among its siblings: invalid HTML nesting split the range when the browser parsed it (a block element inside `

`, a `` outside a table), or a CDN/minifier stripped the comment. + +:::note[Server rendering] +The server runtime reports on the same channel, with the same `in › ` line. +Two groups. +**Findings** ([`SSR_RENDER_ERROR_CONTAINED`](/reference/solid-js/diagnostics/ssr-render-error-contained), [`SSR_SUBTREE_ABANDONED`](/reference/solid-js/diagnostics/ssr-subtree-abandoned), [`SSR_STREAM_ABANDONED`](/reference/solid-js/diagnostics/ssr-stream-abandoned), [`LATE_HEADER_WRITE`](/reference/solid-js/diagnostics/late-header-write), [`DYNAMIC_ASYNC_COMPONENT`](/reference/solid-js/diagnostics/dynamic-async-component), [`SERVER_ERROR_SANITIZED`](/reference/solid-js/diagnostics/server-error-sanitized), `FRAME_MARKER_CORRUPTED`) are facts about a render that exist in observe builds too — an APM sees them in production; in dev they print. +**Checks** (the rest, [`SSR_BOUNDARY_WATERFALL`](/reference/solid-js/diagnostics/ssr-boundary-waterfall) and [`SSR_CLIENT_CONTENT_MASKED`](/reference/solid-js/diagnostics/ssr-client-content-masked) included) are dev-only guidance. +A captured artifact from a server render carries both. +::: + +## How to fix it + +Fix the nesting in the server component's markup; serve frame documents with `Cache-Control: no-transform`. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `FRAME_MARKER_CORRUPTED` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/graph-growth.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/graph-growth.mdx new file mode 100644 index 000000000..7fdb405b9 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/graph-growth.mdx @@ -0,0 +1,73 @@ +--- +title: "GRAPH_GROWTH" +category: "Diagnostics" +use_cases: "graph_growth diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "perf" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "GRAPH_GROWTH: The live graph and what outlived each navigation climbed at a route's settle on 3 visits to 1.25× — something each visit leaves behind (attribution enabled)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +The live graph and what outlived each navigation climbed at a route's settle on 3 visits to 1.25× — something each visit leaves behind (attribution enabled). + +## What you saw + +- **Kind:** `perf` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[GRAPH_GROWTH]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "GRAPH_GROWTH"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +The live graph, counted when each navigation settles, got bigger on every one of the last `data.history.length` visits to `data.route`, and so did the part of it that outlived each of those navigations (`data.survived`, beside `data.history`: what was alive as this or an earlier navigation began and still is) — something each visit creates is never disposed. +A page that is merely bigger on one visit than the last does not report: its content is new at each arrival, so it is not in `data.survived`. +Long-lived module state (a global store, the HMR registry signal of each component in dev) counts the same at every settle once a navigation has met it, so it never climbs. +Navigations that only change a route's params or query continue the visit and are not compared. +This is the leak a heap snapshot finds; the engine finds it by measuring the reactive graph as each navigation opens and at its settle. +`data.grew` says which measure climbed, and that names the leak: `owners` is an undisposed root or a Portal mounted per visit; `computations` with `owners` flat is an effect or memo created with no owner, kept alive only by the sources it reads; `edges` alone is a subscription per visit to something long-lived. +The count is the whole app's, so `data.routes` names every route seen while it climbed; the culprit is on one of them. + +:::note[Performance pathologies (from the attribution engine)] +These fire only while attribution is enabled and describe cost, not incorrect behavior. +The numbers in the message are measurements, not guesses. +::: + +## How to fix it + +Look for, in order: + +- A `createRoot()` in an effect, an event handler, or a module-level helper whose disposer is dropped. Return it from `onSettled` (the component's setup-and-teardown), or call it in `onCleanup`. +- A subscription registered outside the owner that should tear it down — an `addEventListener`, an observer, a store subscription — made in a plain function rather than under the component. Move it under the owner and pair it with `onCleanup`. +- A `Portal`, a panel or a modal mounted on each visit into a long-lived root, never removed when the route leaves. +- A cache (entities kept by key, each under its own root) that only ever adds. Its entries outlive every navigation, so to the walk it is the same growth as a leak, and it is: memory that never comes back. Bound it or evict entries no visit still reads; an evicting cache stays quiet. + +Do NOT reach for `dispose()` on the app root or a periodic sweep; the fix is ownership — create the thing under the owner whose lifetime it should share. +`graphSize()` from `solid-js/attribution` gives the count on demand; `OBSERVE.records.subscribe("graph", …)` gives it at every navigation's settle. + +The initial route declaration (`initial: true`) emits a `graph` record but is excluded from growth comparisons because it settles before route content mounts. +Later visits to that route count as usual. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `GRAPH_GROWTH` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +`expectRerunBudget(artifact, max, { scope })` bounds how often a named scope may re-run, and `expectNoWaste(artifact)` fails on re-runs whose result was unchanged. +Both read the attribution records, which `captureArtifact` collects unless `attribution: false` is passed. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. +- [Attribution](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution): the engine whose records explain a re-run, a hold, or a waterfall. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/head-tag-invalid.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/head-tag-invalid.mdx new file mode 100644 index 000000000..8f9aa161b --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/head-tag-invalid.mdx @@ -0,0 +1,56 @@ +--- +title: "HEAD_TAG_INVALID" +category: "Diagnostics" +use_cases: "head_tag_invalid diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "head" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "HEAD_TAG_INVALID: `useHead` registration the render could not honor; `data.reason` names the rule (dev)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +`useHead` registration the render could not honor; `data.reason` names the rule (dev). + +## What you saw + +- **Kind:** `head` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[HEAD_TAG_INVALID]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "HEAD_TAG_INVALID"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A `useHead` registration the render could not honor; `data.reason` names the rule. + +:::note[Server rendering] +The server runtime reports on the same channel, with the same `in › ` line. +Two groups. +**Findings** ([`SSR_RENDER_ERROR_CONTAINED`](/reference/solid-js/diagnostics/ssr-render-error-contained), [`SSR_SUBTREE_ABANDONED`](/reference/solid-js/diagnostics/ssr-subtree-abandoned), [`SSR_STREAM_ABANDONED`](/reference/solid-js/diagnostics/ssr-stream-abandoned), [`LATE_HEADER_WRITE`](/reference/solid-js/diagnostics/late-header-write), [`DYNAMIC_ASYNC_COMPONENT`](/reference/solid-js/diagnostics/dynamic-async-component), [`SERVER_ERROR_SANITIZED`](/reference/solid-js/diagnostics/server-error-sanitized), [`FRAME_MARKER_CORRUPTED`](/reference/solid-js/diagnostics/frame-marker-corrupted)) are facts about a render that exist in observe builds too — an APM sees them in production; in dev they print. +**Checks** (the rest, [`SSR_BOUNDARY_WATERFALL`](/reference/solid-js/diagnostics/ssr-boundary-waterfall) and [`SSR_CLIENT_CONTENT_MASKED`](/reference/solid-js/diagnostics/ssr-client-content-masked) included) are dev-only guidance. +A captured artifact from a server render carries both. +::: + +## How to fix it + +`non-head-tag`/`invalid-attribute`: only head elements and their attributes; `props-error`/`group-error`: the props function threw — fix the throw; `after-shell-flush`: register the tag before the first flush (above the `` whose fallback shipped); `outside-render`: call `useHead` from a component body; `duplicate-title`: one `` per group, the last wins. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `HEAD_TAG_INVALID` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/hot-scope-fanout.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/hot-scope-fanout.mdx new file mode 100644 index 000000000..d94bac2a1 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/hot-scope-fanout.mdx @@ -0,0 +1,56 @@ +--- +title: "HOT_SCOPE_FANOUT" +category: "Diagnostics" +use_cases: "hot_scope_fanout diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "perf" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "HOT_SCOPE_FANOUT: 5+/50+/500+ scopes hot from one root cause (attribution enabled)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +5+/50+/500+ scopes hot from one root cause (attribution enabled). + +## What you saw + +- **Kind:** `perf` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[HOT_SCOPE_FANOUT]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "HOT_SCOPE_FANOUT"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +Many scopes went hot from ONE root cause (named in the message) — the aggregate form of [`HOT_SCOPE_RERUNS`](/reference/solid-js/diagnostics/hot-scope-reruns), emitted so a single culprit doesn't produce one warning per victim. + +:::note[Performance pathologies (from the attribution engine)] +These fire only while attribution is enabled and describe cost, not incorrect behavior. +The numbers in the message are measurements, not guesses. +::: + +## How to fix it + +Fix the cause, not the scopes: if consumers ask keyed questions of it (every row vs. one selected id), invert it into a store used as a map keyed by id; if it's a per-frame value, gate it behind a slower derivation. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `HOT_SCOPE_FANOUT` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +`expectRerunBudget(artifact, max, { scope })` bounds how often a named scope may re-run, and `expectNoWaste(artifact)` fails on re-runs whose result was unchanged. +Both read the attribution records, which `captureArtifact` collects unless `attribution: false` is passed. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. +- [Attribution](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution): the engine whose records explain a re-run, a hold, or a waterfall. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/hot-scope-reruns.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/hot-scope-reruns.mdx new file mode 100644 index 000000000..52ba576c6 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/hot-scope-reruns.mdx @@ -0,0 +1,56 @@ +--- +title: "HOT_SCOPE_RERUNS" +category: "Diagnostics" +use_cases: "hot_scope_reruns diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "perf" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "HOT_SCOPE_RERUNS: 120+ re-runs of one scope in 1s (attribution enabled)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +120+ re-runs of one scope in 1s (attribution enabled). + +## What you saw + +- **Kind:** `perf` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[HOT_SCOPE_RERUNS]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "HOT_SCOPE_RERUNS"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A scope re-ran far more often than any UI cadence justifies — a hot signal (often per-frame or per-event) is leaking into it. + +:::note[Performance pathologies (from the attribution engine)] +These fire only while attribution is enabled and describe cost, not incorrect behavior. +The numbers in the message are measurements, not guesses. +::: + +## How to fix it + +The message names the latest cause; either move that read out of the scope or derive a slower value (e.g. a memo with an equality gate) between them. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `HOT_SCOPE_RERUNS` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +`expectRerunBudget(artifact, max, { scope })` bounds how often a named scope may re-run, and `expectNoWaste(artifact)` fails on re-runs whose result was unchanged. +Both read the attribution records, which `captureArtifact` collects unless `attribution: false` is passed. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. +- [Attribution](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution): the engine whose records explain a re-run, a hold, or a waterfall. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/hot-scope-time.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/hot-scope-time.mdx new file mode 100644 index 000000000..a2cff6ad7 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/hot-scope-time.mdx @@ -0,0 +1,56 @@ +--- +title: "HOT_SCOPE_TIME" +category: "Diagnostics" +use_cases: "hot_scope_time diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "perf" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "HOT_SCOPE_TIME: 8ms+ self-time in one scope in 1s (attribution enabled)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +8ms+ self-time in one scope in 1s (attribution enabled). + +## What you saw + +- **Kind:** `perf` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[HOT_SCOPE_TIME]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "HOT_SCOPE_TIME"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A scope's summed compute time blew its per-window budget — a few-but-expensive scope that run counts miss. + +:::note[Performance pathologies (from the attribution engine)] +These fire only while attribution is enabled and describe cost, not incorrect behavior. +The numbers in the message are measurements, not guesses. +::: + +## How to fix it + +Profile what it computes; usually the fix is memoizing sub-derivations or moving work off the reactive path. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `HOT_SCOPE_TIME` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +`expectRerunBudget(artifact, max, { scope })` bounds how often a named scope may re-run, and `expectNoWaste(artifact)` fails on re-runs whose result was unchanged. +Both read the attribution records, which `captureArtifact` collects unless `attribution: false` is passed. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. +- [Attribution](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution): the engine whose records explain a re-run, a hold, or a waterfall. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/huge-fan-in.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/huge-fan-in.mdx new file mode 100644 index 000000000..8a7faeeb4 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/huge-fan-in.mdx @@ -0,0 +1,58 @@ +--- +title: "HUGE_FAN_IN" +category: "Diagnostics" +use_cases: "huge_fan_in diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "graph" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "HUGE_FAN_IN: One recompute tracked 2000 sources (always on)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +One recompute tracked 2000 sources (always on). + +## What you saw + +- **Kind:** `graph` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[HUGE_FAN_IN]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "HUGE_FAN_IN"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +The always-on backstop: one computation tracked 2000 or more sources in a single pass, so it re-runs when any of them change. +The message names the computation and the count (`data: { count }`); it does NOT list the sources. + +:::note[Performance pathologies (from the attribution engine)] +These fire only while attribution is enabled and describe cost, not incorrect behavior. +The numbers in the message are measurements, not guesses. +::: + +## How to fix it + +To see which sources, enable the attribution engine and read [`WIDE_SCOPE_DEPS`](/reference/solid-js/diagnostics/wide-scope-deps), which fires earlier (200 sources) and names up to 12 of them. +The repair is the same: narrow the reads or split the derivation into smaller memos that each track only what they need. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `HUGE_FAN_IN` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +`expectRerunBudget(artifact, max, { scope })` bounds how often a named scope may re-run after a write. +It reads the attribution records, which `captureArtifact` collects unless `attribution: false` is passed. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. +- [Attribution](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution): the engine whose records explain a re-run, a hold, or a waterfall. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/huge-fan-out.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/huge-fan-out.mdx new file mode 100644 index 000000000..77feccd66 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/huge-fan-out.mdx @@ -0,0 +1,59 @@ +--- +title: "HUGE_FAN_OUT" +category: "Diagnostics" +use_cases: "huge_fan_out diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "graph" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "HUGE_FAN_OUT: One change reached 2000 live subscribers (always on), or 250 — `fanOut` — on a stamped root write (attribution enabled)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +One change reached 2000 live subscribers (always on), or 250 — `fanOut` — on a stamped root write (attribution enabled). + +## What you saw + +- **Kind:** `graph` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[HUGE_FAN_OUT]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "HUGE_FAN_OUT"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +One value has very many subscribers, so a single change re-runs all of them (`data.count`). +Always on from 2000; while attribution is enabled the same code fires from `fanOut` (default 250) on a stamped root write, and `data.write` says which — a `write`, a `refresh`, or an `async` landing. +Classic signature: every row of a list comparing against one selected id. + +:::note[Performance pathologies (from the attribution engine)] +These fire only while attribution is enabled and describe cost, not incorrect behavior. +The numbers in the message are measurements, not guesses. +::: + +## How to fix it + +Invert the question: keep the answer in a store used as a map keyed by id (`selected[row.id]` instead of `row.id === selectedId()`), so each consumer reads its own key and only the keys that flipped update. +When that map is derived from other state, `createProjection` builds it. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `HUGE_FAN_OUT` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +`expectRerunBudget(artifact, max, { scope })` bounds how often a named scope may re-run after a write. +It reads the attribution records, which `captureArtifact` collects unless `attribution: false` is passed. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. +- [Attribution](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution): the engine whose records explain a re-run, a hold, or a waterfall. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/immutable-update-in-store.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/immutable-update-in-store.mdx new file mode 100644 index 000000000..e71e806af --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/immutable-update-in-store.mdx @@ -0,0 +1,59 @@ +--- +title: "IMMUTABLE_UPDATE_IN_STORE" +category: "Diagnostics" +use_cases: "immutable_update_in_store diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "perf" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "IMMUTABLE_UPDATE_IN_STORE: Store container replaced by a mostly-identical copy (attribution enabled)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Store container replaced by a mostly-identical copy (attribution enabled). + +## What you saw + +- **Kind:** `perf` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[IMMUTABLE_UPDATE_IN_STORE]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "IMMUTABLE_UPDATE_IN_STORE"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A store setter replaced a container with a fresh object/array whose leaves are mostly the same values: `draft.user = { ...draft.user, name }`, `draft.items = [...draft.items, x]`, `draft.items = draft.items.filter(…)`. +The store tracks leaves; a fresh container makes every reader of the container's path (anything under `user`) re-run for the one leaf that moved. + +:::note[Performance pathologies (from the attribution engine)] +These fire only while attribution is enabled and describe cost, not incorrect behavior. +The numbers in the message are measurements, not guesses. +::: + +## How to fix it + +Repair: mutate the draft — `draft.user.name = name`, `draft.items.push(x)`, `draft.items.splice(i, 1)` — so only readers of the touched key or index re-run. +Data arriving from outside (a fetch result for the same records) merges with `reconcile(data, "id")(draft.items)`, which keeps identity for records that did not change. +Once per store path; `data.unchanged/total` says how much of the container was carried over. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `IMMUTABLE_UPDATE_IN_STORE` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +`expectRerunBudget(artifact, max, { scope })` bounds how often a named scope may re-run, and `expectNoWaste(artifact)` fails on re-runs whose result was unchanged. +Both read the attribution records, which `captureArtifact` collects unless `attribution: false` is passed. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. +- [Attribution](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution): the engine whose records explain a re-run, a hold, or a waterfall. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/invalid-affects-target.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/invalid-affects-target.mdx new file mode 100644 index 000000000..d52c7e141 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/invalid-affects-target.mdx @@ -0,0 +1,48 @@ +--- +title: "INVALID_AFFECTS_TARGET" +category: "Diagnostics" +use_cases: "invalid_affects_target diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "write" + - "error" + - "reference" + - "v2" +version: "2.0" +description: "INVALID_AFFECTS_TARGET: `affects()` given a key path, or a key on an accessor." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +`affects()` given a key path, or a key on an accessor. + +## What you saw + +- **Kind:** `write` +- **Severity:** `error` (broken behavior) + +The console report opens with `[INVALID_AFFECTS_TARGET]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "INVALID_AFFECTS_TARGET"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +`affects()` expects a Solid source accessor or store node, with at most one optional key (keys only valid on store targets). + +## How to fix it + +Fix the target or drop the extra keys. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `INVALID_AFFECTS_TARGET` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/invalid-refresh-target.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/invalid-refresh-target.mdx new file mode 100644 index 000000000..6d659d8d4 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/invalid-refresh-target.mdx @@ -0,0 +1,48 @@ +--- +title: "INVALID_REFRESH_TARGET" +category: "Diagnostics" +use_cases: "invalid_refresh_target diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "write" + - "error" + - "reference" + - "v2" +version: "2.0" +description: "INVALID_REFRESH_TARGET: `refresh()` target is not a source accessor or refreshable store." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +`refresh()` target is not a source accessor or refreshable store. + +## What you saw + +- **Kind:** `write` +- **Severity:** `error` (broken behavior) + +The console report opens with `[INVALID_REFRESH_TARGET]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "INVALID_REFRESH_TARGET"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +`refresh()` expects a Solid source accessor or refreshable store — not a wrapper function or a derived property read. + +## How to fix it + +Pass the original source. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `INVALID_REFRESH_TARGET` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/invariant-violation.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/invariant-violation.mdx new file mode 100644 index 000000000..997674e3d --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/invariant-violation.mdx @@ -0,0 +1,48 @@ +--- +title: "INVARIANT_VIOLATION" +category: "Diagnostics" +use_cases: "invariant_violation diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "error" + - "error" + - "reference" + - "v2" +version: "2.0" +description: "INVARIANT_VIOLATION: Internal consistency check failed (throws under `__TEST__`, reported in dev)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Internal consistency check failed (throws under `__TEST__`, reported in dev). + +## What you saw + +- **Kind:** `error` +- **Severity:** `error` (broken behavior) + +The console report opens with `[INVARIANT_VIOLATION]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "INVARIANT_VIOLATION"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +The reactive system contradicted itself — an internal bug, not user error. + +## How to fix it + +Report it upstream with a reproduction; do not work around it silently. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `INVARIANT_VIOLATION` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/late-header-write.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/late-header-write.mdx new file mode 100644 index 000000000..dcbc073b6 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/late-header-write.mdx @@ -0,0 +1,57 @@ +--- +title: "LATE_HEADER_WRITE" +category: "Diagnostics" +use_cases: "late_header_write diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "head" + - "error" + - "reference" + - "v2" +version: "2.0" +description: "LATE_HEADER_WRITE: Response header written after the head was sent; dropped (observe + dev; dev throws)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Response header written after the head was sent; dropped (observe + dev; dev throws). + +## What you saw + +- **Kind:** `head` +- **Severity:** `error` (broken behavior) + +The console report opens with `[LATE_HEADER_WRITE]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "LATE_HEADER_WRITE"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A response header was written after the head was sent; the write was dropped (dev throws instead). +`data.method`/`data.name` say which. + +:::note[Server rendering] +The server runtime reports on the same channel, with the same `in <App> › <Page>` line. +Two groups. +**Findings** ([`SSR_RENDER_ERROR_CONTAINED`](/reference/solid-js/diagnostics/ssr-render-error-contained), [`SSR_SUBTREE_ABANDONED`](/reference/solid-js/diagnostics/ssr-subtree-abandoned), [`SSR_STREAM_ABANDONED`](/reference/solid-js/diagnostics/ssr-stream-abandoned), `LATE_HEADER_WRITE`, [`DYNAMIC_ASYNC_COMPONENT`](/reference/solid-js/diagnostics/dynamic-async-component), [`SERVER_ERROR_SANITIZED`](/reference/solid-js/diagnostics/server-error-sanitized), [`FRAME_MARKER_CORRUPTED`](/reference/solid-js/diagnostics/frame-marker-corrupted)) are facts about a render that exist in observe builds too — an APM sees them in production; in dev they print. +**Checks** (the rest, [`SSR_BOUNDARY_WATERFALL`](/reference/solid-js/diagnostics/ssr-boundary-waterfall) and [`SSR_CLIENT_CONTENT_MASKED`](/reference/solid-js/diagnostics/ssr-client-content-masked) included) are dev-only guidance. +A captured artifact from a server render carries both. +::: + +## How to fix it + +Move the write before the first flush — before any `<Loading>` fallback can ship — or before the handler returns; a cookie set from inside a late-streaming component never reaches the browser. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `LATE_HEADER_WRITE` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/lazy-asset-unmapped.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/lazy-asset-unmapped.mdx new file mode 100644 index 000000000..2a5f9bdba --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/lazy-asset-unmapped.mdx @@ -0,0 +1,56 @@ +--- +title: "LAZY_ASSET_UNMAPPED" +category: "Diagnostics" +use_cases: "lazy_asset_unmapped diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "ssr" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "LAZY_ASSET_UNMAPPED: `lazy()` component's client assets could not be resolved for the page (dev)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +`lazy()` component's client assets could not be resolved for the page (dev). + +## What you saw + +- **Kind:** `ssr` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[LAZY_ASSET_UNMAPPED]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "LAZY_ASSET_UNMAPPED"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A `lazy()` component's client chunk could not be resolved for the page (`data.reason`: the resolver threw, or the module has no `$$moduleUrl`), so it is not preloaded and loads late during hydration. + +:::note[Server rendering] +The server runtime reports on the same channel, with the same `in <App> › <Page>` line. +Two groups. +**Findings** ([`SSR_RENDER_ERROR_CONTAINED`](/reference/solid-js/diagnostics/ssr-render-error-contained), [`SSR_SUBTREE_ABANDONED`](/reference/solid-js/diagnostics/ssr-subtree-abandoned), [`SSR_STREAM_ABANDONED`](/reference/solid-js/diagnostics/ssr-stream-abandoned), [`LATE_HEADER_WRITE`](/reference/solid-js/diagnostics/late-header-write), [`DYNAMIC_ASYNC_COMPONENT`](/reference/solid-js/diagnostics/dynamic-async-component), [`SERVER_ERROR_SANITIZED`](/reference/solid-js/diagnostics/server-error-sanitized), [`FRAME_MARKER_CORRUPTED`](/reference/solid-js/diagnostics/frame-marker-corrupted)) are facts about a render that exist in observe builds too — an APM sees them in production; in dev they print. +**Checks** (the rest, [`SSR_BOUNDARY_WATERFALL`](/reference/solid-js/diagnostics/ssr-boundary-waterfall) and [`SSR_CLIENT_CONTENT_MASKED`](/reference/solid-js/diagnostics/ssr-client-content-masked) included) are dev-only guidance. +A captured artifact from a server render carries both. +::: + +## How to fix it + +This is bundler-plugin configuration — the plugin injects `$$moduleUrl` and provides the resolver; check the module is under its scope. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `LAZY_ASSET_UNMAPPED` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/loading-on-outside-hold.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/loading-on-outside-hold.mdx new file mode 100644 index 000000000..3536a1edb --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/loading-on-outside-hold.mdx @@ -0,0 +1,50 @@ +--- +title: "LOADING_ON_OUTSIDE_HOLD" +category: "Diagnostics" +use_cases: "loading_on_outside_hold diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "async" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "LOADING_ON_OUTSIDE_HOLD: `<Loading on>` changed but its data is also read outside the boundary and holds the frame: the fallback can never be seen." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +`<Loading on>` changed but its data is also read outside the boundary and holds the frame: the fallback can never be seen. + +## What you saw + +- **Kind:** `async` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[LOADING_ON_OUTSIDE_HOLD]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "LOADING_ON_OUTSIDE_HOLD"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +`on` re-armed a Loading boundary, but a source it is waiting on is also read outside the boundary and holds the frame. +The frame waits for that same source, so the fallback cannot appear. + +## How to fix it + +Move the outside read under the boundary. +If the fallback should appear immediately beside the held frame, use a `latest()` read in `on`. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `LOADING_ON_OUTSIDE_HOLD` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/long-hold.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/long-hold.mdx new file mode 100644 index 000000000..d2d6199bf --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/long-hold.mdx @@ -0,0 +1,65 @@ +--- +title: "LONG_HOLD" +category: "Diagnostics" +use_cases: "long_hold diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "responsiveness" + - "info" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "LONG_HOLD: Acknowledged hold whose tail (last input → commit) ran 500ms+/1000ms+ (attribution enabled)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Acknowledged hold whose tail (last input → commit) ran 500ms+/1000ms+ (attribution enabled). + +## What you saw + +- **Kind:** `responsiveness` +- **Severity:** `info` (advisory, delivered on the diagnostics channel and never printed) or `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[LONG_HOLD]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "LONG_HOLD"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +The hold WAS acknowledged — an `isPending()` spinner, a `latest()` header, an optimistic value — and still ran on: the time from the user's last input to the commit (`data.tailMs`; `holdMs` is the whole wait when input kept arriving) reached `longHolds.infoMs` (default 500ms), `warn` from `longHolds.warnMs` (default 1000ms, where the user loses the thread). +A hold is the stale-while-revalidate tool: right when the old screen stays useful for the wait (tab switch, sort toggle, a fast page turn). +At this length the old screen has stopped being useful and started being a lie; "loading" glued over it reads as broken. + +:::note[Responsiveness (from the attribution engine)] +The runtime did the correct thing; the user saw nothing while it did. +These fire only while attribution is enabled. +::: + +## How to fix it + +Show a fallback instead: + +- Put the reader behind a `Loading` boundary keyed on what changed: `<Loading on={page()} fallback={<Skeleton />}>`. With `on`, the write commits at once and the fallback shows where the data lands. Without it a boundary that has already revealed keeps the old content — that IS the hold, so wrapping alone changes nothing. +- A boundary that has not revealed yet (a fresh route segment, a keyed subtree) takes the wait the same way; `on` is the switch for one that has. +- If the data itself is the problem, preload it (`markFlight` the kickoff so it is not misread as a waterfall) or cache it so the tail never gets there. + +Do NOT remove the acknowledgement to "fix" this, and do not move the write off the async path. +The measure is the tail, not the lifetime, so a hold that keeps taking input (typing) is judged by each wait, not by the sum. +`feedback().sources[].long`/`longMs` counts these at the table level, acknowledged or not. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `LONG_HOLD` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. +- [Attribution](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution): the engine whose records explain a re-run, a hold, or a waterfall. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/lowercase-event-attribute.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/lowercase-event-attribute.mdx new file mode 100644 index 000000000..d95a79e78 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/lowercase-event-attribute.mdx @@ -0,0 +1,58 @@ +--- +title: "LOWERCASE_EVENT_ATTRIBUTE" +category: "Diagnostics" +use_cases: "lowercase_event_attribute diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "render" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "LOWERCASE_EVENT_ATTRIBUTE: A function was set on a lowercase `on*` (or `on:`) attribute, which is not an event handler in 2.0; use `onXxx` (dev; client)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +A function was set on a lowercase `on*` (or `on:`) attribute, which is not an event handler in 2.0; use `onXxx` (dev; client). + +## What you saw + +- **Kind:** `render` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[LOWERCASE_EVENT_ATTRIBUTE]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "LOWERCASE_EVENT_ATTRIBUTE"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A function was set on an attribute whose name starts with `on` but is not an event handler (`data.name`): a lowercase `onclick`, or a leftover 1.x `on:click`. +In 2.0 only `on` + an uppercase letter binds an event; anything else is a plain attribute, so the function was stringified into it and never runs. +Once per attribute name. + +:::note[Server rendering] +The server runtime reports on the same channel, with the same `in <App> › <Page>` line. +Two groups. +**Findings** ([`SSR_RENDER_ERROR_CONTAINED`](/reference/solid-js/diagnostics/ssr-render-error-contained), [`SSR_SUBTREE_ABANDONED`](/reference/solid-js/diagnostics/ssr-subtree-abandoned), [`SSR_STREAM_ABANDONED`](/reference/solid-js/diagnostics/ssr-stream-abandoned), [`LATE_HEADER_WRITE`](/reference/solid-js/diagnostics/late-header-write), [`DYNAMIC_ASYNC_COMPONENT`](/reference/solid-js/diagnostics/dynamic-async-component), [`SERVER_ERROR_SANITIZED`](/reference/solid-js/diagnostics/server-error-sanitized), [`FRAME_MARKER_CORRUPTED`](/reference/solid-js/diagnostics/frame-marker-corrupted)) are facts about a render that exist in observe builds too — an APM sees them in production; in dev they print. +**Checks** (the rest, [`SSR_BOUNDARY_WATERFALL`](/reference/solid-js/diagnostics/ssr-boundary-waterfall) and [`SSR_CLIENT_CONTENT_MASKED`](/reference/solid-js/diagnostics/ssr-client-content-masked) included) are dev-only guidance. +A captured artifact from a server render carries both. +::: + +## How to fix it + +Fix: use the camelCase handler (`data.handler` — `onclick={save}` → `onClick={save}`); keep a lowercase name only for a string inline-handler attribute. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `LOWERCASE_EVENT_ATTRIBUTE` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/missing-effect-fn.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/missing-effect-fn.mdx new file mode 100644 index 000000000..46b26d5ef --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/missing-effect-fn.mdx @@ -0,0 +1,49 @@ +--- +title: "MISSING_EFFECT_FN" +category: "Diagnostics" +use_cases: "missing_effect_fn diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "lifecycle" + - "error" + - "reference" + - "v2" +version: "2.0" +description: "MISSING_EFFECT_FN: `createEffect` called without the effect function." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +`createEffect` called without the effect function. + +## What you saw + +- **Kind:** `lifecycle` +- **Severity:** `error` (broken behavior) + +The console report opens with `[MISSING_EFFECT_FN]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "MISSING_EFFECT_FN"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +`createEffect(compute)` with a single argument is not supported. + +## How to fix it + +Split the work: `createEffect(() => signal(), value => doWork(value))`. +For a derived value use `createMemo`; for a one-shot side effect call the function. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `MISSING_EFFECT_FN` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/no-owner-boundary.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/no-owner-boundary.mdx new file mode 100644 index 000000000..d805ac14c --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/no-owner-boundary.mdx @@ -0,0 +1,49 @@ +--- +title: "NO_OWNER_BOUNDARY" +category: "Diagnostics" +use_cases: "no_owner_boundary diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "lifecycle" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "NO_OWNER_BOUNDARY: Boundary created without owner." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Boundary created without owner. + +## What you saw + +- **Kind:** `lifecycle` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[NO_OWNER_BOUNDARY]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "NO_OWNER_BOUNDARY"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A boundary was created outside any reactive context. +Like an unowned effect, it needs an owner to dispose it. + +## How to fix it + +Create it under a component or `createRoot`, or attach it to an existing owner with `runWithOwner`. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `NO_OWNER_BOUNDARY` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/no-owner-cleanup.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/no-owner-cleanup.mdx new file mode 100644 index 000000000..650c461f0 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/no-owner-cleanup.mdx @@ -0,0 +1,48 @@ +--- +title: "NO_OWNER_CLEANUP" +category: "Diagnostics" +use_cases: "no_owner_cleanup diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "lifecycle" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "NO_OWNER_CLEANUP: `onCleanup` called without owner." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +`onCleanup` called without owner. + +## What you saw + +- **Kind:** `lifecycle` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[NO_OWNER_CLEANUP]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "NO_OWNER_CLEANUP"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +`onCleanup` was called outside a reactive context; the callback will never run. + +## How to fix it + +Same fix: register it under an owner. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `NO_OWNER_CLEANUP` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/no-owner-effect.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/no-owner-effect.mdx new file mode 100644 index 000000000..d997ad187 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/no-owner-effect.mdx @@ -0,0 +1,48 @@ +--- +title: "NO_OWNER_EFFECT" +category: "Diagnostics" +use_cases: "no_owner_effect diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "lifecycle" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "NO_OWNER_EFFECT: Effect created without reactive owner." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Effect created without reactive owner. + +## What you saw + +- **Kind:** `lifecycle` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[NO_OWNER_EFFECT]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "NO_OWNER_EFFECT"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +An effect was created outside any reactive context (no root, no component). + +## How to fix it + +Create it under a component or `createRoot`, or use `runWithOwner` to attach it to an existing owner so it can be disposed. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `NO_OWNER_EFFECT` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/optimistic-reverted.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/optimistic-reverted.mdx new file mode 100644 index 000000000..ddb52f736 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/optimistic-reverted.mdx @@ -0,0 +1,64 @@ +--- +title: "OPTIMISTIC_REVERTED" +category: "Diagnostics" +use_cases: "optimistic_reverted diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "responsiveness" + - "info" + - "reference" + - "v2" +version: "2.0" +description: "OPTIMISTIC_REVERTED: An optimistic value the screen showed was replaced by a different one — reverted at settle, or superseded by the truth (attribution enabled)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +An optimistic value the screen showed was replaced by a different one — reverted at settle, or superseded by the truth (attribution enabled). + +## What you saw + +- **Kind:** `responsiveness` +- **Severity:** `info` (advisory, delivered on the diagnostics channel and never printed) + +The console report opens with `[OPTIMISTIC_REVERTED]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "OPTIMISTIC_REVERTED"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +An optimistic value the screen showed was replaced by a different one: the person saw the guess, then the correction. +`data.how` says which road: `reverted` — the action ended without the guess coming true and the value snapped back to what it covered; `superseded` — the real value landed and differed from the guess (the server counted differently, rejected part of the input). +Both are the runtime doing exactly what optimistic UI promises, which is why this is `info` and never reaches the console; it is a count to read, not a warning to silence. + +:::note[Responsiveness (from the attribution engine)] +The runtime did the correct thing; the user saw nothing while it did. +These fire only while attribution is enabled. +::: + +## How to fix it + +Read it two ways: + +- One source, many reverts: the failure is common and the UI is hiding it. Show the failure where the value renders — catch in the action and write an error the UI reads, or put the reader behind an `Errored` boundary — so the snap-back is explained rather than silent. +- One source, many supersessions: the guess is systematically wrong for this input (it ignores what the server adds or normalizes). Guess less: write only the part of the value the client can know, or none, and read `isPending()` for the rest. + +Do NOT "fix" a revert by writing the guess to the authoritative signal so it cannot revert — that is not optimistic UI, it is lying to the user about what happened. +The runtime's own optimistic nodes — the `isPending()`/`latest()` companions that acknowledge a hold — are never reported; only values you wrote optimistically are. +Optimistic stores are not covered yet; a store-path revert shows up only as the reader's re-run. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `OPTIMISTIC_REVERTED` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +The assertion counts an `info` event like any other, so a scenario that accepts this one lists the code in its `allow` option. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. +- [Attribution](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution): the engine whose records explain a re-run, a hold, or a waterfall. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/pending-async-forbidden-scope.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/pending-async-forbidden-scope.mdx new file mode 100644 index 000000000..831cbf625 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/pending-async-forbidden-scope.mdx @@ -0,0 +1,48 @@ +--- +title: "PENDING_ASYNC_FORBIDDEN_SCOPE" +category: "Diagnostics" +use_cases: "pending_async_forbidden_scope diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "async" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "PENDING_ASYNC_FORBIDDEN_SCOPE: Pending async read in trackedEffect/onSettled." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Pending async read in trackedEffect/onSettled. + +## What you saw + +- **Kind:** `async` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[PENDING_ASYNC_FORBIDDEN_SCOPE]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "PENDING_ASYNC_FORBIDDEN_SCOPE"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A pending async value was read inside `createTrackedEffect` or `onSettled`, which cannot suspend — it throws. + +## How to fix it + +Use `createEffect` (separate compute and effect phases) which is async-aware: put the async read in the compute function. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `PENDING_ASYNC_FORBIDDEN_SCOPE` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/pending-async-untracked-read.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/pending-async-untracked-read.mdx new file mode 100644 index 000000000..1d6cd4d63 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/pending-async-untracked-read.mdx @@ -0,0 +1,48 @@ +--- +title: "PENDING_ASYNC_UNTRACKED_READ" +category: "Diagnostics" +use_cases: "pending_async_untracked_read diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "async" + - "error" + - "reference" + - "v2" +version: "2.0" +description: "PENDING_ASYNC_UNTRACKED_READ: Reading pending async outside tracking scope." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Reading pending async outside tracking scope. + +## What you saw + +- **Kind:** `async` +- **Severity:** `error` (broken behavior) + +The console report opens with `[PENDING_ASYNC_UNTRACKED_READ]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "PENDING_ASYNC_UNTRACKED_READ"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +The same shape as [`STRICT_READ_UNTRACKED`](/reference/solid-js/diagnostics/strict-read-untracked), but the value was a pending async computation, so there is nothing to read yet at all. + +## How to fix it + +Async values must be read where tracking can suspend and resume: JSX, a memo, or an effect's compute function. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `PENDING_ASYNC_UNTRACKED_READ` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/preload-descriptor-invalid.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/preload-descriptor-invalid.mdx new file mode 100644 index 000000000..ecd03327f --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/preload-descriptor-invalid.mdx @@ -0,0 +1,56 @@ +--- +title: "PRELOAD_DESCRIPTOR_INVALID" +category: "Diagnostics" +use_cases: "preload_descriptor_invalid diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "head" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: 'PRELOAD_DESCRIPTOR_INVALID: `registerAsset("preload")` descriptor broke a field rule; link dropped or field ignored (dev).' +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +`registerAsset("preload")` descriptor broke a field rule; link dropped or field ignored (dev). + +## What you saw + +- **Kind:** `head` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[PRELOAD_DESCRIPTOR_INVALID]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "PRELOAD_DESCRIPTOR_INVALID"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A `registerAsset("preload")` descriptor broke a field rule (`data.field` names it — a missing `as`, a non-string `href`, a malformed `imagesrcset`) and the link was dropped or the field ignored. + +:::note[Server rendering] +The server runtime reports on the same channel, with the same `in <App> › <Page>` line. +Two groups. +**Findings** ([`SSR_RENDER_ERROR_CONTAINED`](/reference/solid-js/diagnostics/ssr-render-error-contained), [`SSR_SUBTREE_ABANDONED`](/reference/solid-js/diagnostics/ssr-subtree-abandoned), [`SSR_STREAM_ABANDONED`](/reference/solid-js/diagnostics/ssr-stream-abandoned), [`LATE_HEADER_WRITE`](/reference/solid-js/diagnostics/late-header-write), [`DYNAMIC_ASYNC_COMPONENT`](/reference/solid-js/diagnostics/dynamic-async-component), [`SERVER_ERROR_SANITIZED`](/reference/solid-js/diagnostics/server-error-sanitized), [`FRAME_MARKER_CORRUPTED`](/reference/solid-js/diagnostics/frame-marker-corrupted)) are facts about a render that exist in observe builds too — an APM sees them in production; in dev they print. +**Checks** (the rest, [`SSR_BOUNDARY_WATERFALL`](/reference/solid-js/diagnostics/ssr-boundary-waterfall) and [`SSR_CLIENT_CONTENT_MASKED`](/reference/solid-js/diagnostics/ssr-client-content-masked) included) are dev-only guidance. +A captured artifact from a server render carries both. +::: + +## How to fix it + +Fix the descriptor at its source; the values are usually the bundler manifest's. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `PRELOAD_DESCRIPTOR_INVALID` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/primitive-in-effect-callback.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/primitive-in-effect-callback.mdx new file mode 100644 index 000000000..8866eed63 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/primitive-in-effect-callback.mdx @@ -0,0 +1,50 @@ +--- +title: "PRIMITIVE_IN_EFFECT_CALLBACK" +category: "Diagnostics" +use_cases: "primitive_in_effect_callback diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "lifecycle" + - "error" + - "reference" + - "v2" +version: "2.0" +description: "PRIMITIVE_IN_EFFECT_CALLBACK: Memo, effect, or root created inside an effect callback or its cleanup, where there is no ambient owner (throws)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Memo, effect, or root created inside an effect callback or its cleanup, where there is no ambient owner (throws). + +## What you saw + +- **Kind:** `lifecycle` +- **Severity:** `error` (broken behavior) + +The console report opens with `[PRIMITIVE_IN_EFFECT_CALLBACK]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "PRIMITIVE_IN_EFFECT_CALLBACK"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A memo, effect, or root was created without an owner inside an effect callback or its cleanup. +The effect phase has no ambient owner, so those computations cannot be disposed or held by a parent. + +## How to fix it + +Create them in the compute phase, or attach them to an existing owner with `runWithOwner`. +Creating a signal alone does not trigger this error. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `PRIMITIVE_IN_EFFECT_CALLBACK` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/primitive-in-forbidden-scope.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/primitive-in-forbidden-scope.mdx new file mode 100644 index 000000000..890f28997 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/primitive-in-forbidden-scope.mdx @@ -0,0 +1,48 @@ +--- +title: "PRIMITIVE_IN_FORBIDDEN_SCOPE" +category: "Diagnostics" +use_cases: "primitive_in_forbidden_scope diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "lifecycle" + - "error" + - "reference" + - "v2" +version: "2.0" +description: "PRIMITIVE_IN_FORBIDDEN_SCOPE: Reactive primitive created inside trackedEffect/onSettled." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Reactive primitive created inside trackedEffect/onSettled. + +## What you saw + +- **Kind:** `lifecycle` +- **Severity:** `error` (broken behavior) + +The console report opens with `[PRIMITIVE_IN_FORBIDDEN_SCOPE]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "PRIMITIVE_IN_FORBIDDEN_SCOPE"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +Reactive primitives cannot be created inside `createTrackedEffect` or owner-backed `onSettled`. + +## How to fix it + +Hoist the primitive to the component body. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `PRIMITIVE_IN_FORBIDDEN_SCOPE` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/reactive-write-in-owned-scope.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/reactive-write-in-owned-scope.mdx new file mode 100644 index 000000000..0934756c5 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/reactive-write-in-owned-scope.mdx @@ -0,0 +1,50 @@ +--- +title: "REACTIVE_WRITE_IN_OWNED_SCOPE" +category: "Diagnostics" +use_cases: "reactive_write_in_owned_scope diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "write" + - "error" + - "reference" + - "v2" +version: "2.0" +description: "REACTIVE_WRITE_IN_OWNED_SCOPE: Reactive write/invalidation inside component/computation." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Reactive write/invalidation inside component/computation. + +## What you saw + +- **Kind:** `write` +- **Severity:** `error` (broken behavior) + +The console report opens with `[REACTIVE_WRITE_IN_OWNED_SCOPE]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "REACTIVE_WRITE_IN_OWNED_SCOPE"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A signal/store write (or `refresh()`) executed during an owned scope — a component body or a computation. +Pure scopes must not cause state changes. + +## How to fix it + +Move the write to an event handler or effect phase. +If the write is genuinely intentional initialization, pass the `ownedWrite` option on the setter call. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `REACTIVE_WRITE_IN_OWNED_SCOPE` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/reactivity-halted.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/reactivity-halted.mdx new file mode 100644 index 000000000..16a6ba337 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/reactivity-halted.mdx @@ -0,0 +1,48 @@ +--- +title: "REACTIVITY_HALTED" +category: "Diagnostics" +use_cases: "reactivity_halted diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "error" + - "error" + - "reference" + - "v2" +version: "2.0" +description: "REACTIVITY_HALTED: Uncaught error escaped every boundary; scheduling stopped (reported, cause to `reportError`)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Uncaught error escaped every boundary; scheduling stopped (reported, cause to `reportError`). + +## What you saw + +- **Kind:** `error` +- **Severity:** `error` (broken behavior) + +The console report opens with `[REACTIVITY_HALTED]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "REACTIVITY_HALTED"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +An earlier uncaught error halted the reactive system; subsequent updates are ignored. + +## How to fix it + +Do not treat this code as the bug — find the original error above it (or add an error boundary with `<Errored>`) and fix that. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `REACTIVITY_HALTED` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/reveal-in-render-to-string.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/reveal-in-render-to-string.mdx new file mode 100644 index 000000000..0582e819d --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/reveal-in-render-to-string.mdx @@ -0,0 +1,56 @@ +--- +title: "REVEAL_IN_RENDER_TO_STRING" +category: "Diagnostics" +use_cases: "reveal_in_render_to_string diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "ssr" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "REVEAL_IN_RENDER_TO_STRING: Nested `<Reveal>` with collapsed/together under `renderToString` (dev)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Nested `<Reveal>` with collapsed/together under `renderToString` (dev). + +## What you saw + +- **Kind:** `ssr` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[REVEAL_IN_RENDER_TO_STRING]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "REVEAL_IN_RENDER_TO_STRING"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +Nested `<Reveal>` with `collapsed`/`together` needs a stream to coordinate on; `renderToString` has none. + +:::note[Server rendering] +The server runtime reports on the same channel, with the same `in <App> › <Page>` line. +Two groups. +**Findings** ([`SSR_RENDER_ERROR_CONTAINED`](/reference/solid-js/diagnostics/ssr-render-error-contained), [`SSR_SUBTREE_ABANDONED`](/reference/solid-js/diagnostics/ssr-subtree-abandoned), [`SSR_STREAM_ABANDONED`](/reference/solid-js/diagnostics/ssr-stream-abandoned), [`LATE_HEADER_WRITE`](/reference/solid-js/diagnostics/late-header-write), [`DYNAMIC_ASYNC_COMPONENT`](/reference/solid-js/diagnostics/dynamic-async-component), [`SERVER_ERROR_SANITIZED`](/reference/solid-js/diagnostics/server-error-sanitized), [`FRAME_MARKER_CORRUPTED`](/reference/solid-js/diagnostics/frame-marker-corrupted)) are facts about a render that exist in observe builds too — an APM sees them in production; in dev they print. +**Checks** (the rest, [`SSR_BOUNDARY_WATERFALL`](/reference/solid-js/diagnostics/ssr-boundary-waterfall) and [`SSR_CLIENT_CONTENT_MASKED`](/reference/solid-js/diagnostics/ssr-client-content-masked) included) are dev-only guidance. +A captured artifact from a server render carries both. +::: + +## How to fix it + +Use `renderToStream`, or drop the ordering. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `REVEAL_IN_RENDER_TO_STRING` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/run-with-disposed-owner.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/run-with-disposed-owner.mdx new file mode 100644 index 000000000..78082039f --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/run-with-disposed-owner.mdx @@ -0,0 +1,48 @@ +--- +title: "RUN_WITH_DISPOSED_OWNER" +category: "Diagnostics" +use_cases: "run_with_disposed_owner diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "owner" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "RUN_WITH_DISPOSED_OWNER: `runWithOwner` with disposed owner." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +`runWithOwner` with disposed owner. + +## What you saw + +- **Kind:** `owner` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[RUN_WITH_DISPOSED_OWNER]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "RUN_WITH_DISPOSED_OWNER"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +`runWithOwner` received an owner that was already disposed; anything created inside leaks. + +## How to fix it + +This usually means a stale owner captured across an await or stored past its lifetime — re-capture the owner at call time or guard with `isDisposed()`. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `RUN_WITH_DISPOSED_OWNER` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/server-error-sanitized.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/server-error-sanitized.mdx new file mode 100644 index 000000000..97f63cfae --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/server-error-sanitized.mdx @@ -0,0 +1,60 @@ +--- +title: "SERVER_ERROR_SANITIZED" +category: "Diagnostics" +use_cases: "server_error_sanitized diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "ssr" + - "error" + - "info" + - "reference" + - "v2" +version: "2.0" +description: 'SERVER_ERROR_SANITIZED: Error replaced with the generic Error on the wire — `data.source` `"server-function"` (`error`) or `"ssr"` (`info`); `data.error` is the original (observe + dev).' +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Error replaced with the generic Error on the wire — `data.source` `"server-function"` (`error`) or `"ssr"` (`info`); `data.error` is the original (observe + dev). + +## What you saw + +- **Kind:** `ssr` +- **Severity:** `error` (broken behavior) or `info` (advisory, delivered on the diagnostics channel and never printed) + +The console report opens with `[SERVER_ERROR_SANITIZED]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "SERVER_ERROR_SANITIZED"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +The production wire replaced an error with the generic `Error`; `data.error` is the original, `data.wire` what the client got, and `data.source` says which road: + +- `"server-function"` (`error`): a server function threw. Fix the failure it names. If the client is meant to see this error, brand it with `markSafeError` or map it in `wrapInvocation`; do not turn sanitization off. +- `"ssr"` (`info`): a render failure reached the client — an `<Errored>` record, a rejected async source in the stream, a fragment's rejection, a frame's error chunk. Advisory: the failure itself is the [`SSR_RENDER_ERROR_CONTAINED`](/reference/solid-js/diagnostics/ssr-render-error-contained) finding beside it — fix that. If the client is meant to see this error, brand it with `markSafeError`; do not turn sanitization off. A fallback that prints `err().message` shows "Internal Server Error" in production by design. + +:::note[Server rendering] +The server runtime reports on the same channel, with the same `in <App> › <Page>` line. +Two groups. +**Findings** ([`SSR_RENDER_ERROR_CONTAINED`](/reference/solid-js/diagnostics/ssr-render-error-contained), [`SSR_SUBTREE_ABANDONED`](/reference/solid-js/diagnostics/ssr-subtree-abandoned), [`SSR_STREAM_ABANDONED`](/reference/solid-js/diagnostics/ssr-stream-abandoned), [`LATE_HEADER_WRITE`](/reference/solid-js/diagnostics/late-header-write), [`DYNAMIC_ASYNC_COMPONENT`](/reference/solid-js/diagnostics/dynamic-async-component), `SERVER_ERROR_SANITIZED`, [`FRAME_MARKER_CORRUPTED`](/reference/solid-js/diagnostics/frame-marker-corrupted)) are facts about a render that exist in observe builds too — an APM sees them in production; in dev they print. +**Checks** (the rest, [`SSR_BOUNDARY_WATERFALL`](/reference/solid-js/diagnostics/ssr-boundary-waterfall) and [`SSR_CLIENT_CONTENT_MASKED`](/reference/solid-js/diagnostics/ssr-client-content-masked) included) are dev-only guidance. +A captured artifact from a server render carries both. +::: + +## How to fix it + +To _report_ these failures from a production build (no `OBSERVE`), register the server error hook — `configureServerErrors({ onError })` from `@solidjs/web` — which hears every handled failure once, and may return the value the client should see instead. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `SERVER_ERROR_SANITIZED` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/server-write.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/server-write.mdx new file mode 100644 index 000000000..f85be0d94 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/server-write.mdx @@ -0,0 +1,57 @@ +--- +title: "SERVER_WRITE" +category: "Diagnostics" +use_cases: "server_write diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "write" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "SERVER_WRITE: Signal/store/optimistic setter ran during a server render; inert, will throw (dev; once per category)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Signal/store/optimistic setter ran during a server render; inert, will throw (dev; once per category). + +## What you saw + +- **Kind:** `write` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[SERVER_WRITE]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "SERVER_WRITE"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A signal, store or optimistic setter ran during a server render. +Server render is pure — state enters through async sources, never setters — so the write landed as inert data and will throw in a later release. + +:::note[Server rendering] +The server runtime reports on the same channel, with the same `in <App> › <Page>` line. +Two groups. +**Findings** ([`SSR_RENDER_ERROR_CONTAINED`](/reference/solid-js/diagnostics/ssr-render-error-contained), [`SSR_SUBTREE_ABANDONED`](/reference/solid-js/diagnostics/ssr-subtree-abandoned), [`SSR_STREAM_ABANDONED`](/reference/solid-js/diagnostics/ssr-stream-abandoned), [`LATE_HEADER_WRITE`](/reference/solid-js/diagnostics/late-header-write), [`DYNAMIC_ASYNC_COMPONENT`](/reference/solid-js/diagnostics/dynamic-async-component), [`SERVER_ERROR_SANITIZED`](/reference/solid-js/diagnostics/server-error-sanitized), [`FRAME_MARKER_CORRUPTED`](/reference/solid-js/diagnostics/frame-marker-corrupted)) are facts about a render that exist in observe builds too — an APM sees them in production; in dev they print. +**Checks** (the rest, [`SSR_BOUNDARY_WATERFALL`](/reference/solid-js/diagnostics/ssr-boundary-waterfall) and [`SSR_CLIENT_CONTENT_MASKED`](/reference/solid-js/diagnostics/ssr-client-content-masked) included) are dev-only guidance. +A captured artifact from a server render carries both. +::: + +## How to fix it + +Derive the state (`createStore(fn, seed)`, a memo over the promise); if the write bridges a subscription, make the subscription the async source itself. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `SERVER_WRITE` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/settle-walk-uninitialized-source.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/settle-walk-uninitialized-source.mdx new file mode 100644 index 000000000..dbcf3b232 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/settle-walk-uninitialized-source.mdx @@ -0,0 +1,50 @@ +--- +title: "SETTLE_WALK_UNINITIALIZED_SOURCE" +category: "Diagnostics" +use_cases: "settle_walk_uninitialized_source diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "lifecycle" + - "error" + - "reference" + - "v2" +version: "2.0" +description: "SETTLE_WALK_UNINITIALIZED_SOURCE: Internal: settle walk reached a source that never produced a value (reported)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Internal: settle walk reached a source that never produced a value (reported). + +## What you saw + +- **Kind:** `lifecycle` +- **Severity:** `error` (broken behavior) + +The console report opens with `[SETTLE_WALK_UNINITIALIZED_SOURCE]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "SETTLE_WALK_UNINITIALIZED_SOURCE"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +The runtime tried to settle a source before it produced a value or an error. +Waking its parked readers at that point would expose its initial value instead of settled data. +This is an internal consistency check, reported in development. + +## How to fix it + +Keep the full diagnostic and reduce the async sequence to a reproduction when reporting it; adding `flush()` or silencing the diagnostic does not repair the missing value. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `SETTLE_WALK_UNINITIALIZED_SOURCE` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/settled-cleanup-unowned.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/settled-cleanup-unowned.mdx new file mode 100644 index 000000000..5a8001ecf --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/settled-cleanup-unowned.mdx @@ -0,0 +1,48 @@ +--- +title: "SETTLED_CLEANUP_UNOWNED" +category: "Diagnostics" +use_cases: "settled_cleanup_unowned diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "lifecycle" + - "error" + - "reference" + - "v2" +version: "2.0" +description: "SETTLED_CLEANUP_UNOWNED: `onSettled` returned a cleanup in an unowned (out-of-band) scope." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +`onSettled` returned a cleanup in an unowned (out-of-band) scope. + +## What you saw + +- **Kind:** `lifecycle` +- **Severity:** `error` (broken behavior) + +The console report opens with `[SETTLED_CLEANUP_UNOWNED]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "SETTLED_CLEANUP_UNOWNED"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +`onSettled` returned a cleanup while running in an unowned scope, so the cleanup cannot be honored. + +## How to fix it + +Call the setup helper from an owned scope (the component body), not from an event handler, tracked effect, or another `onSettled`. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `SETTLED_CLEANUP_UNOWNED` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/silent-hold.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/silent-hold.mdx new file mode 100644 index 000000000..55c49a287 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/silent-hold.mdx @@ -0,0 +1,69 @@ +--- +title: "SILENT_HOLD" +category: "Diagnostics" +use_cases: "silent_hold diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "responsiveness" + - "info" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "SILENT_HOLD: Write held 100ms+/200ms+ by pending async with no on-screen acknowledgement (attribution enabled)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Write held 100ms+/200ms+ by pending async with no on-screen acknowledgement (attribution enabled). + +## What you saw + +- **Kind:** `responsiveness` +- **Severity:** `info` (advisory, delivered on the diagnostics channel and never printed) or `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[SILENT_HOLD]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "SILENT_HOLD"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A write landed on async work, so the runtime held it (and everything derived from it) until the data came back — that hold is correct; it is what keeps the screen from tearing. +But for the whole wait nothing on screen acknowledged it: no `isPending()`/`latest()` reader downstream of the write or its blocker, no optimistic value, no `affects()` mark, and no effect ran at all. +From the user's side the click was dead for the duration in the message — which, when the write came from a JSX event handler, starts with what they did (`click on button#next "Next →" wrote "page" (1 → 2)`) and is measured from that event. + +:::note[Responsiveness (from the attribution engine)] +The runtime did the correct thing; the user saw nothing while it did. +These fire only while attribution is enabled. +::: + +## How to fix it + +The fix is ALWAYS to add feedback, never to remove the hold: + +- Show the wait: read `isPending(() => blocker())` (the blocker is named in the message) or `isPending(() => derivedFromIt())` in the affected UI and render a busy state from it. +- Reveal the input immediately: `latest(source)` shows the NEW value of the held write (a page number, a filter, a query string) while the data catches up, so the control the user touched reflects the touch. +- Predict the outcome: `createOptimistic`/`createOptimisticStore` written alongside the real write (or inside the action) shows the expected result now and reverts on failure. For actions this is the primary repair — the message says "an action held" when the hold came from one. +- Do NOT: move the write off the async path, wrap it in `untrack`, or split the read so the write commits "faster" — that trades the hold for a torn screen (old data under new controls). + +Thresholds sit at the strict end of the band on purpose: the engine measures to the commit, not the paint, so every number is a floor on what the user saw. +From `holds.infoMs` (default 100ms — past "feels instant") the event is `info`-severity, structured channel only; from `holds.warnMs` (default 200ms — the INP "good" ceiling) it reaches the console. +`attribution.history("hold")` lists every hold (acknowledged or not) with what was held, what blocked it, and which affordances answered it; each carries the engine's verdicts as `silent`/`long` fields. +When the silent hold's tail also crossed the long-hold threshold (`data.long: true`) the message carries the [`LONG_HOLD`](/reference/solid-js/diagnostics/long-hold) repair as well — the fallback is the honest UI at that length. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `SILENT_HOLD` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +`expectNoSilentHolds(artifact)` fails on every hold the screen never acknowledged, at any length, not only the ones long enough to warn. +In a budget file the same check is `maxSilentHoldMs: 0`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. +- [Attribution](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution): the engine whose records explain a re-run, a hold, or a waterfall. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/ssr-boundary-waterfall.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/ssr-boundary-waterfall.mdx new file mode 100644 index 000000000..3cbb0db6b --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/ssr-boundary-waterfall.mdx @@ -0,0 +1,60 @@ +--- +title: "SSR_BOUNDARY_WATERFALL" +category: "Diagnostics" +use_cases: "ssr_boundary_waterfall diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "ssr" + - "info" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "SSR_BOUNDARY_WATERFALL: A `<Loading>` boundary needed 3+/4+ render passes — 2+/3+ sequential async waits (dev)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +A `<Loading>` boundary needed 3+/4+ render passes — 2+/3+ sequential async waits (dev). + +## What you saw + +- **Kind:** `ssr` +- **Severity:** `info` (advisory, delivered on the diagnostics channel and never printed) or `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[SSR_BOUNDARY_WATERFALL]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "SSR_BOUNDARY_WATERFALL"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A `<Loading>` boundary rendered in `data.passes` passes — discovery, then one per wait — and each pass past the first is a read that could only start once the previous pass's async answered: `passes - 1` sequential waits, `data.sequentialMs` end to end. +Unlike the client's [`ASYNC_WATERFALL`](/reference/solid-js/diagnostics/async-waterfall) this proof is exact (the pass structure IS the chain), so there is no `markFlight` false positive to rule out. + +:::note[Server rendering] +The server runtime reports on the same channel, with the same `in <App> › <Page>` line. +Two groups. +**Findings** ([`SSR_RENDER_ERROR_CONTAINED`](/reference/solid-js/diagnostics/ssr-render-error-contained), [`SSR_SUBTREE_ABANDONED`](/reference/solid-js/diagnostics/ssr-subtree-abandoned), [`SSR_STREAM_ABANDONED`](/reference/solid-js/diagnostics/ssr-stream-abandoned), [`LATE_HEADER_WRITE`](/reference/solid-js/diagnostics/late-header-write), [`DYNAMIC_ASYNC_COMPONENT`](/reference/solid-js/diagnostics/dynamic-async-component), [`SERVER_ERROR_SANITIZED`](/reference/solid-js/diagnostics/server-error-sanitized), [`FRAME_MARKER_CORRUPTED`](/reference/solid-js/diagnostics/frame-marker-corrupted)) are facts about a render that exist in observe builds too — an APM sees them in production; in dev they print. +**Checks** (the rest, `SSR_BOUNDARY_WATERFALL` and [`SSR_CLIENT_CONTENT_MASKED`](/reference/solid-js/diagnostics/ssr-client-content-masked) included) are dev-only guidance. +A captured artifact from a server render carries both. +::: + +## How to fix it + +The same repairs apply, in the same order: derive both reads from the same inputs so they start together (read ALL async sources before using any), or, if the dependency is intrinsic, preload the dependent data or join the requests. +Two waits are `info` — a lead; three or more `warn`. +The boundary is `data.boundary`; a captured artifact has its record in `artifact.records.boundary` (same `id`) and the server-function calls under it in `artifact.records.invocation` (`boundary` field). + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `SSR_BOUNDARY_WATERFALL` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/ssr-client-content-masked.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/ssr-client-content-masked.mdx new file mode 100644 index 000000000..035b7dc22 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/ssr-client-content-masked.mdx @@ -0,0 +1,57 @@ +--- +title: "SSR_CLIENT_CONTENT_MASKED" +category: "Diagnostics" +use_cases: "ssr_client_content_masked diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "ssr" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "SSR_CLIENT_CONTENT_MASKED: Client-only content in a `<Loading>` surfaced only after a server wait; the server's work was discarded (dev)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Client-only content in a `<Loading>` surfaced only after a server wait; the server's work was discarded (dev). + +## What you saw + +- **Kind:** `ssr` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[SSR_CLIENT_CONTENT_MASKED]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "SSR_CLIENT_CONTENT_MASKED"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A `<Loading>` boundary's content turned out to be client-only (a `ssrSource: "client"` read) but only after `data.passes - 1` real server waits: an async read on an earlier pass masked it. +The server did the work (`data.durationMs`), streamed the fallback, then handed the whole subtree to the client anyway — the work was discarded and the user saw the fallback for the wait, then a client render. + +:::note[Server rendering] +The server runtime reports on the same channel, with the same `in <App> › <Page>` line. +Two groups. +**Findings** ([`SSR_RENDER_ERROR_CONTAINED`](/reference/solid-js/diagnostics/ssr-render-error-contained), [`SSR_SUBTREE_ABANDONED`](/reference/solid-js/diagnostics/ssr-subtree-abandoned), [`SSR_STREAM_ABANDONED`](/reference/solid-js/diagnostics/ssr-stream-abandoned), [`LATE_HEADER_WRITE`](/reference/solid-js/diagnostics/late-header-write), [`DYNAMIC_ASYNC_COMPONENT`](/reference/solid-js/diagnostics/dynamic-async-component), [`SERVER_ERROR_SANITIZED`](/reference/solid-js/diagnostics/server-error-sanitized), [`FRAME_MARKER_CORRUPTED`](/reference/solid-js/diagnostics/frame-marker-corrupted)) are facts about a render that exist in observe builds too — an APM sees them in production; in dev they print. +**Checks** (the rest, [`SSR_BOUNDARY_WATERFALL`](/reference/solid-js/diagnostics/ssr-boundary-waterfall) and `SSR_CLIENT_CONTENT_MASKED` included) are dev-only guidance. +A captured artifact from a server render carries both. +::: + +## How to fix it + +Give the client-only read its own `<Loading>` so it hands off with the shell while the async data streams, or read it before the async data so the boundary hands off on its first pass (no finding for that case). + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `SSR_CLIENT_CONTENT_MASKED` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/ssr-live-hole-latched.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/ssr-live-hole-latched.mdx new file mode 100644 index 000000000..25295a20d --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/ssr-live-hole-latched.mdx @@ -0,0 +1,59 @@ +--- +title: "SSR_LIVE_HOLE_LATCHED" +category: "Diagnostics" +use_cases: "ssr_live_hole_latched diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "ssr" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "SSR_LIVE_HOLE_LATCHED: A live hole that shipped its markers was closed by the impurity gate on re-run and stopped updating (dev)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +A live hole that shipped its markers was closed by the impurity gate on re-run and stopped updating (dev). + +## What you saw + +- **Kind:** `ssr` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[SSR_LIVE_HOLE_LATCHED]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "SSR_LIVE_HOLE_LATCHED"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A live markup hole (or attribute) in a server component shipped its markers and then stopped updating: re-running it created a reactive scope still waiting on async work (`data.cause: "owners"`) or emitted slot records (`"records"`), so the runtime closed it and the client keeps the markup it last received. +Scopes that finish synchronously (a nested `<For>`, a condition memo such as `{m.usage && …}`, a component) are fine: each re-run gets a throwaway scope. +The usual cause is a memo over a fetch or a stream created inside the markup that re-renders, which would restart on every re-run. + +:::note[Server rendering] +The server runtime reports on the same channel, with the same `in <App> › <Page>` line. +Two groups. +**Findings** ([`SSR_RENDER_ERROR_CONTAINED`](/reference/solid-js/diagnostics/ssr-render-error-contained), [`SSR_SUBTREE_ABANDONED`](/reference/solid-js/diagnostics/ssr-subtree-abandoned), [`SSR_STREAM_ABANDONED`](/reference/solid-js/diagnostics/ssr-stream-abandoned), [`LATE_HEADER_WRITE`](/reference/solid-js/diagnostics/late-header-write), [`DYNAMIC_ASYNC_COMPONENT`](/reference/solid-js/diagnostics/dynamic-async-component), [`SERVER_ERROR_SANITIZED`](/reference/solid-js/diagnostics/server-error-sanitized), [`FRAME_MARKER_CORRUPTED`](/reference/solid-js/diagnostics/frame-marker-corrupted)) are facts about a render that exist in observe builds too — an APM sees them in production; in dev they print. +**Checks** (the rest, [`SSR_BOUNDARY_WATERFALL`](/reference/solid-js/diagnostics/ssr-boundary-waterfall) and [`SSR_CLIENT_CONTENT_MASKED`](/reference/solid-js/diagnostics/ssr-client-content-masked) included) are dev-only guidance. +A captured artifact from a server render carries both. +::: + +## How to fix it + +Create the async source in the component body instead. +The finding is located by the component the hole is in. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `SSR_LIVE_HOLE_LATCHED` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/ssr-render-error-contained.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/ssr-render-error-contained.mdx new file mode 100644 index 000000000..9b1c5c96d --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/ssr-render-error-contained.mdx @@ -0,0 +1,59 @@ +--- +title: "SSR_RENDER_ERROR_CONTAINED" +category: "Diagnostics" +use_cases: "ssr_render_error_contained diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "ssr" + - "error" + - "reference" + - "v2" +version: "2.0" +description: "SSR_RENDER_ERROR_CONTAINED: Server render error routed by a boundary: `data.handling` fallback / client / failed (observe + dev)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Server render error routed by a boundary: `data.handling` fallback / client / failed (observe + dev). + +## What you saw + +- **Kind:** `ssr` +- **Severity:** `error` (broken behavior) + +The console report opens with `[SSR_RENDER_ERROR_CONTAINED]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "SSR_RENDER_ERROR_CONTAINED"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A component threw during a server render and a boundary routed the error; `data.handling` says how. +`fallback` — an `<Errored>` rendered its fallback (the response was a 200 showing the fallback; if this fires on every request the fallback is the page). +`client` — the enclosing `<Loading>` fragment rejected and the client re-rendered the subtree after hydration (the user paid a client render and a flash). +`failed` — nothing contained it and the request failed. + +:::note[Server rendering] +The server runtime reports on the same channel, with the same `in <App> › <Page>` line. +Two groups. +**Findings** (`SSR_RENDER_ERROR_CONTAINED`, [`SSR_SUBTREE_ABANDONED`](/reference/solid-js/diagnostics/ssr-subtree-abandoned), [`SSR_STREAM_ABANDONED`](/reference/solid-js/diagnostics/ssr-stream-abandoned), [`LATE_HEADER_WRITE`](/reference/solid-js/diagnostics/late-header-write), [`DYNAMIC_ASYNC_COMPONENT`](/reference/solid-js/diagnostics/dynamic-async-component), [`SERVER_ERROR_SANITIZED`](/reference/solid-js/diagnostics/server-error-sanitized), [`FRAME_MARKER_CORRUPTED`](/reference/solid-js/diagnostics/frame-marker-corrupted)) are facts about a render that exist in observe builds too — an APM sees them in production; in dev they print. +**Checks** (the rest, [`SSR_BOUNDARY_WATERFALL`](/reference/solid-js/diagnostics/ssr-boundary-waterfall) and [`SSR_CLIENT_CONTENT_MASKED`](/reference/solid-js/diagnostics/ssr-client-content-masked) included) are dev-only guidance. +A captured artifact from a server render carries both. +::: + +## How to fix it + +In every case the repair is the error in `data.error`, in the component `ownerPath` names; then consider whether an `<Errored>` closer to the throw would contain it more narrowly. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `SSR_RENDER_ERROR_CONTAINED` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/ssr-stream-abandoned.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/ssr-stream-abandoned.mdx new file mode 100644 index 000000000..e9f226902 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/ssr-stream-abandoned.mdx @@ -0,0 +1,57 @@ +--- +title: "SSR_STREAM_ABANDONED" +category: "Diagnostics" +use_cases: "ssr_stream_abandoned diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "ssr" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "SSR_STREAM_ABANDONED: Response stream cancelled or sink failed with fragments pending (observe + dev)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Response stream cancelled or sink failed with fragments pending (observe + dev). + +## What you saw + +- **Kind:** `ssr` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[SSR_STREAM_ABANDONED]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "SSR_STREAM_ABANDONED"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +The client went away (`data.reason: "consumer"`), the sink failed (`"sink"`), or the request's `signal` aborted (`"signal"` — `renderToStream`'s `signal` option, how a frame-stream response learns its reader is gone) while `data.pendingFragments` were still rendering; the render was torn down. +Not an app bug. + +:::note[Server rendering] +The server runtime reports on the same channel, with the same `in <App> › <Page>` line. +Two groups. +**Findings** ([`SSR_RENDER_ERROR_CONTAINED`](/reference/solid-js/diagnostics/ssr-render-error-contained), [`SSR_SUBTREE_ABANDONED`](/reference/solid-js/diagnostics/ssr-subtree-abandoned), `SSR_STREAM_ABANDONED`, [`LATE_HEADER_WRITE`](/reference/solid-js/diagnostics/late-header-write), [`DYNAMIC_ASYNC_COMPONENT`](/reference/solid-js/diagnostics/dynamic-async-component), [`SERVER_ERROR_SANITIZED`](/reference/solid-js/diagnostics/server-error-sanitized), [`FRAME_MARKER_CORRUPTED`](/reference/solid-js/diagnostics/frame-marker-corrupted)) are facts about a render that exist in observe builds too — an APM sees them in production; in dev they print. +**Checks** (the rest, [`SSR_BOUNDARY_WATERFALL`](/reference/solid-js/diagnostics/ssr-boundary-waterfall) and [`SSR_CLIENT_CONTENT_MASKED`](/reference/solid-js/diagnostics/ssr-client-content-masked) included) are dev-only guidance. +A captured artifact from a server render carries both. +::: + +## How to fix it + +At volume it is the cost of renders nobody waited for: make the pending data faster or move it behind navigation. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `SSR_STREAM_ABANDONED` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/ssr-subtree-abandoned.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/ssr-subtree-abandoned.mdx new file mode 100644 index 000000000..c192461a5 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/ssr-subtree-abandoned.mdx @@ -0,0 +1,56 @@ +--- +title: "SSR_SUBTREE_ABANDONED" +category: "Diagnostics" +use_cases: "ssr_subtree_abandoned diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "ssr" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "SSR_SUBTREE_ABANDONED: A failed fragment's pending descendants were discarded (observe + dev)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +A failed fragment's pending descendants were discarded (observe + dev). + +## What you saw + +- **Kind:** `ssr` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[SSR_SUBTREE_ABANDONED]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "SSR_SUBTREE_ABANDONED"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A fragment failed with descendants still pending, and their work (fetches, serialized values — the counts are in `data`) was thrown away; the client rebuilds the subtree. + +:::note[Server rendering] +The server runtime reports on the same channel, with the same `in <App> › <Page>` line. +Two groups. +**Findings** ([`SSR_RENDER_ERROR_CONTAINED`](/reference/solid-js/diagnostics/ssr-render-error-contained), `SSR_SUBTREE_ABANDONED`, [`SSR_STREAM_ABANDONED`](/reference/solid-js/diagnostics/ssr-stream-abandoned), [`LATE_HEADER_WRITE`](/reference/solid-js/diagnostics/late-header-write), [`DYNAMIC_ASYNC_COMPONENT`](/reference/solid-js/diagnostics/dynamic-async-component), [`SERVER_ERROR_SANITIZED`](/reference/solid-js/diagnostics/server-error-sanitized), [`FRAME_MARKER_CORRUPTED`](/reference/solid-js/diagnostics/frame-marker-corrupted)) are facts about a render that exist in observe builds too — an APM sees them in production; in dev they print. +**Checks** (the rest, [`SSR_BOUNDARY_WATERFALL`](/reference/solid-js/diagnostics/ssr-boundary-waterfall) and [`SSR_CLIENT_CONTENT_MASKED`](/reference/solid-js/diagnostics/ssr-client-content-masked) included) are dev-only guidance. +A captured artifact from a server render carries both. +::: + +## How to fix it + +Find the failure (`data.error`), and give the failing read its own `<Loading>` so its siblings ship independently. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `SSR_SUBTREE_ABANDONED` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/ssr-undeclared-live-source.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/ssr-undeclared-live-source.mdx new file mode 100644 index 000000000..6897f6edd --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/ssr-undeclared-live-source.mdx @@ -0,0 +1,57 @@ +--- +title: "SSR_UNDECLARED_LIVE_SOURCE" +category: "Diagnostics" +use_cases: "ssr_undeclared_live_source diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "ssr" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "SSR_UNDECLARED_LIVE_SOURCE: A document render is still pumping an async iterable in server-component scope 5s in — declare the function `live(...)` or bound the source (dev)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +A document render is still pumping an async iterable in server-component scope 5s in — declare the function `live(...)` or bound the source (dev). + +## What you saw + +- **Kind:** `ssr` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[SSR_UNDECLARED_LIVE_SOURCE]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "SSR_UNDECLARED_LIVE_SOURCE"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A server component rendered into a document read an async iterable that was still producing `data.afterMs` (5s) later: an undeclared unbounded source pumps into the document and holds it open for as long as it produces. + +:::note[Server rendering] +The server runtime reports on the same channel, with the same `in <App> › <Page>` line. +Two groups. +**Findings** ([`SSR_RENDER_ERROR_CONTAINED`](/reference/solid-js/diagnostics/ssr-render-error-contained), [`SSR_SUBTREE_ABANDONED`](/reference/solid-js/diagnostics/ssr-subtree-abandoned), [`SSR_STREAM_ABANDONED`](/reference/solid-js/diagnostics/ssr-stream-abandoned), [`LATE_HEADER_WRITE`](/reference/solid-js/diagnostics/late-header-write), [`DYNAMIC_ASYNC_COMPONENT`](/reference/solid-js/diagnostics/dynamic-async-component), [`SERVER_ERROR_SANITIZED`](/reference/solid-js/diagnostics/server-error-sanitized), [`FRAME_MARKER_CORRUPTED`](/reference/solid-js/diagnostics/frame-marker-corrupted)) are facts about a render that exist in observe builds too — an APM sees them in production; in dev they print. +**Checks** (the rest, [`SSR_BOUNDARY_WATERFALL`](/reference/solid-js/diagnostics/ssr-boundary-waterfall) and [`SSR_CLIENT_CONTENT_MASKED`](/reference/solid-js/diagnostics/ssr-client-content-masked) included) are dev-only guidance. +A captured artifact from a server render carries both. +::: + +## How to fix it + +Declare the server function `live(...)` — the document then takes each source's first value and closes it, and the client connects for the rest after hydration — or bound the source. +Frame-stream renders (a live connection) are never judged. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `SSR_UNDECLARED_LIVE_SOURCE` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/stacked-holds.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/stacked-holds.mdx new file mode 100644 index 000000000..b1fe3faf2 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/stacked-holds.mdx @@ -0,0 +1,61 @@ +--- +title: "STACKED_HOLDS" +category: "Diagnostics" +use_cases: "stacked_holds diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "responsiveness" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "STACKED_HOLDS: 3+ interactions were waiting in one hold when it committed — repeats piled behind the same source (attribution enabled)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +3+ interactions were waiting in one hold when it committed — repeats piled behind the same source (attribution enabled). + +## What you saw + +- **Kind:** `responsiveness` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[STACKED_HOLDS]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "STACKED_HOLDS"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +`data.interactions` interactions were waiting in the same hold when it committed: the person clicked or typed again while the first answer was in the air, and the runtime folded every repeat into one wait on the same source (`data.blockers`). +The pile is the symptom; the hold's own verdict is the cause — check the [`SILENT_HOLD`](/reference/solid-js/diagnostics/silent-hold)/[`LONG_HOLD`](/reference/solid-js/diagnostics/long-hold) entry for the same hold first. + +:::note[Responsiveness (from the attribution engine)] +The runtime did the correct thing; the user saw nothing while it did. +These fire only while attribution is enabled. +::: + +## How to fix it + +Then stop the repeats at the control: + +- Read `isPending(() => blocker())` where the control renders and disable or dim it while the flight is out, so a second click is not possible. +- For typed input, debounce so a burst of keystrokes is one write. +- Show progress (`latest()` of the input, an optimistic value) so the person does not click again because nothing moved. + +Do NOT block input by removing the hold or making the write synchronous; the repeats are a reaction to silence, and the hold is what keeps the screen consistent while the answer arrives. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `STACKED_HOLDS` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. +- [Attribution](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution): the engine whose records explain a re-run, a hold, or a waterfall. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/strict-read-untracked.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/strict-read-untracked.mdx new file mode 100644 index 000000000..8cf917c2b --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/strict-read-untracked.mdx @@ -0,0 +1,50 @@ +--- +title: "STRICT_READ_UNTRACKED" +category: "Diagnostics" +use_cases: "strict_read_untracked diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "strict-read" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "STRICT_READ_UNTRACKED: Untracked reactive read in component/effect body." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Untracked reactive read in component/effect body. + +## What you saw + +- **Kind:** `strict-read` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[STRICT_READ_UNTRACKED]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "STRICT_READ_UNTRACKED"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A reactive value was read outside any tracking scope (e.g. destructured props or a signal called in the component body). +The read got the current value once and will never update. + +## How to fix it + +Move the read into a tracking scope: JSX, a memo, or an effect's compute function. +If you intentionally want a one-time snapshot, wrap the read in `untrack()` to say so explicitly. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `STRICT_READ_UNTRACKED` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/sync-node-received-async.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/sync-node-received-async.mdx new file mode 100644 index 000000000..e4ff2e30f --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/sync-node-received-async.mdx @@ -0,0 +1,48 @@ +--- +title: "SYNC_NODE_RECEIVED_ASYNC" +category: "Diagnostics" +use_cases: "sync_node_received_async diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "lifecycle" + - "error" + - "reference" + - "v2" +version: "2.0" +description: "SYNC_NODE_RECEIVED_ASYNC: `sync: true` computation returned a Promise / AsyncIterable." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +`sync: true` computation returned a Promise / AsyncIterable. + +## What you saw + +- **Kind:** `lifecycle` +- **Severity:** `error` (broken behavior) + +The console report opens with `[SYNC_NODE_RECEIVED_ASYNC]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "SYNC_NODE_RECEIVED_ASYNC"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A computed/effect created with `sync: true` returned a Promise or AsyncIterable; the value would be stored as-is, never awaited, in production. + +## How to fix it + +Remove `sync: true` to use async-aware behavior, or unwrap before returning. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `SYNC_NODE_RECEIVED_ASYNC` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/unrecognized-insert-value.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/unrecognized-insert-value.mdx new file mode 100644 index 000000000..18b6238b2 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/unrecognized-insert-value.mdx @@ -0,0 +1,56 @@ +--- +title: "UNRECOGNIZED_INSERT_VALUE" +category: "Diagnostics" +use_cases: "unrecognized_insert_value diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "render" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "UNRECOGNIZED_INSERT_VALUE: Value at an insert position the renderer cannot render; skipped (dev; server and client)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Value at an insert position the renderer cannot render; skipped (dev; server and client). + +## What you saw + +- **Kind:** `render` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[UNRECOGNIZED_INSERT_VALUE]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "UNRECOGNIZED_INSERT_VALUE"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A value the renderer cannot render (a plain object, a symbol — `data.type`) sat at an insert position and was skipped, on either platform. + +:::note[Server rendering] +The server runtime reports on the same channel, with the same `in <App> › <Page>` line. +Two groups. +**Findings** ([`SSR_RENDER_ERROR_CONTAINED`](/reference/solid-js/diagnostics/ssr-render-error-contained), [`SSR_SUBTREE_ABANDONED`](/reference/solid-js/diagnostics/ssr-subtree-abandoned), [`SSR_STREAM_ABANDONED`](/reference/solid-js/diagnostics/ssr-stream-abandoned), [`LATE_HEADER_WRITE`](/reference/solid-js/diagnostics/late-header-write), [`DYNAMIC_ASYNC_COMPONENT`](/reference/solid-js/diagnostics/dynamic-async-component), [`SERVER_ERROR_SANITIZED`](/reference/solid-js/diagnostics/server-error-sanitized), [`FRAME_MARKER_CORRUPTED`](/reference/solid-js/diagnostics/frame-marker-corrupted)) are facts about a render that exist in observe builds too — an APM sees them in production; in dev they print. +**Checks** (the rest, [`SSR_BOUNDARY_WATERFALL`](/reference/solid-js/diagnostics/ssr-boundary-waterfall) and [`SSR_CLIENT_CONTENT_MASKED`](/reference/solid-js/diagnostics/ssr-client-content-masked) included) are dev-only guidance. +A captured artifact from a server render carries both. +::: + +## How to fix it + +Usually a component function inserted where its call was meant, or an object where one of its properties was. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `UNRECOGNIZED_INSERT_VALUE` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/unscoped-hole-allocated-ids.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/unscoped-hole-allocated-ids.mdx new file mode 100644 index 000000000..987e18a61 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/unscoped-hole-allocated-ids.mdx @@ -0,0 +1,61 @@ +--- +title: "UNSCOPED_HOLE_ALLOCATED_IDS" +category: "Diagnostics" +use_cases: "unscoped_hole_allocated_ids diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "render" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "UNSCOPED_HOLE_ALLOCATED_IDS: Unscoped hole was handed a function whose content took ids at a position the other side does not share; keys permute (dev)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Unscoped hole was handed a function whose content took ids at a position the other side does not share; keys permute (dev). + +## What you saw + +- **Kind:** `render` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[UNSCOPED_HOLE_ALLOCATED_IDS]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "UNSCOPED_HOLE_ALLOCATED_IDS"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A JSX hole was handed a **function** (`data.name`) instead of a value — `const renderHead = () => props.header; <div>{renderHead}</div>` — and calling it built hydratable content. +The compiler scopes every hole that could build JSX so server and client agree on hydration ids, but a bare identifier bound to a function is a value to the compiler and a thunk to the runtime: unscoped, its content takes ids from the enclosing counter where each side happens to evaluate it — the client at the statement, the server in the walk after the scoped holes that follow it reserved theirs — and the keys of its content and of the holes after it permute: detached copies on hydration, dead handlers. +A function hole with nothing scoped after it lands on the same ids both sides and is silent; the finding is the shift. +The server reports it structurally (`data.registered` ≠ `data.before`, the counter's next id when the hole was registered vs. evaluated; `data.after` after it ran), the client when the content it built in place (`data.before` → `data.after`) missed a server-rendered key. +Once per site. +`JSX.Element` excludes functions, so this shape is reached only from JavaScript or through a cast. + +:::note[Server rendering] +The server runtime reports on the same channel, with the same `in <App> › <Page>` line. +Two groups. +**Findings** ([`SSR_RENDER_ERROR_CONTAINED`](/reference/solid-js/diagnostics/ssr-render-error-contained), [`SSR_SUBTREE_ABANDONED`](/reference/solid-js/diagnostics/ssr-subtree-abandoned), [`SSR_STREAM_ABANDONED`](/reference/solid-js/diagnostics/ssr-stream-abandoned), [`LATE_HEADER_WRITE`](/reference/solid-js/diagnostics/late-header-write), [`DYNAMIC_ASYNC_COMPONENT`](/reference/solid-js/diagnostics/dynamic-async-component), [`SERVER_ERROR_SANITIZED`](/reference/solid-js/diagnostics/server-error-sanitized), [`FRAME_MARKER_CORRUPTED`](/reference/solid-js/diagnostics/frame-marker-corrupted)) are facts about a render that exist in observe builds too — an APM sees them in production; in dev they print. +**Checks** (the rest, [`SSR_BOUNDARY_WATERFALL`](/reference/solid-js/diagnostics/ssr-boundary-waterfall) and [`SSR_CLIENT_CONTENT_MASKED`](/reference/solid-js/diagnostics/ssr-client-content-masked) included) are dev-only guidance. +A captured artifact from a server render carries both. +::: + +## How to fix it + +Fix: call the function at the hole (`{renderHead()}` — a call hole is scoped on both sides) or assign the built value first and insert that. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `UNSCOPED_HOLE_ALLOCATED_IDS` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/unstable-list-identity.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/unstable-list-identity.mdx new file mode 100644 index 000000000..4288861af --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/unstable-list-identity.mdx @@ -0,0 +1,58 @@ +--- +title: "UNSTABLE_LIST_IDENTITY" +category: "Diagnostics" +use_cases: "unstable_list_identity diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "perf" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "UNSTABLE_LIST_IDENTITY: `mapArray`/`For` recreated rows for equivalent items (attribution enabled)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +`mapArray`/`For` recreated rows for equivalent items (attribution enabled). + +## What you saw + +- **Kind:** `perf` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[UNSTABLE_LIST_IDENTITY]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "UNSTABLE_LIST_IDENTITY"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A `mapArray`/`<For>` update disposed and recreated most rows while the entering items were field-for-field equivalent to the ones they replaced: a re-fetch (or a spread-copy, see above) handed back fresh objects for the same records, and identity keying treated each as a new row — DOM, state, focus, and scroll position thrown away and rebuilt. + +:::note[Performance pathologies (from the attribution engine)] +These fire only while attribution is enabled and describe cost, not incorrect behavior. +The numbers in the message are measurements, not guesses. +::: + +## How to fix it + +Repairs: key the list by a stable field (`<For each={rows()} keyed={r => r.id}>`), or merge the data into a store with `reconcile(data, "id")` so the same records keep the same identity. +If a key function is already in use (`data.keyed: true`) it is returning unstable keys (the object itself, or something that changes with the fetch) — return the stable field. +Once per list. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `UNSTABLE_LIST_IDENTITY` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +`expectRerunBudget(artifact, max, { scope })` bounds how often a named scope may re-run, and `expectNoWaste(artifact)` fails on re-runs whose result was unchanged. +Both read the attribution records, which `captureArtifact` collects unless `attribution: false` is passed. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. +- [Attribution](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution): the engine whose records explain a re-run, a hold, or a waterfall. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/unstable-memo-output.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/unstable-memo-output.mdx new file mode 100644 index 000000000..1874e2814 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/unstable-memo-output.mdx @@ -0,0 +1,56 @@ +--- +title: "UNSTABLE_MEMO_OUTPUT" +category: "Diagnostics" +use_cases: "unstable_memo_output diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "perf" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "UNSTABLE_MEMO_OUTPUT: Memo returned a new-but-equivalent container 4+ runs running (attribution enabled)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Memo returned a new-but-equivalent container 4+ runs running (attribution enabled). + +## What you saw + +- **Kind:** `perf` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[UNSTABLE_MEMO_OUTPUT]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "UNSTABLE_MEMO_OUTPUT"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +A memo keeps producing referentially-new but shallowly-equivalent objects/arrays, so its equality gate never closes and every subscriber re-runs on every upstream change. + +:::note[Performance pathologies (from the attribution engine)] +These fire only while attribution is enabled and describe cost, not incorrect behavior. +The numbers in the message are measurements, not guesses. +::: + +## How to fix it + +Return stable references or pass an `equals` option. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `UNSTABLE_MEMO_OUTPUT` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +`expectRerunBudget(artifact, max, { scope })` bounds how often a named scope may re-run, and `expectNoWaste(artifact)` fails on re-runs whose result was unchanged. +Both read the attribution records, which `captureArtifact` collects unless `attribution: false` is passed. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. +- [Attribution](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution): the engine whose records explain a re-run, a hold, or a waterfall. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/untracked-async-handler.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/untracked-async-handler.mdx new file mode 100644 index 000000000..93d52dbbe --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/untracked-async-handler.mdx @@ -0,0 +1,65 @@ +--- +title: "UNTRACKED_ASYNC_HANDLER" +category: "Diagnostics" +use_cases: "untracked_async_handler diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "responsiveness" + - "info" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "UNTRACKED_ASYNC_HANDLER: Handler awaited 100ms+/200ms+ past its frame with no write before the `await` and no `action()`: a dead click no hold could judge (attribution enabled)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Handler awaited 100ms+/200ms+ past its frame with no write before the `await` and no `action()`: a dead click no hold could judge (attribution enabled). + +## What you saw + +- **Kind:** `responsiveness` +- **Severity:** `info` (advisory, delivered on the diagnostics channel and never printed) or `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[UNTRACKED_ASYNC_HANDLER]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "UNTRACKED_ASYNC_HANDLER"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +The dead click that is not a hold. +The handler was `async` and awaited something — `onClick={async () => setResult(await save())}` — but wrote nothing before its first `await`. +No hold opened (there was no write to hold), so [`SILENT_HOLD`](/reference/solid-js/diagnostics/silent-hold) could not see the wait, yet from the user's side the click did nothing for `data.continuationMs` (handler return → the promise settling). +The interaction record stayed open for the wait, so `attribution.history("interaction")` shows it with `continuationMs` set. +Same thresholds as [`SILENT_HOLD`](/reference/solid-js/diagnostics/silent-hold) (`holds.infoMs`/`warnMs`; off with `holds: false`); the wait is capped at 10s for a promise that never settles (`data.capped`). + +:::note[Responsiveness (from the attribution engine)] +The runtime did the correct thing; the user saw nothing while it did. +These fire only while attribution is enabled. +::: + +## How to fix it + +Two repairs, in order of preference: + +- Make the async work an action: `const save = action(function* () { const r = yield api.save(); setResult(r); })` and call `save()` from the handler. An action's steps stay attributed to the click across yields, its writes are held while it runs, and that hold is judged — so if the screen still shows nothing you get [`SILENT_HOLD`](/reference/solid-js/diagnostics/silent-hold) with the action repair (an optimistic value, an `isPending()` reader), which is the real fix. +- Or acknowledge before awaiting: write a `createOptimistic(false)` "saving" flag (or `setSaving(true)` on a signal the UI reads) as the handler's first line. Any root write before the `await` clears this finding; whether the screen then shows the wait is the hold's question. + +Do NOT: keep the plain `await` and add a `try/finally` — nothing reactive learns about the wait either way. +The finding names the interaction (`click on button#save`), not a node; there is no scope to name because no scope ran. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `UNTRACKED_ASYNC_HANDLER` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. +- [Attribution](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution): the engine whose records explain a re-run, a hold, or a waterfall. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/untracked-read-after-await.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/untracked-read-after-await.mdx new file mode 100644 index 000000000..28e00fe43 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/untracked-read-after-await.mdx @@ -0,0 +1,52 @@ +--- +title: "UNTRACKED_READ_AFTER_AWAIT" +category: "Diagnostics" +use_cases: "untracked_read_after_await diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "async" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "UNTRACKED_READ_AFTER_AWAIT: Async computation first read a signal/memo/store key after an `await`; never a dependency (dev; V8 engines only)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Async computation first read a signal/memo/store key after an `await`; never a dependency (dev; V8 engines only). + +## What you saw + +- **Kind:** `async` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[UNTRACKED_READ_AFTER_AWAIT]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "UNTRACKED_READ_AFTER_AWAIT"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +An async computation read a signal, memo, or store property for the first time after an `await`. +Only reads before the first `await` are tracked, so the computation keeps its old result when that value changes. + +## How to fix it + +Read the value before the first `await` (capture it in a local), or wrap the read in `untrack()` if a one-time value is intended. +Dev-only and V8-only (Chromium browsers, Node, Deno, Bun); Firefox and Safari never report it, so treat it as complementary to lint — it follows the read into helpers, `.then` callbacks, and awaited utilities that lint cannot see. +`async function*` bodies are not covered. +Warns once per computation per signal/memo and once per store; a read inside an effect callback, cleanup, or `action()` body that the continuation itself triggered is not blamed on it. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `UNTRACKED_READ_AFTER_AWAIT` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/wasted-recompute.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/wasted-recompute.mdx new file mode 100644 index 000000000..2b7049afe --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/wasted-recompute.mdx @@ -0,0 +1,67 @@ +--- +title: "WASTED_RECOMPUTE" +category: "Diagnostics" +use_cases: "wasted_recompute diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "perf" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "WASTED_RECOMPUTE: 80%+ of a scope's 5+ runs in a second produced an unchanged value for 2ms+ of compute — inputs change, result does not (attribution enabled)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +80%+ of a scope's 5+ runs in a second produced an unchanged value for 2ms+ of compute — inputs change, result does not (attribution enabled). + +## What you saw + +- **Kind:** `perf` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[WASTED_RECOMPUTE]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "WASTED_RECOMPUTE"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +The mirror of [`UNSTABLE_MEMO_OUTPUT`](/reference/solid-js/diagnostics/unstable-memo-output): this scope's equality gate closes almost every time. +`data.wasted` of `data.runs` runs in the window produced the same value as before — the scope re-ran because an input changed, did its work, compared equal, and told nobody. +The compute (`data.wastedMs`) bought nothing. +`data.causes` names the input that keeps triggering it. + +:::note[Performance pathologies (from the attribution engine)] +These fire only while attribution is enabled and describe cost, not incorrect behavior. +The numbers in the message are measurements, not guesses. +::: + +## How to fix it + +Fix upstream, where the change originates: + +- Read a narrower slice: `user().name` re-runs on every `user` write; if `user` is a store, `user.name` re-runs only when the name changes. For a signal holding an object, derive the field first — `const name = createMemo(() => user().name)` — and read the memo. +- Put an equality boundary on the source: an `equals` option that compares the part that matters, so writes that do not change it never notify. +- If the scope is expensive and the input legitimately churns, split it: a cheap memo that extracts what it needs, feeding the expensive one. + +Do NOT add an `equals` to the wasted scope itself — its gate already closes; the cost is the run before the gate. +The run is the problem, so stop the notification that starts it. +Held and overlay runs are never counted here; see `costs().scopes[].wastedMs` for the total per scope. + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `WASTED_RECOMPUTE` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +`expectRerunBudget(artifact, max, { scope })` bounds how often a named scope may re-run, and `expectNoWaste(artifact)` fails on re-runs whose result was unchanged. +Both read the attribution records, which `captureArtifact` collects unless `attribution: false` is passed. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. +- [Attribution](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution): the engine whose records explain a re-run, a hold, or a waterfall. diff --git a/src/routes/reference/(1)solid-js/(8)diagnostics/wide-scope-deps.mdx b/src/routes/reference/(1)solid-js/(8)diagnostics/wide-scope-deps.mdx new file mode 100644 index 000000000..896c0c967 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(8)diagnostics/wide-scope-deps.mdx @@ -0,0 +1,57 @@ +--- +title: "WIDE_SCOPE_DEPS" +category: "Diagnostics" +use_cases: "wide_scope_deps diagnostic, console warning repair, diagnostics assertion" +tags: + - "diagnostics" + - "perf" + - "warn" + - "reference" + - "v2" +version: "2.0" +description: "WIDE_SCOPE_DEPS: Scope subscribed to 200+ sources (attribution cost checks on)." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/solid/skills/reactivity-diagnostics/SKILL.md" +--- + +{/* Generated by scripts/extract-diagnostics.mjs from packages/solid/skills/reactivity-diagnostics/SKILL.md and packages/solid/diagnostics.json in solidjs/solid. Edit the skill, then regenerate. */} + +Scope subscribed to 200+ sources (attribution cost checks on). + +## What you saw + +- **Kind:** `perf` +- **Severity:** `warn` (code that works but is structurally wrong or expensive) + +The console report opens with `[WIDE_SCOPE_DEPS]` and the message, then an `in` line naming the owners that enclose the subject, root first, and an `at` line with the source location when the code was compiled with one. +In a captured artifact the same finding is an event with `code: "WIDE_SCOPE_DEPS"`, `ownerPath`, and `loc`. +[Diagnostics](/reference/solid-js/diagnostics#read-a-report) explains each line. + +## Why it fires + +Attribution engine cost checks only: a scope is subscribed to 200 or more sources (it re-warns on 50% further growth). +HMR plumbing sources are not counted, and in dev the renderer's insert child-resolution pass (which tracks each row's resolved child) is not judged — [`HUGE_FAN_IN`](/reference/solid-js/diagnostics/huge-fan-in) still is. + +:::note[Performance pathologies (from the attribution engine)] +These fire only while attribution is enabled and describe cost, not incorrect behavior. +The numbers in the message are measurements, not guesses. +::: + +## How to fix it + +The message lists up to 12 of them — start there, and narrow the reads or split the scope as described under [`HUGE_FAN_IN`](/reference/solid-js/diagnostics/huge-fan-in). + +## In tests + +`captureArtifact` from `@solidjs/diagnostics` runs a scenario with the diagnostics channel open and returns the events it saw. +`expectNoDiagnostics(artifact)` fails while `WIDE_SCOPE_DEPS` is among them, with the events as evidence; after the fix, the code is gone from `artifact.diagnostics`. +`expectRerunBudget(artifact, max, { scope })` bounds how often a named scope may re-run, and `expectNoWaste(artifact)` fails on re-runs whose result was unchanged. +Both read the attribution records, which `captureArtifact` collects unless `attribution: false` is passed. +[Check for regressions in tests](/guides/debugging-reactivity#check-for-regressions-in-tests) shows the loop and the Vitest matcher form. + +## Learn more + +- [Diagnostics](/reference/solid-js/diagnostics): every code, with its kind, severity, and trigger. +- [Debugging reactivity](/guides/debugging-reactivity): how to read a report and what each group of codes points at. +- [Attribution](/reference/solid-js/advanced/diagnostics-dev-hooks/attribution): the engine whose records explain a re-run, a hold, or a waterfall. diff --git a/src/routes/reference/(5)vite-plugin-solid/(1)options.mdx b/src/routes/reference/(5)vite-plugin-solid/(1)options.mdx index 40e9fcef9..c5f67c252 100644 --- a/src/routes/reference/(5)vite-plugin-solid/(1)options.mdx +++ b/src/routes/reference/(5)vite-plugin-solid/(1)options.mdx @@ -82,7 +82,7 @@ The same flag is passed to the compilers as `dev` and decides the default for [` - **Default:** `false` Selects the observe exports of `solid-js` and `@solidjs/web`: the production-speed runtime that keeps the diagnostics and attribution channels (`OBSERVE`) alive. -Adds the `observe` condition to every environment, client and server, and turns on every kind of [`solid.sourceNames`](#sourcenames) so graph labels survive minification. +Adds the `observe` condition to every environment, client and server, and turns on every naming kind of [`solid.sourceNames`](#sourcenames) so graph labels survive minification; `locations` stays off unless set. Applies to `build` and `preview`; during `serve` the `development` condition still wins. ### `diagnostics` @@ -187,6 +187,7 @@ interface SourceNamesOptions { components?: boolean; bindings?: boolean; primitives?: boolean; + locations?: boolean; } ``` @@ -206,17 +207,24 @@ Selects which names written in source are carried into the output so the develop - `bindings` names compiled binding effects by what they write, such as `span.textContent` or `div.children`. - `primitives` names `createSignal`, `createMemo`, `createStore`, and the other primitives after the identifier they are declared as (`count`, `todos.title`), prefixed with the enclosing non-component function for composed primitives (`createCounter.value`). An explicit `name` option is never overridden. - -`components` and `bindings` are passed to the selected JSX compiler as its `sourceNames` option. -`primitives` runs `@solidjs/compiler`'s standalone `transformSourceNames` pass on every module outside `node_modules` that imports `solid-js` or `@solidjs/signals`, `.ts` and `.js` files included, with either JSX compiler. -A `@solidjs/compiler` without that pass leaves primitives unnamed and warns once. - -| `sourceNames` | `dev` or `observe` | Production build | -| ---------------------- | --------------------- | ---------------------- | -| Omitted | Every kind on | Every kind off | -| `true` | Every kind on | Every kind on | -| `false` | Every kind off | Every kind off | -| `{ kind: true/false }` | As given; the rest on | As given; the rest off | +- `locations` adds the source position to every label the other three kinds write, as `file:line:col` relative to the Vite root (`src/App.tsx:18:3`): a component's JSX element, a binding's attribute or hole, a primitive's call. + The development and observe runtimes carry it as `loc` on the node, print it as the `at` line of a console report, and deliver it as `DiagnosticEvent.loc`; [Diagnostics](/reference/solid-js/diagnostics#read-a-report) shows the report. + An explicit `loc` option is never overridden. + +`components`, `bindings`, and `locations` are passed to the selected JSX compiler as its `sourceNames` option. +`primitives` runs `@solidjs/compiler`'s standalone `transformSourceNames` pass on every module outside `node_modules` that imports `solid-js` or `@solidjs/signals`, `.ts` and `.js` files included, with either JSX compiler; `locations` is passed to that pass as well. +The plugin passes the Vite `root` to both compilers and to the pass, so a location is root-relative and a dev server resolves `${origin}/${path}` to the module. +A `@solidjs/compiler` without that pass leaves primitives unnamed and warns once; a compiler that predates `locations` or `root` does not receive them, with the same one-time warning. + +| `sourceNames` | `dev` or `observe` | Production build | +| ---------------------- | --------------------------------------- | ---------------------- | +| Omitted | Every kind on (`locations`: `dev` only) | Every kind off | +| `true` | Every kind on | Every kind on | +| `false` | Every kind off | Every kind off | +| `{ kind: true/false }` | As given; the rest on | As given; the rest off | + +`locations` is the one kind that `observe` leaves off: an observe build is a production bundle, and a source path inside it is something to opt into with `sourceNames: { locations: true }`. +Under `dev` it is on like the others. The plugin always passes the resolved value to the compiler, so `sourceNames: false` disables names during `serve` rather than falling through to the compiler's own development default. diff --git a/vite.config.ts b/vite.config.ts index 40eeffe12..046c0e545 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -38,6 +38,10 @@ function transformSidebarItem(item: SidebarItemWithMeta): SidebarItemWithMeta { if (item.title === "Advanced") { return { ...item, collapsed: true }; } + // One page per diagnostic code; the group's index is the way in. + if (item.title === "Diagnostics" && item.filePath?.includes("/reference/")) { + return { ...item, collapsed: true }; + } if (item.title === "Jsx Properties") { return { ...item, title: "JSX properties" }; }