Skip to content

docs: correct drifted claims in five posts and the CSP comment - #1293

Merged
vivek7405 merged 10 commits into
mainfrom
docs/fix-factual-drift-blog-csp
Aug 6, 2026
Merged

docs: correct drifted claims in five posts and the CSP comment#1293
vivek7405 merged 10 commits into
mainfrom
docs/fix-factual-drift-blog-csp

Conversation

@vivek7405

@vivek7405 vivek7405 commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Closes #1256

Summary

Published statements about WebJs contradicted the code they described. Most were in blog posts, which is the surface search engines and AI agents read most, and one was the csp.js comment that is the upstream cause of a claim already corrected elsewhere. A reader who tried to satisfy a webjs check rule that never existed, or who expected an esbuild fallback that was deleted, was chasing something the framework has never had.

Prose and comments only. No behaviour changes; the two source files touched (csp.js, check.js) are edited in their comments alone, and one test is added.

What changed

  • Phantom webjs check rules are gone. tests-exist, no-json-data-files and light-dom-css-prefix were never in RULES, and no-server-imports-in-components was a wrong spelling of no-server-import-in-browser-module with a wrong description. A second phantom, no-scaffold-placeholder (retired in dogfood: design-distinctness machinery is over-engineered and mis-layered #969), was still named as live in the agent skill and shipped as a token in every generated api app. The enumerated rule list and the "~10 rules" count are gone rather than corrected, since a hand-maintained list inside a post is exactly what drifted; the posts now name real rules and point at webjs check --rules.
  • strip-types-not-esbuild.md described an esbuild.transform fallback that no longer exists, showed a const TS_CACHE = new Map() identifier that does not exist, and showed a named import of stripTypeScriptTypes that ts-strip.js documents as a link-time SyntaxError. The 500 it describes is now scoped to dev, since production is deliberately terse so no path leaks.
  • csp.js blamed Tailwind's browser runtime for the style-src inline allowance. The real mechanism is that style-src carries no nonce source at all, so the nonce SSR stamps on the framework's own head <style> is inert and 'unsafe-inline' admits every inline style, WebJs's own included. The comment now says that and enumerates the app-authored shapes that keep the allowance non-droppable. The same wrong contrast was corrected in blog/no-build-via-jspm-io.md.
  • client-router-turbo-drive-style.md described the pre-keyed boundary format, claimed the router needs an import in the root layout, attributed the morph tier's state preservation to the replace tier, and carried a line count off by more than 3x. The marker format, the X-Webjs-Have entry shape, the two-tier swap rule, and the auto-enable behaviour now agree with the source. Its nested-route data-deduplication non-goal was restored after turning out to be true.
  • The View Transitions opt-in is <meta name="view-transition" content="same-origin"> everywhere it is stated. The bare tag enables nothing, and AGENTS.md plus the scaffold's own demo were stating the bare form.
  • The check-rule bar now reads the same on all seven surfaces that state it, including check.js itself, after the docs site turned out to carry a category the others dropped.

Verification

  • Phantom-rule scan over blog/, website/app/docs/ and the agent skill, against RULES imported from check.js (not counted by eye, and not read off CLI output where long names shift the column spacing). Clean.
  • CSP mechanism established from the directive set itself: __NONCE__ appears only in script-src, which is why the nonce is inert for styles. An earlier empirical probe measured only whether the nonce attribute was present, which is the wrong question, and its conclusion has been corrected.
  • csp.js and check.js are comment-only against main, and csp.test.js (16/16) and check.test.js (65/65) pass with both test files untouched, which is itself the check that no value moved.
  • Scaffold generated and booted for both affected templates. A full-stack app serves /features/view-transitions with its prose matching the meta it emits; an api app passes webjs check clean and serves its routes. Scaffold tests 13/13.
  • New test asserts the api showcase ships no retired-gate marker, with a verified counterfactual (it fails against the previous generator). The absence of an api-side assertion is why the full-stack cleanup never reached that template.
  • Website renders every edited post and doc page at 200 with no broken modulepreload, asserting each corrected claim present and each stale one absent.
  • CI green on all ten checks.

Doc surfaces

  • Updated: six blog posts, two docs-site pages, packages/server/src/csp.js and check.js (comments), AGENTS.md, .agents/skills/webjs/references/testing.md, website/app/docs/configuration/page.ts, packages/cli/bin/webjs.js (the --rules banner), packages/cli/lib/api-gallery.js, packages/cli/templates/gallery/app/features/view-transitions/page.ts, examples/blog/CONVENTIONS.md, and one scaffold test.
  • Docs site: website/app/docs/deployment/page.ts and error-handling/page.ts carried the same wrong CSP contrast and are corrected. conventions/page.ts and client-router/page.ts were already right and are untouched.
  • N/A, MCP, editor plugins, README, marketing copy: no introspection surface, grammar, or landing-page claim changes.

Tests

One test added (the api marker assertion, with its counterfactual). No test pins blog prose, and none should: pinning sentences to RULES would red every future rule addition on an unrelated file. website/test/ssr/pre-block-a11y.test.ts renders the shared markdown post body from a synthetic string rather than a real post, so nothing pinned the edited sentences. The verification above stands in for a counterfactual on the prose, which is why every claim cites the file and line that governs it.

Browser, e2e and Bun layers are N/A for the prose: the only source edits are comments, and csp.js is not on the Bun-parity hook's runtime-sensitive list. CI ran all of them anyway and they pass.

@vivek7405 vivek7405 self-assigned this Aug 5, 2026
@vivek7405

Copy link
Copy Markdown
Collaborator Author

Design rationale: why the rule list is deleted rather than corrected, and how the CSP comment was settled

Four decisions here are not obvious from the diff.

The rule list and the rule count are gone, not fixed. The straightforward repair was to drop the four names that were never in RULES and change "~10" to "20". I did not, because a hand-maintained copy of the rule set inside a blog post is the thing that drifted in the first place, and a corrected count is the same failure mode with a one-release fuse on it. The posts now name a few real rules as examples and send the reader to webjs check --rules, which is the only surface that cannot go stale. Same reasoning killed the "new rules are about 20 lines apiece" line rather than restating it with a bigger number.

The bullets kept their colon form, against my own earlier note. I had written down that `rule-name`: description trips the prose hook and that any rewritten bullet had to switch to the comma form. Reading the hook says otherwise: its colon rule fires on )</code>:, on a hyphenated <my-tag>:, and on a comment line shaped like foo(): prose, and a backtick span holding a plain identifier is none of those. So the replacement bullet joins the five correct ones in the style they already use, instead of restyling five accurate lines to satisfy a constraint that was not real. The comment-line form of that rule does bind csp.js, and the new comment there avoids it.

A sixth drifted claim came from the scan, not from reading. I checked every rule-shaped backticked token across blog/ and website/app/docs/ against RULES imported from check.js, which turned up light-dom-css-prefix in a third post I had not been looking at. The same scan is what gives me confidence there is no seventh: it comes back clean now apart from a no-store false positive, which is a Cache-Control token. The strip-types post's const TS_CACHE = new Map() came the same way, from checking the identifier rather than the prose around it.

The CSP comment was settled by booting an app, not by reading the code. The old comment blamed Tailwind's browser runtime, which is wrong twice over, since a scaffolded app compiles Tailwind to a static file and has no browser runtime at all. Rather than swap one single cause for another, I ran it: an app with csp on and no Tailwind anywhere, carrying a page-level inline <style>, a style="..." attribute, a light-DOM component with a <style>, and a shadow component with static styles. Four style tags came back, exactly one carrying a nonce, and that one is the framework's own @layer webjs-host rule in the head. That result is the comment, line for line, including the attribute case, which can never be covered because a nonce applies to elements and not to attributes.

One thing I deliberately left alone: the client-router-turbo-drive-style slug. It is a published URL, another post links to it, and the section that was wrong is fixable without moving the page.

@vivek7405 vivek7405 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went through this against the source it describes, since a PR that exists to remove wrong claims has to be held to its own standard. The CSP comment is right and I like that it was settled by booting an app rather than by reading; same for the rule names, the light-DOM convention wording, the state.tsCache correction, and the prefetch and X-Webjs-Have corrections. Those all check out.

Five problems, and the pattern in them is worth naming: four are in strip-types-not-esbuild.md and client-router-turbo-drive-style.md, where correcting one paragraph left the paragraphs around it saying something else. The rule for this PR should be that a post is internally consistent when I am done with it, not just that the sentence I targeted is now true.

One is a new wrong claim this PR introduces (the view-transition opt-in), which is the worst kind to ship here. Two are untouched lines that my own edit turned into contradictions. One is a claim I made stricter than the code supports. One is true in dev and false in prod.

Path-level, since it sits outside the diff: strip-types-not-esbuild.md:29 shows import { createRequire, stripTypeScriptTypes } from node:module. dev.js:6 imports only createRequire, and ts-strip.js:28 spells out that the named import of the builtin is forbidden because it is a link-time SyntaxError on a runtime that lacks the export. That is the Bun case the new paragraph at line 64 introduces, so the post now shows a snippet the framework documents as unsafe, twelve lines above the paragraph explaining why Bun needs a different backend.

Comment thread blog/client-router-turbo-drive-style.md Outdated
Comment thread blog/strip-types-not-esbuild.md
Comment thread blog/strip-types-not-esbuild.md Outdated
Comment thread blog/ai-first-is-plumbing.md Outdated

@vivek7405 vivek7405 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second read, scoped to the fix commit and its blast radius. Five more, and they are the same failure as the first round rather than a new one: I fixed the sentence and left the surfaces and paragraphs around it disagreeing with it.

Worth stating plainly, because it is the lesson of this PR. Correcting a claim is not done when the claim is true. It is done when nothing else in the post, the reference docs, or the scaffold still says the old thing, and when the corrected sentence does not quietly depend on a stale paragraph above it.

  1. The view-transition correction never left the blog. AGENTS.md and the scaffold's own view-transitions demo both still showed the bare meta tag that enables nothing, and the demo contradicted its own metadata.other block two lines above it. A scaffolding agent reads those, not the blog.
  2. The strip-types paragraph I added gave the wrong reason for the namespace import. The hazard ts-strip.js documents is an older Node crashing the import before the version preflight can report the real problem. A runtime with no stripper is the case the namespace import handles gracefully, so I had named the good case as the failure.
  3. The client-router post still described the pre-keyed boundary format, and the line I added leaned on that section. Sharing a layout chain is not sufficient for the short-circuit, since the route key has to match too, which is the entire reason the entries are keyed.
  4. The same post said the router needs an ES module import in the root layout. It auto-enables when core loads.
  5. The rule framing I wrote dropped a category the docs site carries. A reactive prop that silently stops re-rendering is not a crash, a leak, or a strip failure, and several rules catch only that.

All five are fixed. I generated a scaffold app and booted it to confirm the demo route serves and its visible prose now matches the meta it actually emits, since that one is generated code rather than prose.

@vivek7405 vivek7405 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Third read, scoped to the previous fix commit. Five more, and the shape has shifted in a way worth recording.

Two were the keyed-swap correction being half-applied. I added sentences describing the keyed behaviour to a post whose step list and "how it knows what to swap" section still described the single-tier rule the key replaced, so adjacent paragraphs contradicted each other and following the older one gave the reader exactly the morph the newer one disclaimed. The real rule is two-tier, a changed route key wins first and anchors at the parent of the shallowest change, deepest-shared applies only when no key changed, and the morph is chosen only when that boundary is the leaf on both sides. I had also counted only layout boundaries, where SSR emits one around the page too, which is the pair that makes a bare param change remount.

One was a second phantom rule, and it is the most useful finding of the three rounds. The agent skill named no-scaffold-placeholder as a shipping webjs check rule. It was removed in #969, with a test asserting it is no longer registered. That is precisely the defect this PR exists to remove, sitting on the canonical agent surface that gets bundled into every scaffolded app, and my own phantom-rule scan never saw it because I had scoped the scan to blog/ and the docs site. The scan now covers the skill too.

One was the category I added last round being applied to the blog alone. The same framing is stated in AGENTS.md, in the skill, in the webjs check --rules banner that my corrected sentence explicitly sends readers to as the authority, and in a second post. All four now match the docs site.

All fixed. A generated app passes webjs check clean and boots, and the scaffold gallery tests still pass.

@vivek7405 vivek7405 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fourth read. Three findings, down from five and five, and for the first time none of them is a new claim I introduced this round.

Two were the keyed-swap rewrite still not finished. Step 4 promised the swap preserves input values, scroll, popover state and DOM identity, which is true of the morph tier and false of the replace tier that the step directly above had just said a changed route key triggers. A replace tears the live range out and inserts fresh nodes, carrying across only data-webjs-permanent. I had also called the morph the cheaper path when it is the more expensive one, chosen for state preservation rather than speed. And the section on what stays mounted still said replacing only the innermost pair is what preserves it, which is exactly what the keyed rule overrides.

The third is the better find. The api gallery generator told every generated app, in five route files and its own docstring, that webjs check fails while the scaffold marker remains. That is the same removed rule as last round, but this time in GENERATED SOURCE rather than in docs, so a developer running webjs create --template api reads a false instruction in their own repo. The full-stack gallery had already dropped the claim and only its clear is asserted by a test, so nothing covered the api side. A freshly generated api app now passes webjs check clean, which is what the corrected comment says.

Verified by generating an api app, running check on it, and booting two of its routes. All 59 scaffold tests pass.

Three rounds of this PR have now turned up the same failure in three different guises: a claim corrected in one place and left standing in the places around it. The phantom-rule scan I built for this PR only looked at blog/ and the docs site, which is why the skill copy survived round two and the generator copy survived round three. Worth remembering that the scan's SCOPE was the defect both times, not the individual lines.

@vivek7405 vivek7405 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fifth read. Seven findings, and one of them falsified the claim this PR leads with, so this round is the most useful of the five.

The CSP comment had the mechanism backwards. style-src is 'self' 'unsafe-inline' and carries no nonce source at all; __NONCE__ appears only in script-src. So the nonce SSR stamps on the framework's own head <style> is INERT under this policy, and that rule is admitted by 'unsafe-inline' exactly like the four app-authored kinds it was being contrasted with. Drop 'unsafe-inline' and the framework's own style is blocked too. The contrast the comment drew does not exist, and my empirical probe did not catch it because it measured whether the attribute was PRESENT, not whether CSP honours it. Presence was the wrong question. The comment now says the allowance covers all inline style, framework included, and enumerates the app-authored shapes as what keeps it non-droppable without claiming WebJs escapes it.

I deleted a statement that was true. The original post's third non-goal was nested-route DATA deduplication, pointing at the HTTP cache and cache() memoization. That is not what X-Webjs-Have does, and it is still accurate. I replaced it with a claim that the mechanism "has since been built", when the post's own # The X-Webjs-Have optimization section was there at first publish. So the issue's premise on that item was wrong, and I introduced drift while removing drift. Restored, with prefetch left as the one that genuinely changed. This is the inverse of every other finding in this PR and the one I would most want flagged.

The rest: the post's "1400-line" figure for router-client.js is off by more than 3x (it is 4777), and a count in prose is the same fuse this PR removed from another post, so it is gone rather than corrected. The api showcase still shipped a webjs-scaffold-placeholder token named after the rule retired in #969, which the full-stack gallery had deleted outright rather than merely reworded; it is gone from generated source now, with a test asserting absence for the api template, since the missing api-side assertion is precisely why the full-stack cleanup never reached it (counterfactual: that test fails against the previous generator). And the widened rule-catches framing reached four surfaces last round and missed three, including check.js itself, the source the other four describe.

Verified: csp.js and check.js remain comment-only against main, csp 16/16 and check 65/65 pass untouched, 13/13 scaffold gallery tests, a generated api app passes webjs check clean and serves, and the docs and blog pages render with the corrected text.

@vivek7405 vivek7405 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sixth read, and the last one this cycle takes. Three findings, all fixed in the commit above, and the branch now sits with those three fixes UNREVIEWED. I am stopping here rather than running a seventh round, and I would not merge this without another read over the whole diff.

The findings:

The style-src correction reached csp.js but not no-build-via-jspm-io.md, which stated the same wrong contrast in almost the same words ("The framework's own head <style> carries the nonce. App styles generally do not"). That is the third place this claim lived, and the PR body had asserted only two needed correcting.

The csp.js docstring sentence I wrote in the previous commit was garbled. It listed app-authored style inside an enumeration of WebJs's own output, and its trailing relative clause had no working antecedent, so read literally it said the policy nonces inline style for scripts only.

The scaffold-sync rule swapped one unmet requirement for another. I replaced the retired-marker instruction with "a line naming what to delete when the demo is pruned", which the full-stack gallery deliberately does not carry, since gallery:clear sheds the whole gallery in one step. An author following it would have added prune comments to 26 demos designed not to need them.

On the cycle as a whole. Six rounds found 28 real problems, and the count per round did not converge (5, 5, 3, 7, 3). Two of those findings were mine in a direction worth naming: I introduced a wrong claim about the view-transition opt-in, and I deleted a nested-route data-deduplication non-goal that was true, on a premise from the issue that turned out to be wrong. So this PR both removed drift and briefly added some.

The recurring failure was never a hard fact. It was scope. Every round found the corrected claim still standing somewhere I had not looked: the reference docs, the scaffold, generated source, a sibling post. The phantom-rule scan I built covered blog/ and the docs site only, which is exactly why the skill copy survived round two and the generator copy survived round three. When a claim lives in N places, fixing it in one and verifying that one is not progress.

@vivek7405 vivek7405 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Short delta round on the three previously unreviewed fixes. Two findings, both mine, both fixed in 33e8cd59.

The CSP correction had one more home. website/app/docs/deployment/page.ts stated the same framework-versus-app nonce contrast the source comment now denies, in nearly the same words as the blog sentence already corrected. I had written the docs site off as N/A in the PR body, which is exactly what let it be skipped, and that line of the body is now fixed too. The related global-error guidance in AGENTS.md and error-handling said an inline style there must carry the nonce; under the default policy that is inert for style and load-bearing only for script, so both now say which is which.

The scaffold-sync rule I rewrote last round overcorrected. I had written that the full-stack demos deliberately carry no prune note, which is false: the auth card, the server-actions card that depends on it, the gallery nav module and the icon route all carry one, and AGENTS.md documents selective pruning that a blanket prohibition contradicted. The rule now describes when a prune note earns its place, which is a card spanning several paths or one another card depends on, rather than forbidding it.

Worth noting for anyone reading this trail cold: that is the third time a correction landed in one place while the same sentence sat in another. The lesson stands at the end of the PR as it did at the start, and it is about search scope rather than about any individual claim.

@vivek7405 vivek7405 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix-check on the previous commit, and it found two, so this is the second consecutive check to come back with something. I am stopping the cycle here rather than running another, which is the rule doing its job and not a judgement that the branch is finished.

Both are fixed in 7b762002, and both were mine. The global-error demo's comment named @webjsdev/server as the source of cspNonce() while the file imports it from @webjsdev/core two lines below, which is also the import every other surface recommends; I rewrote that sentence last commit and carried the stale package name through. And the prune-note rule cited a header on the generated app/page.ts that does not exist, since that instruction is a comment in the generator sitting above the writeFile call and never reaches a generated app. I had swapped one wrong pointer for a more wrong one. It now points only at gallery:clear, which is a claim that holds.

The CSP fact itself is settled. This check swept the repo and found no remaining place claiming an inline <style> needs or is admitted by a nonce, and no remaining contrast between the framework's nonced style and un-nonced app style. That was the PR's headline correction and it is now consistent across the source comment, two blog posts, two docs-site pages, AGENTS.md, and the scaffold.

What is NOT settled is the branch's state. The two fixes above are unreviewed. Eight rounds have now found 34 problems, and the last four rounds were all cleanup of my own corrections rather than of the original drift, which says the corrections themselves need a read more than the original claims do. My recommendation to the owner is one fresh review over the whole diff before merging, not another delta: every delta round since the second has been scoped to a fix commit, and scope is exactly what kept letting the same claim hide one file over.

@vivek7405
vivek7405 marked this pull request as ready for review August 6, 2026 05:42
Six published statements contradicted the code they described, and the
blog is the surface search engines and AI agents read most, so a reader
was being sent after rules and fallbacks the framework has never had.

Four rule names in the posts were never in RULES, and one of them was
promised as a linter that would catch unprefixed light-DOM selectors.
The rule list and rule count are gone rather than corrected, because a
list copied into a post is what drifted in the first place; the posts
now name a few real rules and point at `webjs check --rules`.

The esbuild fallback was removed from the strip path long ago, and the
post describing it was the last place claiming it still existed. The
same post showed a TS_CACHE identifier that does not exist.

The CSP comment blamed Tailwind's browser runtime for the style-src
inline allowance. A scaffolded app compiles Tailwind to a static file
and has no browser runtime at all, so the comment now enumerates the
four things that genuinely carry no nonce, and contrasts them with the
framework's own head style, which is signed. Verified by booting an app
with csp on and no Tailwind in it: four style tags, one nonce.

Two router non-goals had since been built, prefetch and the per-nav
full refetch, the second by a mechanism the same post already explains.

Comment-only in csp.js; every directive string is untouched and
packages/server/test/csp/csp.test.js passes unedited.
The first pass fixed the sentence it targeted in each post and left the
paragraphs around it saying something else. Five follow-ons.

The view-transition opt-in needs content="same-origin"; the router reads
that value and enables nothing without it, so the bare meta tag the last
commit named would have left a reader with no transitions.

strip-types-not-esbuild showed a named import of stripTypeScriptTypes,
which ts-strip.js documents as unsafe: a named import of a missing
builtin export is a link-time SyntaxError, which is exactly the Bun case
the same post now explains twelve lines below. It also said amaro would
only be installed directly if a future Node dropped it, while the new
paragraph says amaro is the Bun backend today. It is an optional
dependency now, so the two paragraphs described different worlds.

The clean 500 naming the file is the dev response. Production stays
terse on purpose so no path leaks, with the detail in the log.

"Every rule covers an invariant that crashes in production" was stricter
than the rule set: array-prop-uses-array-type says of itself that the
wrong one does not crash, and no-server-env-in-components is a leak.
AGENTS.md frames the bar as a crash, a leak, or a strip failure.
The blog correction landed without the surfaces around it. AGENTS.md and
the scaffold's view-transitions demo both still showed the bare meta tag,
which enables nothing, and the demo contradicted its own metadata block
two lines above. Both now name content="same-origin".

The client-router post described the pre-keyed boundary format, so the
line added last commit leaned on a stale section and generalized wrongly:
sharing a layout chain is not enough, the route key has to match too, or
a dynamic layout held for other params would wrongly short-circuit. The
marker format, the X-Webjs-Have entry shape, and that line now agree with
ssr.js. The same post also still said the router needs an import in the
root layout; it auto-enables when core loads.

The strip-types post gave the wrong reason for the namespace import. The
hazard is an older Node crashing the import before the version preflight
can report the real problem, not a runtime without a stripper, which is
the case a namespace import exists to handle gracefully.

The rule framing dropped a category the docs site carries: a reactive
prop that silently stops re-rendering is neither a crash nor a leak nor
a strip failure, and several rules only ever catch that.
The client-router post described the pre-keyed, single-tier swap while
the sentences added last commit described the keyed one, so adjacent
paragraphs contradicted each other. The real rule is two-tier: a changed
route key wins first and anchors a replace at the PARENT of the
shallowest change, and only when no key changed does the deepest shared
boundary become the target, morphing just when it is the leaf on both
sides. Following the old text gave the reader exactly the morph the new
text disclaimed. SSR also emits a pair around the page itself, which is
what makes a bare param change remount, and the post counted only
layouts.

The agent skill named `no-scaffold-placeholder` as a shipping check rule.
It was removed in #969, and a test asserts it is no longer registered.
That file is the canonical agent surface and is bundled into every
scaffolded app, so it is the same defect this PR exists to remove, on the
surface where it does the most damage.

The rule-catches framing that gained a category last commit is stated in
four places, and only the blog got it. AGENTS.md, the skill, the
`webjs check --rules` banner the corrected sentence points readers to,
and a second post now match the docs site.
Step 4 promised that the swap preserves input values, scroll, popover
state and DOM identity, but that is the morph tier only. A replace tears
the live range out and inserts fresh nodes, carrying across just the
elements marked data-webjs-permanent, so the step contradicted the step
above it, which had just said a changed route key replaces. The morph was
also called the cheaper path when it is the more expensive one, chosen
for state preservation rather than for speed.

The section on what stays mounted still said replacing only the innermost
pair is what preserves it. Under the keyed rule the innermost is exactly
not the target when a route key changed.

The api gallery generator told every generated app, in five route files
and its own docstring, that webjs check fails while the scaffold marker
remains. That rule was removed in #969, so a freshly generated api app
passes check clean and the comment was false in generated source rather
than only in docs. The full-stack gallery had already dropped the claim
and only its clear is asserted, so nothing caught the api side.
The style-src comment had the mechanism backwards. That directive carries
no nonce source, so the nonce SSR stamps on the framework's own head
style is inert under this policy and every inline style, WebJs's own
included, is admitted by 'unsafe-inline'. Dropping it would block the
head rule too, so the contrast the comment drew between framework style
and app style does not exist. What keeps the allowance necessary is
inline style with no realistic nonce path, which the comment now
enumerates without claiming the framework escapes it.

The client-router post lost a non-goal that was accurate. Nested-route
DATA deduplication is not what X-Webjs-Have does, and it still does not
happen: the HTTP cache and cache() memoization cover that layer. The
X-Webjs-Have section also predates the post's publication, so nothing
about it "has since been built". Prefetch is the one that changed, and
the section now says so and nothing more. The post's 1400-line count for
router-client.js was off by a factor of three, and a count in prose is
the same fuse this PR removed elsewhere, so it is gone rather than
corrected.

The api showcase kept shipping a webjs-scaffold-placeholder token named
after the rule retired in #969, which the full-stack gallery deleted
outright. It is gone from generated source too, with a test asserting
absence for the api template, since the missing api-side assertion is
why the full-stack cleanup never reached it.

The widened rule-catches framing reached four surfaces last time and
missed three, including check.js itself, which is the source the others
describe.
The style-src fix reached csp.js but not the post that states the same
thing in the same words. no-build-via-jspm-io said the framework's head
style carries the nonce while app styles do not, which is the contrast
that does not exist: style-src has no nonce source, so unsafe-inline
admits every inline style including WebJs's own head rule.

The csp.js docstring sentence introduced with that fix was garbled. It
listed app-authored style as part of WebJs's own output and trailed a
relative clause with no antecedent. It now names the head style as the
framework's one inline style and states plainly that the nonce covers
scripts only.

The scaffold-sync rule swapped one unmet requirement for another. The
full-stack gallery deliberately carries no per-demo prune comment,
because gallery:clear sheds the whole gallery in one step, so telling an
author to add one would mean touching 26 demos designed not to need it.
The deployment page carried the same framework-versus-app nonce contrast
the source comment now denies, in nearly the same words as the blog
sentence already fixed. style-src has no nonce source, so unsafe-inline
admits every inline style including WebJs's own head rule, and the page
now says that before listing what makes the allowance hard to give up.

The global-error guidance said an inline style there must carry the
nonce. Under the default policy that is inert for style and load-bearing
only for script, so both places now say which is which.

The scaffold-sync rule went too far the other way. Several cards do carry
a prune note, and they are the ones where removal spans paths or another
card depends on them, so the rule now describes that shape instead of
forbidding it. AGENTS.md also documents selective pruning, which a
blanket prohibition contradicted.
The scaffold's own global-error demo was the third surface of that one
piece of guidance, and the previous commit fixed only two. It told the
reader an inline style there needs a nonce, and that the nonce is what
allows it, when under the default policy unsafe-inline is what allows it
and the nonce is inert. The stamped nonce stays, since it is what makes
the page keep working if you tighten style-src, and the comment now says
that instead of claiming it is load-bearing today. The route-handler demo
described the same inert operation.

The prune-note rule cited the wrong example. The auth card names its own
paths rather than the server-actions card, so it illustrates the
spanning-several-paths shape, not the depends-on-another shape. The
per-route delete instruction a generated app carries is in its app/page.ts
header, not in its AGENTS.md.
The global-error demo's comment named @webjsdev/server while the file
imports cspNonce from @webjsdev/core two lines below, which is also the
import every other doc surface recommends. The previous commit rewrote
that sentence and carried the stale package name through.

The prune-note rule cited a header on the generated app/page.ts that does
not exist. That instruction is a comment in the generator, above the
writeFile call, so it never reaches a generated app. The rule now points
only at gallery:clear, which is a claim that holds.
@vivek7405
vivek7405 force-pushed the docs/fix-factual-drift-blog-csp branch from 7b76200 to 6f8458a Compare August 6, 2026 05:43
@vivek7405
vivek7405 merged commit 34c504f into main Aug 6, 2026
10 checks passed
@vivek7405
vivek7405 deleted the docs/fix-factual-drift-blog-csp branch August 6, 2026 05:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix factual drift in three blog posts and the csp.js policy comment

1 participant