@imfusion/web-ui 0.5.1-dev.53.gb710322e → 0.5.1-dev.6.g9d275fa8

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.
Files changed (97) hide show
  1. package/README.md +20 -59
  2. package/bin/install-skill.js +180 -0
  3. package/dist/code-qBbqAHK-.js +190 -0
  4. package/dist/components/callout/callout.d.ts +1 -1
  5. package/dist/components/chip-link/chip-link.d.ts +1 -1
  6. package/dist/components/code/code.d.ts +18 -10
  7. package/dist/components/input/input.d.ts +1 -3
  8. package/dist/components/typo/typo.d.ts +2 -2
  9. package/dist/hooks/index.d.ts +0 -1
  10. package/dist/index.d.ts +0 -3
  11. package/dist/index.js +5140 -5746
  12. package/dist/integrations/code-highlight/code-highlight.d.ts +6 -8
  13. package/dist/integrations/code-highlight/highlighter.d.ts +3 -32
  14. package/dist/integrations/code-highlight.js +59 -198
  15. package/dist/integrations/image-display-options.js +56 -56
  16. package/dist/meta-B8C51eyL.js +74 -0
  17. package/dist/provider/web-ui-provider.d.ts +1 -4
  18. package/dist/style.css +1 -1
  19. package/dist/{tabs-CYqtw1q9.js → tabs-DqBFSqq6.js} +2 -3
  20. package/package.json +26 -46
  21. package/src/docgen/doc.gen.json +38 -381
  22. package/src/llms/llms.gen.txt +0 -17
  23. package/src/llms/skills/imf-web-ui/SKILL.md +12 -13
  24. package/src/llms/skills/imf-web-ui-components/SKILL.md +3 -56
  25. package/src/llms/skills/imf-web-ui-frontend-patterns/SKILL.md +93 -0
  26. package/src/llms/skills/imf-web-ui-frontend-patterns/references/code-conventions.md +133 -0
  27. package/src/llms/skills/imf-web-ui-frontend-patterns/references/react-patterns.md +94 -0
  28. package/src/llms/skills/imf-web-ui-imfusion-frontend-setup/SKILL.md +201 -0
  29. package/src/llms/skills/imf-web-ui-setup/SKILL.md +37 -67
  30. package/src/llms/skills/imf-web-ui-ux/SKILL.md +4 -4
  31. package/src/llms/skills/imf-web-ui-ux/references/forms.md +2 -2
  32. package/bin/install.js +0 -428
  33. package/bin/install.test.ts +0 -329
  34. package/dist/build/vite-css-module-names/index.d.ts +0 -20
  35. package/dist/build/vite-css-module-names.js +0 -17
  36. package/dist/chunk-DA_OvQe2.js +0 -11
  37. package/dist/code-BRy_lg-h.js +0 -135
  38. package/dist/codegen/gen-icons.d.ts +0 -24
  39. package/dist/components/field/field.d.ts +0 -104
  40. package/dist/components/field/field.meta.d.ts +0 -2
  41. package/dist/components/field/index.d.ts +0 -2
  42. package/dist/components/fieldset/fieldset.d.ts +0 -29
  43. package/dist/components/fieldset/fieldset.meta.d.ts +0 -2
  44. package/dist/components/fieldset/index.d.ts +0 -2
  45. package/dist/components/icon/icon.d.ts +0 -16
  46. package/dist/components/icon/icon.meta.d.ts +0 -2
  47. package/dist/components/icon/index.d.ts +0 -4
  48. package/dist/components/icon/types.d.ts +0 -2
  49. package/dist/docgen/component-sources.d.ts +0 -7
  50. package/dist/hooks/use-resize-observer.d.ts +0 -2
  51. package/dist/icons/catalog.gen.d.ts +0 -8357
  52. package/dist/icons/icon-config-provider.d.ts +0 -8
  53. package/dist/icons/icon-context.d.ts +0 -4
  54. package/dist/icons/icons.gen.d.ts +0 -1672
  55. package/dist/icons/index.d.ts +0 -3
  56. package/dist/icons-BhogvWBV.js +0 -77
  57. package/dist/icons.js +0 -2
  58. package/dist/integrations/code-highlight/language-patterns.d.ts +0 -7
  59. package/dist/integrations/code-highlight/languages/cmake.d.ts +0 -1
  60. package/dist/integrations/code-highlight/languages/cpp.d.ts +0 -1
  61. package/dist/integrations/code-highlight/languages/python.d.ts +0 -1
  62. package/dist/llms/gen-tokens.d.ts +0 -7
  63. package/dist/meta-BwCupYyq.js +0 -64
  64. package/src/llms/icon-catalog.gen.json +0 -11203
  65. package/src/llms/install-templates/AGENTS.md +0 -34
  66. package/src/llms/install-templates/codex-hooks.json +0 -44
  67. package/src/llms/install-templates/hooks/baseline-staleness.sh +0 -17
  68. package/src/llms/install-templates/hooks/session-start.sh +0 -5
  69. package/src/llms/install-templates/hooks/stop.sh +0 -18
  70. package/src/llms/install-templates/hooks/subagent-start.sh +0 -5
  71. package/src/llms/install-templates/hooks/user-prompt-submit.sh +0 -5
  72. package/src/llms/install-templates/settings.json +0 -45
  73. package/src/llms/skills/imf-web-ui-audit/SKILL.md +0 -119
  74. package/src/llms/skills/imf-web-ui-conventions/SKILL.md +0 -57
  75. package/src/llms/skills/imf-web-ui-conventions/templates/AUDIT_CHECKLIST.md +0 -141
  76. package/src/llms/skills/imf-web-ui-conventions/templates/REPORT.md +0 -45
  77. package/src/llms/skills/imf-web-ui-conventions/topics/agent-tooling.md +0 -82
  78. package/src/llms/skills/imf-web-ui-conventions/topics/assets.md +0 -27
  79. package/src/llms/skills/imf-web-ui-conventions/topics/authentication.md +0 -65
  80. package/src/llms/skills/imf-web-ui-conventions/topics/class-names.md +0 -50
  81. package/src/llms/skills/imf-web-ui-conventions/topics/components.md +0 -101
  82. package/src/llms/skills/imf-web-ui-conventions/topics/data.md +0 -221
  83. package/src/llms/skills/imf-web-ui-conventions/topics/docs-structure.md +0 -40
  84. package/src/llms/skills/imf-web-ui-conventions/topics/git.md +0 -34
  85. package/src/llms/skills/imf-web-ui-conventions/topics/library-boundary.md +0 -35
  86. package/src/llms/skills/imf-web-ui-conventions/topics/library-setup.md +0 -26
  87. package/src/llms/skills/imf-web-ui-conventions/topics/npm-project.md +0 -53
  88. package/src/llms/skills/imf-web-ui-conventions/topics/project-structure.md +0 -44
  89. package/src/llms/skills/imf-web-ui-conventions/topics/react.md +0 -109
  90. package/src/llms/skills/imf-web-ui-conventions/topics/styling.md +0 -88
  91. package/src/llms/skills/imf-web-ui-conventions/topics/testing.md +0 -25
  92. package/src/llms/skills/imf-web-ui-conventions/topics/tokens.md +0 -7
  93. package/src/llms/skills/imf-web-ui-conventions/topics/tooling.md +0 -116
  94. package/src/llms/skills/imf-web-ui-conventions/topics/typescript.md +0 -73
  95. package/src/llms/skills/imf-web-ui-conventions/topics/validation.md +0 -62
  96. package/src/llms/skills/imf-web-ui-update/SKILL.md +0 -157
  97. package/src/llms/tokens.gen.json +0 -887
@@ -1,65 +0,0 @@
1
- # Authentication
2
-
3
- One route-protection shape for every frontend; provider, session mechanism, endpoint paths, and login/logout transport stay
4
- project-owned — no provider is prescribed or named.
5
-
6
- ## Starter shape
7
-
8
- - Authentication sits at the route-group boundary, before child routes render.
9
- - `_public/` and `_app/`: pathless TanStack Router groups — guards and layouts without `public`/`app` in the URL.
10
- - The authenticated group keeps this shape even without an `AppShell`.
11
-
12
- ```text
13
- src/
14
- api/auth/ # getUser query options, keys.ts, types.ts, index.ts barrel
15
- lib/auth/
16
- login-url.ts # pure login navigation helper; not an API topic file
17
- login-url.test.ts # focused helper tests
18
- routes/
19
- __root.tsx # global Outlet and error/not-found boundaries; no auth guard
20
- _public/route.tsx # anonymous route group
21
- _app/route.tsx # authenticated route group
22
- ```
23
-
24
- ## One current-user query
25
-
26
- - One identity source of truth: a server-backed current-user query; never copy session credentials or access tokens into
27
- React state.
28
- - The auth topic follows the default [data.md](data.md) shape: `getUser` in `api/auth/queries.ts`, key leading with the
29
- `auth` topic, schema in `types.ts`.
30
- - `lib/auth/login-url.ts`: project-owned navigation helper, not an options factory; tests colocated.
31
- - Reached as `context.api.auth.getUser()`; returns query options, never a hook or a user value — callers pick
32
- `ensureQueryData` or a query hook.
33
- - Never infer authentication from local storage, a decoded token, a route flag, or permission-gated chrome — stale or
34
- forgeable; the server response and its schema define the current user.
35
-
36
- ## The two route groups
37
-
38
- Both resolve the current-user query in `beforeLoad` when they need the answer; only the meaning of a 401 differs:
39
-
40
- | Group | 401 means | Result |
41
- | ---------- | ----------------- | ------------------------------------------- |
42
- | `_app/` | not logged in | navigate to the server-owned login endpoint |
43
- | `_public/` | anonymous visitor | continue with `user: null` |
44
-
45
- - The `_app/route.tsx` guard awaits the query before children render, converts only an authentication 401 into login
46
- navigation, rethrows router redirects, and lets 5xx, connection failures, and schema mismatches reach the error boundary.
47
- - A public landing route may redirect an authenticated user into `_app/`.
48
- - The root route stays neutral: global `Outlet` and error/not-found boundaries, no public/authenticated decision.
49
-
50
- ## Login and logout
51
-
52
- - Follow the project's documented transport. Server-owned flow: login is a browser navigation, not a Query fetch; logout is
53
- the server's documented state-changing action, not an ad-hoc client request.
54
- - Preserve a validated same-origin return path when the server supports returning to the interrupted route.
55
- - Never hard-code an endpoint shape or provider into shared frontend conventions.
56
- - No intermediate login route when the server owns the flow.
57
- - A failed API request is an auth failure only when its typed error is specifically a 401.
58
-
59
- ## App shell
60
-
61
- - `AppShell` is authenticated chrome, not the authentication mechanism.
62
- - Greenfield default: propose a minimal shell — ImFusion logo, route navigation, stable session action area around the
63
- `_app/` outlet.
64
- - An explicit no-persistent-navigation decision may omit the shell; the `_app/` guard stays.
65
- - An established project keeps its working choice; public pages may use a small branded header.
@@ -1,50 +0,0 @@
1
- # Class names in components
2
-
3
- How a component turns props into a `className` string. What the build does with the result: [tooling.md](tooling.md); what
4
- goes in the stylesheet: [styling.md](styling.md).
5
-
6
- ## CVA is the only tool
7
-
8
- - `class-variance-authority` maps variant props to CSS Module classes. No `clsx`, no `tailwind-merge`, no local `cn()`
9
- helper.
10
- - `cx` is CVA's own concatenator, re-exported by `@imfusion/web-ui` — import it from the library alongside the components.
11
- - Base class is CVA's first argument; variant values are CSS Module references, never string literals. A boolean axis uses
12
- `null` for its off-state.
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.
15
-
16
- ```tsx
17
- interface Props extends HTMLAttributes<HTMLSpanElement> {
18
- appearance?: "outline" | "solid";
19
- inline?: boolean;
20
- }
21
-
22
- const chip = cva(classes.root, {
23
- variants: {
24
- appearance: { outline: classes.appearanceOutline, solid: classes.appearanceSolid },
25
- inline: { false: null, true: classes.inline }
26
- }
27
- });
28
-
29
- export function Chip({ appearance = "outline", inline = false, className, ...props }: Props) {
30
- return <span {...props} className={chip({ appearance, inline, className })} />;
31
- }
32
- ```
33
-
34
- ## Merging `className`
35
-
36
- - Every component that accepts `className` merges it; a deliberately closed surface omits the prop entirely rather than
37
- accepting and ignoring it.
38
- - The incoming `className` goes into CVA's `className` slot, which appends it last so a caller's class always wins. With no
39
- variants to map, `cx(classes.inline, className)` does the same job.
40
- - Resolve a function-form `className` before merging — components built on a library that passes render state
41
- (`className={state => …}`) receive either shape:
42
-
43
- ```tsx
44
- className={state => cx(classes.root, typeof className === "function" ? className(state) : className)}
45
- ```
46
-
47
- ## Shared CVA modules
48
-
49
- - Two components sharing one visual share one CVA module, a `{name}.cva.ts` next to them, so their variant axes can't drift
50
- apart.
@@ -1,101 +0,0 @@
1
- # Components
2
-
3
- How a component lives on disk and how dumb it stays. Roles, state, effects: [react.md](react.md); styling:
4
- [styling.md](styling.md).
5
-
6
- ## As dumb as possible
7
-
8
- - Props-fed, never self-fetching: no query hook, no store access, no route awareness — render what's given, report events
9
- upward.
10
- - Presentational logic (formatting a label, deriving a display state) is fine; owning data is not.
11
- - Local UI state (`isOpen`, a draft value) is allowed; anything whose truth lives elsewhere is not ([react.md](react.md)).
12
-
13
- ```tsx
14
- // components/user-card/user-card.tsx
15
- interface Props {
16
- name: string;
17
- onEdit: () => void;
18
- }
19
-
20
- export function UserCard({ name, onEdit }: Props) {
21
- return (
22
- <Card.Root>
23
- <Typo>{name}</Typo>
24
- <Button onClick={onEdit}>Edit</Button>
25
- </Card.Root>
26
- );
27
- }
28
- ```
29
-
30
- ## Grouping
31
-
32
- - `components/` groups by kind (the component roles in [react.md](react.md)) — a layout component under `layouts/`, not
33
- beside a domain widget.
34
- - Flat is fine while there are few; let grouping follow what the project has, don't impose it up front.
35
-
36
- ## One file or a folder
37
-
38
- - More than one file (sub-components, styles, tests) → a folder with an `index.ts` that only re-exports; single-file
39
- components stay single files, directly in their group.
40
- - Imports read `#/components/data-table`, so the inside can be restructured without touching call sites.
41
-
42
- The full anatomy of a folder component:
43
-
44
- ```
45
- components/data-table/
46
- data-table.tsx # the component: props type + export, CVA variants when it has axes
47
- data-table-row.tsx # sub-component, private unless index.ts re-exports it
48
- data-table.module.css # co-located stylesheet (styling.md)
49
- data-table.test.tsx # only when the component carries decisions worth testing (testing.md)
50
- index.ts # re-exports only
51
- ```
52
-
53
- Most components need only a subset — start with the `.tsx`, add files as they earn their place.
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
- ```
96
-
97
- ## Colocation
98
-
99
- - Stylesheet: `<component>.module.css` next to the component; only dumb components have one — a container that wants CSS is
100
- asking for a layout component instead ([react.md](react.md)).
101
- - Tests and local types sit next to their subject: a file hunted for in a parallel tree gets edited less carefully.
@@ -1,221 +0,0 @@
1
- # Data
2
-
3
- The in-house pattern **for TanStack Query + Router**, the default stack ([tooling.md](tooling.md)); other data layers keep
4
- the boundary principles — validate at the edge, errors as values — not this file layout.
5
-
6
- ## The shape
7
-
8
- - One folder per API topic: options factories, key factory, Zod schemas together, so a query key is never spelled out at a
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.
18
-
19
- ```text
20
- src/
21
- api/
22
- topics.ts # one topic registry, reused by keys and namespaces
23
- index.ts # topic barrels composed as api.<topic>
24
- <topic>/
25
- index.ts # barrel: named re-exports of what other modules call
26
- keys.ts # createApiKeys — query and mutation keys
27
- queries.ts # query options factories — present when the topic has queries
28
- mutations.ts # mutation options factories — present when the topic has mutations
29
- types.ts # Zod schemas + z.infer types
30
- types.test.ts # schema cases, when the schema encodes product behaviour
31
- http/ # transport: client, error normalisation — the only transport-aware place
32
- lib/ # framework-free helpers shared by API topics and other callers
33
- ```
34
-
35
- ## The network boundary validates
36
-
37
- - `http/` returns `unknown`; the topic schema parses the response in its query or mutation function and sources the
38
- TypeScript type. Full rules: [validation.md](validation.md).
39
-
40
- ## Errors are values
41
-
42
- - A failed request becomes an `ApiError` with a machine-readable code — a value crossing the boundary, not an ad-hoc caught
43
- exception; the frontend maps codes to what the user reads.
44
-
45
- ## Topic registry and key factory
46
-
47
- - One registry of topic names, reused for the barrel's namespaces and the first segment of every query and mutation key.
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.
50
-
51
- ```ts
52
- // api/topics.ts — one list of topic names, ApiTopic derived from it
53
- export const API_TOPICS = { AUTH: "auth", USER: "user", ORDERS: "orders" } as const;
54
- export type ApiTopic = (typeof API_TOPICS)[keyof typeof API_TOPICS];
55
- export type QueryKey<Topic extends ApiTopic> = readonly [Topic, ...ReadonlyArray<unknown>] | readonly [Topic];
56
- export type QueryKeyFactory<Topic extends ApiTopic> = (...args: never[]) => QueryKey<Topic>;
57
-
58
- export function createApiKeys<
59
- Topic extends ApiTopic,
60
- QueryKeys extends Record<string, QueryKey<Topic> | QueryKeyFactory<Topic>>,
61
- MutationKeys extends Record<string, QueryKey<Topic>>
62
- >(topic: Topic, keys: { queries: QueryKeys; mutations: MutationKeys }) {
63
- return { topic: [topic] as const, queries: keys.queries, mutations: keys.mutations };
64
- }
65
- ```
66
-
67
- ```ts
68
- // api/user/keys.ts — createApiKeys enforces topic-first
69
- import { API_TOPICS, createApiKeys } from "../topics";
70
-
71
- export const keys = createApiKeys(API_TOPICS.USER, {
72
- queries: { details: ["user", "details"], byId: (id: string) => ["user", "detail", id] },
73
- mutations: { update: ["user", "update"] }
74
- });
75
-
76
- // api/user/index.ts — the topic barrel
77
- export * from "./keys";
78
- export * from "./queries";
79
- export * from "./mutations";
80
- ```
81
-
82
- ```ts
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
- ```
97
-
98
- ## Options factories and naming
99
-
100
- - Topic functions return **options** — never a hook, never data; the caller picks `ensureQueryData` or `useSuspenseQuery`.
101
- - No topic prefix, no `Options` suffix: `context.api.<topic>.<fn>()` carries both.
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).
106
-
107
- ```ts
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";
113
-
114
- export function getDetails(args: { options?: Omit<QueryOptions<User>, "queryKey" | "queryFn"> } = {}) {
115
- return queryOptions({
116
- ...args.options,
117
- queryKey: keys.queries.details,
118
- queryFn: async () => userSchema.parse(await bffClient("/me"))
119
- });
120
- }
121
- ```
122
-
123
- ```ts
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
- ```
139
-
140
- ## Router context access
141
-
142
- - Loaders call `context.api.<topic>.<fn>()`; components use `Route.useRouteContext()`.
143
- - `createRouter` seeds a placeholder `queryClient`; `router.update()` fills it once the client exists — required because the
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";
151
-
152
- interface RouterContext {
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
- });
175
-
176
- function UserPage() {
177
- const context = Route.useRouteContext();
178
- const user = useSuspenseQuery(context.api.user.getDetails());
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
- ```
187
-
188
- ## Automatic invalidation
189
-
190
- - No hand invalidation at call sites: a `MutationCache` `onSuccess` invalidates `mutationKey[0]` centrally, on by default via
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).
197
-
198
- ```ts
199
- // query-client.ts
200
- export function createQueryClient() {
201
- const queryClient = new QueryClient({
202
- defaultOptions: {
203
- mutations: { meta: { autoInvalidate: true } }
204
- },
205
- mutationCache: new MutationCache({
206
- onSuccess: async (_data, _variables, _context, mutation) => {
207
- if (!mutation.meta?.autoInvalidate) return;
208
- const topic = mutation.options.mutationKey?.[0];
209
- if (topic !== undefined) await queryClient.invalidateQueries({ queryKey: [topic] });
210
- }
211
- })
212
- });
213
- return queryClient;
214
- }
215
- ```
216
-
217
- ## Testing the data layer
218
-
219
- - Stub the network at the `fetch` boundary and run the real query client: same parse and error path as production.
220
- - Assert behaviour in schemas, clients, and query options — schema cases: [validation.md](validation.md), philosophy:
221
- [testing.md](testing.md).
@@ -1,40 +0,0 @@
1
- # Documentation structure
2
-
3
- What a repo documents, where, and how. `imf-web-ui-setup` scaffolds this shape from its `templates/` and audits it.
4
-
5
- ## The shape
6
-
7
- ```
8
- <app>/
9
- README.md # humans: what the app is, setup, scripts
10
- AGENTS.md # agents: tooling and architecture pointers into docs/ — restates nothing
11
- docs/
12
- index.md # registers every doc with a one-line "covers" summary
13
- <topic>.md # repo-unique content only
14
- ```
15
-
16
- ## Repo docs hold only what is unique to the repo
17
-
18
- - Litmus, per sentence: would this be true in every ImFusion frontend? Then it's baseline — don't restate it; it's already
19
- vendored in-repo under `.agents/skills/imf-web-ui-conventions/`, human-readable and versioned.
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.
22
-
23
- ## How docs are written
24
-
25
- - **Docs explain concepts; code is the source of truth for facts.** Capture the why — invariants, rationale, decisions. Never
26
- restate a fact that lives in code (a script definition, a type shape, a config value); point at the file — a copied fact
27
- rots the moment the code changes.
28
- - **State what is — no decision residue.** Present-tense statements about the current state; never narrate the delta from a
29
- past decision or refute alternatives nobody raised ("there is no X mode", "Y was dropped") — history belongs in commits and
30
- tickets. A negation earns its place only as a guardrail or to preempt a wrong assumption a present reader would actually
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.
35
-
36
- ## Staleness at commit time
37
-
38
- - Docs are checked when they can go stale: at the commit. A staged change that invalidates a doc (a renamed script, a moved
39
- folder, a changed flow) updates it **in the same commit**, never a follow-up.
40
- - Pre-commit carries the advisory staleness checks (vendored baseline, docs) — mechanics in [git.md](git.md).
@@ -1,34 +0,0 @@
1
- # Git
2
-
3
- ## git:config
4
-
5
- - One script holds the repo's git configuration, run by hand once per clone (the README names it):
6
-
7
- ```
8
- git config core.hooksPath .githooks && git config pull.rebase true && git config merge.ff only
9
- ```
10
-
11
- - Hooks live in a tracked directory; history strategy doesn't depend on personal git config.
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.
14
-
15
- ## Verify scopes
16
-
17
- Two scopes, both blocking:
18
-
19
- - **staged** — `verify:staged`, called by the pre-commit hook: lint, format, restage, plus the relevant project-wide checks,
20
- including `verify:knip` when staged source or project config can change the reachability graph. Fast; a passing commit is
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".
26
-
27
- ## Staleness at commit time
28
-
29
- Pre-commit also runs the advisory staleness checks — warn, never block:
30
-
31
- - **Vendored baseline** — `.agents/hooks/imf-web-ui/baseline-staleness.sh` compares installed `imf-web-ui-*` skill markers
32
- against the installed package version; the fix it names is `npx web-ui-install`.
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.
@@ -1,35 +0,0 @@
1
- # Library boundary
2
-
3
- The contract between an app and `@imfusion/web-ui`.
4
-
5
- ## Stay behind the library
6
-
7
- - Never import Base UI (or any other upstream the library wraps) directly — no upstream stylesheets, no upstream components,
8
- even when upstream docs show it that way: everything a component needs ships in `@imfusion/web-ui`.
9
- - Library missing something upstream has → report the gap (see `imf-web-ui-components`); don't reach around it.
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.
12
-
13
- ## Wrap primitives when the app has a reason to
14
-
15
- - Repeated adaptation around a primitive (default props, a styling override, a composition, an accessibility refinement, a
16
- restriction of the API) → wrap it once in an app-level dumb component in `components/` ([components.md](components.md)).
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.
22
-
23
- ## Derive types, don't import them
24
-
25
- - Prop types come from the components themselves: `React.ComponentProps<typeof Button>`.
26
- - The library deliberately exports no `Props` types — don't look for them, don't re-declare prop shapes by hand.
27
-
28
- ## Integrations own their peers
29
-
30
- - `@imfusion/web-ui/integrations/*` components depend on optional peers (e.g. `@tanstack/highlight` for `CodeHighlight`). Add
31
- the peer explicitly to the consumer's `package.json` — never rely on hoisting.
32
-
33
- ## Styling crosses the boundary through seams
34
-
35
- - Tokens in, sanctioned selectors at the edge, never the library's internals — the full contract is [styling.md](styling.md).
@@ -1,26 +0,0 @@
1
- # Library setup
2
-
3
- Library wiring (styles import + `WebUIProvider`) for anyone using `@imfusion/web-ui` — `imf-web-ui-setup library-setup`
4
- proposes a bootstrap change, `imf-web-ui-audit library-setup` is the read-only health check.
5
-
6
- ## Entry-point wiring
7
-
8
- - Every consumer entry point has exactly two lines, in this order:
9
-
10
- ```tsx
11
- import "@imfusion/web-ui/styles.css";
12
- import { WebUIProvider, Button } from "@imfusion/web-ui";
13
- ```
14
-
15
- - Wrap the app root in `<WebUIProvider>` once — components rendered outside it lack the theme and CSS-variable context they
16
- expect.
17
- - Never import a Base UI (or other upstream) stylesheet or component directly — everything a web-ui component needs is inside
18
- `styles.css` and the package's own exports. What may be reached for when the library lacks a component:
19
- [library boundary](library-boundary.md).
20
-
21
- ## Symptoms of broken setup
22
-
23
- - Components render but look unstyled → the `styles.css` import is missing from the entry point.
24
- - Components render but ignore the theme (wrong colors, CSS variables not resolving) → mounted outside `<WebUIProvider>`.
25
- - An integration component throws on import → its optional peer dependency is not installed; check the component's
26
- description in the docgen index (`imf-web-ui-components`) for which peer to add to `package.json`.
@@ -1,53 +0,0 @@
1
- # npm project
2
-
3
- The npm side of an ImFusion frontend: `package.json`, scripts, dependencies. `imf-web-ui-setup` audits against this file.
4
-
5
- ## package.json
6
-
7
- ```jsonc
8
- {
9
- "name": "@imfusion/scan-review",
10
- "type": "module",
11
- "private": true, // only when the package is not meant to be published
12
- "engines": { "node": ">=22" },
13
- "imports": { "#/*": "./src/*" }
14
- }
15
- ```
16
-
17
- - `"type": "module"` always.
18
- - `"private": true` only for apps that never publish.
19
- - Pin Node via `engines.node` or `.nvmrc` — never a personal version manager's config.
20
- - Wire the `#/` alias through `imports` ([project-structure.md](project-structure.md)).
21
-
22
- ## Scripts
23
-
24
- Same name, same meaning, every repo — "what can I run to check this?" is tab-completing `verify:`.
25
-
26
- | Script | Runs |
27
- | ------------------ | --------------------------------------------------------------------- |
28
- | `dev` | dev server |
29
- | `build` | production build |
30
- | `verify:deps` | exact-pin check over `dependencies` and `devDependencies` |
31
- | `verify:format` | `prettier --check .` |
32
- | `verify:lint` | `eslint . --cache --max-warnings=0` |
33
- | `verify:typecheck` | `tsc --noEmit` (or `tsc -b --noEmit` in a project-references setup) |
34
- | `verify:tests` | `vitest run` |
35
- | `verify:knip` | `knip` — dead-code and unused-export check |
36
- | `verify:staged` | staged-file subset, called by the pre-commit hook |
37
- | `verify:full` | every `verify:*` check plus the build; what CI runs |
38
- | `format` | `prettier --write .` |
39
- | `lint` | `eslint . --cache --fix` |
40
- | `git:config` | see [git.md](git.md); run by hand once per clone, named in the README |
41
-
42
- - **Every check is `verify:*`** — one namespace for everything that reads and reports.
43
- - **Write-mode scripts keep the tool name** (`format`, `lint`): changing files isn't verifying — no prefix, no `:fix` suffix.
44
- - `verify:staged` is fast and partial: staged-file format/lint plus the relevant project-wide checks for staged source or
45
- config changes, including `verify:knip` — a passing commit is not CI green.
46
- - `verify:full` is the CI gate.
47
-
48
- ## Dependencies
49
-
50
- - **Pin exactly** — no `^`, `~`, or `latest`, in `dependencies` and `devDependencies` alike: `verify:deps` catches drift,
51
- `save-exact=true` in `.npmrc` prevents it.
52
- - **`ignore-scripts=true` in `.npmrc`** — blocks lifecycle scripts on install (the supply-chain vector); setup that matters
53
- is a command someone runs, not a hook that fires on install.