diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e52588347b..7ed7e6bbacf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -128,6 +128,16 @@ jobs: run: pnpm knip - name: Check TypeScript regressions (Betterer) run: pnpm exec betterer ci + # The workspace packages are formatted and linted by oxfmt/oxlint, not Biome, so they need + # their own steps. Each runs the package's own script, which keeps CI and local in step. + - name: "Check formatting (oxfmt): packages/ui" + run: pnpm --filter @infrahub/ui run format:check + - name: "Check linting (oxlint): packages/ui" + run: pnpm --filter @infrahub/ui run lint + - name: "Check formatting (oxfmt): packages/graph" + run: pnpm --filter @infrahub/graph run format:check + - name: "Check linting (oxlint): packages/graph" + run: pnpm --filter @infrahub/graph run lint frontend-validate-openapi-types: if: | diff --git a/changelog/+frontend-packages-lint-ci.housekeeping.md b/changelog/+frontend-packages-lint-ci.housekeeping.md new file mode 100644 index 00000000000..00796faff44 --- /dev/null +++ b/changelog/+frontend-packages-lint-ci.housekeeping.md @@ -0,0 +1 @@ +The frontend design-system packages (`@infrahub/ui` and `@infrahub/graph`) are now format- and lint-checked in CI with their own toolchain (oxfmt and oxlint), alongside the app's existing Biome check. Both packages shipped that toolchain but nothing ever ran it, so a misformatted or lint-breaking file in either package could ship green. diff --git a/dev/knowledge/frontend/design-system.md b/dev/knowledge/frontend/design-system.md index 5a4ae18f8d5..f4aae204810 100644 --- a/dev/knowledge/frontend/design-system.md +++ b/dev/knowledge/frontend/design-system.md @@ -57,6 +57,24 @@ Source of truth: `frontend/packages/graph/src/index.ts`. Adopted by `path-traver A third directory exists under `frontend/packages/`: `plugins/` holds a single standalone Vite + module-federation plugin template (`plugins/template`) that is **not** a pnpm workspace member (the workspace lists `app`, `packages/schema-visualizer`, `packages/ui`, and `packages/graph`) and is unrelated to the design system. +## Formatting and linting: two toolchains, split by directory + +`frontend/app` uses Biome. Both workspace packages use oxfmt and oxlint instead, each with its own +`oxfmt.config.ts` and `oxlint.config.ts` (`@infrahub/ui` also sorts Tailwind classes through oxfmt). +There is no Biome config anywhere under `frontend/packages/`, so **never run Biome there**: with no +`biome.jsonc` to find it walks up out of the checkout, picks up an unrelated config, and reformats +the whole file. + +```bash +pnpm --filter @infrahub/ui run format # oxfmt --write +pnpm --filter @infrahub/ui run format:check # what CI runs +pnpm --filter @infrahub/ui run lint # oxlint +pnpm --filter @infrahub/ui run lint:fix # oxlint --fix +``` + +Same four scripts for `@infrahub/graph`. CI's `frontend-lint` job runs `format:check` and `lint` for +both packages next to the app's Biome check, so a misformatted package file fails the build. + ## When to consume from `@infrahub/ui` Always, for the components above. Do not reimplement them inline in feature code, even if it "feels lighter": diff --git a/frontend/app/AGENTS.md b/frontend/app/AGENTS.md index 8dc98331b77..4c7219d69d6 100644 --- a/frontend/app/AGENTS.md +++ b/frontend/app/AGENTS.md @@ -20,7 +20,7 @@ cd frontend/app && pnpm codegen # Generate GraphQL types ## Before pushing (run the full CI gate locally) -`pnpm biome:fix` alone is **not** the CI gate. The `frontend-lint` job runs three checks and +`pnpm biome:fix` alone is **not** the CI gate. The `frontend-lint` job runs several checks and `frontend-tests` runs the browser test suite. Run all of them before pushing — they each fail CI independently: @@ -31,6 +31,15 @@ cd frontend/app && pnpm exec betterer ci # TypeScript-regression gate (NOT plai cd frontend/app && pnpm test # vitest (browser mode) ``` +Biome covers `frontend/app` only. `frontend/packages/ui` and `frontend/packages/graph` are formatted +and linted by oxfmt/oxlint, and the same `frontend-lint` job gates them — never run Biome against +them. See `dev/knowledge/frontend/design-system.md` for why and for the per-package commands: + +```bash +cd frontend/app && pnpm --filter @infrahub/ui run format:check && pnpm --filter @infrahub/ui run lint +cd frontend/app && pnpm --filter @infrahub/graph run format:check && pnpm --filter @infrahub/graph run lint +``` + ## See Also ### Guidelines (How to write code) diff --git a/frontend/packages/ui/src/styles/theme.css b/frontend/packages/ui/src/styles/theme.css index 734af777e8d..31c13c3eb8d 100644 --- a/frontend/packages/ui/src/styles/theme.css +++ b/frontend/packages/ui/src/styles/theme.css @@ -53,20 +53,12 @@ --selected-shadow: 0 0 #0000; --selected-highlight: var(--highlight), var(--selected); - --card: linear-gradient( - to bottom, - var(--color-stone-50) 0%, - var(--color-white) 10% - ); + --card: linear-gradient(to bottom, var(--color-stone-50) 0%, var(--color-white) 10%); --card-shadow: inset 0 2px 0 rgb(255 255 255), inset 0 -1px 2px 1px rgb(0 0 0 / 0.03), 0 1px 1px rgb(0 0 0 / 0.02); - --card-header: linear-gradient( - to bottom, - var(--color-white) 0%, - var(--color-neutral-50) 100% - ); + --card-header: linear-gradient(to bottom, var(--color-white) 0%, var(--color-neutral-50) 100%); --card-header-foreground: var(--color-neutral-700); --card-header-shadow: inset 0 1px 0 rgb(255 255 255 / 0.85); @@ -78,8 +70,7 @@ --modal-frame: --alpha(var(--color-white) / 25%); --modal-frame-border: var(--border); - --modal-shadow: - 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + --modal-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); --table-cell: var(--color-stone-50); --table-cell-pinned: var(--color-white); @@ -121,44 +112,25 @@ --popover: --alpha(var(--color-stone-900) / 70%); --highlight: - radial-gradient( - 72% 100% at 50% 100%, - rgb(255 255 255 / 0.09), - transparent 72% - ), + radial-gradient(72% 100% at 50% 100%, rgb(255 255 255 / 0.09), transparent 72%), linear-gradient(rgb(255 255 255 / 0.02), rgb(255 255 255 / 0.02)); --highlight-foreground: var(--foreground); --selected: - radial-gradient( - 72% 100% at 50% 100%, - rgb(255 255 255 / 0.16), - transparent 72% - ), + radial-gradient(72% 100% at 50% 100%, rgb(255 255 255 / 0.16), transparent 72%), linear-gradient(rgb(255 255 255 / 0.05), rgb(255 255 255 / 0.05)); --selected-foreground: var(--foreground); - --selected-shadow: - inset 0 1px 0 rgb(255 255 255 / 0.08), 0 1px 2px rgb(0 0 0 / 0.6); + --selected-shadow: inset 0 1px 0 rgb(255 255 255 / 0.08), 0 1px 2px rgb(0 0 0 / 0.6); --card: linear-gradient(180deg, #201d1c 0%, #191716 55%, #141312 100%); - --card-shadow: - inset 0 2px 2px 1px rgb(32 28 26 / 0.2), 0 1px 2px rgb(0 0 0 / 0.5); + --card-shadow: inset 0 2px 2px 1px rgb(32 28 26 / 0.2), 0 1px 2px rgb(0 0 0 / 0.5); - --card-header: linear-gradient( - 180deg, - rgb(255 251 246 / 0.045) 0%, - rgb(255 251 246 / 0.03) 100% - ); + --card-header: linear-gradient(180deg, rgb(255 251 246 / 0.045) 0%, rgb(255 251 246 / 0.03) 100%); --card-header-foreground: var(--color-stone-300); - --card-header-shadow: - inset 0 1px 0 lch(24.13 2.26 37.96), 0 0 2px oklch(0.16 0.01 0); + --card-header-shadow: inset 0 1px 0 lch(24.13 2.26 37.96), 0 0 2px oklch(0.16 0.01 0); --secondary: - radial-gradient( - 120% 55% at 0% 0%, - rgb(255 255 255 / 0.005), - transparent 62% - ), + radial-gradient(120% 55% at 0% 0%, rgb(255 255 255 / 0.005), transparent 62%), linear-gradient(180deg, #1a1817 0%, #0d0c0b 18%, #000000 100%); --secondary-shadow: 0 0 #0000; diff --git a/frontend/packages/ui/src/theme/theme-provider.tsx b/frontend/packages/ui/src/theme/theme-provider.tsx index e77f69c69ab..6019de654a2 100644 --- a/frontend/packages/ui/src/theme/theme-provider.tsx +++ b/frontend/packages/ui/src/theme/theme-provider.tsx @@ -29,9 +29,11 @@ export function ThemeProvider({ canChoose, defaultTheme, children }: ThemeProvid const theme: ResolvedTheme = canChoose ? (choice ?? defaultTheme) : defaultTheme; - // Before paint, not after: the pre-paint script can only replay a theme this browser has already - // resolved once, so a first-time visitor starts with no class at all. A passive effect would let - // that first commit paint in the wrong palette and then snap. + /* + * Before paint, not after: the pre-paint script can only replay a theme this browser has already + * resolved once, so a first-time visitor starts with no class at all. A passive effect would let + * that first commit paint in the wrong palette and then snap. + */ useLayoutEffect(() => { applyTheme(theme); mirrorResolvedTheme(theme);