Repository navigation
styling rules: carve out data-driven author-declared colour, so the written rule matches the practised one #5185
Description
Activity
- addeddocumentationImprovements or additions to documentationImprovements or additions to documentationand removed
on Aug 18, 2026 Claim: PM loop round 1 (maintainer direct-dispatch channel — ruling given in-session 2026-08-18, quoted verbatim in the card body)
Session:session_01AMHbqfPiETJHA95r6WzZWS
Branch:claude/issue-5185-styling-data-driven-carveout
Worktree:objectui-issue-5185
Domain:repo:objectui(execution seat)
File surface:AGENTS.md(§2) andskills/objectui/rules/styling.md— nothing else (stop on breach; explain in the report)
Container & model:S–M,mode:subagent,model: opus—dispatch-gates.mjs --tiergives no path-derived mandate for these paths, and the ⛔ mandatory fable clause ① binds only.claude/skills/pm-dispatch/**, which this is not. My judgment: the ruling is already fully specified, so what remains is precise rule-boundary drafting, not design — the default judgment tier. I considered raising it, since this text becomes the rule future agents apply, and settled on opus because the PR gets a seat review and a human merge before it can bind anyone.
Clause-②: no — prose only; no code, no contract, no accept/reject behaviour, no public surface.
Serial constraints cleared: no open PR and no in-flight claim touchesAGENTS.mdorskills/objectui/**in this repo. Adjacent but disjoint: PR5124, PR5080 and PR4930 all sit in the governed queue awaiting a human merge and all touchskills/**— none touchesskills/objectui/rules/styling.mdorAGENTS.md. Merge-order risk is therefore nil, but if any of them lands first, mergemainbefore opening this PR.⛔ Governed surface — landing rule, stated up front
AGENTS.mdandskills/**are governed. This PR gets drafted, pushed, and reviewed by me, then left visibly waiting for the maintainer to merge by hand. I will not flip it ready for auto-merge, will not enqueue it, and will not merge it. "Reviewed + green" is not an exception. Open it as a draft and leave it there.The ruling to implement (settled; do not re-litigate)
Option A of three, chosen by the maintainer: amend the docs to carve out data-driven author-declared colour, keeping the ban on component-authored static colour. Options B (case-by-case exceptions) and C (ESLint-enforced flat ban) were explicitly not chosen — in particular, do not add or propose a lint rule, and do not audit the 254 existing
style={{sites.What makes this card easy to get wrong
The failure mode is writing a carve-out so loose it reads as "inline styles are fine now". The boundary has to be sharp enough to decide the next case without a human:
- the permitted shape is author-declared, data-driven colour published as CSS custom properties consumed by static utilities — the worked reference is
getBadgeHexAppearance/getDotHexAppearanceinpackages/fields/src/index.tsx(PR fix(fields): render the author's declared option hex instead of quantizing it to nine palette families #5184, landing now); - the hazard the original rule exists to prevent should be named: an inline style that hard-codes a colour so dark mode renders identically. Write that down, because it is the property that actually distinguishes the two cases — not the presence of the word
style; - carry the rationale, not just the rule: semantic tokens exist so theming and dark mode work automatically. A pattern preserving that satisfies the rule; one defeating it does not.
Verify before you write
The evidence in the card body was measured by me on
origin/mainand is a clue, not a specification — re-derive it. In particular re-check the254count and the three existing author-colour paths (plugin-ganttbars,conditionalFormatting, Studio's option swatch) before citing any number in the docs. Prefer not to cite a raw count in the doc text at all — a number in prose rots the moment someone adds a call site, and this repo already has cards open about exactly that failure mode. Describe the shape, not the census.Read the current text of both files first: if §2 or
styling.mdalready carries a partial exception, amend it in place rather than appending a second, competing paragraph.Gates
Derive from your actual diff. A docs-only change here implicates at least
Internal Docs Link Check,Skill Guide Path Check,Control Byte Scan, and the changeset gates. That list is a clue, not a spec. NoteSkill Guide Path Checkspecifically — you are editing underskills/, so it is live for this diff.Changeset: docs-only. This repo has no
skip-changesetlabel and one must never be minted; if no released package changes, an empty-frontmatter changeset is the sanctioned declaration.Premise-first
If the current text already says something equivalent, or the ruling turns out not to fit the file structure, report
premise_still_valid: falsewith evidence rather than forcing an edit.Deliver
Draft PR against
main, first lineFixes #5185, and leave it draft. Push the branch early.Return the structured JSON report AND post it as an issue comment whose first line is
<!-- os-dev-report -->.
Generated by Claude Code
- the permitted shape is author-declared, data-driven colour published as CSS custom properties consumed by static utilities — the worked reference is
{ "issue": 5185, "status": "done", "branch": "claude/issue-5185-styling-data-driven-carveout", "pr": "https://github.com/objectstack-ai/objectui/pull/5188", "premise_still_valid": true, "summary": "Implemented ruling option A: amended AGENTS.md section 2 and skills/objectui/rules/styling.md in place to carve out author-declared, data-driven colour from the flat inline-style ban. Permitted shape is narrow - the declared colour published as CSS custom properties consumed by STATIC Tailwind utilities, so dark: stays a real variant and the design system keeps theme control; a new 'Rule: Author-Declared, Data-Driven Colour' section in styling.md carries the correct/forbidden pair, the still-banned list, the rationale, and an explicit colour-only scope note (runtime geometry is named as a separate question this does not answer). The hazard clause - any inline style that hard-codes a colour such that dark mode renders identically - is named explicitly in BOTH files as the property that decides a case. Three sections that would otherwise have disagreed now point at the owning section (top summary blockquote, 'Use Tailwind Utility Classes Only', 'Dark Mode with Semantic Tokens' - the last being the direct tension, since its 'no manual dark: variants' line is exactly backwards for the carrier pattern), and 'Use Semantic Color Tokens' was sharpened to ban the colour LITERAL rather than arbitrary-value syntax, because the sanctioned carrier bg-[color:var(--os-badge-bg)] is itself an arbitrary value. No lint rule added or proposed; no existing style={{ site audited or changed; packages/fields untouched. Draft PR left draft per the governed-surface rule.", "premise_evidence": "Neither file carried any partial exception before this change - both stated the flat ban, so there was nothing to amend in place and no competing paragraph risk from a pre-existing carve-out. Card evidence re-derived on origin/main@6ca910bdf: the style={{ census is 260 under packages/*/src, NOT the 254 the card measured the same day (which is why no raw count appears in the doc text - the shape is described instead). All three author-colour paths confirmed by location: packages/plugin-gantt/src/GanttView.tsx:3637, ruleToStyle in packages/core/src/evaluator/listConditional.ts:381, packages/app-shell/src/views/metadata-admin/SchemaForm.tsx:1701. Material finding for review: all three use the DIRECT form the new rule does not permit, so the doc cites them as the practice that made the flat rule untrue rather than as conforming examples.", "files_changed": [ "AGENTS.md", "skills/objectui/rules/styling.md", ".changeset/styling-data-driven-colour-carveout-5185.md" ], "tests": "Docs-only diff; no build or test suite is implicated, so no heavy-verify lock was taken. Gate union re-derived from the actual diff and re-run at the final commit 3c146dd0b with a clean working tree (git status --porcelain = 0 lines): check-control-bytes exit=0 'OK (scanned 4595 tracked text file(s); skipped 85 binary)'; check-skills-paths exit=0 'OK (94/95 stated path(s) resolve across 18 guide file(s); 1 baselined)' - live for this diff since it edits under skills/, and its checked-assertion count rises with the change while staying green; check-doc-links exit=0 'Links are valid across 13 scan roots'; check-changeset-presence exit=0 'No source of a released package changed in this range, so no changeset is owed'; check-changeset-fixed exit=0; check-changeset-no-major exit=0. Beyond the gate, the three changed files were scanned directly for control bytes (grep -naP over the C0 range plus DEL): no hits. check-links.yml was inspected and does NOT run on pull_request - it is schedule + workflow_dispatch only, so external-link flake cannot gate this PR. No ablation applies to a prose change.", "gates_run": [ "check-control-bytes: pass (Control Byte Scan)", "check-skills-paths: pass (Skill Guide Path Check)", "check-doc-links: pass (Internal Docs Link Check)", "check-changeset-presence: pass", "check-changeset-fixed: pass", "check-changeset-no-major: pass" ], "governed_surface": "PR #5188 is draft and left draft. Auto-merge not enabled, not enqueued, not merged. Awaiting the maintainer's hand merge.", "deviations": [ "Amended FOUR sections of styling.md plus the top blockquote, not only the two files' primary ban lines. Reason: the dispatch forbids two rules in one document that disagree, and 'Rule: Dark Mode with Semantic Tokens' ('DO NOT manually add dark: variants') plus 'Rule: Use Semantic Color Tokens' ('bg-[#3b82f6] // Arbitrary value') both contradict the sanctioned carrier pattern on its own worked example. Each amendment is one to four lines and points at the single owning section; total diff is +102/-2 across three files.", "Did NOT cite the 254 (or the re-derived 260) count in the doc text, per the dispatch's strong preference - the same-day drift from 254 to 260 is itself the evidence, and it is recorded in the PR body and the commit message instead of the doc.", "The worked reference getBadgeHexAppearance / getDotHexAppearance is NOT on main - PR #5184 is still open (draft). The symbols were read from that PR's head 26e78af0f rather than assumed. The doc attributes them to PR #5184 and states the pattern in full in a fenced example, so the rule stands alone if that reference moves. The cited path packages/fields/src/index.tsx does exist on main, which is what Skill Guide Path Check asserts (fenced blocks are excluded from that gate; only inline prose spans are checked)." ], "open_questions": [], "out_of_scope_findings": [] }
Generated by Claude Code
ACCEPT — and held for a human merge, permanently. PR #5188 (
repo:objectuiseat PM, sessionsession_01AMHbqfPiETJHA95r6WzZWS, reviewer of record).Landing path: governed surface, terminal three-piece.
AGENTS.mdandskills/**are agent-instruction files that only the maintainer may merge. PR #5188 stays draft; I have not flipped it ready, not enabled auto-merge, not enqueued it, and will not. Listed under awaiting a human merge in my round report. Green and reviewed is not an exception — the human merge is the review record for this surface.CI: 19 checks, all terminal — 17 success, 2 skipped, zero failures. (19 rather than 21 because a docs-only diff doesn't run Bundle Analysis or Live E2E.)
Skill Guide Path Checkis green and is genuinely live for this diff, which matters: it's the gate that would catch a broken path reference in askills/guide.Scope: exactly the declared surface —
AGENTS.md,skills/objectui/rules/styling.md, and an empty-frontmatter changeset (the sanctioned docs-only declaration; noskip-changesetlabel minted, correctly). No lint rule added or proposed, no existingstyle={{}}site audited,packages/fieldsuntouched. Options B and C stayed shut.The deviation is the best thing in this PR. The dispatch named two files; the dev amended four sections plus the top blockquote, and was right to. Two of them were live contradictions that a minimal edit would have left in place:
- "Rule: Dark Mode with Semantic Tokens" says "DO NOT manually add
dark:variants" — which is exactly backwards for the sanctioned carrier, where an explicitdark:variant is what keeps dark mode real. Left alone, the guide would have banned the pattern it had just permitted, two screens apart. - "Rule: Use Semantic Color Tokens" banned
bg-[#3b82f6]as "Arbitrary value" — but the sanctioned carrierbg-[color:var(--os-badge-bg)]is an arbitrary value. Sharpening the ban to the colour literal rather than the syntax is the correct fix and is the difference between a rule that decides cases and one that contradicts itself.
That is precisely the "two rules in one document that disagree is worse than the flat rule you are fixing" hazard, found and closed without being told which sections.
The text is decidable, which was the whole point. It names the hazard in both files — an inline style that hard-codes a colour such that dark mode renders identically — and says outright that this, not the presence of the word
style, is the property that decides a case. It carries the rationale as a test ("Read that rationale as the test. It decides cases this page does not list"), and it scopes hard: colour only, author-declared only, with runtime geometry explicitly named as a separate question this carve-out does not answer. That last sentence is what stops the next reader widening this into "inline styles are fine now".Two measurement notes I'm recording because they correct me:
- My census was already stale when I wrote it. I measured 254
style={{underpackages/*/src; the dev re-derived 260 the same day. Neither number is in the doc — which is exactly why I asked for the shape rather than a count, and the same-day drift is the argument, not a hypothetical. - The carve-out does not retroactively bless the three paths I cited to the maintainer. Re-measured, all three —
plugin-gantt/src/GanttView.tsx:3637,ruleToStyleincore/src/evaluator/listConditional.ts:381,app-shell/.../SchemaForm.tsx:1701— use the direct form (style={{ backgroundColor: … }}), which the new rule forbids. The doc correctly cites them as the practice that made the flat rule untrue, not as conforming examples. So after this lands they are non-conforming-but-unenforced, exactly as they were before. Auditing them was explicitly out of scope here; whether they should be migrated to the carrier pattern is a real follow-up question and I am flagging it rather than filing unilaterally, since it is capability-shaped work across three packages.
Worked reference now resolves. It was drafted while PR #5184 was still open, so the dev read the symbols from that PR's head and stated the pattern in full inline so the rule stands alone. #5184 has since merged (
a09bc33ff), and I confirmed onmainthatgetBadgeHexAppearance(:1409),getDotHexAppearance(:1427) and the exact quoted carrier string (:1393) are all live.
Generated by Claude Code
- "Rule: Dark Mode with Semantic Tokens" says "DO NOT manually add
Filed and routed by the
repo:objectuiexecution seat (sessionsession_01AMHbqfPiETJHA95r6WzZWS) under the maintainer's direct-dispatch channel, on an explicit ruling given in-session on 2026-08-18.Authorising instruction (quoted verbatim, not paraphrased)
Asked how to resolve the conflict between the flat styling ban and the repo's practice, the maintainer chose option A:
Why
AGENTS.md§2 andskills/objectui/rules/styling.mdstate a flat rule: no inline styles, no hard-coded colour values. The repo does not actually work that way, and the gap is not marginal — measured onorigin/mainduring objectui#5141's implementation:style={{occurrences underpackages/*/src;plugin-ganttbars,conditionalFormatting, and Studio's own option swatch — the last of which paints the sameoptions[].colormetadata key straight from the raw hex;The cost of leaving it flat is not theoretical. It is what made #5141's triage stop-clause ambiguous: the implementing agent had to decide for itself whether a written, unenforced rule constrained how or whether, and correctly escalated rather than guessing. The next agent on any data-driven-colour card hits the same fork.
What to change
Both files, minimally:
AGENTS.md§2skills/objectui/rules/styling.mdState the carve-out precisely enough that it decides the next case without a human:
dark:variants stay real variants and the design system keeps theme control. The worked reference isgetBadgeHexAppearance/getDotHexAppearanceinpackages/fields/src/index.tsx(PR fix(fields): render the author's declared option hex instead of quantizing it to nine palette families #5184).styleappears.Do not silently widen this into "inline styles are fine". The ban survives for everything except author-declared data-driven colour routed through custom properties.
Explicitly out of scope
style={{sites.packages/fields— PR fix(fields): render the author's declared option hex instead of quantizing it to nine palette families #5184 already carries the implementation.Governance
AGENTS.mdandskills/**are governed surfaces. The PR for this card gets drafted, pushed, and reviewed by the seat, then left for the maintainer to merge by hand. No AI seat may enqueue it, flip it ready for auto-merge, or merge it.