Summary
site_apply_css reports cssRulesCreated for rules that are then never published.
Same call, same response shape, but whether a rule reaches the stylesheet depends on the
form of its selector, and the return value does not distinguish the two.
Concretely: I supplied 18 rules, got {"cssRulesCreated": 18}, and 8 of them appeared in
the published CSS. The other 10 were silently discarded. Nothing in the response says so,
and the only way to find out is to fetch the published stylesheet and diff it.
Steps to reproduce
On a page imported from static HTML whose elements carry class="field-stage" etc. as
imported HTML attributes:
site_apply_css with operation: "merge" and 18 rules, all prefixed the same way:
.field-stage { position: relative; perspective: 1100px; ... }
.field-image { position: relative; padding: var(--space-3); ... }
.field-image img { width: 100%; height: auto; }
.field-motion:hover { background: var(--action-bg); ... }
.field-footer > span { color: var(--muted); }
.field-heading span:last-child { text-align: right; }
...
- Response:
{"cssRulesCreated": 18, "cssRulesUpdated": 0}
site_publish
- Fetch
/_instatic/css/style-<hash>.css and grep for the selectors.
Expected behavior
Either all 18 ship, or the response reports which ones did not and why. A rule that cannot
be emitted should not be counted as created.
Actual behavior
8 of 18 shipped. The split is exact and reproducible: a rule ships only if its selector
ends in something other than a bare class.
Shipped (trailing tag, pseudo-class, pseudo-element, or child combinator):
.field-heading span:last-child
.field-image img
.field-image::before, .field-image::after
.field-image::before
.field-image::after
.field-footer > span
.field-motion:hover
.field-motion:active
Discarded (selector ends in a plain class):
.field-heading .field-stage .field-tilt .field-float .field-plane
.field-plane--grid .field-image .field-scan .field-footer .field-motion
I then retried the same 10 with two different ancestors, .hero-field .field-stage and
figure.hero-field .field-stage. Identical outcome both times: reported created, never
published. So it is the trailing simple selector that decides, not the ancestor.
This is presumably the kind: 'class' vs ambient split described in #404. A selector
ending in a bare class is registered as a class rule, and a class rule only ships when
that class is assigned to a node. These elements carry the class in the published HTML
(it is in the emitted markup, which is how the page selects them) but it arrived as an
imported HTML attribute rather than an Instatic class binding, so the rule has nothing to
attach to. The important part is not the internal policy, it is that the tool reports
success either way.
Worth noting [data-od-id="hero"] .field-stage does ship on the same page, from the
original import. So the same selector shape is publishable; the difference is invisible
from the API.
Two related failures in the same session
Silent no-op on an equal-specificity selector. Supplying 10 rules under a new ancestor
that ties on specificity with existing ones:
site_apply_css merge '[data-od-id="hero-photograph"] .field-stage { ... }' (x10)
-> {"cssRulesCreated": 0, "cssRulesUpdated": 0}
No error, nothing written. Raising specificity was the workaround. This is the same
behaviour I noted as item 3 of #490, now reproduced a second time on unrelated selectors,
so it is not specific to that stylesheet.
Partial application, unreported. Supplying 10 rules under
figure.hero-field[data-od-id="hero-photograph"] returned
{"cssRulesCreated": 0, "cssRulesUpdated": 5}. Ten went in, five were accounted for, five
vanished. The counts do not sum to the input and there is no per-rule result, so a caller
cannot tell which five landed.
Why this matters
The failure mode is the dangerous one: the tool says it worked. An agent or script driving
site_apply_css has no signal to retry or fall back, and the page silently keeps rendering
unstyled. In our case a component rendered as a raw full bleed image on a production page,
and four separate apply-and-publish cycles reported success before I fetched the stylesheet
and diffed it.
A per-rule result, or simply not counting discarded rules as created, would make this
self-evident.
Workaround
Write the CSS as a stylesheet code asset instead of into the rule store:
site_write_code_asset path: src/styles/field-plate.css type: style
runtime: { enabled: true, scope: { type: "all-pages" } }
That bypasses the class/ambient classification entirely, ships verbatim, and loads after
style.css. Every one of the 18 rules published on the first attempt.
Version or commit
Hosted Railway deployment behind a custom domain, tested 2026-09-06. Driven over the HTTP
MCP connector.
Deployment mode
Hosted (Railway)
Related
#404 (commitStyleRules only reconciles kind:'class') describes the same class/ambient
split from the import side. #249 (cross-page class-name collisions silently drop styles)
is the same silent-drop failure mode on the import path. #490 item 3 is the
equal-specificity no-op.
Summary
site_apply_cssreportscssRulesCreatedfor rules that are then never published.Same call, same response shape, but whether a rule reaches the stylesheet depends on the
form of its selector, and the return value does not distinguish the two.
Concretely: I supplied 18 rules, got
{"cssRulesCreated": 18}, and 8 of them appeared inthe published CSS. The other 10 were silently discarded. Nothing in the response says so,
and the only way to find out is to fetch the published stylesheet and diff it.
Steps to reproduce
On a page imported from static HTML whose elements carry
class="field-stage"etc. asimported HTML attributes:
site_apply_csswithoperation: "merge"and 18 rules, all prefixed the same way:{"cssRulesCreated": 18, "cssRulesUpdated": 0}site_publish/_instatic/css/style-<hash>.cssand grep for the selectors.Expected behavior
Either all 18 ship, or the response reports which ones did not and why. A rule that cannot
be emitted should not be counted as created.
Actual behavior
8 of 18 shipped. The split is exact and reproducible: a rule ships only if its selector
ends in something other than a bare class.
Shipped (trailing tag, pseudo-class, pseudo-element, or child combinator):
Discarded (selector ends in a plain class):
I then retried the same 10 with two different ancestors,
.hero-field .field-stageandfigure.hero-field .field-stage. Identical outcome both times: reported created, neverpublished. So it is the trailing simple selector that decides, not the ancestor.
This is presumably the
kind: 'class'vs ambient split described in #404. A selectorending in a bare class is registered as a class rule, and a class rule only ships when
that class is assigned to a node. These elements carry the class in the published HTML
(it is in the emitted markup, which is how the page selects them) but it arrived as an
imported HTML attribute rather than an Instatic class binding, so the rule has nothing to
attach to. The important part is not the internal policy, it is that the tool reports
success either way.
Worth noting
[data-od-id="hero"] .field-stagedoes ship on the same page, from theoriginal import. So the same selector shape is publishable; the difference is invisible
from the API.
Two related failures in the same session
Silent no-op on an equal-specificity selector. Supplying 10 rules under a new ancestor
that ties on specificity with existing ones:
No error, nothing written. Raising specificity was the workaround. This is the same
behaviour I noted as item 3 of #490, now reproduced a second time on unrelated selectors,
so it is not specific to that stylesheet.
Partial application, unreported. Supplying 10 rules under
figure.hero-field[data-od-id="hero-photograph"]returned{"cssRulesCreated": 0, "cssRulesUpdated": 5}. Ten went in, five were accounted for, fivevanished. The counts do not sum to the input and there is no per-rule result, so a caller
cannot tell which five landed.
Why this matters
The failure mode is the dangerous one: the tool says it worked. An agent or script driving
site_apply_csshas no signal to retry or fall back, and the page silently keeps renderingunstyled. In our case a component rendered as a raw full bleed image on a production page,
and four separate apply-and-publish cycles reported success before I fetched the stylesheet
and diffed it.
A per-rule result, or simply not counting discarded rules as created, would make this
self-evident.
Workaround
Write the CSS as a stylesheet code asset instead of into the rule store:
That bypasses the class/ambient classification entirely, ships verbatim, and loads after
style.css. Every one of the 18 rules published on the first attempt.Version or commit
Hosted Railway deployment behind a custom domain, tested 2026-09-06. Driven over the HTTP
MCP connector.
Deployment mode
Hosted (Railway)
Related
#404 (
commitStyleRulesonly reconcileskind:'class') describes the same class/ambientsplit from the import side. #249 (cross-page class-name collisions silently drop styles)
is the same silent-drop failure mode on the import path. #490 item 3 is the
equal-specificity no-op.