Repository navigation
Skeleton: make the plugin CSS contract the default for new plugins - #23
Merged
Merged
Conversation
A plugin bundle ships no CSS. makePluginConfig.mjs builds one ESM file and emits nothing else, so the UI is styled entirely by the host app's compiled Tailwind sheet — which is generated by scanning the HOST's src/**, never this repo, since a plugin is fetched at runtime from a URL long after the host was built. A class written only in a plugin therefore lands in the DOM with no rule behind it, silently. That had broken all four plugins at once: bottom-3, pl-1.5, py-5, h-64, m-4, mx-5, min-h-16, min-h-20, -mt-2.5, align-top and translate-x-full all had no rule, while max-h-64 beside h-64 did. Each plugin branch has now been fixed, but nothing stopped the fifth plugin repeating it — the skeleton said nothing about styling at all. - scripts/check-host-classes.mjs resolves every class a plugin writes against a real host stylesheet. The complete check, and the only ground truth. - scripts/templates/hostCssClasses.test.ts is the offline half, scaffolded into plugins/<id>/ui/ by create-plugin.mjs: it gates arbitrary-value sizing and layout classes, which no host-side safelist can ever cover. It starts with an empty allowlist, so a new plugin inherits the strict rule. - shared/components/ui/code-editor-base.tsx takes a `style` prop, because a plugin had no other way to set an editor's height that survives — tailwind-merge cannot override a rule that was never emitted. - README.md documents the contract where a plugin author will meet it. Both files are byte-identical to the copies already on the four plugin branches, so those need no merge and no follow-up; the point is that create-plugin.mjs forks app/<new-id>/main from here, so every future plugin inherits this by default. Verified by scaffolding a throwaway plugin with the real create-plugin.mjs: lint:manifests, typecheck, test and build all pass on a fresh scaffold, where `test` previously failed for having no tests at all. Adding `h-[68vh] max-h-[calc(100vh-13rem)]` to the placeholder UI fails the guard, as it should. Merging this branch into each plugin branch produces exactly the same conflict set as merging origin/main does today (11-14 files, all pre-existing divergence), so it adds none. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Nz2ELEkkMQzn6N429NkFy
I documented the styling contract without mentioning that the host has
carried externalPluginClassnamesSafelist.ts all along — a snapshot of
plugin class names taken once when these tools left that repo. A plugin
author reading this branch would have gone looking for a mechanism that
already exists.
It is not broken, it is stale, exactly as its own comment warned ("no
automated re-sync"). That is the whole story behind the classes that had
no rule: they were added to plugin code after the snapshot. It also
explains what looked arbitrary — max-h-64 was in the snapshot and h-64
was not.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Nz2ELEkkMQzn6N429NkFy
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.
Companion to #19, #20, #21, #22 — those fix the four existing plugins; this stops the fifth repeating it.
Why
A plugin bundle ships no CSS.
makePluginConfig.mjsbuilds one ESM file and emits nothing else, so the UI is styled entirely by the host app's compiled Tailwind sheet — generated by scanning the host'ssrc/**, never this repo, since a plugin is fetched at runtime from a URL long after the host was built.So a class written only in a plugin lands in the DOM with no rule behind it, silently. It had broken all four plugins at once:
bottom-3·pl-1.5·py-5·h-64·m-4·mx-5·min-h-16·min-h-20·-mt-2.5·align-top·translate-x-full…while
max-h-64, besideh-64in the same file, did have a rule.Each branch is fixed now. But the skeleton said nothing about styling at all, so nothing stopped the next plugin walking into it.
What this adds
scripts/check-host-classes.mjsscripts/templates/hostCssClasses.test.tsplugins/<id>/ui/bycreate-plugin.mjs. Gates arbitrary-value sizing/layout classes, which no host-side safelist can ever cover. Starts with an empty allowlist, so a new plugin inherits the strict rule.shared/.../code-editor-base.tsxstyleprop — a plugin had no other way to set an editor's height that survives, sincetailwind-mergecannot override a rule that was never emitted.README.mdThis needs no follow-up on the four plugin branches
Both files are byte-identical to the copies already there (
scripts/check-host-classes.mjsis blobdb38879on all four). Nothing to merge, nothing to dedupe. The payoff is forward-looking:create-plugin.mjsforksapp/<new-id>/mainfrom here, so every future plugin inherits this by default.Verification
Scaffolded a throwaway plugin with the real
create-plugin.mjsin a clean clone:Then added
h-[68vh] max-h-[calc(100vh-13rem)]to the placeholder UI — the guard fails, naming both classes.That second class matters: the guard's tokeniser used to split on parentheses and commas, which tore
max-h-[calc(100vh-13rem)]andgrid-cols-[minmax(0,1fr)_minmax(0,1.4fr)]into fragments that no longer looked like classes. Exactly backwards — a value with a function call in it is the one a host file will never spell identically. Fixed here and in #19, with a test for both shapes.Merging this branch into each plugin branch produces exactly the same conflict set as merging
origin/maindoes today (11–14 files), so it introduces none:mainSeparate problem, flagged not fixed
Those 11–14 conflicts are pre-existing and worth an issue of their own. The merge base still holds all 171 plugin files from the old monorepo layout, and
maindeleted them when it became the skeleton — sogit merge origin/mainon a plugin branch today producesUDmodify/delete conflicts on every plugin file that branch has touched, plusdependabot.yml,catalog.json, both workflows,README.mdandpackage.json. Resolved carelessly, that deletes the plugin's own source.The README advertises
git fetch origin main && git merge origin/mainas the way to pull skeleton updates. It does not work cleanly. I have not touched it — it reaches the whole branch model and deserves its own decision.🤖 Generated with Claude Code
https://claude.ai/code/session_017Nz2ELEkkMQzn6N429NkFy
Generated by Claude Code