Skip to content

🍕 Stop component client.js from running in Kiln edit mode - #256

Merged
jjpaulino merged 2 commits into
masterfrom
jordan/edit-no-mount
Aug 14, 2026
Merged

🍕 Stop component client.js from running in Kiln edit mode#256
jjpaulino merged 2 commits into
masterfrom
jordan/edit-no-mount

Conversation

@jjpaulino

@jjpaulino jjpaulino commented Aug 13, 2026

Copy link
Copy Markdown
Member

TL;DR — for reviewers short on time


Feature Info

Jira Ticket Url

  • None yet. This was reported in Slack while validating 6.0.0-rc.4 on dev — happy to file a ticket and add the ID to the title if we want one on the board.

Description

Component client.js files were executing in Kiln edit mode under clay vite. They never did under clay compile.

getViteViewScripts() returns the hashed view bootstrap, and resolveModuleScripts() put it first in edit mode ([...viewScripts, ...editScripts]). That bootstrap calls mountComponentModules() at module scope, so the moment the module evaluates it scans the DOM and imports a client.js chunk for every component on the page.

The legacy pipeline never did this: the edit branch of getDependencies() resolves model.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.js import 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.modules stub Kiln's preloader reads, _env-init.js, _globals-init.js, and the sticky-events shim — and only the view entry gets _clayClientModules plus the mount runtime.

No opt-in flag. An earlier revision of this branch had a mountInEditMode option. It's gone: mounting in edit mode has no legitimate use, it was never possible under clay compile, and a component that wants client-rendered output visible while editing should provide that through kiln.js rather than by running every ad call, analytics beacon and comment embed on the page inside the editing surface. So this needs no resolve-media.js change — sites get the fix on upgrade.

QA Testing Notes

  • Load any article with ?edit=true and confirm no component client.js side effects: no GPT ad iframes injected into slots, no Coral comment embed, no analytics beacons.
  • Confirm edit mode still works end to end — Kiln boots, components are selectable, model.js and kiln plugins still read env (the prelude is what makes that work, so a regression here would show up as Kiln failing outright rather than subtly).
  • Load the same page without ?edit=true and confirm view mode is unchanged: ads, comments and analytics all mount.
  • clay vite --only js then check public/js/_manifest.json has both .clay/vite-bootstrap and .clay/vite-bootstrap-no-mount.

Validation Points

  • Stale 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-run clay vite to 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.
  • Chunk duplication: the no-mount entry rides in the same Rollup pass as the view bootstrap, so the shared initializer modules are emitted once and both entries import them.
  • Watch mode: the new entry is in the watcher's input map, so it rebuilds with the view bootstrap on client.js and global/js add/unlink.
  • Preloads: edit mode now preloads the bootstrap it actually loads. Previously it preloaded the view bootstrap while loading it too, so this removes a wasted hint rather than adding one.
  • npm test (lint + jest): 447 tests pass, including 6 new.

Related: clay/clay-kiln#1575 hardens addIframeOverlays so 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.

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>
@coveralls

coveralls commented Aug 13, 2026

Copy link
Copy Markdown

Coverage Status

coverage: 87.018% (-0.07%) from 87.084% — jordan/edit-no-mount into master

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>
@jjpaulino jjpaulino self-assigned this Aug 13, 2026
@jjpaulino
jjpaulino merged commit 6e9465e into master Aug 14, 2026
6 checks passed
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.

2 participants