Skip to content

Skeleton: make the plugin CSS contract the default for new plugins - #23

Merged
DianaSensei merged 2 commits into
mainfrom
claude/miniapp-css-audit-skeleton
Sep 22, 2026
Merged

DianaSensei merged 2 commits into
mainfrom
claude/miniapp-css-audit-skeleton

Conversation

@DianaSensei

Copy link
Copy Markdown
Owner

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.mjs builds 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's src/**, 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, beside h-64 in 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.mjs The complete check: resolves every class a plugin writes against a real host stylesheet. The only ground truth.
scripts/templates/hostCssClasses.test.ts The offline half, scaffolded into plugins/<id>/ui/ by create-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.tsx Takes a style prop — a plugin had no other way to set an editor's height that survives, since tailwind-merge cannot override a rule that was never emitted.
README.md Documents the contract where a plugin author will actually meet it.

This needs no follow-up on the four plugin branches

Both files are byte-identical to the copies already there (scripts/check-host-classes.mjs is blob db38879 on all four). Nothing to merge, nothing to dedupe. The payoff is forward-looking: create-plugin.mjs forks app/<new-id>/main from here, so every future plugin inherits this by default.

Verification

Scaffolded a throwaway plugin with the real create-plugin.mjs in a clean clone:

  ✓ lint:manifests
  ✓ typecheck
  ✓ test          ← previously ✗, for having no tests at all
  ✓ build

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)] and grid-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/main does today (11–14 files), so it introduces none:

plugin branch merging main merging this
container-manager 14 14
redis-client 11 11
rabbit-client 11 11
kafka-explorer 13 13

Separate 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 main deleted them when it became the skeleton — so git merge origin/main on a plugin branch today produces UD modify/delete conflicts on every plugin file that branch has touched, plus dependabot.yml, catalog.json, both workflows, README.md and package.json. Resolved carelessly, that deletes the plugin's own source.

The README advertises git fetch origin main && git merge origin/main as 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

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
@DianaSensei
DianaSensei merged commit ab92526 into main Sep 22, 2026
2 checks passed
@DianaSensei
DianaSensei deleted the claude/miniapp-css-audit-skeleton branch September 22, 2026 08:41
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.

1 participant