mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Move CSS lint policy to Biome
This commit is contained in:
parent
6afe931d01
commit
f125a4cf17
14 changed files with 189 additions and 1378 deletions
18
biome.jsonc
18
biome.jsonc
|
|
@ -25,7 +25,10 @@
|
|||
"./scripts/lint/grit/no-non-reexport-index.grit",
|
||||
"./scripts/lint/grit/no-self-referential-initializer-callback.grit",
|
||||
"./scripts/lint/grit/no-unsafe-iterator-value.grit",
|
||||
"./scripts/lint/grit/no-uncontrolled-preact-form-state.grit"
|
||||
"./scripts/lint/grit/no-uncontrolled-preact-form-state.grit",
|
||||
|
||||
// CSS source policy.
|
||||
"./scripts/lint/grit/no-restricted-css-policy.grit"
|
||||
],
|
||||
"files": {
|
||||
"includes": [
|
||||
|
|
@ -72,17 +75,18 @@
|
|||
"noFloatingPromises": "error",
|
||||
"noMisusedPromises": "error",
|
||||
"noUnsafePlusOperands": "error",
|
||||
"useExhaustiveSwitchCases": "error"
|
||||
"useExhaustiveSwitchCases": "error",
|
||||
"noExcessiveSelectorClasses": {
|
||||
"level": "error",
|
||||
"options": {
|
||||
"maxClasses": 4
|
||||
}
|
||||
}
|
||||
},
|
||||
"suspicious": {
|
||||
"noArrayIndexKey": "error",
|
||||
"noImportCycles": "error"
|
||||
}
|
||||
}
|
||||
},
|
||||
"css": {
|
||||
"linter": {
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ Use this as the normal edit loop. `npm run format` applies Biome formatting and
|
|||
|
||||
Use focused scripts for tight loops: `npm run typecheck`, `npm run test`, `npm run lint`, or `npm run build`. The `*:ci` variants match CI cache behavior.
|
||||
|
||||
Biome owns formatting, import organization, general JavaScript/TypeScript/JSON linting, GritQL source-shape plugins, and GritQL import-boundary restrictions. Biome warnings fail `npm run lint` and `npm run check`; info diagnostics stay advisory, including accessibility while Obsidian-specific UI semantics are reviewed rule by rule. CSS lint policy stays in Stylelint and the CSS usage script. ESLint remains for typed unsafe-any checks, Obsidian plugin policy, and Codex Panel responsibility-boundary rules that need TypeScript type information.
|
||||
Biome owns formatting, import organization, general JavaScript/TypeScript/JSON/CSS linting, GritQL source-shape plugins, GritQL import-boundary restrictions, and GritQL CSS source policy. Biome warnings fail `npm run lint` and `npm run check`; info diagnostics stay advisory, including accessibility while Obsidian-specific UI semantics are reviewed rule by rule. The CSS usage script still checks dead authored classes. ESLint remains for typed unsafe-any checks, Obsidian plugin policy, and Codex Panel responsibility-boundary rules that need TypeScript type information.
|
||||
|
||||
## Generated and Loaded Files
|
||||
|
||||
|
|
|
|||
1181
package-lock.json
generated
1181
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -42,8 +42,6 @@
|
|||
"jsdom": "^29.1.1",
|
||||
"knip": "^6.16.1",
|
||||
"obsidian": "~1.12.3",
|
||||
"stylelint": "^17.13.0",
|
||||
"stylelint-config-standard": "^40.0.0",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.61.0",
|
||||
"vitest": "^4.1.8"
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ function nonBiomeLintCommands({ ciMode, namePrefix = "" }) {
|
|||
name: `${namePrefix}eslint`,
|
||||
command: `eslint src --max-warnings=0${eslintCacheArgs}`,
|
||||
},
|
||||
{ name: `${namePrefix}css`, command: 'stylelint "src/**/*.css" --max-warnings=0' },
|
||||
{ name: `${namePrefix}css-usage`, command: "node scripts/lint/check-css-usage.mjs" },
|
||||
{ name: `${namePrefix}unused`, command: "knip --no-progress" },
|
||||
];
|
||||
|
|
|
|||
57
scripts/lint/grit/no-restricted-css-policy.grit
Normal file
57
scripts/lint/grit/no-restricted-css-policy.grit
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
engine biome(1.0)
|
||||
language css
|
||||
|
||||
or {
|
||||
// Keep colors on Obsidian or Codex Panel design tokens.
|
||||
CssDeclarationWithSemicolon() as $decl where {
|
||||
$decl <: r"\s*(?:background(?:-color)?|border(?:-(?:block|inline|top|right|bottom|left))?(?:-color)?|box-shadow|caret-color|color|fill|outline(?:-color)?|stroke|text-shadow): .*?(?:#[0-9a-fA-F]{3,8}\b|\b(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch)\(|\b(?:black|white)\b).*",
|
||||
register_diagnostic(span=$decl, message="Use Obsidian or Codex Panel design tokens instead of hardcoded colors.")
|
||||
},
|
||||
CssDeclarationWithSemicolon() as $decl where {
|
||||
$decl <: r".*--codex-panel-.*(?:accent|background|border|color|danger|error|faint|muted|ring|success|surface|text|warning).*: .*?(?:#[0-9a-fA-F]{3,8}\b|\b(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch)\(|\b(?:black|white)\b).*",
|
||||
register_diagnostic(span=$decl, message="Use Obsidian or Codex Panel design tokens instead of hardcoded colors.")
|
||||
},
|
||||
CssDeclarationWithSemicolon() as $decl where {
|
||||
$decl <: r".*\b(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch)\(.*",
|
||||
register_diagnostic(span=$decl, message="Use Obsidian or Codex Panel design tokens instead of color functions.")
|
||||
},
|
||||
// Keep typography and layout dimensions on shared variables.
|
||||
CssDeclarationWithSemicolon() as $decl where {
|
||||
$decl <: r"\s*font-size: .*?\b\d*\.?\d+(?:px|rem|em|%)\b.*",
|
||||
register_diagnostic(span=$decl, message="Use Obsidian or Codex Panel typography tokens instead of hardcoded font sizes.")
|
||||
},
|
||||
CssDeclarationWithSemicolon() as $decl where {
|
||||
$decl <: r"\s*font-weight:\s*(?:[1-9]00|bold|bolder|lighter|normal)\s*;.*",
|
||||
register_diagnostic(span=$decl, message="Use Obsidian or Codex Panel typography tokens instead of hardcoded font weights.")
|
||||
},
|
||||
CssDeclarationWithSemicolon() as $decl where {
|
||||
$decl <: r"\s*line-height: .*?(?:\b\d*\.?\d+\b|\b\d*\.?\d+(?:px|rem|em|%)\b).*",
|
||||
register_diagnostic(span=$decl, message="Use Obsidian or Codex Panel typography tokens instead of hardcoded line heights.")
|
||||
},
|
||||
CssDeclarationWithSemicolon() as $decl where {
|
||||
$decl <: r"\s*(?:border(?:-(?:block|inline|top|right|bottom|left))?-width|border-radius|bottom|column-gap|gap|height|inset|inset-(?:block|inline)|left|margin|margin-(?:block|inline|top|right|bottom|left)|max-height|max-width|min-height|min-width|padding|padding-(?:block|inline|top|right|bottom|left)|right|row-gap|top|width): .*?\b\d*\.?\d+(?:px|rem|em)\b.*",
|
||||
register_diagnostic(span=$decl, message="Prefer Obsidian or Codex Panel spacing and size tokens for layout dimensions.")
|
||||
},
|
||||
// Keep selectors local and predictable.
|
||||
CssPseudoClassSelector() as $selector where {
|
||||
$selector <: r".*:has.*",
|
||||
register_diagnostic(span=$selector, message="Avoid :has() because it can cause broad selector invalidation.")
|
||||
},
|
||||
CssPseudoClassSelector() as $selector where {
|
||||
$selector <: r".*:where[(].*(?:[.]|#|\[).*",
|
||||
register_diagnostic(span=$selector, message="Do not hide class, id, or attribute selectors inside :where().")
|
||||
},
|
||||
CssIdSelector() as $selector where { register_diagnostic(span=$selector, message="Avoid ID selectors in Codex Panel CSS.") },
|
||||
CssUniversalSelector() as $selector where {
|
||||
register_diagnostic(span=$selector, message="Avoid universal selectors in Codex Panel CSS.")
|
||||
},
|
||||
CssTypeSelector() as $selector where {
|
||||
not $selector <: r"(?:dd|dt|pre|span|summary|svg|ul)",
|
||||
register_diagnostic(span=$selector, message="Avoid unnecessary type selectors in Codex Panel CSS.")
|
||||
},
|
||||
CssAtRule() as $rule where {
|
||||
$rule <: r"@keyframes ([a-zA-Z0-9_-]+)[\s\S]*"($name),
|
||||
not $name <: r"codex-panel-[a-z0-9-]+",
|
||||
register_diagnostic(span=$rule, message="Prefix keyframes with codex-panel-.")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
import stylelint from "stylelint";
|
||||
|
||||
const ruleName = "codex-panel/no-specificity-where";
|
||||
|
||||
const messages = stylelint.utils.ruleMessages(ruleName, {
|
||||
rejected: (selector) => `Do not hide class, id, or attribute selectors inside :where(): "${selector}"`,
|
||||
});
|
||||
|
||||
function noSpecificityWhereRule(primary) {
|
||||
return (root, result) => {
|
||||
if (primary === false) return;
|
||||
|
||||
root.walkRules((ruleNode) => {
|
||||
const selector = ruleNode.selector;
|
||||
if (!selector.includes(":where(")) return;
|
||||
|
||||
for (const range of whereRanges(selector)) {
|
||||
const body = selector.slice(range.bodyStart, range.bodyEnd);
|
||||
if (!/[.#[]/.test(body)) continue;
|
||||
|
||||
stylelint.utils.report({
|
||||
message: messages.rejected,
|
||||
messageArgs: [selector.slice(range.start, range.end)],
|
||||
node: ruleNode,
|
||||
result,
|
||||
ruleName,
|
||||
index: range.start,
|
||||
endIndex: range.end,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function whereRanges(selector) {
|
||||
const ranges = [];
|
||||
let searchFrom = 0;
|
||||
|
||||
while (searchFrom < selector.length) {
|
||||
const start = selector.indexOf(":where(", searchFrom);
|
||||
if (start === -1) break;
|
||||
|
||||
const bodyStart = start + ":where(".length;
|
||||
let depth = 1;
|
||||
let index = bodyStart;
|
||||
|
||||
for (; index < selector.length && depth > 0; index += 1) {
|
||||
const char = selector[index];
|
||||
if (char === "(") depth += 1;
|
||||
if (char === ")") depth -= 1;
|
||||
}
|
||||
|
||||
if (depth === 0) {
|
||||
ranges.push({ start, bodyStart, bodyEnd: index - 1, end: index });
|
||||
searchFrom = index;
|
||||
} else {
|
||||
searchFrom = bodyStart;
|
||||
}
|
||||
}
|
||||
|
||||
return ranges;
|
||||
}
|
||||
|
||||
noSpecificityWhereRule.ruleName = ruleName;
|
||||
noSpecificityWhereRule.messages = messages;
|
||||
|
||||
export default stylelint.createPlugin(ruleName, noSpecificityWhereRule);
|
||||
|
|
@ -54,6 +54,14 @@
|
|||
color: var(--icon-color);
|
||||
}
|
||||
|
||||
.codex-panel-ui__icon-button svg,
|
||||
.codex-panel-ui__toolbar-action svg,
|
||||
.codex-panel-ui__nav-row-action svg {
|
||||
width: var(--icon-size);
|
||||
height: var(--icon-size);
|
||||
stroke-width: var(--icon-stroke);
|
||||
}
|
||||
|
||||
.codex-panel-ui__toolbar-action.is-active,
|
||||
.codex-panel-ui__toolbar-action.is-active:hover,
|
||||
.codex-panel-ui__toolbar-action.is-active:focus-visible,
|
||||
|
|
@ -216,14 +224,6 @@
|
|||
outline: none;
|
||||
}
|
||||
|
||||
.codex-panel-ui__icon-button svg,
|
||||
.codex-panel-ui__toolbar-action svg,
|
||||
.codex-panel-ui__nav-row-action svg {
|
||||
width: var(--icon-size);
|
||||
height: var(--icon-size);
|
||||
stroke-width: var(--icon-stroke);
|
||||
}
|
||||
|
||||
.codex-panel-ui__text-input {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
|
|
|||
|
|
@ -129,6 +129,16 @@
|
|||
font-weight: var(--codex-panel-secondary-label-weight);
|
||||
}
|
||||
|
||||
.codex-panel__message-content {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
font-family: var(--codex-panel-content-font);
|
||||
font-size: var(--codex-panel-content-font-size);
|
||||
overflow-wrap: anywhere;
|
||||
cursor: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.codex-panel__message--system .codex-panel__message-content {
|
||||
color: var(--text-muted);
|
||||
font-family: var(--font-interface);
|
||||
|
|
@ -220,6 +230,20 @@
|
|||
color: var(--codex-panel-color-accent);
|
||||
}
|
||||
|
||||
.codex-panel__reasoning {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
margin: 0 0 var(--codex-panel-section-gap);
|
||||
padding-left: var(--codex-panel-message-indent);
|
||||
border-left: var(--codex-panel-rail);
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-ui-small);
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
cursor: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.codex-panel__activity-group {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
|
|
@ -252,16 +276,6 @@
|
|||
margin-bottom: var(--codex-panel-section-gap);
|
||||
}
|
||||
|
||||
.codex-panel__message-content {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
font-family: var(--codex-panel-content-font);
|
||||
font-size: var(--codex-panel-content-font-size);
|
||||
overflow-wrap: anywhere;
|
||||
cursor: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.codex-panel__message-content.markdown-rendered {
|
||||
white-space: normal;
|
||||
}
|
||||
|
|
@ -396,20 +410,6 @@
|
|||
color: var(--text-faint);
|
||||
}
|
||||
|
||||
.codex-panel__reasoning {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
margin: 0 0 var(--codex-panel-section-gap);
|
||||
padding-left: var(--codex-panel-message-indent);
|
||||
border-left: var(--codex-panel-rail);
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-ui-small);
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
cursor: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.codex-panel__reasoning-role {
|
||||
margin-bottom: var(--codex-panel-panel-gap);
|
||||
color: var(--codex-panel-role-label-color);
|
||||
|
|
|
|||
|
|
@ -71,6 +71,11 @@
|
|||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.codex-panel__output,
|
||||
.codex-panel-diff-file {
|
||||
margin-top: var(--codex-panel-section-gap);
|
||||
}
|
||||
|
||||
.codex-panel__detail > .codex-panel__output,
|
||||
.codex-panel__detail > .codex-panel-diff-file,
|
||||
.codex-panel__detail > .codex-panel__meta-grid,
|
||||
|
|
@ -157,11 +162,6 @@
|
|||
color: var(--text-faint);
|
||||
}
|
||||
|
||||
.codex-panel__output,
|
||||
.codex-panel-diff-file {
|
||||
margin-top: var(--codex-panel-section-gap);
|
||||
}
|
||||
|
||||
.codex-panel__output-title {
|
||||
line-height: var(--line-height-tight);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,9 +64,20 @@
|
|||
content: "";
|
||||
}
|
||||
|
||||
.codex-panel-threads__row-main:hover .codex-panel-threads__row-title,
|
||||
.codex-panel-threads__row-main:focus .codex-panel-threads__row-title {
|
||||
color: var(--nav-item-color-hover, var(--text-normal));
|
||||
.codex-panel-threads__row-main,
|
||||
.codex-panel-threads__row-title {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.codex-panel-threads__row-title {
|
||||
display: block;
|
||||
color: var(--codex-panel-threads-row-title-color);
|
||||
font-size: var(--font-ui-small);
|
||||
font-weight: var(--nav-item-weight, var(--font-normal));
|
||||
line-height: var(--line-height-tight);
|
||||
}
|
||||
|
||||
.codex-panel-threads__row--selected {
|
||||
|
|
@ -78,6 +89,11 @@
|
|||
font-weight: var(--nav-item-weight-active, var(--font-medium));
|
||||
}
|
||||
|
||||
.codex-panel-threads__row-main:hover .codex-panel-threads__row-title,
|
||||
.codex-panel-threads__row-main:focus .codex-panel-threads__row-title {
|
||||
color: var(--nav-item-color-hover, var(--text-normal));
|
||||
}
|
||||
|
||||
.codex-panel-threads__row--pending {
|
||||
--codex-panel-threads-gutter-color: color-mix(in srgb, var(--codex-panel-color-warning) 72%, transparent);
|
||||
}
|
||||
|
|
@ -104,22 +120,6 @@
|
|||
padding: var(--codex-panel-panel-gap);
|
||||
}
|
||||
|
||||
.codex-panel-threads__row-main,
|
||||
.codex-panel-threads__row-title {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.codex-panel-threads__row-title {
|
||||
display: block;
|
||||
color: var(--codex-panel-threads-row-title-color);
|
||||
font-size: var(--font-ui-small);
|
||||
font-weight: var(--nav-item-weight, var(--font-normal));
|
||||
line-height: var(--line-height-tight);
|
||||
}
|
||||
|
||||
.codex-panel-threads__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
|||
|
|
@ -1,62 +0,0 @@
|
|||
import noSpecificityWhereRule from "./scripts/lint/stylelint-no-specificity-where.mjs";
|
||||
|
||||
export default {
|
||||
extends: ["stylelint-config-standard"],
|
||||
plugins: [noSpecificityWhereRule],
|
||||
rules: {
|
||||
"codex-panel/no-specificity-where": true,
|
||||
"color-named": "never",
|
||||
"color-no-hex": true,
|
||||
"custom-property-empty-line-before": null,
|
||||
"declaration-block-no-redundant-longhand-properties": null,
|
||||
"declaration-empty-line-before": null,
|
||||
"declaration-no-important": true,
|
||||
"declaration-property-value-disallowed-list": [
|
||||
{
|
||||
"/^(?:background(?:-color)?|border(?:-(?:block|inline|top|right|bottom|left))?(?:-color)?|box-shadow|caret-color|color|fill|outline(?:-color)?|stroke|text-shadow)$/":
|
||||
["/(?:#[0-9a-fA-F]{3,8}\\b|\\b(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch)\\()/", "/\\b(?:black|white)\\b/"],
|
||||
"/^--codex-panel-.*(?:accent|background|border|color|danger|error|faint|muted|ring|success|surface|text|warning).*$/": [
|
||||
"/(?:#[0-9a-fA-F]{3,8}\\b|\\b(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch)\\()/",
|
||||
"/\\b(?:black|white)\\b/",
|
||||
],
|
||||
"font-size": ["/\\b\\d*\\.?\\d+(?:px|rem|em|%)\\b/"],
|
||||
"font-weight": ["/^(?:[1-9]00|bold|bolder|lighter|normal)$/"],
|
||||
"line-height": ["/^(?:\\d*\\.?\\d+|\\d*\\.?\\d+(?:px|rem|em|%))$/"],
|
||||
},
|
||||
{
|
||||
message: "Use Obsidian or Codex Panel design tokens instead of hardcoded colors or typography.",
|
||||
},
|
||||
],
|
||||
"declaration-property-unit-disallowed-list": [
|
||||
{
|
||||
"/^(?:border(?:-(?:block|inline|top|right|bottom|left))?-width|border-radius|bottom|column-gap|gap|height|inset|inset-(?:block|inline)|left|margin|margin-(?:block|inline|top|right|bottom|left)|max-height|max-width|min-height|min-width|padding|padding-(?:block|inline|top|right|bottom|left)|right|row-gap|top|width)$/":
|
||||
["em", "px", "rem"],
|
||||
},
|
||||
{
|
||||
message: "Prefer Obsidian or Codex Panel spacing/size tokens for layout dimensions.",
|
||||
},
|
||||
],
|
||||
"function-disallowed-list": ["rgb", "rgba", "hsl", "hsla", "hwb", "lab", "lch", "oklab", "oklch"],
|
||||
"keyframes-name-pattern": "^codex-panel-[a-z0-9-]+$",
|
||||
"max-nesting-depth": 2,
|
||||
"no-descending-specificity": null,
|
||||
"selector-class-pattern": null,
|
||||
"selector-max-compound-selectors": 4,
|
||||
"selector-max-specificity": "0,4,0",
|
||||
"selector-max-type": [
|
||||
0,
|
||||
{
|
||||
ignoreTypes: ["dd", "dt", "pre", "span", "summary", "svg", "ul"],
|
||||
},
|
||||
],
|
||||
"selector-max-id": 0,
|
||||
"selector-disallowed-list": [
|
||||
["/:has\\(/"],
|
||||
{
|
||||
message: "Avoid :has() because it can cause broad selector invalidation.",
|
||||
},
|
||||
],
|
||||
"selector-max-universal": 0,
|
||||
"selector-not-notation": null,
|
||||
},
|
||||
};
|
||||
|
|
@ -15,7 +15,6 @@ describe("check command plan", () => {
|
|||
command:
|
||||
"eslint src --max-warnings=0 --cache --cache-strategy content --cache-location node_modules/.cache/eslint/.eslintcache",
|
||||
},
|
||||
{ name: "lint:css", command: 'stylelint "src/**/*.css" --max-warnings=0' },
|
||||
{ name: "lint:css-usage", command: "node scripts/lint/check-css-usage.mjs" },
|
||||
{ name: "lint:unused", command: "knip --no-progress" },
|
||||
],
|
||||
|
|
|
|||
|
|
@ -556,6 +556,68 @@ runner = new Runner(() => runner.stop());
|
|||
]);
|
||||
expect(pluginDiagnostics(biomeLint(["src/plugin-runtime-declared.ts"], cwd, { expectErrors: false }))).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps CSS on design tokens and scoped selectors", async () => {
|
||||
const cwd = await tempBiomeWorkspace(["no-restricted-css-policy.grit"]);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/styles/bad.css"),
|
||||
`
|
||||
.codex-panel:has(.codex-panel__item) {
|
||||
color: black;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
:where(.codex-panel__item) {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#codex-panel-id {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/styles/good.css"),
|
||||
`
|
||||
.codex-panel__item:where(:hover, :focus-visible) {
|
||||
color: var(--text-normal);
|
||||
margin-top: var(--codex-panel-item-gap);
|
||||
font-size: var(--font-ui-small);
|
||||
font-weight: var(--font-normal);
|
||||
}
|
||||
|
||||
@keyframes codex-panel-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
`.trimStart(),
|
||||
);
|
||||
|
||||
expect(pluginMessages(biomeLint(["src/styles/bad.css"], cwd))).toEqual([
|
||||
"Avoid :has() because it can cause broad selector invalidation.",
|
||||
"Use Obsidian or Codex Panel design tokens instead of hardcoded colors.",
|
||||
"Prefer Obsidian or Codex Panel spacing and size tokens for layout dimensions.",
|
||||
"Do not hide class, id, or attribute selectors inside :where().",
|
||||
"Use Obsidian or Codex Panel typography tokens instead of hardcoded font sizes.",
|
||||
"Avoid ID selectors in Codex Panel CSS.",
|
||||
"Use Obsidian or Codex Panel typography tokens instead of hardcoded font weights.",
|
||||
"Prefix keyframes with codex-panel-.",
|
||||
]);
|
||||
expect(pluginDiagnostics(biomeLint(["src/styles/good.css"], cwd, { expectErrors: false }))).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
async function tempBiomeWorkspace(plugins) {
|
||||
|
|
@ -577,6 +639,7 @@ async function tempBiomeWorkspace(plugins) {
|
|||
await mkdir(path.join(cwd, "src/app-server/services"), { recursive: true });
|
||||
await mkdir(path.join(cwd, "src/shared"), { recursive: true });
|
||||
await mkdir(path.join(cwd, "src/shared/ui"), { recursive: true });
|
||||
await mkdir(path.join(cwd, "src/styles"), { recursive: true });
|
||||
await mkdir(path.join(cwd, "tests/app-server"), { recursive: true });
|
||||
await writeFile(
|
||||
path.join(cwd, "biome.jsonc"),
|
||||
|
|
@ -584,6 +647,7 @@ async function tempBiomeWorkspace(plugins) {
|
|||
$schema: "https://biomejs.dev/schemas/2.5.1/schema.json",
|
||||
vcs: { enabled: false },
|
||||
plugins: plugins.map((plugin) => path.join(repoRoot, "scripts", "lint", "grit", plugin)),
|
||||
css: { linter: { enabled: true } },
|
||||
}),
|
||||
);
|
||||
return cwd;
|
||||
|
|
|
|||
Loading…
Reference in a new issue