@imfusion/web-ui 0.6.1-dev.9.g317bd6f2 → 0.6.1
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/LICENSE.txt +30 -0
- package/README.md +104 -172
- package/THIRD_PARTY_NOTICES.md +34 -0
- package/bin/install.js +28 -10
- package/dist/code-BFMQnmu9.js +147 -0
- package/dist/codegen/gen-code-highlight-theme.d.ts +1 -0
- package/dist/components/code/code.d.ts +5 -4
- package/dist/components/stack/stack.d.ts +1 -1
- package/dist/components/toast/index.d.ts +2 -0
- package/dist/components/toast/toast.d.ts +200 -0
- package/dist/components/toast/toast.meta.d.ts +2 -0
- package/dist/components/typo/typo.d.ts +23 -22
- package/dist/icons/icon-config.d.ts +12 -0
- package/dist/{icons-wBmF0U2x.js → icons-Cy1HAosO.js} +1 -1
- package/dist/icons.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1278 -1069
- package/dist/integrations/code-highlight/highlighter.d.ts +24 -0
- package/dist/integrations/code-highlight.js +80 -47
- package/dist/integrations/image-display-options.js +2 -2
- package/dist/provider/web-ui-provider.d.ts +3 -3
- package/dist/style.css +1 -1
- package/dist/{tabs-CMKvMF4E.js → tabs-DIe1Utiy.js} +2 -0
- package/docs/assets/imfusion-banner.svg +16 -0
- package/package.json +14 -7
- package/src/docgen/doc.gen.json +515 -1
- package/src/llms/install-templates/AGENTS.md +15 -18
- package/src/llms/llms.gen.txt +39 -33
- package/src/llms/skills/imf-web-ui/SKILL.md +30 -39
- package/src/llms/skills/imf-web-ui-audit/SKILL.md +50 -102
- package/src/llms/skills/imf-web-ui-components/SKILL.md +47 -104
- package/src/llms/skills/imf-web-ui-conventions/SKILL.md +44 -52
- package/src/llms/skills/imf-web-ui-conventions/templates/AUDIT_CHECKLIST.md +1 -0
- package/src/llms/skills/imf-web-ui-conventions/topics/agent-tooling.md +40 -62
- package/src/llms/skills/imf-web-ui-conventions/topics/assets.md +11 -12
- package/src/llms/skills/imf-web-ui-conventions/topics/authentication.md +31 -46
- package/src/llms/skills/imf-web-ui-conventions/topics/class-names.md +18 -23
- package/src/llms/skills/imf-web-ui-conventions/topics/components.md +20 -69
- package/src/llms/skills/imf-web-ui-conventions/topics/data.md +50 -146
- package/src/llms/skills/imf-web-ui-conventions/topics/docs-structure.md +17 -23
- package/src/llms/skills/imf-web-ui-conventions/topics/git.md +15 -20
- package/src/llms/skills/imf-web-ui-conventions/topics/library-boundary.md +28 -19
- package/src/llms/skills/imf-web-ui-conventions/topics/library-setup.md +20 -16
- package/src/llms/skills/imf-web-ui-conventions/topics/npm-project.md +28 -42
- package/src/llms/skills/imf-web-ui-conventions/topics/project-structure.md +28 -30
- package/src/llms/skills/imf-web-ui-conventions/topics/react.md +28 -74
- package/src/llms/skills/imf-web-ui-conventions/topics/styling.md +65 -62
- package/src/llms/skills/imf-web-ui-conventions/topics/testing.md +12 -14
- package/src/llms/skills/imf-web-ui-conventions/topics/tokens.md +9 -4
- package/src/llms/skills/imf-web-ui-conventions/topics/tooling.md +40 -68
- package/src/llms/skills/imf-web-ui-conventions/topics/typescript.md +26 -50
- package/src/llms/skills/imf-web-ui-conventions/topics/validation.md +19 -25
- package/src/llms/skills/imf-web-ui-setup/SKILL.md +45 -64
- package/src/llms/skills/imf-web-ui-update/SKILL.md +48 -114
- package/src/llms/skills/imf-web-ui-ux/SKILL.md +64 -92
- package/src/llms/skills/imf-web-ui-ux/references/forms.md +16 -36
- package/src/llms/skills/imf-web-ui-ux/references/usability-heuristics.md +14 -27
- package/src/llms/skills/imf-web-ui-ux/references/visual-design.md +22 -38
- package/src/llms/tokens.gen.json +5 -5
- package/bin/install.test.ts +0 -329
- package/dist/code-Blo48PGr.js +0 -136
- package/dist/icons/icon-config-provider.d.ts +0 -8
- package/dist/icons/icon-context.d.ts +0 -4
|
@@ -1,27 +1,22 @@
|
|
|
1
1
|
# Class names in components
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
goes in the stylesheet: [styling.md](styling.md).
|
|
3
|
+
Use CVA to turn design props into CSS Module classes, then merge the caller's `className`.
|
|
5
4
|
|
|
6
5
|
## CVA is the only tool
|
|
7
6
|
|
|
8
|
-
- `class-variance-authority`
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
- Defaults live in the props destructuring, not CVA's `defaultVariants`: react-docgen-typescript reads the destructuring, so
|
|
14
|
-
defaults declared in CVA don't reach the generated docs.
|
|
7
|
+
- Use `class-variance-authority` for variant axes. Do not add `clsx`, `tailwind-merge`, or a local `cn` helper.
|
|
8
|
+
- Import `cx` from `@imfusion/web-ui` when a component only needs concatenation.
|
|
9
|
+
- Pass CSS Module references to CVA, not string class names.
|
|
10
|
+
- Represent a boolean axis with `false: null` and `true: classes.axisOn`.
|
|
11
|
+
- Put defaults in function destructuring. CVA `defaultVariants` does not reach the generated docs.
|
|
15
12
|
|
|
16
13
|
```tsx
|
|
17
|
-
interface Props extends HTMLAttributes<HTMLSpanElement> {
|
|
18
|
-
appearance?: "outline" | "solid";
|
|
19
|
-
inline?: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
14
|
const chip = cva(classes.root, {
|
|
23
15
|
variants: {
|
|
24
|
-
appearance: {
|
|
16
|
+
appearance: {
|
|
17
|
+
outline: classes.appearanceOutline,
|
|
18
|
+
solid: classes.appearanceSolid
|
|
19
|
+
},
|
|
25
20
|
inline: { false: null, true: classes.inline }
|
|
26
21
|
}
|
|
27
22
|
});
|
|
@@ -33,18 +28,18 @@ export function Chip({ appearance = "outline", inline = false, className, ...pro
|
|
|
33
28
|
|
|
34
29
|
## Merging `className`
|
|
35
30
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
- Resolve a function-form `className` before merging — components built on a library that passes render state
|
|
41
|
-
(`className={state => …}`) receive either shape:
|
|
31
|
+
A component that accepts `className` must merge it. Put the incoming value in CVA's `className` slot so it is appended after
|
|
32
|
+
the component classes. If there is no CVA configuration, use `cx`.
|
|
33
|
+
|
|
34
|
+
A library render prop may make `className` a function. Resolve it with the render state before merging:
|
|
42
35
|
|
|
43
36
|
```tsx
|
|
44
37
|
className={state => cx(classes.root, typeof className === "function" ? className(state) : className)}
|
|
45
38
|
```
|
|
46
39
|
|
|
40
|
+
A closed component should omit `className`, not accept and ignore it.
|
|
41
|
+
|
|
47
42
|
## Shared CVA modules
|
|
48
43
|
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
When two components share the same visual variant axes, put their CVA call in a `{name}.cva.ts` module next to them. This
|
|
45
|
+
keeps the visual contract in one place.
|
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
# Components
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Keep consumer components small and easy to replace. React roles are described in [react.md](react.md); CSS ownership is in
|
|
4
4
|
[styling.md](styling.md).
|
|
5
5
|
|
|
6
6
|
## As dumb as possible
|
|
7
7
|
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
8
|
+
- Feed components data and callbacks through props.
|
|
9
|
+
- Keep fetching, routing, and business logic in the page or feature layer.
|
|
10
|
+
- Local UI state such as an open state or draft value is fine.
|
|
11
|
+
- Formatting and display-only decisions are fine; owning server state is not.
|
|
12
12
|
|
|
13
13
|
```tsx
|
|
14
|
-
|
|
15
|
-
interface Props {
|
|
14
|
+
interface UserCardProps {
|
|
16
15
|
name: string;
|
|
17
16
|
onEdit: () => void;
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
export function UserCard({ name, onEdit }:
|
|
19
|
+
export function UserCard({ name, onEdit }: UserCardProps) {
|
|
21
20
|
return (
|
|
22
21
|
<Card.Root>
|
|
23
|
-
<Typo>{name}</Typo>
|
|
22
|
+
<Typo.P>{name}</Typo.P>
|
|
24
23
|
<Button onClick={onEdit}>Edit</Button>
|
|
25
24
|
</Card.Root>
|
|
26
25
|
);
|
|
@@ -29,73 +28,25 @@ export function UserCard({ name, onEdit }: Props) {
|
|
|
29
28
|
|
|
30
29
|
## Grouping
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- Flat is fine while there are few; let grouping follow what the project has, don't impose it up front.
|
|
31
|
+
Group by component role, such as layout or domain UI, according to the project structure. Do not create a hierarchy before
|
|
32
|
+
the project needs one.
|
|
35
33
|
|
|
36
34
|
## One file or a folder
|
|
37
35
|
|
|
38
|
-
-
|
|
39
|
-
|
|
40
|
-
- Imports read `#/components/data-table`, so the inside can be restructured without touching call sites.
|
|
36
|
+
A one-file component can stay in its group. A component with parts, styles, or tests gets a folder with an `index.ts` barrel.
|
|
37
|
+
Call sites import the folder path:
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
```
|
|
39
|
+
```text
|
|
45
40
|
components/data-table/
|
|
46
|
-
data-table.tsx
|
|
47
|
-
data-table
|
|
48
|
-
data-table.
|
|
49
|
-
|
|
50
|
-
index.ts # re-exports only
|
|
41
|
+
data-table.tsx
|
|
42
|
+
data-table.module.css
|
|
43
|
+
data-table.test.tsx
|
|
44
|
+
index.ts
|
|
51
45
|
```
|
|
52
46
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
```tsx
|
|
56
|
-
// data-table.tsx — CVA even with one variant axis, so future axes slot in (class-names.md)
|
|
57
|
-
import { cva, type VariantProps } from "class-variance-authority";
|
|
58
|
-
import classes from "./data-table.module.css";
|
|
59
|
-
|
|
60
|
-
const dataTable = cva(classes.root, {
|
|
61
|
-
variants: { density: { comfortable: classes.densityComfortable, compact: classes.densityCompact } }
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// Intersection, not `interface extends` — VariantProps is an object type. Defaults live in the destructuring.
|
|
65
|
-
type Props = React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof dataTable> & { rows: TableRow[] };
|
|
66
|
-
|
|
67
|
-
export function DataTable({ className, density = "comfortable", rows, ...props }: Props) {
|
|
68
|
-
return (
|
|
69
|
-
<div {...props} className={dataTable({ density, className })}>
|
|
70
|
-
{/* … */}
|
|
71
|
-
</div>
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
```css
|
|
77
|
-
/* data-table.module.css — native nesting + library tokens, no preprocessor, no @layer (styling.md) */
|
|
78
|
-
.root {
|
|
79
|
-
border-radius: var(--imf-ui-border-radius-2);
|
|
80
|
-
font-size: var(--imf-ui-font-size-1);
|
|
81
|
-
|
|
82
|
-
&:focus-visible {
|
|
83
|
-
/* states nest under the root */
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
.densityCompact {
|
|
88
|
-
/* one class per CVA variant value */
|
|
89
|
-
}
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
```ts
|
|
93
|
-
// index.ts — re-exports only; callers import #/components/data-table
|
|
94
|
-
export { DataTable } from "./data-table";
|
|
95
|
-
```
|
|
47
|
+
The barrel only re-exports the public pieces.
|
|
96
48
|
|
|
97
49
|
## Colocation
|
|
98
50
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
- Tests and local types sit next to their subject: a file hunted for in a parallel tree gets edited less carefully.
|
|
51
|
+
Keep a component's stylesheet, tests, and local types beside the component. A container that needs styling usually wants to
|
|
52
|
+
be a layout or dumb component instead.
|
|
@@ -1,207 +1,111 @@
|
|
|
1
1
|
# Data
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
This topic defines the ImFusion pattern for TanStack Query and TanStack Router. Other data libraries keep the same boundary
|
|
4
|
+
rules: validate external data at the edge and represent errors as values.
|
|
5
5
|
|
|
6
6
|
## The shape
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
call site.
|
|
10
|
-
- Name a file for what it holds, never for the topic it sits in — the folder already says that: `keys.ts`, not `<topic>.ts`
|
|
11
|
-
or `query-key.ts` (it holds mutation keys too).
|
|
12
|
-
- A file appears only when the topic needs it: a topic with only queries has no `mutations.ts`, and vice versa.
|
|
13
|
-
- A GraphQL (or similarly document-based) transport may split `queries.ts`/`mutations.ts` further into
|
|
14
|
-
`queryFns.ts`/`mutationFns.ts`; that's an elaboration of the same options-vs-mutations split, not its origin.
|
|
15
|
-
- Transport code (base client, error normalisation): `http/` and nowhere else.
|
|
16
|
-
- Topic helpers: `lib/`, framework-free, tested beside themselves (`build-query-string.ts` + its test) — never inside the
|
|
17
|
-
topic folder.
|
|
8
|
+
Use one folder per API topic:
|
|
18
9
|
|
|
19
10
|
```text
|
|
20
11
|
src/
|
|
21
12
|
api/
|
|
22
|
-
topics.ts
|
|
23
|
-
index.ts
|
|
13
|
+
topics.ts
|
|
14
|
+
index.ts
|
|
24
15
|
<topic>/
|
|
25
|
-
index.ts
|
|
26
|
-
keys.ts
|
|
27
|
-
queries.ts
|
|
28
|
-
mutations.ts
|
|
29
|
-
types.ts
|
|
30
|
-
types.test.ts
|
|
31
|
-
http/
|
|
32
|
-
lib/
|
|
16
|
+
index.ts
|
|
17
|
+
keys.ts
|
|
18
|
+
queries.ts # when the topic has queries
|
|
19
|
+
mutations.ts # when it has mutations
|
|
20
|
+
types.ts # schemas and inferred types
|
|
21
|
+
types.test.ts # when schema rules encode behavior
|
|
22
|
+
http/ # transport and error normalization
|
|
23
|
+
lib/ # framework-free shared helpers
|
|
33
24
|
```
|
|
34
25
|
|
|
26
|
+
`keys.ts` owns query and mutation keys. Topic functions return options, not hooks or data. Keep transport code in `http/` and
|
|
27
|
+
keep topic helpers out of the API folder when they are shared and framework-free.
|
|
28
|
+
|
|
35
29
|
## The network boundary validates
|
|
36
30
|
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
The transport returns `unknown`. The topic schema parses it in the query or mutation function, and `z.infer` supplies the
|
|
32
|
+
TypeScript type. A handwritten generic on `fetch` is not validation. See [validation.md](validation.md).
|
|
39
33
|
|
|
40
34
|
## Errors are values
|
|
41
35
|
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
Normalize failed requests into an `ApiError` with a machine-readable code. The UI maps the code to user-facing text instead
|
|
37
|
+
of handling arbitrary thrown shapes at every call site.
|
|
44
38
|
|
|
45
39
|
## Topic registry and key factory
|
|
46
40
|
|
|
47
|
-
|
|
48
|
-
- `createApiKeys(topic, { queries, mutations })` types every key `readonly [Topic, ...unknown[]]` and returns `keys.topic`:
|
|
49
|
-
cross-topic invalidation names the topic, never reconstructs a key prefix.
|
|
41
|
+
Keep one registry of topic names. Use it for API namespaces and as the first segment of every query and mutation key:
|
|
50
42
|
|
|
51
43
|
```ts
|
|
52
|
-
// api/topics.ts — one list of topic names, ApiTopic derived from it
|
|
53
44
|
export const API_TOPICS = { AUTH: "auth", USER: "user", ORDERS: "orders" } as const;
|
|
54
45
|
export type ApiTopic = (typeof API_TOPICS)[keyof typeof API_TOPICS];
|
|
55
|
-
|
|
56
|
-
export type QueryKeyFactory<Topic extends ApiTopic> = (...args: never[]) => QueryKey<Topic>;
|
|
46
|
+
type QueryKey<Topic extends ApiTopic> = readonly [Topic, ...ReadonlyArray<unknown>] | readonly [Topic];
|
|
57
47
|
|
|
58
48
|
export function createApiKeys<
|
|
59
49
|
Topic extends ApiTopic,
|
|
60
|
-
QueryKeys extends Record<string, QueryKey<Topic> |
|
|
50
|
+
QueryKeys extends Record<string, QueryKey<Topic> | ((...args: never[]) => QueryKey<Topic>)>,
|
|
61
51
|
MutationKeys extends Record<string, QueryKey<Topic>>
|
|
62
52
|
>(topic: Topic, keys: { queries: QueryKeys; mutations: MutationKeys }) {
|
|
63
53
|
return { topic: [topic] as const, queries: keys.queries, mutations: keys.mutations };
|
|
64
54
|
}
|
|
65
55
|
```
|
|
66
56
|
|
|
67
|
-
|
|
68
|
-
// api/user/keys.ts — createApiKeys enforces topic-first
|
|
69
|
-
import { API_TOPICS, createApiKeys } from "../topics";
|
|
57
|
+
Use the factory in each topic so every key starts with that topic:
|
|
70
58
|
|
|
59
|
+
```ts
|
|
71
60
|
export const keys = createApiKeys(API_TOPICS.USER, {
|
|
72
|
-
queries: {
|
|
73
|
-
|
|
61
|
+
queries: {
|
|
62
|
+
details: [API_TOPICS.USER, "details"],
|
|
63
|
+
byId: (id: string) => [API_TOPICS.USER, "detail", id]
|
|
64
|
+
},
|
|
65
|
+
mutations: { update: [API_TOPICS.USER, "update"] }
|
|
74
66
|
});
|
|
75
|
-
|
|
76
|
-
// api/user/index.ts — the topic barrel
|
|
77
|
-
export * from "./keys";
|
|
78
|
-
export * from "./queries";
|
|
79
|
-
export * from "./mutations";
|
|
80
67
|
```
|
|
81
68
|
|
|
82
|
-
|
|
83
|
-
// api/index.ts — namespace imports keyed by the topic registry
|
|
84
|
-
import { API_TOPICS } from "./topics";
|
|
85
|
-
import * as auth from "./auth";
|
|
86
|
-
import * as orders from "./orders";
|
|
87
|
-
import * as user from "./user";
|
|
88
|
-
|
|
89
|
-
export const api = {
|
|
90
|
-
[API_TOPICS.AUTH]: auth,
|
|
91
|
-
[API_TOPICS.USER]: user,
|
|
92
|
-
[API_TOPICS.ORDERS]: orders
|
|
93
|
-
} as const;
|
|
94
|
-
|
|
95
|
-
export type API = typeof api;
|
|
96
|
-
```
|
|
69
|
+
Cross-topic invalidation can then use `keys.topic` instead of rebuilding a prefix by hand.
|
|
97
70
|
|
|
98
71
|
## Options factories and naming
|
|
99
72
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
- Queries `get*` (`getAll`, `getById`, `getBySlug`, `getDetails`, `getActive`, `getFiltered`); mutations bare verbs
|
|
103
|
-
(`update`, `login`, `addItem`, `deleteAddress`, `setShippingMethod`).
|
|
104
|
-
- Same suffix-free naming applies to the local binding a query or mutation resolves to —
|
|
105
|
-
[typescript.md](typescript.md#naming).
|
|
73
|
+
Queries use `get*` names such as `getAll`, `getById`, and `getDetails`. Mutations use bare verbs such as `update` or
|
|
74
|
+
`delete`. Do not add a topic prefix or `Options` suffix; `context.api.user.getDetails()` already supplies the context.
|
|
106
75
|
|
|
107
|
-
|
|
108
|
-
// api/user/queries.ts — returns options; keys come from the factory
|
|
109
|
-
import { queryOptions, type QueryOptions } from "@tanstack/react-query";
|
|
110
|
-
import { bffClient } from "../http/bff-client";
|
|
111
|
-
import { userSchema, type User } from "./types";
|
|
112
|
-
import { keys } from "./keys";
|
|
76
|
+
A query factory owns its key and parsing:
|
|
113
77
|
|
|
114
|
-
|
|
78
|
+
```ts
|
|
79
|
+
export function getDetails(options?: QueryOptions<User>) {
|
|
115
80
|
return queryOptions({
|
|
116
|
-
...
|
|
81
|
+
...options,
|
|
117
82
|
queryKey: keys.queries.details,
|
|
118
83
|
queryFn: async () => userSchema.parse(await bffClient("/me"))
|
|
119
84
|
});
|
|
120
85
|
}
|
|
121
86
|
```
|
|
122
87
|
|
|
123
|
-
|
|
124
|
-
// api/user/mutations.ts — overrides merge over the base, mutationKey leads with the topic
|
|
125
|
-
import type { MutationOptions } from "@tanstack/react-query";
|
|
126
|
-
import { bffClient } from "../http/bff-client";
|
|
127
|
-
import { userSchema, type User, type UserPatch } from "./types";
|
|
128
|
-
import { keys } from "./keys";
|
|
129
|
-
import type { ApiError } from "../http/api-error";
|
|
130
|
-
|
|
131
|
-
export function update(options?: Omit<MutationOptions<User, ApiError, UserPatch>, "mutationFn">) {
|
|
132
|
-
return {
|
|
133
|
-
mutationKey: keys.mutations.update,
|
|
134
|
-
mutationFn: async (patch: UserPatch) => userSchema.parse(await bffClient("/me", { method: "PATCH", body: patch })),
|
|
135
|
-
...options
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
```
|
|
88
|
+
Callers choose `ensureQueryData`, `useSuspenseQuery`, or another Query API.
|
|
139
89
|
|
|
140
90
|
## Router context access
|
|
141
91
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
`MutationCache` closes over both `queryClient` and `router`.
|
|
145
|
-
- Direct imports stay for plain utilities and tests outside the router tree.
|
|
146
|
-
|
|
147
|
-
```tsx
|
|
148
|
-
// routes/__root.tsx
|
|
149
|
-
import type { QueryClient } from "@tanstack/react-query";
|
|
150
|
-
import type { API } from "../api";
|
|
92
|
+
Put the API object and `QueryClient` in router context. Loaders use `context.api.<topic>.<fn>()`; route components read the
|
|
93
|
+
same context with `Route.useRouteContext()`. Direct imports are for plain utilities and tests outside the route tree.
|
|
151
94
|
|
|
152
|
-
|
|
153
|
-
api: API;
|
|
154
|
-
queryClient: QueryClient;
|
|
155
|
-
}
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
```tsx
|
|
159
|
-
// router.tsx
|
|
160
|
-
import { createRouter } from "@tanstack/react-router";
|
|
161
|
-
import { api } from "./api";
|
|
162
|
-
import { createQueryClient } from "./query-client";
|
|
163
|
-
|
|
164
|
-
const queryClient = createQueryClient();
|
|
165
|
-
const router = createRouter({ routeTree, context: { api, queryClient } });
|
|
166
|
-
router.update({ context: { api, queryClient } });
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
```tsx
|
|
170
|
-
// routes/user.tsx
|
|
171
|
-
export const Route = createFileRoute("/user")({
|
|
172
|
-
loader: ({ context }) => context.queryClient.ensureQueryData(context.api.user.getDetails()),
|
|
173
|
-
component: UserPage
|
|
174
|
-
});
|
|
95
|
+
## Automatic invalidation
|
|
175
96
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const updateUser = useMutation(
|
|
180
|
-
context.api.user.update({
|
|
181
|
-
onSuccess: () => context.queryClient.invalidateQueries({ queryKey: context.api.orders.keys.topic })
|
|
182
|
-
})
|
|
183
|
-
);
|
|
184
|
-
return <input defaultValue={user.data.name} onBlur={e => updateUser.mutate({ name: e.target.value })} />;
|
|
185
|
-
}
|
|
186
|
-
```
|
|
97
|
+
Mutation keys start with the topic they change. The shared `MutationCache` invalidates that topic after success by default.
|
|
98
|
+
Use `meta: { autoInvalidate: false }` for a mutation where a topic-wide refetch is wrong, then invalidate exact keys
|
|
99
|
+
yourself.
|
|
187
100
|
|
|
188
|
-
|
|
101
|
+
For a related topic, add an explicit success handler that invalidates that topic's `keys.topic`.
|
|
189
102
|
|
|
190
|
-
|
|
191
|
-
`meta.autoInvalidate` — hence every mutation key leads with the topic it dirties.
|
|
192
|
-
- Cross-topic invalidation: per-call-site `onSuccess` override naming `keys.topic` (see `routes/user.tsx`).
|
|
193
|
-
- Opt out with `meta: { autoInvalidate: false }` when a topic-wide refetch is wrong (huge list, targeted optimistic update);
|
|
194
|
-
invalidate precisely via the key factory.
|
|
195
|
-
- Background: [query invalidation](https://tanstack.com/query/latest/docs/framework/react/guides/query-invalidation),
|
|
196
|
-
[automatic invalidation after mutations](https://tkdodo.eu/blog/automatic-query-invalidation-after-mutations).
|
|
103
|
+
Configure the shared cache once, so the default applies to every mutation:
|
|
197
104
|
|
|
198
105
|
```ts
|
|
199
|
-
// query-client.ts
|
|
200
106
|
export function createQueryClient() {
|
|
201
107
|
const queryClient = new QueryClient({
|
|
202
|
-
defaultOptions: {
|
|
203
|
-
mutations: { meta: { autoInvalidate: true } }
|
|
204
|
-
},
|
|
108
|
+
defaultOptions: { mutations: { meta: { autoInvalidate: true } } },
|
|
205
109
|
mutationCache: new MutationCache({
|
|
206
110
|
onSuccess: async (_data, _variables, _context, mutation) => {
|
|
207
111
|
if (!mutation.meta?.autoInvalidate) return;
|
|
@@ -216,6 +120,6 @@ export function createQueryClient() {
|
|
|
216
120
|
|
|
217
121
|
## Testing the data layer
|
|
218
122
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
123
|
+
Stub `fetch` at the transport boundary and run the real query client. Test schemas, transport normalization, and query
|
|
124
|
+
options where they encode decisions. Schema cases belong beside the schema; the general test boundary is in
|
|
125
|
+
[testing.md](testing.md).
|
|
@@ -1,40 +1,34 @@
|
|
|
1
1
|
# Documentation structure
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Keep repository docs useful to a human who has just joined the project. Invoke `/documentation-writer` for new or updated
|
|
4
|
+
documentation. The setup skill can propose the shape; the audit skill can check it.
|
|
4
5
|
|
|
5
6
|
## The shape
|
|
6
7
|
|
|
7
|
-
```
|
|
8
|
+
```text
|
|
8
9
|
<app>/
|
|
9
|
-
README.md
|
|
10
|
-
AGENTS.md
|
|
10
|
+
README.md # what the app is, setup, and scripts
|
|
11
|
+
AGENTS.md # short pointers for agents
|
|
11
12
|
docs/
|
|
12
|
-
index.md
|
|
13
|
-
<topic>.md
|
|
13
|
+
index.md # index of repo-specific docs
|
|
14
|
+
<topic>.md
|
|
14
15
|
```
|
|
15
16
|
|
|
16
17
|
## Repo docs hold only what is unique to the repo
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- Write deviations as **named deviations** — what the baseline prescribes, what this repo does instead, and why: a deviation
|
|
21
|
-
written as freestanding convention gets copied into the next repo as house style.
|
|
19
|
+
If a rule should be true in every ImFusion frontend, keep it in the shipped conventions skill. Repo docs should explain local
|
|
20
|
+
decisions and deviations. A deviation names the baseline, the local choice, and why it exists.
|
|
22
21
|
|
|
23
22
|
## How docs are written
|
|
24
23
|
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
arrive at.
|
|
32
|
-
- **Boy Scout rule.** Discovered rot (a stale pointer, a doc contradicting the code) is always your responsibility: fix it in
|
|
33
|
-
place if trivial, otherwise report it. Equal failure modes: stepping over rot, and cramming unrelated cleanup into an
|
|
34
|
-
unrelated change.
|
|
24
|
+
- Give each page one reader goal.
|
|
25
|
+
- Use plain language and active verbs.
|
|
26
|
+
- Explain a reason when it helps someone make the right choice.
|
|
27
|
+
- Let code own changing facts such as scripts, types, token names, and config. Link to the source instead of copying it.
|
|
28
|
+
- State the current situation. History belongs in commits and tickets.
|
|
29
|
+
- Use examples that a reader could adapt, not placeholder prose.
|
|
35
30
|
|
|
36
31
|
## Staleness at commit time
|
|
37
32
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
- Pre-commit carries the advisory staleness checks (vendored baseline, docs) — mechanics in [git.md](git.md).
|
|
33
|
+
When a change makes a doc inaccurate, update the doc in the same commit. The repository's commit workflow runs the staged
|
|
34
|
+
docs audit; the check is advisory, but the correction belongs with the change.
|
|
@@ -1,34 +1,29 @@
|
|
|
1
1
|
# Git
|
|
2
2
|
|
|
3
|
+
Use the repository's tracked configuration and keep verification scopes clear.
|
|
4
|
+
|
|
3
5
|
## git:config
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
Run once in a clone:
|
|
6
8
|
|
|
7
|
-
```
|
|
8
|
-
git config core.hooksPath .githooks
|
|
9
|
+
```sh
|
|
10
|
+
git config core.hooksPath .githooks
|
|
11
|
+
git config pull.rebase true
|
|
12
|
+
git config merge.ff only
|
|
9
13
|
```
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
- Check both silent failure modes — config **and** directory: `git:config` never run (hooks exist only on the machine that
|
|
13
|
-
configured by hand), and `core.hooksPath` pointing at a missing directory.
|
|
15
|
+
The hooks are tracked in `.githooks/`. Check both the Git setting and the directory; a missing hook path fails silently.
|
|
14
16
|
|
|
15
17
|
## Verify scopes
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
There are two scopes:
|
|
18
20
|
|
|
19
|
-
- **staged
|
|
20
|
-
|
|
21
|
-
not CI green.
|
|
22
|
-
- **full** — `verify:full`: the build plus every `verify:*` check; what CI runs.
|
|
23
|
-
- One script owns each scope's step list; npm scripts and hooks only launch them.
|
|
24
|
-
- Name by depth, not by occasion: a `preflight` needs explaining and invites a near-identical sibling that drifts into
|
|
25
|
-
"passes locally, fails in CI".
|
|
21
|
+
- **staged**: `verify:staged`, run by pre-commit. It is fast and checks the staged files plus relevant project-wide checks.
|
|
22
|
+
- **full**: `verify:full`, the build and complete verification used by CI.
|
|
26
23
|
|
|
27
|
-
|
|
24
|
+
The scripts under `scripts/verify/` own the step lists. Hooks and npm scripts only launch them.
|
|
28
25
|
|
|
29
|
-
|
|
26
|
+
## Staleness at commit time
|
|
30
27
|
|
|
31
|
-
-
|
|
32
|
-
|
|
33
|
-
- **Docs** — a staged change that invalidates a doc updates it in the same commit ([docs-structure.md](docs-structure.md)).
|
|
34
|
-
An agent commit workflow, where the repo has one, carries a staged docs audit.
|
|
28
|
+
Pre-commit also runs advisory checks for vendored skill markers and documentation drift. If a staged change makes a doc
|
|
29
|
+
stale, update the doc in the same commit. For an outdated installed skill, run `npx web-ui-install` in the consumer project.
|
|
@@ -1,35 +1,44 @@
|
|
|
1
1
|
# Library boundary
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Keep the application behind `@imfusion/web-ui`. The library owns the public component API, implementation packages, and
|
|
4
|
+
integration seams.
|
|
4
5
|
|
|
5
6
|
## Stay behind the library
|
|
6
7
|
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
- Import icons from `@imfusion/web-ui/icons` and render them through its `Icon` component. Direct imports from
|
|
11
|
-
`iconoir-react` are outside the library boundary.
|
|
8
|
+
- Do not import Base UI or another implementation package directly, including its stylesheet.
|
|
9
|
+
- If Web UI is missing a component or part, report the gap instead of reaching around it.
|
|
10
|
+
- Import glyphs from `@imfusion/web-ui/icons` and render them with `Icon`.
|
|
12
11
|
|
|
13
12
|
## Wrap primitives when the app has a reason to
|
|
14
13
|
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
- The wrapper derives its props from the primitive (`React.ComponentProps<typeof Button>`, narrowed or extended) and styles
|
|
18
|
-
itself through the sanctioned seams — never the library's internals.
|
|
19
|
-
- Wrap for a reason — any repeated adaptation counts; a wrapper that only renames a primitive is indirection with no payoff.
|
|
20
|
-
- Components marked `experimental` in the identity index get wrapped **always**, even with nothing added yet — a breaking
|
|
21
|
-
upstream change then lands in one file instead of every call site.
|
|
14
|
+
Create an app-level dumb wrapper when you repeat a default, composition, styling override, accessibility improvement, or API
|
|
15
|
+
restriction. A wrapper that only renames a component adds indirection without helping.
|
|
22
16
|
|
|
23
|
-
|
|
17
|
+
Derive its props from the primitive:
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
```tsx
|
|
20
|
+
type SaveButtonProps = React.ComponentProps<typeof Button> & {
|
|
21
|
+
busy?: boolean;
|
|
22
|
+
};
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Style the wrapper through tokens, your own classes, and the library's public data attributes. Do not target library CSS
|
|
26
|
+
Module classes.
|
|
27
|
+
|
|
28
|
+
An `experimental` primitive should be wrapped once before it is used throughout an app. That gives an API change one place to
|
|
29
|
+
land.
|
|
30
|
+
|
|
31
|
+
## Derive types, don't copy them
|
|
32
|
+
|
|
33
|
+
The library intentionally does not export component `Props` interfaces. Use `React.ComponentProps<typeof Component>` and
|
|
34
|
+
narrow or extend that type locally.
|
|
27
35
|
|
|
28
36
|
## Integrations own their peers
|
|
29
37
|
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
An integration under `@imfusion/web-ui/integrations/*` has an optional peer. Add that peer explicitly to the consumer's
|
|
39
|
+
`package.json`; npm hoisting is not a dependency contract.
|
|
32
40
|
|
|
33
41
|
## Styling crosses the boundary through seams
|
|
34
42
|
|
|
35
|
-
|
|
43
|
+
Use token overrides, your own classes, and `data-imf-ui-component` or state attributes. The complete CSS contract is in
|
|
44
|
+
[styling.md](styling.md).
|