Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions .agents/skills/quality-check/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: "Runs type-check, lint, test, check-dts, and formatting for the App

# Quality Check

Run type-check, lint, test, `check-dts`, and formatting in sequence. Fix errors where possible and re-run until clean. Formatting runs last so that any code changes from fixing errors are also formatted.
Run type-check, lint, test, `check-dts`, browser style tests, and formatting in sequence. Fix errors where possible and re-run until clean. Formatting runs last so that any code changes from fixing errors are also formatted.

These are the same tasks CI runs in `.github/actions/ci`. Keep this list in step with that file — a task that runs there but not here passes locally and fails on the PR.

Expand Down Expand Up @@ -53,7 +53,19 @@ Step 1 cannot catch what this catches: `type-check` sets `skipLibCheck: true` an
the emitted declarations, while `check-dts` packs a tarball and type-checks it with
`skipLibCheck: false`. A declaration that only breaks for consumers fails here and nowhere else.

### Step 5: Format
### Step 5: Browser style contracts

Run the shipped CSS contracts in Chromium (the script builds the package first):

```sh
pnpm --filter @tailor-platform/app-shell test:browser
```

On first use, install Chromium with `pnpm --filter @tailor-platform/app-shell exec playwright install chromium`.
Linux CI uses `playwright install --with-deps chromium` to install system libraries too.
Keep these tests enabled in CI when replacing Node-based style checks; `pnpm test` alone does not run them.

### Step 6: Format

Run formatting:

Expand All @@ -76,4 +88,5 @@ Provide a summary of:
2. Lint errors found and fixed (if any)
3. Test failures found and fixed (if any)
4. Declaration (`check-dts`) errors found and fixed (if any)
5. Formatting changes (if any)
5. Browser style test failures found and fixed (if any)
6. Formatting changes (if any)
6 changes: 6 additions & 0 deletions .github/actions/ci/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ runs:
# sets skipLibCheck: true and never looks at the emitted declarations.
- run: pnpm exec turbo run lint test type-check check-dts
shell: bash
- name: Install Chromium for style contract tests
run: pnpm --filter @tailor-platform/app-shell exec playwright install --with-deps chromium
shell: bash
- name: Check styles in Chromium
run: pnpm --filter @tailor-platform/app-shell exec vitest run --config vitest.browser.config.ts
shell: bash
# Deterministic docs + generated-skill drift gate (no LLM). Runs uncached
# so a docs/ change always re-validates the manifest and consumer skill.
- run: pnpm docs:check
Expand Down
2 changes: 1 addition & 1 deletion decisions/typography-roles.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ Limit: this does not let a role replace a size that core sets on its own element
- **API.** `Text` takes the role as `variant`. The prop is not called `role`, because `role` is an ARIA attribute. The element is set with Base UI's `render` prop, as in `Button`, the sidebar primitives, and the breadcrumb. AppShell has no `as` or `asChild` prop.
- **Default elements.** The three heading roles render `h2`. The body roles, including the relaxed variants, render `p`. `label-md`, `label-sm`, and `code-sm` render `span`. All headings share one default so that no heading level is implied by size. `h2` is the default because a page has one `h1` (`Layout.Header`), and `Dialog.Title` from Base UI also renders `h2`. A default is hard to change later, because every call site without `render` would get a different element.
- **Lint.** The `jsx-a11y/heading-has-content` rule reports `render={<h3 />}` as an empty heading, and the function form `render={(props) => <h3 {...props} />}` too. Apps that enable the rule disable it on that line. A `level` prop would avoid this, but it would add a second way to choose the element next to `render`. The documentation shows the disable comment instead.
- **No color or modifier props.** Color and the four modifiers stay in `className`, as for the utilities. A consumer `leading-none`, `font-semibold`, or `tracking-*` overrides the role even though AppShell's prefixed sheet loads later, because the role reads the shared `--tw-leading`, `--tw-font-weight`, and `--tw-tracking` variables first. `tests/typography-compile.test.ts` checks this.
- **No color or modifier props.** Color and the four modifiers stay in `className`, as for the utilities. A consumer `leading-none`, `font-semibold`, or `tracking-*` overrides the role even though AppShell's prefixed sheet loads later, because the role reads the shared `--tw-leading`, `--tw-font-weight`, and `--tw-tracking` variables first. `tests/browser/theme.test.tsx` checks the computed styles in Chromium; `tests/typography-compile.test.ts` retains the full role-declaration checks.
- **No `textVariants` export.** tailor-inc/platform-planning#1580 asked for a variants export. It is descoped, not deferred. The `text-<role>` utilities already apply the same role mappings, so a cva export would be a third way to do the same thing, and it would put `astw:`-prefixed classes into consumer code. Unlike `Button` and `Badge`, `Text` does not export its cva function.
- **`code-sm` includes `font-mono`.** The utility cannot set the font family, but the component can.
- **cream and bloom.** The `h1` to `h6` rule is unchanged. The default `h2` takes the palette's line height and letter spacing under these palettes, the same as existing headings in AppShell components today.
Expand Down
3 changes: 3 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"vendor:fonts": "node scripts/sync-noto-fonts.mjs",
"lint": "oxlint -c .oxlintrc.jsonc",
"test": "vitest run",
"test:browser": "pnpm build && vitest run --config vitest.browser.config.ts",
"type-check": "tsc --incremental"
},
"dependencies": {
Expand Down Expand Up @@ -84,8 +85,10 @@
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@vitejs/plugin-react": "catalog:",
"@vitest/browser-playwright": "4.1.11",
"@vitest/coverage-v8": "4.1.11",
"happy-dom": "21.0.4",
"playwright": "1.63.0",
"postcss": "8.5.31",
"react": "catalog:",
"react-dom": "catalog:",
Expand Down
258 changes: 0 additions & 258 deletions packages/core/src/assets/semantic-contrast.test.ts

This file was deleted.

17 changes: 17 additions & 0 deletions packages/core/src/assets/theme-bridge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,23 @@ describe("theme bridge", () => {
expect(bridgedTokens(SEMANTIC_PREFIX).size).toBe(40);
});

it.each([
["--destructive", "--danger-solid"],
["--destructive-foreground", "--danger-contrast"],
])("keeps %s linked to %s for consumer overrides", (alias, target) => {
const sheets = [
defaultTheme,
...["cream", "bloom"].map((name) => readFileSync(join(here, `themes/${name}.css`), "utf8")),
];
const values = sheets.flatMap((css) =>
[...css.matchAll(new RegExp(`^\\s*${alias}:\\s*([^;]+);`, "gm"))].map((match) =>
match[1].trim(),
),
);
expect(values.length).toBeGreaterThan(0);
expect(new Set(values)).toEqual(new Set([`var(${target})`]));
});

describe("typography roles", () => {
const slots = ["size", "line-height", "weight", "letter-spacing"] as const;
/** `--type-<role>-<slot>` variables in the default palette, grouped by role. */
Expand Down
3 changes: 3 additions & 0 deletions packages/core/tests/browser/consumer.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@import "tailwindcss" source(none);
@import "@tailor-platform/app-shell/styles";
@source "./";
Loading
Loading