🍕 Stop component client.js from running in Kiln edit mode - #256
Merged
Conversation
The Vite pipeline serves the view-mode bootstrap in edit mode, and that bootstrap calls mountComponentModules() at module scope — so every on-page component's client.js executes as soon as the module evaluates, while an editor is in Kiln. The legacy clay compile pipeline resolved client.js for view mode only (the edit branch of getDependencies() ships model.js, kiln.js and kiln plugins, but no client bundle and no _client-init.js), so this is a regression from clay compile. It runs ads (GPT injects an iframe per slot), analytics and comment embeds inside the editing surface, where they mutate the DOM Kiln is trying to decorate. Generate a second entry, .clay/vite-bootstrap-no-mount.js, from the same initializer prelude — the window.modules stub, _env-init, _globals-init and the sticky-events shim, all of which edit mode genuinely needs — but without the mount runtime, and serve that in edit mode. resolveModuleScripts() takes mountInEditMode to opt back in, and falls back to the mounting bootstrap when public/js was built by a claycli predating the new entry. Co-authored-by: Cursor <cursoragent@cursor.com>
Nothing should ever want component client.js running in edit mode. It was never possible under clay compile, so there is no prior behavior to preserve, and the option had no caller — it only existed because the fix was framed as opt-in before it became the default. A global flag is also the wrong granularity for the one need that sounds plausible. A component that wants client-rendered output visible while editing should provide it through kiln.js, not by running every ad call, analytics beacon and comment embed on the page inside the editing surface. The stale-manifest fallback stays. When public/js predates the no-mount entry, serving no initializers at all would break Kiln outright, so that path degrades rather than fails. Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TL;DR — for reviewers short on time
public/jsbuilt by an older claycli degrades to today's behavior rather than failing.lib/cmd/vite/index.js→getViteEditBootstrapScriptsL83–L101 — the entry-point swap and the stale-manifest fallback. Everything else follows from it.lib/cmd/vite/generate-bootstrap.js→buildInitPreludeL208–L265 — foundational. Separates the initializer prelude, which edit mode genuinely needs, from the mount runtime, which it must not run. No behavior change on its own.lib/cmd/vite/generate-bootstrap.js→generateViteBootstrapL267–L355 — writes two entries from that prelude:vite-bootstrap.js(view, same output as before) and the newvite-bootstrap-no-mount.js(edit).lib/cmd/vite/scripts.js→withNoMountBootstrapL666–L684 — adds the new entry to the view Rollup graph and the watcher, so it gets a content hash and shares the initializer chunks instead of duplicating them.lib/cmd/vite/index.js→resolveModuleScriptsL133–L165 — serves the no-mount entry in edit mode, and preloads the entry it actually loads rather than the view bootstrap.lib/cmd/vite/index.test.jsL229 andgenerators.test.jsL139 — new coverage for the swap, the fallback, and the generated no-mount content.CLAY-VITE.mdL450 — documents the parity guarantee. Docs only.Feature Info
Jira Ticket Url
6.0.0-rc.4on dev — happy to file a ticket and add the ID to the title if we want one on the board.Description
Component
client.jsfiles were executing in Kiln edit mode underclay vite. They never did underclay compile.getViteViewScripts()returns the hashed view bootstrap, andresolveModuleScripts()put it first in edit mode ([...viewScripts, ...editScripts]). That bootstrap callsmountComponentModules()at module scope, so the moment the module evaluates it scans the DOM and imports aclient.jschunk for every component on the page.The legacy pipeline never did this: the
editbranch ofgetDependencies()resolvesmodel.js,kiln.js, templates and kiln plugins, but no client dependency graph and no_client-init.js— the runtime that mounts controllers. Editors only ever ran component controllers on rendered pages.The bootstrap was included in edit mode because its
_env-init.jsimport has to run before the kiln edit bundle. That is a side effect of bundling env init together with component mounting; env init is the only part edit mode needs.So the fix is a second entry rather than dropping the bootstrap. Both are generated from the same prelude — the
window.modulesstub Kiln's preloader reads,_env-init.js,_globals-init.js, and the sticky-events shim — and only the view entry gets_clayClientModulesplus the mount runtime.No opt-in flag. An earlier revision of this branch had a
mountInEditModeoption. It's gone: mounting in edit mode has no legitimate use, it was never possible underclay compile, and a component that wants client-rendered output visible while editing should provide that throughkiln.jsrather than by running every ad call, analytics beacon and comment embed on the page inside the editing surface. So this needs noresolve-media.jschange — sites get the fix on upgrade.QA Testing Notes
?edit=trueand confirm no componentclient.jsside effects: no GPT ad iframes injected into slots, no Coral comment embed, no analytics beacons.?edit=trueand confirm view mode is unchanged: ads, comments and analytics all mount.clay vite --only jsthen checkpublic/js/_manifest.jsonhas both.clay/vite-bootstrapand.clay/vite-bootstrap-no-mount.Validation Points
public/js: a manifest built before this change has no no-mount entry.getViteEditBootstrapScripts()falls back to the mounting bootstrap, which reintroduces the bug but keeps Kiln working — serving no initializers at all would break editing outright. Re-runclay viteto pick up the fix. This is the one case where edit mode can still mount, and it's a build-artifact mismatch rather than a configuration choice.client.jsandglobal/jsadd/unlink.npm test(lint + jest): 447 tests pass, including 6 new.Related: clay/clay-kiln#1575 hardens
addIframeOverlaysso Kiln can't produce a page-covering overlay regardless of where an iframe came from. This PR removes the trigger; that one removes the failure mode. They're independent and can land in either order.