test(workspace): accept only #249 decision markers and state the lint scope

The overlay lint accepted any D-number, so another issue's ruling such
as "D5 (#245)" could mark a concept override. A marker now has to be a
D-number with no other issue cited; the vocabulary rule that cited #245
reads "#245 decision 5". TESTING.md now states that the lint covers
workspace.css only, lists the unmarked overrides still in images.css,
and says PRs C and D run the lint over each page sheet they keep.
This commit is contained in:
Vito Sansevero
2026-10-19 22:48:39 -07:00
parent f09ceb996e
commit 12aa1e51d6
4 changed files with 36 additions and 8 deletions
+15 -5
View File
@@ -319,11 +319,21 @@ other page or canvas script loads workspace CSS.
**Overlay decision-marker lint.** `lintOverlay` in `tools/vendor/sync-concept-css.mjs`
fails any `workspace.css` rule whose selector is also a concept selector unless the
rule carries a `/* D<n> */` comment, before its selector or inside its block. The lint
test fails if `workspace.css` is missing. The match is by exact selector, so a narrower
selector over a concept rule (for example `.ca-tools dialog.prompt-sender`) passes the
lint. Such rules, and the page-stylesheet leftovers such as `library.css`
`.row-actions`, name the PR that retires them, by convention.
rule carries a `/* D<n> */` comment, before its selector or inside its block. The
D-number must be one of this spec's (#249): a comment that cites any other issue, such
as `D5 (#245)`, is that issue's ruling and is not a marker, so write `#245 decision 5`
for those. The lint test fails if `workspace.css` is missing. The match is by exact
selector, so a narrower selector over a concept rule (for example
`.ca-tools dialog.prompt-sender`) passes the lint. Such rules, and the page-stylesheet
leftovers such as `library.css` `.row-actions`, name the PR that retires them, by
convention.
The lint covers `workspace.css` only. The page stylesheets that load before it
(`images.css`, `library.css`, `inspector.css`) are not linted yet, and `images.css`
still has unmarked concept-selector overrides (`.image-grid`, `.prompt-text`,
`.prompt-actions`, `.ca-tools .prompt-actions button`). PRs C and D retire those
sheets: each one runs `lintOverlay` over every page sheet it keeps, and adds that
sheet to the real-file lint test.
**Comparison method.** Production must equal the concept except for the recorded
deviations in the #249 design (spec §8, D-numbers). It is never compared against
+11
View File
@@ -55,6 +55,17 @@ test('overlay lint requires a decision marker on concept selectors', () => {
assert.equal(lintOverlay(conceptCss, '/* D1 */ .card { color: blue; }\n.row { gap: 1px; }').violations.length, 1);
});
test('overlay lint accepts only this spec\'s (#249) decision numbers', () => {
const conceptCss = '.card { color: red; }\n';
assert.deepEqual(lintOverlay(conceptCss, '/* D12 of #249 */ .card { color: blue; }').violations, []);
assert.deepEqual(lintOverlay(conceptCss, '/* #249 D3: reason */ .card { color: blue; }').violations, []);
// Another issue's decision numbers are not #249 rulings, so they never mark a concept override.
assert.equal(lintOverlay(conceptCss, '/* D5 (#245) */ .card { color: blue; }').violations.length, 1);
assert.equal(lintOverlay(conceptCss, '/* D2 of #240 and #249 */ .card { color: blue; }').violations.length, 1);
assert.equal(lintOverlay(conceptCss, '/* #245 decision 5 */ .card { color: blue; }').violations.length, 1);
assert.equal(lintOverlay(conceptCss, '/* AD5 */ .card { color: blue; }').violations.length, 1);
});
test('overlay rules on concept selectors carry a decision marker', () => {
const overlay = join(root, 'web/js/workspace/workspace.css');
assert.ok(existsSync(overlay), 'web/js/workspace/workspace.css is missing');
+9 -2
View File
@@ -6,6 +6,13 @@ import { repositoryRoot } from './vendor-viewerjs.mjs';
const CONCEPT = 'docs/concepts/workspace/style.css';
const COPY = 'web/js/workspace/concept.css';
const MARKER = /\/\*[^*]*\bD\d+\b[^*]*\*\//;
const SPEC_ISSUE = '#249';
// A decision marker names one of this spec's (#249) D-numbers. A comment that cites any other issue is that issue's
// ruling (for example #245's own D5), so it never marks a concept override here.
function isDecisionMarker(comment) {
return MARKER.test(comment) && (comment.match(/#\d+\b/g) ?? []).every(issue => issue === SPEC_ISSUE);
}
// Minimal tokenizer: walks top-level rules and the bodies of @media/@supports,
// returning each rule's normalized selectors and the comments attached to it
@@ -84,8 +91,8 @@ export function lintOverlay(conceptCss, overlayCss) {
const violations = [];
for (const rule of parseRules(overlayCss)) {
const shared = rule.selectors.filter(selector => owned.has(selector));
if (shared.length && !rule.comments.some(comment => MARKER.test(comment))) {
violations.push(`${shared.join(', ')}: overlay rule on a concept selector needs a /* D<n> */ decision marker`);
if (shared.length && !rule.comments.some(isDecisionMarker)) {
violations.push(`${shared.join(', ')}: overlay rule on a concept selector needs a /* D<n> */ (#249) decision marker`);
}
}
return { violations };
+1 -1
View File
@@ -43,7 +43,7 @@ main:focus-visible { outline: 2px solid var(--ca-focus); outline-offset: 4px; }
/* Long tag names wrap inside the Tag cell so the Actions column keeps its intrinsic width; on two-column screens the
minimum keeps the concept's longest sample ("architecture", about 5em) on one line. */
.vocabulary-main td:first-child { overflow-wrap: anywhere; min-width: calc(6em + 20px); }
/* D5 (#245): bounded lists continue with Load more; never a scroll anchor, so appended rows push the end away. */
/* Tags & categories (#245 decision 5): bounded lists continue with Load more; never a scroll anchor, so appended rows push the end away. */
.vocabulary-main .vocabulary-continuation { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; padding-block: 15px 0; overflow-anchor: none; }
.vocabulary-main .vocabulary-continuation p { margin: 0; flex: 1 1 200px; font-size: 12px; color: var(--ca-muted); }
.vocabulary-main .vocabulary-continuation .vocabulary-error { color: var(--ca-error-text); }