@ethlete/agent-rules 0.1.0-next.1 → 0.1.0-next.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +101 -0
- package/README.md +296 -21
- package/content/git-hooks/post-checkout.sh +10 -0
- package/content/git-hooks/pre-push.sh +5 -0
- package/content/hooks/context-warning.py +284 -69
- package/content/output-styles/ste-clarity.md +131 -0
- package/content/rules/comments.md +50 -20
- package/content/skills/angular-patterns/SKILL.md +1 -1
- package/content/skills/api-source/SKILL.md +118 -0
- package/content/skills/figma-export/SKILL.md +193 -0
- package/content/skills/figma-export/dump-figma-layers.py +83 -0
- package/content/skills/figma-export/dump-figma-svg.py +235 -0
- package/content/skills/figma-export/measure-template.mjs +87 -0
- package/content/skills/git-commit/SKILL.md +6 -7
- package/content/skills/git-flow/SKILL.md +87 -0
- package/content/skills/handoff/SKILL.md +4 -0
- package/content/skills/query/SKILL.md +23 -13
- package/content/skills/rxjs-signals/SKILL.md +1 -1
- package/content/skills/sdk-docs/SKILL.md +10 -2
- package/content/skills/sdk-local-build/SKILL.md +115 -0
- package/content/skills/sdk-source/SKILL.md +133 -0
- package/content/skills/styleguide/STYLEGUIDE.md +2 -2
- package/content/skills/theming/SKILL.md +14 -7
- package/content/skills/timetrack/SKILL.md +66 -0
- package/package.json +12 -1
- package/src/index.js +23 -10
- package/src/index.js.map +1 -1
- package/src/lib/commitlint.d.ts +10 -0
- package/src/lib/commitlint.js +51 -0
- package/src/lib/commitlint.js.map +1 -0
- package/src/lib/config.d.ts +35 -6
- package/src/lib/config.js +26 -3
- package/src/lib/config.js.map +1 -1
- package/src/lib/git-flow/build.d.ts +35 -0
- package/src/lib/git-flow/build.js +24 -0
- package/src/lib/git-flow/build.js.map +1 -0
- package/src/lib/git-flow/config.d.ts +59 -0
- package/src/lib/git-flow/config.js +50 -0
- package/src/lib/git-flow/config.js.map +1 -0
- package/src/lib/git-flow/index.d.ts +6 -0
- package/src/lib/git-flow/index.js +10 -0
- package/src/lib/git-flow/index.js.map +1 -0
- package/src/lib/git-flow/parse.d.ts +49 -0
- package/src/lib/git-flow/parse.js +274 -0
- package/src/lib/git-flow/parse.js.map +1 -0
- package/src/lib/git-flow/rename.d.ts +24 -0
- package/src/lib/git-flow/rename.js +70 -0
- package/src/lib/git-flow/rename.js.map +1 -0
- package/src/lib/git-flow/start.d.ts +49 -0
- package/src/lib/git-flow/start.js +57 -0
- package/src/lib/git-flow/start.js.map +1 -0
- package/src/lib/git-flow/validate.d.ts +34 -0
- package/src/lib/git-flow/validate.js +72 -0
- package/src/lib/git-flow/validate.js.map +1 -0
- package/src/lib/git-flow-command.d.ts +4 -0
- package/src/lib/git-flow-command.js +157 -0
- package/src/lib/git-flow-command.js.map +1 -0
- package/src/lib/git-flow-repair.d.ts +17 -0
- package/src/lib/git-flow-repair.js +146 -0
- package/src/lib/git-flow-repair.js.map +1 -0
- package/src/lib/git-flow-start.d.ts +20 -0
- package/src/lib/git-flow-start.js +132 -0
- package/src/lib/git-flow-start.js.map +1 -0
- package/src/lib/git.d.ts +27 -0
- package/src/lib/git.js +49 -0
- package/src/lib/git.js.map +1 -0
- package/src/lib/gitlab.d.ts +35 -0
- package/src/lib/gitlab.js +98 -0
- package/src/lib/gitlab.js.map +1 -0
- package/src/lib/index.d.ts +1 -0
- package/src/lib/index.js +1 -0
- package/src/lib/index.js.map +1 -1
- package/src/lib/output-style-command.d.ts +3 -0
- package/src/lib/output-style-command.js +69 -0
- package/src/lib/output-style-command.js.map +1 -0
- package/src/lib/output-style.d.ts +38 -0
- package/src/lib/output-style.js +126 -0
- package/src/lib/output-style.js.map +1 -0
- package/src/lib/owned-paths.js +19 -1
- package/src/lib/owned-paths.js.map +1 -1
- package/src/lib/plan.d.ts +0 -1
- package/src/lib/plan.js +83 -9
- package/src/lib/plan.js.map +1 -1
- package/src/lib/prompt.d.ts +8 -0
- package/src/lib/prompt.js +27 -0
- package/src/lib/prompt.js.map +1 -0
- package/src/lib/render.d.ts +20 -2
- package/src/lib/render.js +31 -8
- package/src/lib/render.js.map +1 -1
- package/src/lib/sync.d.ts +0 -1
- package/src/lib/sync.js +2 -2
- package/src/lib/sync.js.map +1 -1
- package/src/lib/targets/claude-hooks.d.ts +1 -23
- package/src/lib/targets/claude-hooks.js +15 -84
- package/src/lib/targets/claude-hooks.js.map +1 -1
- package/src/lib/targets/claude.js +1 -1
- package/src/lib/targets/claude.js.map +1 -1
- package/src/lib/targets/codex-hooks.d.ts +12 -0
- package/src/lib/targets/codex-hooks.js +33 -0
- package/src/lib/targets/codex-hooks.js.map +1 -0
- package/src/lib/targets/codex.js +1 -1
- package/src/lib/targets/codex.js.map +1 -1
- package/src/lib/targets/copilot.js +1 -1
- package/src/lib/targets/copilot.js.map +1 -1
- package/src/lib/targets/cursor.js +1 -1
- package/src/lib/targets/cursor.js.map +1 -1
- package/src/lib/targets/git-hooks.d.ts +24 -0
- package/src/lib/targets/git-hooks.js +70 -0
- package/src/lib/targets/git-hooks.js.map +1 -0
- package/src/lib/targets/hooks-shared.d.ts +37 -0
- package/src/lib/targets/hooks-shared.js +95 -0
- package/src/lib/targets/hooks-shared.js.map +1 -0
- package/src/lib/targets/shared.d.ts +0 -1
- package/src/lib/targets/shared.js +1 -1
- package/src/lib/targets/shared.js.map +1 -1
- package/src/lib/timetrack-command.d.ts +11 -0
- package/src/lib/timetrack-command.js +199 -0
- package/src/lib/timetrack-command.js.map +1 -0
- package/src/lib/timetrack.d.ts +86 -0
- package/src/lib/timetrack.js +112 -0
- package/src/lib/timetrack.js.map +1 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Measure real rendered geometry against the numbers in a Figma export.
|
|
3
|
+
*
|
|
4
|
+
* Copy into a scratch directory next to a `styles.css` copied out of a production build,
|
|
5
|
+
* replace MARKUP / WIDTHS / DESIGN / probe(), then `node measure-template.mjs`.
|
|
6
|
+
*/
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
|
+
import { writeFileSync } from 'node:fs';
|
|
9
|
+
|
|
10
|
+
// This runs from a scratch directory, so Playwright cannot be resolved by name — point
|
|
11
|
+
// createRequire at the repo root. Playwright is CommonJS; a named import fails.
|
|
12
|
+
const REPO_ROOT = '/absolute/path/to/repo'; // <-- change me
|
|
13
|
+
const { chromium } = createRequire(`${REPO_ROOT}/`)('playwright');
|
|
14
|
+
|
|
15
|
+
/** One instance of the thing under test. Keep the real component classes verbatim. */
|
|
16
|
+
const MARKUP = (id) => `
|
|
17
|
+
<div id="${id}-card" class="…">
|
|
18
|
+
<h4 id="${id}-title" class="…">Some realistically long label</h4>
|
|
19
|
+
</div>`;
|
|
20
|
+
|
|
21
|
+
/** Container widths to probe, taken from the export's frames — not from viewport breakpoints. */
|
|
22
|
+
const WIDTHS = [956, 640];
|
|
23
|
+
|
|
24
|
+
/** What the export says each width should produce. */
|
|
25
|
+
const DESIGN = {
|
|
26
|
+
956: { cols: 4, cardW: 219, cardH: 60 },
|
|
27
|
+
640: { cols: 3, cardW: 192, cardH: 60 },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Column, never row: in a flex row the harness items shrink, and container queries then
|
|
31
|
+
// report results for a width the component would never actually see.
|
|
32
|
+
const page = (blocks) => `<!doctype html><html class="et-surface--dark"><head><meta charset="utf-8">
|
|
33
|
+
<link rel="stylesheet" href="./styles.css"></head>
|
|
34
|
+
<body class="et-surface--dark" style="margin:0;padding:24px;display:flex;flex-direction:column;gap:24px;align-items:flex-start">
|
|
35
|
+
${blocks}
|
|
36
|
+
</body></html>`;
|
|
37
|
+
|
|
38
|
+
const block = (width) => `<div id="w${width}" class="@container" style="width:${width}px">
|
|
39
|
+
<div class="…">${MARKUP(`w${width}`)}</div>
|
|
40
|
+
</div>`;
|
|
41
|
+
|
|
42
|
+
// page.setContent() renders on about:blank, which blocks file:// subresources — the
|
|
43
|
+
// stylesheet would silently never load. Write a real file and navigate to it.
|
|
44
|
+
writeFileSync('harness.html', page(WIDTHS.map(block).join('\n')));
|
|
45
|
+
|
|
46
|
+
const browser = await chromium.launch();
|
|
47
|
+
const tab = await browser.newPage({ viewport: { width: 1200, height: 900 }, deviceScaleFactor: 2 });
|
|
48
|
+
|
|
49
|
+
await tab.goto(`file://${process.cwd()}/harness.html`);
|
|
50
|
+
await tab.waitForTimeout(300);
|
|
51
|
+
|
|
52
|
+
const measured = await tab.evaluate((widths) => {
|
|
53
|
+
const round = (value) => Math.round(value * 100) / 100;
|
|
54
|
+
const box = (id) => document.getElementById(id).getBoundingClientRect();
|
|
55
|
+
const font = (id) => getComputedStyle(document.getElementById(id));
|
|
56
|
+
|
|
57
|
+
return widths.map((width) => {
|
|
58
|
+
const card = box(`w${width}-card`);
|
|
59
|
+
const title = font(`w${width}-title`);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
width,
|
|
63
|
+
cardW: round(card.width),
|
|
64
|
+
cardH: round(card.height),
|
|
65
|
+
titleInset: round(box(`w${width}-title`).left - card.left),
|
|
66
|
+
fontSize: title.fontSize,
|
|
67
|
+
lineHeight: title.lineHeight,
|
|
68
|
+
letterSpacing: title.letterSpacing,
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
}, WIDTHS);
|
|
72
|
+
|
|
73
|
+
let failures = 0;
|
|
74
|
+
|
|
75
|
+
for (const row of measured) {
|
|
76
|
+
const want = DESIGN[row.width];
|
|
77
|
+
const ok = Math.abs(row.cardW - want.cardW) < 1 && Math.abs(row.cardH - want.cardH) < 0.5;
|
|
78
|
+
|
|
79
|
+
if (!ok) failures++;
|
|
80
|
+
|
|
81
|
+
console.log(`${String(row.width).padStart(4)}px | ${ok ? 'OK ' : 'BAD'} |`, row);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
console.log(failures === 0 ? '\nALL GEOMETRY MATCHES' : `\n${failures} MISMATCHES`);
|
|
85
|
+
|
|
86
|
+
await tab.screenshot({ path: 'verify.png', fullPage: true });
|
|
87
|
+
await browser.close();
|
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: git-commit
|
|
3
|
-
description: How to write git commits in this repo -
|
|
3
|
+
description: How to write git commits in this repo - conventional format (type(scope): Subject), lean messages, no trailers. Read before committing anything (e.g. the user says "commit this").
|
|
4
4
|
kind: skill
|
|
5
5
|
scope: both
|
|
6
|
-
vars: [commitScopes]
|
|
6
|
+
vars: [commitScopes, commitRuleSource, commitValidation]
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Git commits
|
|
10
10
|
|
|
11
|
-
Commits are **lean** and follow
|
|
12
|
-
(conventional commits with a required scope):
|
|
11
|
+
Commits are **lean** and follow {%commitRuleSource%}:
|
|
13
12
|
|
|
14
|
-
- **Format: `type(scope): Subject`** - one line
|
|
13
|
+
- **Format: `type(scope): Subject`** - one line, all three parts required:
|
|
15
14
|
- `type` ∈ `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`,
|
|
16
15
|
`build`, `ci`, `chore`, `revert`
|
|
17
|
-
- `scope`
|
|
16
|
+
- `scope` ∈ {%commitScopes%}
|
|
18
17
|
- Subject is **sentence-case** ("Add the search filter", not
|
|
19
18
|
"add the search filter")
|
|
20
|
-
-
|
|
19
|
+
- {%commitValidation%}
|
|
21
20
|
- Add a short body only when the change genuinely needs context that the diff
|
|
22
21
|
can't convey.
|
|
23
22
|
- **No trailers.** Never append `Co-Authored-By`, `Claude-Session`, or similar
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: git-flow
|
|
3
|
+
description: How to name and base a branch in this repo, and what its merge request must target - feature, sub-feature, release, release fix and hotfix. Read BEFORE creating a branch, opening a merge request, or choosing what to branch from (e.g. the user says "start work on FIP-2177" or "open an MR").
|
|
4
|
+
kind: skill
|
|
5
|
+
scope: both
|
|
6
|
+
vars: [gitFlowDevelopmentBranch, gitFlowProductionBranch, gitFlowTypes, gitFlowEnforcement, gitFlowSubPrefix]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Git flow
|
|
10
|
+
|
|
11
|
+
Work is organised in two levels: a **main feature branch** per Jira Story, into which
|
|
12
|
+
**sub-feature branches** (one per Task) are merged after review. The main feature branch is
|
|
13
|
+
what gets deployed to a test environment and, once accepted, merged into
|
|
14
|
+
`{%gitFlowDevelopmentBranch%}`.
|
|
15
|
+
|
|
16
|
+
**Never name a branch by hand - `start` does it from the grammar:**
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx ethlete-agents git-flow start FIP-2178 # reads the issue, names it, branches off the right base
|
|
20
|
+
npx ethlete-agents git-flow check # is the current branch conforming?
|
|
21
|
+
npx ethlete-agents git-flow explain <branch> # what the parser sees, and what it expects
|
|
22
|
+
npx ethlete-agents git-flow repair <branch> # rename a non-conforming one, retarget its MRs
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`start` prints its plan (branch, base, MR target) and asks before writing anything; add
|
|
26
|
+
`--dry-run` to see the plan alone. It refuses on a dirty working tree, and a Task nests under
|
|
27
|
+
its parent Story's branch, so that branch has to exist first.
|
|
28
|
+
|
|
29
|
+
It reads the issue through the running Timetrack app, which holds this machine's only Jira
|
|
30
|
+
credentials - see the `timetrack` skill. If it reports that the app is not running, ask the
|
|
31
|
+
user to start it. `--subject <text>` names a branch without reading Jira at all.
|
|
32
|
+
|
|
33
|
+
## The five shapes
|
|
34
|
+
|
|
35
|
+
| Shape | Issue | Branch from | MR targets |
|
|
36
|
+
| --------------------------------------------------------------------------------- | ----- | ------------------------------ | ---------------------------------------------------------------- |
|
|
37
|
+
| `feat/FIP-2177-user-management` | Story | `{%gitFlowDevelopmentBranch%}` | `{%gitFlowDevelopmentBranch%}` |
|
|
38
|
+
| `{%gitFlowSubPrefix%}/feat/FIP-2177-user-management/FIP-2178-user-password-reset` | Task | the main feature branch | the main feature branch |
|
|
39
|
+
| `release/2026.04.28` | - | `{%gitFlowDevelopmentBranch%}` | `{%gitFlowDevelopmentBranch%}` and `{%gitFlowProductionBranch%}` |
|
|
40
|
+
| `{%gitFlowSubPrefix%}/release/2026.04.28/FIP-2222-button-not-visible` | Bug | the release branch | the release branch |
|
|
41
|
+
| `hotfix/FIP-2799-password-recovery-broken` | Bug | `{%gitFlowProductionBranch%}` | `{%gitFlowProductionBranch%}` |
|
|
42
|
+
|
|
43
|
+
- The **type** is one of {%gitFlowTypes%}, and a nested branch carries its parent's **full**
|
|
44
|
+
name - that path is what makes the parent machine-readable.
|
|
45
|
+
- The **key** is the Jira issue, uppercase, immediately after the type. The **subject** is the
|
|
46
|
+
Story's subject meta field in kebab-case, not a paraphrase of the summary.
|
|
47
|
+
- A branch with no key still works, but nothing can attribute it to an issue. Add the key.
|
|
48
|
+
|
|
49
|
+
### Why a nested branch starts with `{%gitFlowSubPrefix%}/`
|
|
50
|
+
|
|
51
|
+
Git refuses a ref that is both a branch and a directory of branches. So
|
|
52
|
+
`feat/FIP-2177-user-management/FIP-2178-user-password-reset` **cannot exist** while
|
|
53
|
+
`feat/FIP-2177-user-management` does - git rejects it locally and the push comes back as
|
|
54
|
+
`refname conflict`. The prefix moves the nested tree out of the way and keeps the parent's
|
|
55
|
+
full path inside the child's name, so the MR target is still readable off the name.
|
|
56
|
+
|
|
57
|
+
Do not "fix" a name by dropping the prefix; the branch it produces cannot be created.
|
|
58
|
+
|
|
59
|
+
## Rules that are not about naming
|
|
60
|
+
|
|
61
|
+
- **Never rebase a shared branch.** A main feature branch is published and other people's
|
|
62
|
+
sub-features are based on it - bring it up to date by **merging**
|
|
63
|
+
`{%gitFlowDevelopmentBranch%}` into it. Rebase only a local branch you have not pushed.
|
|
64
|
+
- **A sub-feature merges into its parent, never straight into
|
|
65
|
+
`{%gitFlowDevelopmentBranch%}`** - that is the whole point of the two levels, since the
|
|
66
|
+
parent is what gets tested as a unit.
|
|
67
|
+
- **Delete the source branch on merge** (the checkbox in the merge request) for sub-features,
|
|
68
|
+
release fixes and merged main features.
|
|
69
|
+
- **A hotfix branches off `{%gitFlowProductionBranch%}`** and, after rollout, that branch is
|
|
70
|
+
merged back into `{%gitFlowDevelopmentBranch%}` so the two do not drift.
|
|
71
|
+
- **Never push directly to `{%gitFlowDevelopmentBranch%}` or
|
|
72
|
+
`{%gitFlowProductionBranch%}`.** Open a merge request; it needs another developer's review.
|
|
73
|
+
|
|
74
|
+
## Enforcement is `{%gitFlowEnforcement%}`
|
|
75
|
+
|
|
76
|
+
In `advisory` mode every rule reports and nothing blocks - the older naming shapes (`feature/`
|
|
77
|
+
instead of `feat/`, a lowercase key, no key at all, a `dev-*` integration branch) are accepted
|
|
78
|
+
on purpose while the team adapts. Report the suggestion, do not "fix" someone's existing
|
|
79
|
+
branch unasked - `repair` is how a rename happens, and only when asked for.
|
|
80
|
+
|
|
81
|
+
`dev-*` is the **old spelling of a main feature branch**, not a stray name: sub-features
|
|
82
|
+
legitimately target it. Leave a live one alone.
|
|
83
|
+
|
|
84
|
+
## Commit messages are a separate thing
|
|
85
|
+
|
|
86
|
+
Commits stay conventional (`feat(platform): Prefer a player's common name`) and carry **no
|
|
87
|
+
issue key** - the branch already has it. See {%skill:git-commit%}.
|
|
@@ -15,6 +15,10 @@ continue in a fresh session. Two modes:
|
|
|
15
15
|
- **save** ("handoff", "wrap up") - write a handoff file.
|
|
16
16
|
- **resume** ("continue from the handoff") - read one and continue the work.
|
|
17
17
|
|
|
18
|
+
This skill is installed as `ethlete-handoff`, so under Claude Code the commands
|
|
19
|
+
are `/ethlete-handoff` and `/ethlete-handoff resume [name]` - there is no
|
|
20
|
+
`/handoff`. Name them that way whenever you tell the user what to run.
|
|
21
|
+
|
|
18
22
|
Handoff files live in `{%handoffDir%}/` (gitignored - they are personal,
|
|
19
23
|
ephemeral working state, not team docs).
|
|
20
24
|
|
|
@@ -16,17 +16,17 @@ paged queries, bearer auth, GraphQL, and a socket.io realtime client.
|
|
|
16
16
|
non-trivial query work.** This guide is the index plus the load-bearing facts, so you
|
|
17
17
|
don't re-derive them from source.
|
|
18
18
|
|
|
19
|
-
| Page | Covers
|
|
20
|
-
| ---------------------------------------------------------------------- |
|
|
21
|
-
| {%docsBaseUrl%}/query/ | Overview + the two-generations note
|
|
22
|
-
| {%docsBaseUrl%}/query/queries | **Start here** - client, creators, the query object's signals, auto-execution
|
|
23
|
-
| {%docsBaseUrl%}/query/features | `withArgs`, `withPolling`, `withAutoRefresh`, side-effect handlers
|
|
24
|
-
| {%docsBaseUrl%}/query/http | REST creators, typing requests, response transforms, upload progress
|
|
25
|
-
| {%docsBaseUrl%}/query/auth | Bearer auth: login/refresh, auto token refresh, multi-tab sync
|
|
26
|
-
| {%docsBaseUrl%}/query/caching · `/stacks` · `/errors` · `/gql` · `/ws` | Caching/dedup, pagination, error/retry, GraphQL, WebSockets
|
|
27
|
-
| {%docsBaseUrl%}/query/multi-tab | Opt-in cross-tab sync: shared responses, per-key polling election, mutation fan-out
|
|
28
|
-
| {%docsBaseUrl%}/query/query-forms | Router-synced filter/search forms
|
|
29
|
-
| {%docsBaseUrl%}/query/legacy | The maintenance-mode `V2QueryClient`
|
|
19
|
+
| Page | Covers |
|
|
20
|
+
| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
|
|
21
|
+
| {%docsBaseUrl%}/query/ | Overview + the two-generations note |
|
|
22
|
+
| {%docsBaseUrl%}/query/queries | **Start here** - client, creators, the query object's signals, auto-execution |
|
|
23
|
+
| {%docsBaseUrl%}/query/features | `withArgs`, `withPolling`, `withLongPolling`, `withAutoRefresh`, side-effect handlers |
|
|
24
|
+
| {%docsBaseUrl%}/query/http | REST creators, typing requests, response transforms, upload progress |
|
|
25
|
+
| {%docsBaseUrl%}/query/auth | Bearer auth: login/refresh, auto token refresh, multi-tab sync |
|
|
26
|
+
| {%docsBaseUrl%}/query/caching · `/stacks` · `/errors` · `/gql` · `/ws` | Caching/dedup, pagination, error/retry, GraphQL, WebSockets |
|
|
27
|
+
| {%docsBaseUrl%}/query/multi-tab | Opt-in cross-tab sync: shared responses, per-key polling election, mutation fan-out |
|
|
28
|
+
| {%docsBaseUrl%}/query/query-forms | Router-synced filter/search forms |
|
|
29
|
+
| {%docsBaseUrl%}/query/legacy | The maintenance-mode `V2QueryClient` |
|
|
30
30
|
|
|
31
31
|
## Two generations - use the current one
|
|
32
32
|
|
|
@@ -77,9 +77,19 @@ raw `toObservable`). It emits `null` first - `pipe(filter(r => r !== null))`.
|
|
|
77
77
|
- **`withArgs(() => ({ pathParams, queryParams, body }))`** - runs like a `computed`;
|
|
78
78
|
re-runs when a signal it reads changes and re-executes the query. This is how you
|
|
79
79
|
drive **search-as-you-type**: back it with a search signal
|
|
80
|
-
(`withArgs(() => ({ queryParams: { search: this.search() } }))`). Return
|
|
81
|
-
|
|
80
|
+
(`withArgs(() => ({ queryParams: { search: this.search() } }))`). Return `null`
|
|
81
|
+
to park the query - args reset to `null`, pausing polling/auto-refresh.
|
|
82
|
+
- **Prefer `withArgs` over passing `args` to `execute()`.** Args declared on the query
|
|
83
|
+
stay reactive: a `GET` re-executes itself when they change, and `withPolling` /
|
|
84
|
+
`withAutoRefresh` restart off the same signal - none of which happens for args handed
|
|
85
|
+
to `execute()`. A function route additionally throws without it. With `withArgs` in
|
|
86
|
+
place a mutation is just `.execute()`, which reuses the current `args()`. Reserve
|
|
87
|
+
`execute({ args })` for a one-off payload no signal holds (a form submit).
|
|
82
88
|
- `withPolling({ interval })`, `withAutoRefresh({ onSignalChanges: [...] })`.
|
|
89
|
+
- **`withLongPolling({ nextArgs })`** for a completion-driven chain instead of an interval: each
|
|
90
|
+
round starts once the previous settled, with args (a cursor) derived from its response. `nextArgs`
|
|
91
|
+
returning `null` ends the chain. Not `withPolling` with a small interval - and the two throw when
|
|
92
|
+
combined.
|
|
83
93
|
- Side-effects: `withSuccessHandling`, `withErrorHandling`, `withLogging`.
|
|
84
94
|
|
|
85
95
|
There is no built-in debounce operator - dedup/caching handles repeated identical
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: rxjs-signals
|
|
3
|
-
description: How to choose between signals and RxJS, and use each correctly - synchronous state vs asynchronous work, unsubscribing, and avoiding RxJS inside effects/computeds. Read when adding reactive state, wiring up an observable, or deciding whether something should be a signal or a stream.
|
|
3
|
+
description: How to choose between signals and RxJS, and use each correctly - synchronous state vs asynchronous work, unsubscribing, and avoiding RxJS inside effects/computeds. Read when adding reactive state, wiring up an observable, or deciding whether something should be a signal or a stream.
|
|
4
4
|
kind: skill
|
|
5
5
|
scope: both
|
|
6
6
|
requires: ['@ethlete/core']
|
|
@@ -43,8 +43,15 @@ Component domains under `/components/`:
|
|
|
43
43
|
`tabs` `text-inputs` `time-picker` `toggletip` `tooltip`
|
|
44
44
|
|
|
45
45
|
So the table guide is `{%docsBaseUrl%}/components/table`, the menu guide
|
|
46
|
-
`{%docsBaseUrl%}/components/menu`, and so on.
|
|
47
|
-
|
|
46
|
+
`{%docsBaseUrl%}/components/menu`, and so on.
|
|
47
|
+
|
|
48
|
+
That list is a snapshot. The site itself is machine-readable, so fetch rather than guess:
|
|
49
|
+
|
|
50
|
+
- `{%docsBaseUrl%}/llms.txt` - every page's title and path as of the last deploy. Use it
|
|
51
|
+
when a name isn't in the list above, or to check the list hasn't drifted.
|
|
52
|
+
- Append `.md` to any page URL - `{%docsBaseUrl%}/components/button.md` - to get raw
|
|
53
|
+
markdown instead of the rendered page. Prefer this over `/llms-full.txt`, which is the
|
|
54
|
+
entire site in one ~1 MB file.
|
|
48
55
|
|
|
49
56
|
## How to use them
|
|
50
57
|
|
|
@@ -68,3 +75,4 @@ So the table guide is `{%docsBaseUrl%}/components/table`, the menu guide
|
|
|
68
75
|
|
|
69
76
|
- Data fetching has its own guide: {%skill:query%}
|
|
70
77
|
- Theming tokens and how to register themes: {%skill:theming%}
|
|
78
|
+
- When the docs cannot answer it, read the SDK source: {%skill:sdk-source%}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sdk-local-build
|
|
3
|
+
description: Build the @ethlete SDK from a local ethlete-sdk checkout and install it into this repo through a `file:` dependency, so an unreleased SDK change can be tested against this app before it is published - and reverted cleanly afterwards. Read whenever an SDK-side fix needs verifying here, or when package.json already points at a local build.
|
|
4
|
+
kind: skill
|
|
5
|
+
scope: consumer
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Testing an unreleased SDK build in this repo
|
|
9
|
+
|
|
10
|
+
Swapping an `@ethlete/*` dependency for a locally built one lets you verify an SDK
|
|
11
|
+
change against this app before it ships. It is a **temporary, local-only** state: the
|
|
12
|
+
`file:` specifier and the lockfile entry it produces must never be committed.
|
|
13
|
+
|
|
14
|
+
Prefer a published prerelease when one exists - installing `@ethlete/components@next`
|
|
15
|
+
is faster and reproducible for the whole team. Use a local build when the change is not
|
|
16
|
+
published yet, or when you are iterating on it.
|
|
17
|
+
|
|
18
|
+
## 1. Prerequisites
|
|
19
|
+
|
|
20
|
+
- The checkout path comes from `sdkSourcePath` in `ethlete-agents.config.local.json`;
|
|
21
|
+
{%skill:sdk-source%} covers resolving it and what to check before trusting it.
|
|
22
|
+
- The checkout has its dependencies installed (`yarn install` in the checkout root - the
|
|
23
|
+
SDK repo is a Yarn 4 workspace).
|
|
24
|
+
- Check which branch it is on before building - unless the user said otherwise, that
|
|
25
|
+
should be `next`, up to date with `origin/next`. Building `main` when your app runs
|
|
26
|
+
`-next` prereleases swaps in a completely different API surface, and building a stale
|
|
27
|
+
`next` rebuilds a bug that is already fixed upstream. Ask before changing its branch.
|
|
28
|
+
|
|
29
|
+
## 2. Build the libraries you changed
|
|
30
|
+
|
|
31
|
+
From the **checkout root**, one build per `@ethlete/*` package whose source you touched:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx nx build components # writes dist/libs/components
|
|
35
|
+
npx nx build query # writes dist/libs/query
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Each build also builds the libs it depends on (`types` → `core` → `query` →
|
|
39
|
+
`components`), so a single command is enough to produce a consistent set. Only the
|
|
40
|
+
packages you actually changed need to be installed here; leave the rest on their
|
|
41
|
+
published versions.
|
|
42
|
+
|
|
43
|
+
If a build stalls trying to reach Nx Cloud, re-run it with `NX_NO_CLOUD=true`.
|
|
44
|
+
|
|
45
|
+
## 3. Point this repo at the build
|
|
46
|
+
|
|
47
|
+
Edit the version specifiers in `package.json` (the one declaring the dependency - in a
|
|
48
|
+
workspace that is the workspace package, not necessarily the root):
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@ethlete/components": "file:../ethlete-sdk/dist/libs/components"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The path is resolved relative to that `package.json`; an absolute path works too. Then
|
|
59
|
+
install:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
yarn install
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Expect peer-dependency warnings - the built package pins peers to the SDK's own Angular
|
|
66
|
+
version. They are warnings, not failures; a real version conflict shows up as a build
|
|
67
|
+
error, and means the checkout is on the wrong branch.
|
|
68
|
+
|
|
69
|
+
## 4. Confirm the local build is really what got installed
|
|
70
|
+
|
|
71
|
+
`yarn install` is the only thing that copies the build into `node_modules`, so verifying
|
|
72
|
+
is not optional - a stale package looks exactly like a change that did not work:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
grep -m1 '"version"' node_modules/@ethlete/components/package.json
|
|
76
|
+
rg -n "<a symbol from your change>" node_modules/@ethlete/components/fesm2022/
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Then **restart the dev server**. Bundlers pre-bundle dependencies and will keep serving
|
|
80
|
+
the old copy; if the change still does not show up, delete `.angular/cache` (and
|
|
81
|
+
`node_modules/.vite` if present) and start it again.
|
|
82
|
+
|
|
83
|
+
## 5. Iterating
|
|
84
|
+
|
|
85
|
+
Every SDK edit needs the full loop - there is no watch mode across the boundary:
|
|
86
|
+
|
|
87
|
+
1. rebuild in the checkout (`npx nx build <lib>`)
|
|
88
|
+
2. `yarn install` here
|
|
89
|
+
3. restart the dev server
|
|
90
|
+
|
|
91
|
+
Yarn 4 re-copies a `file:` dependency whenever its contents change, so step 2 does pick
|
|
92
|
+
up the rebuild. It also rewrites that package's `resolution` hash in `yarn.lock` on
|
|
93
|
+
every rebuild - which is one more reason the lockfile must not be committed in this
|
|
94
|
+
state. (With npm, `file:` symlinks instead of copying, so a rebuild is picked up without
|
|
95
|
+
reinstalling; the restart in step 3 is still required.)
|
|
96
|
+
|
|
97
|
+
## 6. Clean up when you are done
|
|
98
|
+
|
|
99
|
+
Leaving a `file:` dependency behind breaks every other checkout and CI, because the path
|
|
100
|
+
does not exist there. Restore it as part of the same task, not later:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
git checkout package.json # or hand-restore the original version specifier
|
|
104
|
+
yarn install
|
|
105
|
+
git status --short # package.json and yarn.lock must both be clean
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Never commit a `file:` specifier or the lockfile it produced. If the verified fix is
|
|
109
|
+
still unreleased, say what has to be published (which lib, which version) instead of
|
|
110
|
+
shipping a local path.
|
|
111
|
+
|
|
112
|
+
## Related
|
|
113
|
+
|
|
114
|
+
- Finding and reading the checkout: {%skill:sdk-source%}
|
|
115
|
+
- What the published packages document: {%skill:sdk-docs%}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sdk-source
|
|
3
|
+
description: How to read the @ethlete SDK's own source from a local ethlete-sdk checkout when the docs and the installed types are not enough. Read when you need an implementation detail, the exact behaviour behind a bug, or an API the docs do not cover - and never edit that checkout as part of work in this repo.
|
|
4
|
+
kind: skill
|
|
5
|
+
scope: consumer
|
|
6
|
+
vars: [docsBaseUrl]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Reading the @ethlete SDK source
|
|
10
|
+
|
|
11
|
+
The SDK is developed in a separate repository (`ethlete-sdk`). Most questions are
|
|
12
|
+
answered faster and more reliably by the documentation - read {%skill:sdk-docs%}
|
|
13
|
+
first. Reach for the source when the docs genuinely cannot answer the question:
|
|
14
|
+
|
|
15
|
+
- a behaviour looks like an SDK bug and you need to see what the code actually does
|
|
16
|
+
- you need an implementation detail the guides omit (event order, internal defaults,
|
|
17
|
+
which host directive writes which attribute)
|
|
18
|
+
- the installed version is ahead of - or behind - the published docs and you have to
|
|
19
|
+
confirm what the code in _this_ version does
|
|
20
|
+
- you are about to report or fix something in the SDK itself
|
|
21
|
+
|
|
22
|
+
## 1. Resolve the checkout
|
|
23
|
+
|
|
24
|
+
The path is per machine, so it lives in the gitignored `ethlete-agents.config.local.json`
|
|
25
|
+
at the repo root:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"sdkSourcePath": "/absolute/path/to/ethlete-sdk"
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Read that file before searching anywhere. A relative path is resolved from the repo root.
|
|
34
|
+
|
|
35
|
+
If the file or the key is missing, **do not guess a path** and do not clone the
|
|
36
|
+
repository. Fall back to the installed package - `node_modules/@ethlete/<lib>/types/`
|
|
37
|
+
holds the full `.d.ts` surface of exactly the version this repo runs - and to
|
|
38
|
+
{%docsBaseUrl%}. Then tell the user a local checkout would help, and offer the snippet
|
|
39
|
+
above (the file is gitignored, so adding it changes nothing for anyone else).
|
|
40
|
+
|
|
41
|
+
## 2. Check the branch before you read anything
|
|
42
|
+
|
|
43
|
+
A checkout sits on whatever branch the developer left it on, so the first command you
|
|
44
|
+
run against it is the one that tells you what you are looking at:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
git -C <sdkSourcePath> fetch --quiet # read-only, safe
|
|
48
|
+
git -C <sdkSourcePath> status -sb # branch, ahead/behind, dirty files
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Unless the user says otherwise, the expected state is `next`, up to date with
|
|
52
|
+
`origin/next`.** That is the branch the SDK develops on, and the one the `-next`
|
|
53
|
+
prereleases are cut from. Anything else and you are reading a different SDK than the
|
|
54
|
+
one you are about to describe.
|
|
55
|
+
|
|
56
|
+
When it is not in that state, **say so and ask** - never switch, pull, stash or reset
|
|
57
|
+
it yourself. It is someone's working tree, and the read-only rule below applies:
|
|
58
|
+
|
|
59
|
+
- **On another branch** - name it and ask whether to read it as-is or whether they want
|
|
60
|
+
`next`. A feature branch may be exactly what you were sent to look at.
|
|
61
|
+
- **Behind `origin/next`** - report how far. The code you would quote may already be
|
|
62
|
+
fixed upstream, so read the missing commits before calling anything a bug:
|
|
63
|
+
`git -C <sdkSourcePath> log --oneline HEAD..origin/next`.
|
|
64
|
+
- **Dirty** - it may contain someone's work in progress. Say so rather than quoting it
|
|
65
|
+
as SDK behaviour.
|
|
66
|
+
|
|
67
|
+
## 3. Check the checkout matches what is installed
|
|
68
|
+
|
|
69
|
+
Even on a clean `next`, the checkout can be months of work ahead of the installed
|
|
70
|
+
package - or behind it:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
grep '"@ethlete/' package.json # what this repo runs
|
|
74
|
+
grep -m1 '"version"' <sdkSourcePath>/libs/<lib>/package.json # what the checkout is at
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Rules when they differ:
|
|
78
|
+
|
|
79
|
+
- **The installed package wins** for anything about how this repo behaves today. The
|
|
80
|
+
`.d.ts` in `node_modules` is the truth about the API you are calling.
|
|
81
|
+
- Source that is ahead describes an **unreleased** API. Never write consumer code
|
|
82
|
+
against it, and never assume it is available - say what release it needs.
|
|
83
|
+
|
|
84
|
+
## 4. Where things live
|
|
85
|
+
|
|
86
|
+
Paths are relative to the checkout root:
|
|
87
|
+
|
|
88
|
+
| Path | What is in it |
|
|
89
|
+
| ----------------------------------- | --------------------------------------------------------------------------- |
|
|
90
|
+
| `libs/components/src/lib/<domain>/` | The active UI library, one folder per domain (`button`, `menu`, `table`, …) |
|
|
91
|
+
| `libs/core/src/lib/` | Framework primitives: directives, signal utils, overlay runtime, theming |
|
|
92
|
+
| `libs/query/src/lib/` | Data fetching: `http`, `gql`, `ws`, auth, query-form |
|
|
93
|
+
| `libs/types/src/lib/` | Shared types |
|
|
94
|
+
| `libs/cdk/` | The predecessor UI toolkit, maintenance mode - only for code still on it |
|
|
95
|
+
| `libs/eslint-plugin/src/` | The lint rules, including the message text explaining each one |
|
|
96
|
+
| `apps/docs/` | The markdown behind {%docsBaseUrl%} |
|
|
97
|
+
| `apps/storybook/` | The Storybook app - stories also live next to each component |
|
|
98
|
+
|
|
99
|
+
Inside a component domain: `<name>.component.ts` with its `.css` next to it,
|
|
100
|
+
`<name>.imports.ts` (the imports array to spread into a consumer component),
|
|
101
|
+
`headless/` for the unstyled directives, `stories/` for the Storybook stories, and
|
|
102
|
+
`index.ts` as the barrel. The lib's public surface is `libs/<lib>/src/index.ts` -
|
|
103
|
+
anything not re-exported from there is internal, whatever it looks like.
|
|
104
|
+
|
|
105
|
+
## 5. Search it, don't read it whole
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
rg -n "etButton" <sdkSourcePath>/libs/components/src --glob '!*.spec.ts' # a selector
|
|
109
|
+
rg -n "export const OVERLAY" <sdkSourcePath>/libs/core/src # an export
|
|
110
|
+
rg -n "menu" <sdkSourcePath>/apps/docs/components # the guide source
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Specs are the cheapest description of intended behaviour - `<name>.component.spec.ts`
|
|
114
|
+
next to a component usually answers "is this supposed to happen?" faster than the
|
|
115
|
+
implementation does.
|
|
116
|
+
|
|
117
|
+
## 6. The checkout is read-only from here
|
|
118
|
+
|
|
119
|
+
It is a different repository with its own branch, lint, docs and changeset workflow.
|
|
120
|
+
Never edit it while working on a task in this repo - that covers its git state too, so
|
|
121
|
+
no `checkout`, `pull`, `stash` or `reset` without the user asking for it (`fetch` is
|
|
122
|
+
fine). Never copy its internals into consumer code either - a private helper is not a
|
|
123
|
+
supported API and disappears without a major version (the same goes for anything under
|
|
124
|
+
a `subtle` namespace).
|
|
125
|
+
|
|
126
|
+
When the fix belongs in the SDK, say so and describe it precisely: file, symbol, and
|
|
127
|
+
the behaviour it should have. If the user wants that fix verified against this app
|
|
128
|
+
before it ships, that is {%skill:sdk-local-build%}.
|
|
129
|
+
|
|
130
|
+
## Related
|
|
131
|
+
|
|
132
|
+
- Docs and Storybook, which come first: {%skill:sdk-docs%}
|
|
133
|
+
- Testing an unreleased SDK build here: {%skill:sdk-local-build%}
|
|
@@ -37,7 +37,7 @@ Run your lint task with `--fix` - the rules below are enforced (and mostly auto-
|
|
|
37
37
|
| Max two function parameters | `max-params` |
|
|
38
38
|
| No `import type` / inline `type` specifiers | `ethlete/no-type-only-import` |
|
|
39
39
|
| Generic params `T`-prefixed (`TValue`), never bare `T` | `@typescript-eslint/naming-convention` |
|
|
40
|
-
| No `async`/`await` - use RxJS | `no-
|
|
40
|
+
| No `async`/`await` - use RxJS | `ethlete/no-async-await` |
|
|
41
41
|
| Arrow fns standalone; methods in classes; no arrow-fn class props; no `function` keyword | `no-restricted-syntax` |
|
|
42
42
|
| Blank line before `return` in multi-line guard clauses | `ethlete/guard-return-newline` |
|
|
43
43
|
| No trivially-inferable explicit return types | `ethlete/no-trivial-return-type` |
|
|
@@ -463,7 +463,7 @@ settings-form/
|
|
|
463
463
|
- **Do not** create a changeset for irrelevant changes (e.g., formatting, comments, internal refactoring).
|
|
464
464
|
- **Do not** create a changeset for fixes to features that have not yet been released.
|
|
465
465
|
- **Do not** include multiple changes in a single changeset. Each changeset should contain only one change.
|
|
466
|
-
- **
|
|
466
|
+
- **A changeset note is a TL;DR: one sentence, two at most, under 40 words.** Never a second paragraph, no matter how much work the change took. It is the line a consumer skims to decide whether the release affects them - not a summary of your work. Mechanism, API inventories, caveats and rationale belong in the docs or the commit body, never here.
|
|
467
467
|
- Create changesets for dependency updates if they are relevant to the project (e.g., major version updates).
|
|
468
468
|
- Write changesets in the imperative mood. For example:
|
|
469
469
|
- Add button component
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: theming
|
|
3
|
-
description: The two runtime theming systems in @ethlete/core - surface theming (elevation-aware neutrals) and color theming (semantic accent palettes)
|
|
3
|
+
description: The two runtime theming systems in @ethlete/core - surface theming (elevation-aware neutrals) and color theming (semantic accent palettes). Read BEFORE writing or reviewing CSS involving color, background, border, or interaction state, and when wiring theme context across overlay/portal boundaries.
|
|
4
4
|
kind: skill
|
|
5
5
|
scope: consumer
|
|
6
6
|
requires: ['@ethlete/core']
|
|
@@ -61,12 +61,12 @@ Surface (each exists as `-solid` = usable color, `-rgb` = `R G B` channels):
|
|
|
61
61
|
|
|
62
62
|
Color (from the nearest `[etProvideColor]` scope):
|
|
63
63
|
|
|
64
|
-
| Token | Use for
|
|
65
|
-
| -------------------------------- |
|
|
66
|
-
| `--et-theme-color-primary` | filled backgrounds
|
|
67
|
-
| `--et-theme-color-primary-solid` | accents at full strength: focus borders, selected marks, spinners
|
|
68
|
-
| `--et-theme-color-on-primary` | text/icons on a primary-filled background
|
|
69
|
-
| `--et-theme-color-ink-solid` | primary-tinted text/border on transparent/tonal fills
|
|
64
|
+
| Token | Use for |
|
|
65
|
+
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
66
|
+
| `--et-theme-color-primary` | filled backgrounds on an element you do **not** tint. To tint per state, set `--et-theme-color-primary-opacity: 0.16` and compose the color yourself: `rgb(var(--et-theme-color-primary-rgb) / var(--et-theme-color-primary-opacity))`. The alias is substituted at the `et-color--*` scope, so an inherited value carries the scope's opacity, never the element's |
|
|
67
|
+
| `--et-theme-color-primary-solid` | accents at full strength: focus borders, selected marks, spinners |
|
|
68
|
+
| `--et-theme-color-on-primary` | text/icons on a primary-filled background |
|
|
69
|
+
| `--et-theme-color-ink-solid` | primary-tinted text/border on transparent/tonal fills |
|
|
70
70
|
|
|
71
71
|
Interaction-state variants (`--et-surface-interaction-{hover,focus,active,disabled}-solid`)
|
|
72
72
|
resolve automatically per CSS state when the element has `[etSurfaceInteractive]`;
|
|
@@ -96,6 +96,13 @@ the interactive element itself, never on a wrapper.
|
|
|
96
96
|
declared via `@property` can never default to a theme token. If the default should
|
|
97
97
|
come from the theme, don't declare an `@property` - consume the theme token directly
|
|
98
98
|
(optionally behind a `--_et-*` indirection var).
|
|
99
|
+
- `@property` `initial-value` also cannot use a **font-relative or container unit**
|
|
100
|
+
(`em`, `rem`, `ex`, `ch`, `lh`, `cap`, `ic`, `cq*`). The value must be computationally
|
|
101
|
+
independent, so the browser drops the whole rule and leaves the token unregistered -
|
|
102
|
+
with no error. Percentages and viewport units (`vh`, `vw`, `dvh`) are fine. When the
|
|
103
|
+
default has to be relative, use `syntax: '*'` with no `initial-value` and put the
|
|
104
|
+
default in a fallback at each use site: `var(--et-skeleton-size, 1em)`.
|
|
105
|
+
`yarn lint:css-properties` checks this, and pre-commit runs it on staged files.
|
|
99
106
|
- `--et-theme-color-primary-*` always resolves to the **nearest color scope**. A
|
|
100
107
|
hardcoded semantic color in CSS can't be replaced by it unless the right theme is
|
|
101
108
|
provided on that element.
|