@ethlete/agent-rules 0.1.0-next.3 → 0.1.0-next.30
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/AGENTS.md +28 -0
- package/CHANGELOG.md +463 -0
- package/CLAUDE.md +1 -0
- package/README.md +362 -29
- package/THIRD-PARTY-LICENSES.md +35 -0
- package/content/defaults.json +1 -1
- package/content/git-hooks/post-checkout.sh +10 -0
- package/content/git-hooks/pre-push.sh +5 -0
- package/content/hooks/context-warning.py +871 -110
- package/content/hooks/subagent-model-policy.py +194 -0
- package/content/mods/.claude-plugin/marketplace.json +11 -0
- package/content/mods/plugins/e2e-progress/.claude-plugin/plugin.json +6 -0
- package/content/mods/plugins/e2e-progress/hooks/hooks.json +1 -0
- package/content/mods/plugins/e2e-progress/hooks/register.tsx +442 -0
- package/content/mods/plugins/e2e-progress/tsconfig.json +3 -0
- package/content/mods/plugins/e2e-progress/types/index.d.ts +23 -0
- package/content/output-styles/ste-clarity.md +131 -0
- package/content/rules/agent-sessions-wake-up.md +11 -0
- package/content/rules/app-styling.md +66 -0
- package/content/rules/comments.md +4 -1
- package/content/rules/lint-and-format.md +7 -5
- package/content/rules/nx-layout.md +50 -0
- package/content/rules/reactive-state.md +1 -1
- package/content/rules/styling.md +9 -7
- package/content/rules/subagent-models.md +39 -0
- package/content/skills/agent-sessions/SKILL.md +81 -0
- package/content/skills/agent-sessions/agent-sessions.mjs +492 -0
- package/content/skills/angular-patterns/SKILL.md +30 -7
- package/content/skills/api-source/SKILL.md +156 -0
- package/content/skills/app-testing/SKILL.md +141 -0
- package/content/skills/codex-subagent/SKILL.md +90 -0
- package/content/skills/codex-subagent/codex-agent.mjs +229 -0
- package/content/skills/design-exploration/SKILL.md +657 -0
- package/content/skills/design-exploration/check-story.mjs +139 -0
- package/content/skills/design-exploration/shoot-template.mjs +61 -0
- package/content/skills/domain-modeling/ADR-FORMAT.md +47 -0
- package/content/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/content/skills/domain-modeling/SKILL.md +80 -0
- package/content/skills/e2e-triage/SKILL.md +80 -0
- package/content/skills/figma-export/SKILL.md +191 -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 +11 -7
- package/content/skills/git-flow/SKILL.md +87 -0
- package/content/skills/grill-with-docs/SKILL.md +13 -0
- package/content/skills/grilling/SKILL.md +51 -0
- package/content/skills/handoff/SKILL.md +125 -5
- package/content/skills/query/SKILL.md +165 -64
- package/content/skills/rxjs-signals/SKILL.md +33 -4
- package/content/skills/sdk-docs/SKILL.md +50 -14
- package/content/skills/sdk-local-build/SKILL.md +26 -8
- package/content/skills/sdk-local-build/sdk-local-baseline.mjs +64 -0
- package/content/skills/sdk-source/SKILL.md +37 -34
- package/content/skills/sdk-update/SKILL.md +103 -0
- package/content/skills/story-styling/SKILL.md +10 -8
- package/content/skills/styleguide/SKILL.md +12 -7
- package/content/skills/styleguide/assets.md +54 -0
- package/content/skills/styleguide/changesets.md +17 -0
- package/content/skills/styleguide/file-structure.md +73 -0
- package/content/skills/styleguide/lint-rule-lookup.md +51 -0
- package/content/skills/styleguide/storybook-structure.md +25 -0
- package/content/skills/theming/SKILL.md +50 -28
- package/content/skills/timetrack/SKILL.md +397 -0
- package/content/skills/verify-in-app/SKILL.md +107 -0
- package/migrations/app-styling-utilities.md +63 -0
- package/migrations/list-state-query-form.md +20 -0
- package/migrations/nx-layout.md +29 -0
- package/migrations/sdk-components-over-hand-built-ui.md +27 -0
- package/migrations/search-query-field.md +52 -0
- package/migrations.json +48 -0
- package/package.json +19 -3
- package/src/index.js +85 -30
- package/src/index.js.map +1 -1
- package/src/lib/cli-args.d.ts +9 -0
- package/src/lib/cli-args.js +48 -0
- package/src/lib/cli-args.js.map +1 -0
- 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 +41 -5
- package/src/lib/config.js +117 -10
- package/src/lib/config.js.map +1 -1
- package/src/lib/filter.d.ts +2 -0
- package/src/lib/filter.js +4 -4
- package/src/lib/filter.js.map +1 -1
- package/src/lib/frontmatter.d.ts +2 -0
- package/src/lib/frontmatter.js +33 -5
- package/src/lib/frontmatter.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 +23 -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 +300 -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 +165 -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 +165 -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 +140 -0
- package/src/lib/git-flow-start.js.map +1 -0
- package/src/lib/git.d.ts +64 -0
- package/src/lib/git.js +131 -0
- package/src/lib/git.js.map +1 -0
- package/src/lib/gitlab.d.ts +35 -0
- package/src/lib/gitlab.js +116 -0
- package/src/lib/gitlab.js.map +1 -0
- package/src/lib/index.d.ts +2 -0
- package/src/lib/index.js +26 -11
- package/src/lib/index.js.map +1 -1
- package/src/lib/migrate.js +6 -1
- package/src/lib/migrate.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 +134 -0
- package/src/lib/output-style.js.map +1 -0
- package/src/lib/owned-paths.js +27 -3
- package/src/lib/owned-paths.js.map +1 -1
- package/src/lib/package-runner.d.ts +4 -0
- package/src/lib/package-runner.js +86 -0
- package/src/lib/package-runner.js.map +1 -0
- package/src/lib/plain-text.d.ts +9 -0
- package/src/lib/plain-text.js +22 -0
- package/src/lib/plain-text.js.map +1 -0
- package/src/lib/plan.d.ts +6 -6
- package/src/lib/plan.js +164 -40
- package/src/lib/plan.js.map +1 -1
- package/src/lib/prompt.d.ts +8 -0
- package/src/lib/prompt.js +35 -0
- package/src/lib/prompt.js.map +1 -0
- package/src/lib/render.d.ts +25 -2
- package/src/lib/render.js +43 -10
- package/src/lib/render.js.map +1 -1
- package/src/lib/sessions-command.d.ts +4 -0
- package/src/lib/sessions-command.js +16 -0
- package/src/lib/sessions-command.js.map +1 -0
- package/src/lib/sync.d.ts +0 -1
- package/src/lib/sync.js +30 -17
- package/src/lib/sync.js.map +1 -1
- package/src/lib/targets/agents-skills.js +1 -1
- package/src/lib/targets/agents-skills.js.map +1 -1
- package/src/lib/targets/claude-hooks.d.ts +3 -23
- package/src/lib/targets/claude-hooks.js +22 -86
- package/src/lib/targets/claude-hooks.js.map +1 -1
- package/src/lib/targets/claude-mods.d.ts +20 -0
- package/src/lib/targets/claude-mods.js +47 -0
- package/src/lib/targets/claude-mods.js.map +1 -0
- package/src/lib/targets/claude.js +2 -3
- 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 +35 -0
- package/src/lib/targets/codex-hooks.js.map +1 -0
- package/src/lib/targets/codex.js +4 -4
- package/src/lib/targets/codex.js.map +1 -1
- package/src/lib/targets/copilot.js +4 -4
- package/src/lib/targets/copilot.js.map +1 -1
- package/src/lib/targets/cursor.js +3 -3
- package/src/lib/targets/cursor.js.map +1 -1
- package/src/lib/targets/git-hooks.d.ts +25 -0
- package/src/lib/targets/git-hooks.js +71 -0
- package/src/lib/targets/git-hooks.js.map +1 -0
- package/src/lib/targets/hooks-shared.d.ts +60 -0
- package/src/lib/targets/hooks-shared.js +130 -0
- package/src/lib/targets/hooks-shared.js.map +1 -0
- package/src/lib/targets/mcp.d.ts +16 -0
- package/src/lib/targets/mcp.js +68 -0
- package/src/lib/targets/mcp.js.map +1 -0
- package/src/lib/targets/shared.d.ts +1 -9
- package/src/lib/targets/shared.js +4 -8
- package/src/lib/targets/shared.js.map +1 -1
- package/src/lib/timetrack-command.d.ts +11 -0
- package/src/lib/timetrack-command.js +1063 -0
- package/src/lib/timetrack-command.js.map +1 -0
- package/src/lib/timetrack-snapshot.d.ts +55 -0
- package/src/lib/timetrack-snapshot.js +120 -0
- package/src/lib/timetrack-snapshot.js.map +1 -0
- package/src/lib/timetrack.d.ts +561 -0
- package/src/lib/timetrack.js +279 -0
- package/src/lib/timetrack.js.map +1 -0
- package/content/skills/styleguide/STYLEGUIDE.md +0 -520
|
@@ -15,11 +15,92 @@ 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
|
+
Text after the slug or the name is an instruction from the user, in both modes. In save mode it is
|
|
19
|
+
for the session that resumes, so it must reach that session word for word. In resume mode it wins
|
|
20
|
+
over the Next steps of the file where the two conflict.
|
|
21
|
+
|
|
22
|
+
Name one invocation form to the user - your own - and never both. The skill is
|
|
23
|
+
installed as `ethlete-handoff`, so under Claude Code the commands are
|
|
24
|
+
`/ethlete-handoff` and `/ethlete-handoff resume [name]`; there is no `/handoff`.
|
|
25
|
+
Codex has no slash commands, so there the same two are plain prompts without the
|
|
26
|
+
leading `/`.
|
|
27
|
+
|
|
18
28
|
Handoff files live in `{%handoffDir%}/` (gitignored - they are personal,
|
|
19
29
|
ephemeral working state, not team docs).
|
|
20
30
|
|
|
31
|
+
## Handoff mode
|
|
32
|
+
|
|
33
|
+
Where this repo installs the `context-warning` hook, it names the budget at session start,
|
|
34
|
+
turns handoff mode on at 70% of it, and repeats a short note with the remaining budget on
|
|
35
|
+
every prompt after that. It reaches you at three points, not only when the user types: at
|
|
36
|
+
the start of the session, after a batch of your own tool calls, and as a turn ends. So a
|
|
37
|
+
long autonomous run learns its token count while it works.
|
|
38
|
+
|
|
39
|
+
Handoff mode is not an order to stop. It is the signal to change how you work, so that
|
|
40
|
+
either ending - you finish, or a fresh session takes over - stays cheap:
|
|
41
|
+
|
|
42
|
+
- Work in slices that each end in a committable state. Never leave two half-finished edits
|
|
43
|
+
in the tree.
|
|
44
|
+
- Send bulky reads, searches and test runs to a sub-agent. Its output stays out of your
|
|
45
|
+
context; its answer does not.
|
|
46
|
+
- Write down each decision and dead end as you reach it, in the commit message or in the
|
|
47
|
+
handoff file. Do not save them for a final summary you may have no room for.
|
|
48
|
+
- Start nothing you cannot finish or hand off inside the remaining budget.
|
|
49
|
+
|
|
50
|
+
At the critical tier (85%) the hook asks you to choose one of two, and to tell the user
|
|
51
|
+
which you chose:
|
|
52
|
+
|
|
53
|
+
1. **Finish the task.** Only when you can name every step that is left, and they plainly
|
|
54
|
+
fit the remaining budget. Finish it, commit, then apply the save test below - the answer
|
|
55
|
+
is then usually that no file is needed at all.
|
|
56
|
+
2. **Hand off.** Everything else. Finish the in-flight atomic edit, nothing new, then save.
|
|
57
|
+
|
|
58
|
+
"Nearly done" means you can name the last steps now. It does not mean the end feels close.
|
|
59
|
+
At the last tier (95%) the hook takes option 1 away, because a budget that small covers no
|
|
60
|
+
task.
|
|
61
|
+
|
|
62
|
+
### Ending a turn over the budget
|
|
63
|
+
|
|
64
|
+
From 85% on, the hook also fires as you try to end the turn, and the turn then continues so
|
|
65
|
+
you can act. It adds a third option to the two above:
|
|
66
|
+
|
|
67
|
+
3. **Cross the boundary on purpose.** Some sessions are worth the premium rate: the task is
|
|
68
|
+
one step from done, or this session holds reasoning that no handoff file survives. Take
|
|
69
|
+
it only for a reason you can name in one sentence right now. Say the reason, say what it
|
|
70
|
+
costs, and keep working.
|
|
71
|
+
|
|
72
|
+
Option 3 is a judgment call, not a way out of the other two. Naming a reason you cannot
|
|
73
|
+
state plainly is the same as taking option 2 late and expensively. The hook asks once per
|
|
74
|
+
tier, so it will not block the end of a turn twice for the same budget. It stays quiet while a
|
|
75
|
+
handoff is being saved, until the user's next prompt.
|
|
76
|
+
|
|
21
77
|
## Save mode
|
|
22
78
|
|
|
79
|
+
**Decide first whether a handoff is needed at all.** One exists so work can resume, so
|
|
80
|
+
write one only when at least one of these holds:
|
|
81
|
+
|
|
82
|
+
- Uncommitted work from this session sits in the tree.
|
|
83
|
+
- A step the user asked for is unfinished, blocked, or waiting on a result.
|
|
84
|
+
- A decision, dead end, or user constraint from this session is written down nowhere else -
|
|
85
|
+
not in a commit message, a changeset, a docs page, or a plan file.
|
|
86
|
+
|
|
87
|
+
Check it, never assume it: `git status`, `git log`, and the task as the user stated it. A
|
|
88
|
+
context warning is a token count, not evidence that work is left.
|
|
89
|
+
|
|
90
|
+
If none of them holds, write no file. Tell the user in two or three lines what landed, name
|
|
91
|
+
the commits, and say a fresh session needs nothing from this one. A handoff nobody resumes is
|
|
92
|
+
noise, and it costs the next session the tokens to read it.
|
|
93
|
+
|
|
94
|
+
Then give a prompt the user can copy into the fresh session, in a code block: the next task, the
|
|
95
|
+
plan file or finding it lives in, and any rule of this session that the repo does not record. Keep
|
|
96
|
+
it to a few lines.
|
|
97
|
+
|
|
98
|
+
An open item counts as the next task: a known gap, an open finding, or an unbuilt step in the plan,
|
|
99
|
+
in the task, or in your own report of this session. "Everything waits for the user" is true only
|
|
100
|
+
when every open item needs a decision of the user. Then name each decision; do not stop silently.
|
|
101
|
+
In a `tmux` session, restart yourself with the next task as the prompt (see the next section) instead
|
|
102
|
+
of handing the prompt to the user.
|
|
103
|
+
|
|
23
104
|
Write `{%handoffDir%}/<slug>.md` where `<slug>` is a short kebab-case name for
|
|
24
105
|
the task (use the user-provided slug if they gave one). If the file exists,
|
|
25
106
|
overwrite it - a handoff always describes the _current_ state.
|
|
@@ -81,18 +162,57 @@ map, not a transcript.
|
|
|
81
162
|
After writing, tell the user where it landed and that a fresh session should
|
|
82
163
|
resume from it.
|
|
83
164
|
|
|
165
|
+
## Clear and resume on your own
|
|
166
|
+
|
|
167
|
+
Run inside a `tmux` session started with the agent-sessions `run` command or the shell functions from `shell-install` (`echo $TMUX` is not empty and
|
|
168
|
+
the agent-sessions script `restart` finds the session)? Then you may clear and
|
|
169
|
+
resume yourself after the save, instead of asking the user to. You decide. Do it only if every line
|
|
170
|
+
is true:
|
|
171
|
+
|
|
172
|
+
- No sub-agent, workflow or background command still runs. A background command from the old
|
|
173
|
+
session reports back into the new one, and a sub-agent answer has no reader after the clear.
|
|
174
|
+
- No answer you wait for is open: a peer session, a mailbox reply, a CI run.
|
|
175
|
+
- The tree is committed, or the handoff file names every uncommitted change.
|
|
176
|
+
- The handoff file is saved and its Next steps are concrete.
|
|
177
|
+
|
|
178
|
+
If one line is false, finish or collect that first. If you cannot, hand the prompt to the user as
|
|
179
|
+
before. Then run, with the slug of the file you saved:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
node <sessions-skill-dir>/agent-sessions.mjs restart <name> '<resume command> <slug> [<text after the slug>]'
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
If the user wrote text after the slug of the save command, append it to the prompt word for word.
|
|
186
|
+
Escape each `'` in it as `'\''`. The restart drops anything you leave out, and the fresh session has
|
|
187
|
+
no other way to see it.
|
|
188
|
+
|
|
189
|
+
`<resume command>` is your own resume form from the top of this guide, with the name this repo
|
|
190
|
+
installed the skill under: `/ethlete-handoff resume` under Claude Code, `ethlete-handoff resume`
|
|
191
|
+
under Codex. A repo that ships its own copy can name it `handoff`, so check the skill directory
|
|
192
|
+
name. A wrong name runs no skill, and the fresh session starts without the file.
|
|
193
|
+
|
|
194
|
+
`<name>` is the sender name of your own session. Read it first with `tmux display-message -p -t "$TMUX_PANE" '#{session_name}'` and drop the `@<repo>` part: `claude-2@ethlete-sdk` gives `claude-2`. Never take the name from an example: a second Claude session is `claude-2`, not `claude`. The command refuses a session you do not run in. It returns at once. It
|
|
195
|
+
types `/clear` (`/new` for a `codex` name) and the prompt into your pane a few seconds later, so
|
|
196
|
+
end your turn after you run it and do not start another tool call. The `/clear` wipes the pane, so
|
|
197
|
+
the user never sees text you write after the command. Before you run it, put the reason as the
|
|
198
|
+
first line of the handoff file: `Restarted because: <one line>`. End the turn with no text.
|
|
199
|
+
|
|
84
200
|
## Resume mode
|
|
85
201
|
|
|
86
202
|
1. List `{%handoffDir%}/`. Pick the file matching the given name, or the most
|
|
87
203
|
recently modified one if no name was given. If the directory is empty, say so
|
|
88
204
|
and stop.
|
|
89
|
-
2. Read the file fully.
|
|
205
|
+
2. Read the file fully. If its first line starts with `Restarted because:`, show that line to the
|
|
206
|
+
user first, then delete it from the file.
|
|
90
207
|
3. Verify reality still matches: current branch, `git status`, last commit. If
|
|
91
208
|
they diverge from the handoff (e.g. someone committed in between), say what
|
|
92
209
|
changed and adapt - the repo is the truth, the handoff is the guide.
|
|
93
|
-
4. Read
|
|
94
|
-
|
|
210
|
+
4. **Read only what you are about to change.** The handoff already carries the decisions,
|
|
211
|
+
the constraints and the dead ends, so do not re-read the plan files it summarises, and do
|
|
212
|
+
not survey the directory. Open a source file when you are going to edit it, and load a
|
|
213
|
+
focused guidance file only when the work needs one.
|
|
95
214
|
5. Continue with the **Next steps** section. Don't redo work listed under
|
|
96
215
|
_Done_; don't re-open questions under _Decisions_.
|
|
97
|
-
6. When every next step is complete
|
|
98
|
-
|
|
216
|
+
6. When every next step is complete, decide whether the handoff can be removed. Delete it
|
|
217
|
+
only if this workflow created it and the save workflow or user authorized cleanup.
|
|
218
|
+
Otherwise report that it is no longer needed and let the user decide.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: query
|
|
3
|
-
description: The signals-first @ethlete/query data-fetching system - the query client, typed query creators, reactive args, and reading results as signals or observables. Read BEFORE writing or reviewing code that fetches data, wires search/autocomplete to an API, adds auth
|
|
3
|
+
description: The signals-first @ethlete/query data-fetching system - the query client, typed query creators, reactive args, and reading results as signals or observables. Read BEFORE writing or reviewing code that fetches data, builds a list with filters/search/sort/paging bound to URL query params (defineQueryForm), wires search/autocomplete to an API, adds auth, route guards or polling, or bridges a query into UI or RxJS.
|
|
4
4
|
kind: skill
|
|
5
5
|
scope: consumer
|
|
6
6
|
requires: ['@ethlete/query']
|
|
@@ -13,38 +13,109 @@ Signals-first, typesafe data fetching for Angular: request dedup, caching, polli
|
|
|
13
13
|
paged queries, bearer auth, GraphQL, and a socket.io realtime client.
|
|
14
14
|
|
|
15
15
|
**The written docs are the source of truth - read the relevant page before
|
|
16
|
-
non-trivial query work.** This guide
|
|
17
|
-
don't re-derive them from source.
|
|
18
|
-
|
|
19
|
-
| Page
|
|
20
|
-
|
|
|
21
|
-
| {%docsBaseUrl%}/query/
|
|
22
|
-
| {%docsBaseUrl%}/query/queries
|
|
23
|
-
| {%docsBaseUrl%}/query/features
|
|
24
|
-
| {%docsBaseUrl%}/query/http
|
|
25
|
-
| {%docsBaseUrl%}/query/auth
|
|
26
|
-
| {%docsBaseUrl%}/query/caching
|
|
27
|
-
| {%docsBaseUrl%}/query/multi-tab
|
|
28
|
-
| {%docsBaseUrl%}/query/
|
|
29
|
-
| {%docsBaseUrl%}/query/
|
|
16
|
+
non-trivial query work.** This guide maps every need to its API and page, plus the
|
|
17
|
+
load-bearing facts, so you don't re-derive them from source or hand-build what ships.
|
|
18
|
+
|
|
19
|
+
| Page | Covers |
|
|
20
|
+
| ------------------------------------------------- | --------------------------------------------------------------------------------------- |
|
|
21
|
+
| {%docsBaseUrl%}/query/ | Overview, the two generations, what else the package ships |
|
|
22
|
+
| {%docsBaseUrl%}/query/queries | **Start here** - client, creators, the query object's signals, auto-execution |
|
|
23
|
+
| {%docsBaseUrl%}/query/features | `withArgs`, polling, auto-refresh, side-effect handlers, custom features |
|
|
24
|
+
| {%docsBaseUrl%}/query/http | REST creators, typing requests, response transforms, upload progress |
|
|
25
|
+
| {%docsBaseUrl%}/query/auth | Bearer auth provider, guards, token refresh, auth features |
|
|
26
|
+
| {%docsBaseUrl%}/query/caching | Cache keys, dedup, `keepUnusedFor`, freshness, refresh and invalidation |
|
|
27
|
+
| {%docsBaseUrl%}/query/multi-tab | Cross-tab response sharing, one poller per key, mutation fan-out |
|
|
28
|
+
| {%docsBaseUrl%}/query/persistence | Successful reads kept in IndexedDB for reloads and offline cold starts |
|
|
29
|
+
| {%docsBaseUrl%}/query/stacks | Many queries of one creator, infinite lists, paged data |
|
|
30
|
+
| {%docsBaseUrl%}/query/dependent-queries | GET → GET dependencies and ordered mutation chains |
|
|
31
|
+
| {%docsBaseUrl%}/query/groups | Several queries behind one state: loading, error and retry follow the one that ran last |
|
|
32
|
+
| {%docsBaseUrl%}/query/batching | Bulk writes with bounded concurrency, per-item results, retry |
|
|
33
|
+
| {%docsBaseUrl%}/query/errors | Error object, opt-in parsers, form submission, violations, retries |
|
|
34
|
+
| {%docsBaseUrl%}/query/testing | Specs: answering requests, `@ethlete/query/testing` helpers and fakes |
|
|
35
|
+
| {%docsBaseUrl%}/query/query-forms | **Any filtered, searched, sorted or paged list** - `defineQueryForm` |
|
|
36
|
+
| {%docsBaseUrl%}/query/gql | GraphQL creators over GET/POST |
|
|
37
|
+
| {%docsBaseUrl%}/query/ws | socket.io rooms and live-updating responses |
|
|
38
|
+
| {%docsBaseUrl%}/query/legacy | The maintenance-mode `V2QueryClient` and its replacements |
|
|
39
|
+
| {%docsBaseUrl%}/query/migrating-from-v2 | Codemods and the screen-by-screen move off the legacy client |
|
|
40
|
+
| {%docsBaseUrl%}/query/migrating-from-ngrx-toolkit | The generator and interop that move a store off `@tomtomb/ngrx-toolkit` |
|
|
41
|
+
| {%docsBaseUrl%}/query-devtools/ | The devtools panel and `provideQueryDevtools()` |
|
|
42
|
+
|
|
43
|
+
## You need → use → read
|
|
44
|
+
|
|
45
|
+
Before writing a helper, find your need here. Pages are under `{%docsBaseUrl%}/query/`.
|
|
46
|
+
|
|
47
|
+
| You need | Use | Read |
|
|
48
|
+
| ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- |
|
|
49
|
+
| One client per API | `createQueryClient`, `injectApi()` | `queries` |
|
|
50
|
+
| REST reads (auto-executing) | `createGetQuery`, `createHeadQuery`, `createOptionsQuery` | `http` |
|
|
51
|
+
| REST writes (manual) | `createPostQuery`, `createPutQuery`, `createPatchQuery`, `createDeleteQuery` | `http` |
|
|
52
|
+
| The same behind a bearer token | `createSecureGetQuery`, `createSecureHeadQuery`, `createSecureOptionsQuery`, `createSecurePostQuery`, `createSecurePutQuery`, `createSecurePatchQuery`, `createSecureDeleteQuery` | `http`, `auth` |
|
|
53
|
+
| Upload/download progress bar | creator option `reportProgress: true`, then `loading().progress` | `http` |
|
|
54
|
+
| Args that follow signals (route params, inputs, filters) | `withArgs` | `features` |
|
|
55
|
+
| A filtered / searched / sorted / paged list synced to the URL | `defineQueryForm`, `queryField`, `searchQueryField`, `sortQueryField` | `query-forms` |
|
|
56
|
+
| Refetch on an interval | `withPolling` | `features` |
|
|
57
|
+
| Next request once the previous settled, with a cursor from it | `withLongPolling` | `features` |
|
|
58
|
+
| Refetch when some other signal changes | `withAutoRefresh` | `features` |
|
|
59
|
+
| Toast / log / react on success or failure | `withSuccessHandling`, `withErrorHandling`, `withLogging` | `features` |
|
|
60
|
+
| Patch a loaded response in place (e.g. from a socket message) | `withResponseUpdate` | `features`, `ws` |
|
|
61
|
+
| Page falls out of range after a filter shrinks the results | `withPageResetOnError`, `isPageOutOfRangeError` | `features` |
|
|
62
|
+
| Package your own reusable query behavior | `createQueryFeature` | `features` |
|
|
63
|
+
| Refresh reads after a mutation (here and in other tabs) | `injectApi().invalidateQueries({ url })` | `caching` |
|
|
64
|
+
| Show a mutation's result in cached reads before the server answers | `withOptimisticUpdate` | `caching` |
|
|
65
|
+
| One loading, error and retry state for whichever of several mutations ran last | `createQueryGroup` | `groups` |
|
|
66
|
+
| Refetch everything after a client-wide header changed | `injectApi().refreshQueriesInUse()` | `caching` |
|
|
67
|
+
| Show the previous data instantly on back navigation | `keepUnusedFor` + `executionState().cachedResponse` | `caching` |
|
|
68
|
+
| Last known data after a reload or offline | `withQueryPersistence`, `createIndexedDbQueryPersistenceAdapter`, `createNoopQueryPersistenceAdapter` | `persistence` |
|
|
69
|
+
| Tabs share responses, poll once, see each other's mutations | `withMultiTabSync` | `multi-tab` |
|
|
70
|
+
| Parallel detail requests for a list of ids | `createQueryStack`, `transformArrayResponse` | `stacks` |
|
|
71
|
+
| Load more / infinite scroll / classic paging | `createPagedQueryStack`, `ethletePaginationAdapter` (and other adapters) | `stacks` |
|
|
72
|
+
| A GET whose args come from another GET's response | `withArgs` reading the first query's `response()` | `dependent-queries` |
|
|
73
|
+
| Run mutations in order, each using the previous result | `querySequence` | `dependent-queries` |
|
|
74
|
+
| Many edits with progress, time remaining and retry of failures | `createQueryBatch` | `batching` |
|
|
75
|
+
| Submit a signal form through a mutation, violations on fields | `createQuerySubmission` | `errors` |
|
|
76
|
+
| Own submit handler, or server-side validation while typing | `executeUntilSettled`, `executeUntilSettled$`, `mapViolationsToFormErrors`, `validateWithQuery` | `errors` |
|
|
77
|
+
| Understand the error shapes your API returns | `withEthleteApiErrors` (all), `withSymfonyErrors`, `withHtmlErrorParsing` | `errors` |
|
|
78
|
+
| Error text for display, or an error built by hand (tests) | `queryErrorMessage`, `queryErrorMessages`, `createQueryErrorResponse`, `<et-query-error>` | `errors` |
|
|
79
|
+
| Retry failed requests | `withDefaultRetry`, `createDefaultRetryFn` | `errors` |
|
|
80
|
+
| Login / refresh / logout with bearer tokens | `createBearerAuthProvider`, `withAuthenticationQuery`, `withRefreshQuery` | `auth` |
|
|
81
|
+
| Protect routes, check roles or permissions | `createAuthGuard`, `canMatchWith` | `auth` |
|
|
82
|
+
| "Remember me" auto-login | `withPersistentAuth` | `auth` |
|
|
83
|
+
| Log out idle users | `withInactivityLogout` | `auth` |
|
|
84
|
+
| Warn before the session expires | `withTokenExpirationWarning` | `auth` |
|
|
85
|
+
| Revoke the token on logout | `withTokenRevocation` | `auth` |
|
|
86
|
+
| Share login/logout across tabs | `withBearerAuthMultiTabSync` | `auth` |
|
|
87
|
+
| Auth telemetry events | `withTracking` | `auth` |
|
|
88
|
+
| Tokens from SSO or a native shell | `provider.setTokens(access, refresh)` | `auth` |
|
|
89
|
+
| Which of several queries is busy (v2 query collections) | each query's `executionState()` in a `computed`; auth: `provider.executionState()` | `migrating-from-v2` |
|
|
90
|
+
| GraphQL | `createGqlQueryViaGet`, `createGqlQueryViaPost`, `createGqlMutationViaGet`, `createGqlMutationViaPost`, `createSecureGqlQueryViaGet`, `createSecureGqlQueryViaPost`, `createSecureGqlMutationViaGet`, `createSecureGqlMutationViaPost` | `gql` |
|
|
91
|
+
| Realtime rooms over socket.io | `createWebSocketClient` | `ws` |
|
|
92
|
+
| Inspect queries, stacks, auth and cache at runtime | `provideQueryDevtools()` + `@ethlete/query-devtools` | `/query-devtools/` |
|
|
30
93
|
|
|
31
94
|
## Two generations - use the current one
|
|
32
95
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- **Legacy (maintenance mode):** class-based `V2QueryClient`, `.prepare().execute()`,
|
|
37
|
-
`queryComputed`. Don't write new code against it.
|
|
96
|
+
Write new code against the signals-first system (`createQueryClient`, `createGetQuery`, …,
|
|
97
|
+
`withArgs`), all from the single entry `@ethlete/query`. The class-based `V2QueryClient`
|
|
98
|
+
(`.prepare().execute()`, `queryComputed`) is in maintenance mode.
|
|
38
99
|
|
|
39
100
|
## Core usage
|
|
40
101
|
|
|
41
|
-
One client per API, one creator per
|
|
102
|
+
One client per API, one bound creator per method, one query per endpoint, one live query
|
|
103
|
+
per component instance:
|
|
42
104
|
|
|
43
105
|
```ts
|
|
44
106
|
import { createQueryClient, createGetQuery, withArgs } from '@ethlete/query';
|
|
45
107
|
|
|
108
|
+
// api.ts
|
|
46
109
|
export const apiClient = createQueryClient({ name: 'api', baseUrl: API_URL });
|
|
47
|
-
export const
|
|
110
|
+
export const getQuery = createGetQuery(apiClient);
|
|
111
|
+
|
|
112
|
+
// posts.queries.ts
|
|
113
|
+
export type GetPostQueryArgs = {
|
|
114
|
+
response: Post;
|
|
115
|
+
pathParams: { postId: string };
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export const getPost = getQuery<GetPostQueryArgs>((p) => `/posts/${p.postId}`);
|
|
48
119
|
|
|
49
120
|
// in a component (injection context):
|
|
50
121
|
postId = input.required<string>();
|
|
@@ -54,61 +125,91 @@ post = computed(() => this.postQuery.response());
|
|
|
54
125
|
|
|
55
126
|
- `GET`/`HEAD`/`OPTIONS` **auto-execute** - immediately when static/argless, or
|
|
56
127
|
whenever `withArgs` produces new args. Mutations (`POST`/`PUT`/`PATCH`/`DELETE`)
|
|
57
|
-
never auto-execute; call `.execute(
|
|
58
|
-
requires `withArgs` (dev-mode error
|
|
128
|
+
never auto-execute; declare their args with `withArgs` too and call `.execute()`. A function route (`pathParams`)
|
|
129
|
+
requires `withArgs` (a type error, and a dev-mode error).
|
|
130
|
+
- A route function receives the path params themselves: `(p) => \`/posts/${p.postId}\``.
|
|
131
|
+
- With bearer auth, bind the secure creators the same way:
|
|
132
|
+
`const secureGetQuery = createSecureGetQuery(apiClient, authProviderRef)`.
|
|
59
133
|
- Queries live in a child injector tied to the creating component; destroyed with it.
|
|
60
134
|
|
|
61
|
-
##
|
|
135
|
+
## Lists with filters, search, sort or paging: `defineQueryForm`
|
|
62
136
|
|
|
63
|
-
|
|
64
|
-
|
|
137
|
+
Never wire list controls to the URL by hand (`injectQueryParams` + `router.navigate` +
|
|
138
|
+
drafts + effects). `defineQueryForm` does URL sync, debounce, defaults and page resets:
|
|
65
139
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
140
|
+
```ts
|
|
141
|
+
qf = defineQueryForm({
|
|
142
|
+
fields: {
|
|
143
|
+
search: searchQueryField(),
|
|
144
|
+
sort: sortQueryField(),
|
|
145
|
+
page: queryField<number>({ defaultValue: 1, isResetBy: ['search', 'sort'] }),
|
|
146
|
+
},
|
|
147
|
+
}).observe();
|
|
148
|
+
|
|
149
|
+
users = getUsers(
|
|
150
|
+
withArgs(() => {
|
|
151
|
+
const { search, sort, page } = this.qf.value();
|
|
152
|
+
|
|
153
|
+
return { queryParams: { query: search, sortBy: sort?.active, sortOrder: sort?.direction, page } };
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
```
|
|
74
157
|
|
|
75
|
-
|
|
158
|
+
Bind form controls with `[formField]="qf.fields.search"`. `et-pagination` and
|
|
159
|
+
`et-page-size-select` are not form controls: bind their `model` to the field's value signal,
|
|
160
|
+
`[(page)]="qf.fields.page().value"`; an `et-table` sort binds the same way to a
|
|
161
|
+
`tableSortQueryField()`, `[(sort)]="qf.fields.sort().value"`, with no mapping. `qf.value()` is the committed, debounced value. Each
|
|
162
|
+
change pushes a history entry; pass `observe({ replaceUrl: true })` to replace it instead. See {%docsBaseUrl%}/query/query-forms for the other field creators,
|
|
163
|
+
filter overlays and `activeFilterCount`.
|
|
76
164
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
- `
|
|
83
|
-
- Side-effects: `withSuccessHandling`, `withErrorHandling`, `withLogging`.
|
|
165
|
+
When you move a list that syncs its params by hand, keep the param names the URL already uses, so
|
|
166
|
+
saved links keep working (`queryParamPrefix` when two lists share a route). Keep each old default as
|
|
167
|
+
the field's `defaultValue`. A URL with sort and direction in two params (`?sort=name&dir=asc`)
|
|
168
|
+
keeps two `queryField`s, `dir` with `skipInFilterCount: true`; `sortQueryField()` writes one
|
|
169
|
+
`name:asc` param, so it breaks old links. A date field bound to `et-date-input`,
|
|
170
|
+
`et-date-time-input` or `et-time-input` needs `dateQueryField({ as: 'string' })`.
|
|
84
171
|
|
|
85
|
-
|
|
86
|
-
requests; debounce at the input if you need it.
|
|
172
|
+
## The query object
|
|
87
173
|
|
|
88
|
-
|
|
174
|
+
Every state member is an **`ObservableSignal`** - call it, or `.asObservable()` it without an
|
|
175
|
+
injection context of your own (it emits `null` first).
|
|
89
176
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
177
|
+
- `response()` → `TResponse | null` (kept while re-executing; cleared on a failed re-exec).
|
|
178
|
+
To keep showing the last good data after a failure, read `cachedResponse` from the
|
|
179
|
+
`loading` or `failure` variant of `executionState()` - do not copy responses into a signal.
|
|
180
|
+
- `loading()`, `error()` (normalized `QueryErrorResponse`), `args()`,
|
|
181
|
+
`executionState()` (`{ type: 'loading' | 'success' | 'failure', … } | null`, great for `@switch`).
|
|
182
|
+
- Methods: `execute({ args?, options? })`, `reset()`, `createSnapshot()`, `asReadonly()`.
|
|
93
183
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
184
|
+
## Reactive args
|
|
185
|
+
|
|
186
|
+
- **`withArgs(() => ({ pathParams, queryParams, body }))`** runs like a `computed` and
|
|
187
|
+
re-executes on change. Return `null` to park the query (pauses polling/auto-refresh).
|
|
188
|
+
Calling `.execute()` on a parked query is ignored and warns in dev mode
|
|
189
|
+
(`queryExecutedWhileParkedMessage`); pass `execute({ args })` or let the source return args.
|
|
190
|
+
- **Prefer `withArgs` over `execute({ args })`.** Declared args keep a `GET` re-executing and
|
|
191
|
+
polling/auto-refresh restarting off the same signal; a function route throws without it. A
|
|
192
|
+
mutation with `withArgs` is just `.execute()`. Reserve `execute({ args })` for a one-off
|
|
193
|
+
payload no signal holds.
|
|
194
|
+
- Search-as-you-type: read a debounced value (`searchQueryField()` debounces 300ms; every
|
|
195
|
+
query field takes `debounce`) - a raw input signal sends one request per keystroke.
|
|
196
|
+
- `withLongPolling` and `withPolling` throw when combined.
|
|
197
|
+
|
|
198
|
+
## Bridging into RxJS or callbacks
|
|
199
|
+
|
|
200
|
+
For a callback that must return one correlated result, run a manual query
|
|
201
|
+
(`{ onlyManualExecution: true }`) through `executeUntilSettled$(query, { args })` - cold, its
|
|
202
|
+
snapshot is frozen to that execution, and unsubscribing aborts the request. Keep the Promise
|
|
203
|
+
`executeUntilSettled` for an `async` signal-forms `submit()`. Never set a signal and return the shared `response`
|
|
204
|
+
stream: the retained previous response can be the first non-null emission. See
|
|
205
|
+
{%docsBaseUrl%}/query/queries#the-query-object.
|
|
106
206
|
|
|
107
207
|
## Gotchas
|
|
108
208
|
|
|
109
|
-
-
|
|
110
|
-
(`?? []` / `filter(Boolean)` as needed).
|
|
111
|
-
- Don't reach for the legacy client for new code.
|
|
209
|
+
- `query.response()` is **nullable** (`?? []` / `filter(Boolean)` as needed).
|
|
112
210
|
- `.execute()` defaults `args` to the current `args()` when omitted.
|
|
113
211
|
- Anything under a query's `subtle` namespace is an unsupported escape hatch - never
|
|
114
212
|
treat it as public API.
|
|
213
|
+
- Use `withArgs` for mutations too. Never reach for `execute({ args })` plus
|
|
214
|
+
`silenceMissingWithArgsFeatureError` unless the args really exist only at call time - the
|
|
215
|
+
flag is an escape hatch, not the mutation pattern.
|
|
@@ -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']
|
|
@@ -34,9 +34,18 @@ const data = toSignal(obs$);
|
|
|
34
34
|
|
|
35
35
|
## Using RxJS correctly
|
|
36
36
|
|
|
37
|
-
- **
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
- **Tear down long-lived or manual subscriptions.** Finite streams that complete on their
|
|
38
|
+
own need no artificial lifecycle operator. For Angular lifecycle cleanup, prefer
|
|
39
|
+
`takeUntilDestroyed()` (it needs an injection context) or explicitly unsubscribe.
|
|
40
|
+
Do not use `takeWhile` as destruction cleanup: without another emission it stays
|
|
41
|
+
subscribed. Use `take(1)` or `first()` only when one emission is the operation's
|
|
42
|
+
intended semantics.
|
|
43
|
+
- **`takeUntilDestroyed()` goes last in its pipe** (`ethlete/take-until-destroyed-last`), so
|
|
44
|
+
higher-order operators such as `switchMap` and their inner subscriptions are covered.
|
|
45
|
+
Completion-only operators (`finalize`, `defaultIfEmpty`, `toArray`, …) may follow it, and
|
|
46
|
+
chained `.pipe(...).pipe(...)` calls count as one pipe.
|
|
47
|
+
Other limiting and finalization operators do not have a universal “last” position;
|
|
48
|
+
place them where their semantics belong.
|
|
40
49
|
- **Side effects go in `tap()`**, never in the `subscribe()` callback - keep
|
|
41
50
|
`subscribe()` empty.
|
|
42
51
|
- **Don't reach for RxJS inside `effect()`/`computed()`.** Subscribing per run
|
|
@@ -56,6 +65,26 @@ toObservable(page)
|
|
|
56
65
|
.subscribe();
|
|
57
66
|
```
|
|
58
67
|
|
|
68
|
+
## An effect that only writes a signal is a derivation
|
|
69
|
+
|
|
70
|
+
If an `effect()` does nothing but `.set()` a signal, even behind an `if`, the value is derived
|
|
71
|
+
state: use `computed()` when it is read-only, `linkedSignal()` when the user can still
|
|
72
|
+
overwrite it. `ethlete/prefer-linked-signal` flags the plain cases. Keep `effect()` for work
|
|
73
|
+
that leaves the signal graph: DOM, storage, logging, third-party APIs.
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
// ❌
|
|
77
|
+
effect(() => {
|
|
78
|
+
if (this.items().length) this.selected.set(this.items()[0]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// ✅ the previous value stays when the list is empty
|
|
82
|
+
selected = linkedSignal<Item[], Item | null>({
|
|
83
|
+
source: this.items,
|
|
84
|
+
computation: (items, previous) => items[0] ?? previous?.value ?? null,
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
59
88
|
## Prefer `@ethlete/core` helpers
|
|
60
89
|
|
|
61
90
|
Lint nudges these, but reach for them by default: `injectViewportSize()`,
|
|
@@ -20,6 +20,13 @@ Two sources, both authoritative for different things:
|
|
|
20
20
|
| **Docs site** | {%docsBaseUrl%} | Prose guides: what a thing is for, options, defaults, behaviour, migration notes |
|
|
21
21
|
| **Storybook** | {%sdkStorybookUrl%} | The live component: every variant rendered, the real controls, and the exact markup a story uses |
|
|
22
22
|
|
|
23
|
+
## First: app setup
|
|
24
|
+
|
|
25
|
+
Before the first component, work through `{%docsBaseUrl%}/components/setup` - the 62.5% root
|
|
26
|
+
font size, Tailwind 4 theme generation, a `type: 'error'` color theme, and the providers
|
|
27
|
+
(`provideOverlay`, `provideDateLocale`, label tokens, …). Missing any of them fails at runtime,
|
|
28
|
+
not at compile time.
|
|
29
|
+
|
|
23
30
|
## Finding the right page
|
|
24
31
|
|
|
25
32
|
Page URLs follow `{%docsBaseUrl%}/<lib>/<topic>`. The library sections:
|
|
@@ -28,23 +35,51 @@ Page URLs follow `{%docsBaseUrl%}/<lib>/<topic>`. The library sections:
|
|
|
28
35
|
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
29
36
|
| `/components/` | The active UI library - one guide per domain (see the list below) |
|
|
30
37
|
| `/core/` | Framework primitives: `theming`, `overlay-runtime`, `signal-utils`, `element-signals`, `animations`, `scrolling`, `drag-resize`, `directives-pipes`, `providers`, `seo`, `utilities` |
|
|
31
|
-
| `/query/` | Data fetching
|
|
38
|
+
| `/query/` | Data fetching; load the dedicated query guide first when this package emitted one |
|
|
32
39
|
| `/cdk/` | The predecessor UI toolkit, maintenance mode. Only for code that still uses it |
|
|
33
40
|
| `/contentful/`, `/cli/`, `/eslint/`, `/types/` | The remaining packages |
|
|
34
41
|
|
|
35
42
|
Component domains under `/components/`:
|
|
36
43
|
|
|
37
|
-
`accordion` `
|
|
38
|
-
`
|
|
39
|
-
`
|
|
40
|
-
`
|
|
41
|
-
`
|
|
42
|
-
`
|
|
43
|
-
`
|
|
44
|
+
`accordion` `avatar` `badge` `banner` `bracket` `bracket-prediction` `bracket-rounds-list`
|
|
45
|
+
`breadcrumb` `button` `calendar` `card` `carousel` `cascader` `chart` `chip`
|
|
46
|
+
`choice-inputs` `command-palette` `copy-button` `date-time-inputs` `description-list`
|
|
47
|
+
`divider` `dropzone` `empty-state` `error-codes` `filter-overlay` `floating-action`
|
|
48
|
+
`focus-ring` `forms` `grid` `icon` `kbd` `line-chart` `loader` `localization` `masonry`
|
|
49
|
+
`match` `menu` `mixed-state` `notification` `overlay-openers` `overlays` `paged-query-trigger`
|
|
50
|
+
`pagination` `picture` `pie-chart` `progress-steps` `query-error` `rich-text-editor` `sankey-chart`
|
|
51
|
+
`scheduler` `scrollable` `scrollbar` `select` `setup` `skeleton` `slider` `sport-recipes` `stat-tile`
|
|
52
|
+
`standings` `standings-pick` `stream` `table` `tabs` `testing` `text-inputs` `time-picker`
|
|
53
|
+
`timeline` `toggletip` `toolbar` `tooltip` `tree`
|
|
54
|
+
|
|
55
|
+
**Check this list before you build any UI by hand.** Some needs hide behind a name you
|
|
56
|
+
would not guess:
|
|
57
|
+
|
|
58
|
+
| You need | Read |
|
|
59
|
+
| ------------------------------------------ | ----------------------------------------- |
|
|
60
|
+
| Bar or stacked bar chart, series legend | `chart` |
|
|
61
|
+
| Line, pie, flow chart | `line-chart`, `pie-chart`, `sankey-chart` |
|
|
62
|
+
| Progress bar, spinner | `loader` |
|
|
63
|
+
| Initials or user picture in a circle | `avatar` |
|
|
64
|
+
| "No results" or empty table state | `empty-state` |
|
|
65
|
+
| Failed request with a retry button | `query-error` |
|
|
66
|
+
| Status pill, count | `badge` |
|
|
67
|
+
| Tab strip that switches a view or a filter | `tabs` (nav tabs) |
|
|
68
|
+
| Dialog, bottom sheet, side panel | `overlays`, `overlay-openers` |
|
|
69
|
+
| Label and value pairs | `description-list` |
|
|
70
|
+
| Stepper, wizard progress | `progress-steps` |
|
|
71
|
+
| Providers and styles a new app needs | `setup` |
|
|
44
72
|
|
|
45
73
|
So the table guide is `{%docsBaseUrl%}/components/table`, the menu guide
|
|
46
|
-
`{%docsBaseUrl%}/components/menu`, and so on.
|
|
47
|
-
|
|
74
|
+
`{%docsBaseUrl%}/components/menu`, and so on.
|
|
75
|
+
|
|
76
|
+
That list is a snapshot. The site itself is machine-readable, so fetch rather than guess:
|
|
77
|
+
|
|
78
|
+
- `{%docsBaseUrl%}/llms.txt` - every page's title and path as of the last deploy. Use it
|
|
79
|
+
when a name isn't in the list above, or to check the list hasn't drifted.
|
|
80
|
+
- Append `.md` to any page URL - `{%docsBaseUrl%}/components/button.md` - to get raw
|
|
81
|
+
markdown instead of the rendered page. Prefer this over `/llms-full.txt`, which is the
|
|
82
|
+
entire site in one ~1 MB file.
|
|
48
83
|
|
|
49
84
|
## How to use them
|
|
50
85
|
|
|
@@ -59,13 +94,14 @@ So the table guide is `{%docsBaseUrl%}/components/table`, the menu guide
|
|
|
59
94
|
`-next` prereleases should read `{%docsBaseUrl%}` and `{%sdkStorybookUrl%}` only if
|
|
60
95
|
they are the matching prerelease deployments, otherwise expect drift and verify
|
|
61
96
|
against the installed `.d.ts` in `node_modules/@ethlete/<lib>`.
|
|
62
|
-
-
|
|
63
|
-
|
|
97
|
+
- **Use the SDK skills instead of searching `node_modules` ad hoc.** The installed `.d.ts`
|
|
98
|
+
files are authoritative for the public type surface this consumer can compile against.
|
|
99
|
+
Source matching that installed build is authoritative for runtime implementation details.
|
|
100
|
+
A dirty or ahead `next` checkout is not automatically source for the installed package.
|
|
101
|
+
If docs and installed types disagree, report the drift.
|
|
64
102
|
- **Never treat a `subtle` namespace as public API.** Anything exposed under `subtle` is
|
|
65
103
|
an unsupported escape hatch that can change without a major version.
|
|
66
104
|
|
|
67
105
|
## Related
|
|
68
106
|
|
|
69
|
-
- Data fetching has its own guide: {%skill:query%}
|
|
70
|
-
- Theming tokens and how to register themes: {%skill:theming%}
|
|
71
107
|
- When the docs cannot answer it, read the SDK source: {%skill:sdk-source%}
|