@imfusion/web-ui 0.6.1-dev.12.ge86ac0a1 → 0.6.1-dev.14.g8fac1dfb
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/README.md +102 -170
- package/dist/{code-Blo48PGr.js → code-C_56u-Vk.js} +2 -2
- package/dist/{icons-wBmF0U2x.js → icons-Cy1HAosO.js} +1 -1
- package/dist/icons.js +1 -1
- package/dist/index.js +31 -31
- package/dist/integrations/code-highlight.js +2 -2
- package/dist/integrations/image-display-options.js +1 -1
- package/package.json +1 -1
- package/src/llms/install-templates/AGENTS.md +15 -18
- package/src/llms/llms.gen.txt +33 -33
- package/src/llms/skills/imf-web-ui/SKILL.md +29 -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 +39 -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 +43 -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
|
@@ -1,44 +1,42 @@
|
|
|
1
1
|
# Project structure
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use kebab-case for folders and files. Exported symbols use PascalCase.
|
|
4
4
|
|
|
5
5
|
## Layout
|
|
6
6
|
|
|
7
|
-
```
|
|
7
|
+
```text
|
|
8
8
|
src/
|
|
9
|
-
main.tsx
|
|
10
|
-
routes/
|
|
11
|
-
__root.tsx
|
|
12
|
-
_public/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
components/ # grouped by kind — anatomy in components.md
|
|
19
|
-
app-shell/ # optional persistent authenticated chrome: brand, navigation, and session action
|
|
20
|
-
http/ # transport: client, error normalisation — the only transport-aware place (data.md)
|
|
21
|
-
lib/ # framework-free helpers, each with a colocated .test.ts when it has decisions to test
|
|
22
|
-
auth/
|
|
23
|
-
login-url.ts
|
|
24
|
-
login-url.test.ts
|
|
9
|
+
main.tsx
|
|
10
|
+
routes/
|
|
11
|
+
__root.tsx
|
|
12
|
+
_public/route.tsx
|
|
13
|
+
_app/route.tsx
|
|
14
|
+
api/
|
|
15
|
+
http/
|
|
16
|
+
lib/
|
|
17
|
+
components/
|
|
25
18
|
```
|
|
26
19
|
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
`main.tsx` owns providers and router setup. Routes compose pages and their route data; API topics own data options and
|
|
21
|
+
schemas; `http/` owns transport; `lib/` holds framework-free helpers; `components/` holds UI.
|
|
22
|
+
|
|
23
|
+
The authenticated route group may contain the persistent `AppShell`. The full data layout is in [data.md](data.md), and
|
|
24
|
+
component folders are in [components.md](components.md).
|
|
29
25
|
|
|
30
26
|
## Application boundary
|
|
31
27
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
around the route `Outlet`.
|
|
36
|
-
- Omit the shell only when the human explicitly says the product has no persistent authenticated navigation; the `_app/`
|
|
37
|
-
guard stays.
|
|
38
|
-
- An established project preserves its working choice; public pages can use a small branded header instead.
|
|
28
|
+
Create the root providers, current-user boundary, and pathless `_public/` and `_app/` groups before adding feature routes. A
|
|
29
|
+
new app starts `_app/` with a minimal `AppShell` unless it has no persistent authenticated navigation. An established app
|
|
30
|
+
keeps its working shell choice.
|
|
39
31
|
|
|
40
32
|
## Imports
|
|
41
33
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
34
|
+
Use `./` for siblings and `#/` for imports elsewhere in `src/`:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { Button } from "#/components/button";
|
|
38
|
+
import { formatDate } from "./format-date";
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Wire `#/` once through `package.json`'s `imports` field and the matching TypeScript paths. It uses Node's `#` subpath prefix,
|
|
42
|
+
so it cannot conflict with an npm package scope.
|
|
@@ -1,90 +1,52 @@
|
|
|
1
1
|
# React
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Keep data flow one-way and components focused. Pages decide how data is loaded; presentational components decide how it
|
|
4
|
+
looks.
|
|
5
5
|
|
|
6
6
|
## Composition: pages (smart containers), partials, dumb components
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- **
|
|
14
|
-
|
|
15
|
-
data down. Zero styling — a page wanting CSS (two panels side by side) means a layout component, not an inline style. A
|
|
16
|
-
standalone container earns its place only off-route (a modal fetching its own data).
|
|
17
|
-
- **Partials** are pure composition: reusable units of dumb and layout components with light logic and zero styling of their
|
|
18
|
-
own, extracted because they read as a unit. A partial with a CSS file is a red flag — the styled element wants to be its
|
|
19
|
-
own dumb component, or the wrapper wants to be a layout component.
|
|
20
|
-
- **Layout components** own _arrangement_ and nothing else: `Stack`-/`Row`-based wrappers with token gaps, a page grid, a
|
|
21
|
-
section frame.
|
|
22
|
-
- **Dumb components** own how things _look_: style and compose library primitives, take plain data and callbacks as props; no
|
|
23
|
-
fetching, routing, or business logic. All non-layout styling lives here and only here — logic-free dumb components (the
|
|
24
|
-
`@imfusion/web-ui` layer) keep screens restylable, testable with plain props, resilient to library updates.
|
|
25
|
-
- Wrapping library primitives in app-level dumb components: [library-boundary.md](library-boundary.md).
|
|
8
|
+
- **Pages** live in `routes/`. They load data, read URL state, coordinate actions, and compose the view. They do not own
|
|
9
|
+
component CSS.
|
|
10
|
+
- **Partials** are reusable compositions extracted because they read as a unit. They have light logic and no styling of their
|
|
11
|
+
own.
|
|
12
|
+
- **Layout components** arrange content with `Stack`, `Row`, grids, and token gaps.
|
|
13
|
+
- **Dumb components** receive data and callbacks, render UI, and own their styles. They do not fetch, route, or contain
|
|
14
|
+
business rules.
|
|
26
15
|
|
|
27
16
|
```tsx
|
|
28
|
-
|
|
29
|
-
function UserCard({ name, role, onEdit }: { name: string; role: string; onEdit: () => void }) {
|
|
17
|
+
function UserCard({ name, onEdit }: { name: string; onEdit: () => void }) {
|
|
30
18
|
return (
|
|
31
19
|
<Card.Root>
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
<Chip>{role}</Chip>
|
|
35
|
-
</Card.Content>
|
|
36
|
-
<Card.Footer>
|
|
37
|
-
<Button onClick={onEdit}>Edit</Button>
|
|
38
|
-
</Card.Footer>
|
|
20
|
+
<Typo.P>{name}</Typo.P>
|
|
21
|
+
<Button onClick={onEdit}>Edit</Button>
|
|
39
22
|
</Card.Root>
|
|
40
23
|
);
|
|
41
24
|
}
|
|
42
25
|
```
|
|
43
26
|
|
|
44
|
-
```tsx
|
|
45
|
-
// Smart — knows where data comes from, renders the dumb component
|
|
46
|
-
function UserCardContainer({ userId }: { userId: string }) {
|
|
47
|
-
const user = useUserQuery(userId);
|
|
48
|
-
const openEditor = useEditorNavigation(userId);
|
|
49
|
-
return <UserCard name={user.data.name} role={user.data.role} onEdit={openEditor} />;
|
|
50
|
-
}
|
|
51
|
-
```
|
|
52
|
-
|
|
53
27
|
## Compose, don't configure
|
|
54
28
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
- Lift state shared between siblings to the nearest common parent
|
|
58
|
-
([Sharing State Between Components](https://react.dev/learn/sharing-state-between-components)); never sync copies.
|
|
29
|
+
Compose small parts instead of making one component configurable through many boolean props. Lift shared state to the nearest
|
|
30
|
+
common parent.
|
|
59
31
|
|
|
60
32
|
## Put state where its truth lives
|
|
61
33
|
|
|
62
|
-
|
|
34
|
+
Choose the first suitable level:
|
|
63
35
|
|
|
64
|
-
1. **URL state
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
4. **Client state** — app-wide and persistent → TanStack Store, and only now: tiers 1–2 usually dissolve the "we need a
|
|
70
|
-
store" instinct.
|
|
71
|
-
5. **Local state** — one component's own (input value, open/closed, a draft, a hover flag) → `useState`. Most state is local;
|
|
72
|
-
keep it in the component, no library.
|
|
36
|
+
1. **URL state**: filters, sort, pagination, and active tab. Use TanStack Router search params and validate them.
|
|
37
|
+
2. **Server state**: API data. Use TanStack Query; do not copy it into `useState`.
|
|
38
|
+
3. **Subtree state**: state shared by a subtree and reset when it leaves. Use React context.
|
|
39
|
+
4. **Client state**: app-wide persistent state. Use TanStack Store only when the earlier levels do not fit.
|
|
40
|
+
5. **Local state**: one component's open state, draft, or input value. Use `useState`.
|
|
73
41
|
|
|
74
|
-
|
|
75
|
-
- Shape the state itself per [Choosing the State Structure](https://react.dev/learn/choosing-the-state-structure) —
|
|
76
|
-
especially its rules on redundant and duplicated state.
|
|
42
|
+
Shape state so it is not redundant or duplicated. Validate a form draft when it becomes a submitted domain value.
|
|
77
43
|
|
|
78
44
|
## Effects: last resort, and named
|
|
79
45
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
- Extract a genuine effect into a custom hook named for its purpose (`useSyncedScroll`, `useDocumentTitle`, `useHotkey`),
|
|
85
|
-
never an anonymous inline `useEffect`: the name documents intent, the hook isolates the dependency array. Pattern:
|
|
86
|
-
[Reusing Logic with Custom Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks).
|
|
87
|
-
- The rare effect that stays inline still names its callback, so intent survives without a comment:
|
|
46
|
+
Use `useEffect` only to synchronize with something outside React. Do not use it for derived values, event responses, or
|
|
47
|
+
server sync.
|
|
48
|
+
|
|
49
|
+
A genuine effect belongs in a named custom hook. If it stays inline, name the callback:
|
|
88
50
|
|
|
89
51
|
```tsx
|
|
90
52
|
useEffect(
|
|
@@ -95,15 +57,7 @@ useEffect(
|
|
|
95
57
|
);
|
|
96
58
|
```
|
|
97
59
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
## Reading list
|
|
101
|
-
|
|
102
|
-
Consult while building; each is the authority for its topic:
|
|
60
|
+
Destructure a primitive before putting it in an effect dependency array. Keep the returned query or route object intact
|
|
61
|
+
elsewhere so its namespace remains visible.
|
|
103
62
|
|
|
104
|
-
|
|
105
|
-
- [Keeping Components Pure](https://react.dev/learn/keeping-components-pure) — why dumb components stay dumb
|
|
106
|
-
- [Choosing the State Structure](https://react.dev/learn/choosing-the-state-structure) — shaping state without duplication
|
|
107
|
-
- [Sharing State Between Components](https://react.dev/learn/sharing-state-between-components) — lifting state
|
|
108
|
-
- [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect) — the effect misuse catalog
|
|
109
|
-
- [Reusing Logic with Custom Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) — named effects live here
|
|
63
|
+
Read the React documentation when a decomposition or effect decision is unclear.
|
|
@@ -1,88 +1,91 @@
|
|
|
1
1
|
# Styling
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use native CSS and Web UI's public seams. The library's CSS Modules and token names are not consumer implementation details.
|
|
4
4
|
|
|
5
5
|
## CSS authoring
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
- Nest pseudo-elements,
|
|
7
|
+
- Use CSS Modules with native nesting. Do not add Sass, CSS-in-JS, or a utility-class framework for ordinary component
|
|
8
|
+
styles.
|
|
9
|
+
- Nest states, pseudo-elements, and child selectors under the root.
|
|
10
|
+
- Lift repeated values into custom properties, especially when a state or variant changes several descendants.
|
|
11
|
+
- Keep structurally different rules explicit; not every repeated value needs an abstraction.
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
```css
|
|
14
|
+
.root {
|
|
15
|
+
transition: opacity var(--imf-ui-duration-quick-2) var(--imf-ui-ease-2);
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
&[data-disabled] {
|
|
20
|
-
opacity: 0.5;
|
|
21
|
-
}
|
|
17
|
+
&:focus-visible {
|
|
18
|
+
outline: var(--imf-ui-border-size-2) solid var(--imf-ui-color-fg-primary);
|
|
22
19
|
}
|
|
23
|
-
```
|
|
24
20
|
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
&[data-disabled] {
|
|
22
|
+
opacity: 0.5;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
29
26
|
|
|
30
27
|
## Build custom UI from tokens
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
Use names from the shipped token index for colors, type, size, radius, and shadow. A literal beside a tokenized concept is a
|
|
30
|
+
defect. Keep a literal only when no token represents it, such as a hairline or a clip-path percentage; give that literal a
|
|
31
|
+
named custom property when it is part of the component's design.
|
|
32
|
+
|
|
33
|
+
Read exact token names from `node_modules/@imfusion/web-ui/src/llms/tokens.gen.json`. Do not invent or copy a token list into
|
|
34
|
+
a consumer project.
|
|
37
35
|
|
|
38
36
|
## Override through the sanctioned seams
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
38
|
+
Library component styles are in `@layer imf-ui.components`. Consumer CSS outside a layer wins. Target:
|
|
39
|
+
|
|
40
|
+
- your own class or wrapper;
|
|
41
|
+
- `data-imf-ui-component` for component identity;
|
|
42
|
+
- the component's documented state attributes.
|
|
43
|
+
|
|
44
|
+
Do not target generated CSS Module class names and do not use `!important` to fight the cascade. `className` is a styling
|
|
45
|
+
hook, not a state flag.
|
|
46
|
+
|
|
47
|
+
```css
|
|
48
|
+
[data-imf-ui-component="Switch"][data-checked] {
|
|
49
|
+
outline: var(--imf-ui-border-size-2) solid var(--imf-ui-color-fg-positive);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
53
52
|
|
|
54
53
|
## The color system
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
three **accent** slots.
|
|
59
|
-
- **Controls** are the 80/20 customization surface — override one and every derived token shifts:
|
|
55
|
+
Use surface roles (`main`, `support`, `minor`), the separate `brand` and `primary` roles, status roles, and the three accent
|
|
56
|
+
slots. Override a high-level control when a whole family should change; consume a semantic token in a component.
|
|
60
57
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
```
|
|
58
|
+
Foreground roles are for text, icons, borders, and focus rings. Background roles are for fills. The provider sets
|
|
59
|
+
`data-imf-ui-color-scheme="light" | "dark"` on `<html>`.
|
|
60
|
+
|
|
61
|
+
## Browser floor
|
|
66
62
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
63
|
+
The library targets Safari 16.2 or newer. `color-mix(in oklch, …)` is available at that floor. Relative color syntax needs a
|
|
64
|
+
guarded fallback for older Safari:
|
|
65
|
+
|
|
66
|
+
```css
|
|
67
|
+
.subtle {
|
|
68
|
+
background: color-mix(in oklch, var(--chip-bg) 15%, transparent);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@supports (color: oklch(from red l c h)) {
|
|
72
|
+
.subtle {
|
|
73
|
+
background: oklch(from var(--chip-bg) l c h / 0.15);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
72
77
|
|
|
73
78
|
## Responsive styling
|
|
74
79
|
|
|
75
|
-
|
|
76
|
-
cross the `@media` boundary, props don't (why sizing is variables, not a `width` prop):
|
|
80
|
+
Override component variables inside your own media queries:
|
|
77
81
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
82
|
+
```css
|
|
83
|
+
@media (min-width: 768px) {
|
|
84
|
+
.shell {
|
|
85
|
+
--imf-ui-appshell-navbar-width: 22rem;
|
|
83
86
|
}
|
|
84
|
-
|
|
87
|
+
}
|
|
88
|
+
```
|
|
85
89
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
- No responsive props — `size={{ sm: … }}` objects are deliberately not offered; write the media query.
|
|
90
|
+
Use literal breakpoint values in consumer CSS; the library's `@custom-media` aliases are build-time only. Use
|
|
91
|
+
`useMediaQuery(minWidth("md"))` for JavaScript behavior, not for styling.
|
|
@@ -1,25 +1,23 @@
|
|
|
1
1
|
# Testing
|
|
2
2
|
|
|
3
|
-
Test
|
|
3
|
+
Test decisions and user behavior, not framework behavior.
|
|
4
4
|
|
|
5
5
|
## Naming
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Colocate tests and name them `*.test.ts` or `*.test.tsx`.
|
|
8
8
|
|
|
9
9
|
## What gets a test
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
- The measure isn't coverage percentage — it's whether a failing test tells you something you didn't already know.
|
|
11
|
+
- Unit-test pure logic such as parsing, permissions, reducers, and date or token calculations.
|
|
12
|
+
- Use an interaction test for behavior a user performs and a static story cannot show.
|
|
13
|
+
- Do not test a presentational component that only maps props onto Web UI or HTML.
|
|
14
|
+
- Extract a hook's decision into a pure function and test that function. A hook that only wraps a browser API has no decision
|
|
15
|
+
to test.
|
|
16
|
+
|
|
17
|
+
A useful test answers something a reader could not know just by reading the implementation. Coverage percentage is not the
|
|
18
|
+
goal.
|
|
20
19
|
|
|
21
20
|
## Layer-specific recipes
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
[validation.md](validation.md).
|
|
22
|
+
For the data layer, stub `fetch` at the transport boundary and run the real query client. For schemas, test accepted,
|
|
23
|
+
rejected, and transformed values when the schema encodes product behavior. Keep both tests next to their source.
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
# Tokens
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use the generated token index when choosing a Web UI token.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Read:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
node_modules/@imfusion/web-ui/src/llms/tokens.gen.json
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
It contains the shipped token names and authored defaults. Look up the exact entry instead of guessing a plausible
|
|
12
|
+
`--imf-ui-*` name or copying a token list into the project.
|
|
@@ -1,50 +1,44 @@
|
|
|
1
1
|
# Tooling
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
config.
|
|
3
|
+
Read this topic before adding a dependency or configuring a tool. Use the tool's current documentation rather than memory.
|
|
5
4
|
|
|
6
5
|
## Topic-to-tool map
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
|
11
|
-
|
|
|
12
|
-
|
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
| Types | `tsc --noEmit` — own script, own CI step |
|
|
24
|
-
| Staged files | [lint-staged](https://github.com/lint-staged/lint-staged) (or nano-staged, drop-in) |
|
|
25
|
-
| Dead code | [Knip](https://knipjs.dev) via `verify:knip` — needs per-repo config |
|
|
7
|
+
| Need | Default |
|
|
8
|
+
| --------------------- | -------------------------------------------- |
|
|
9
|
+
| Build | Vite |
|
|
10
|
+
| Routing and URL state | TanStack Router |
|
|
11
|
+
| Server state | TanStack Query |
|
|
12
|
+
| Forms | TanStack Form |
|
|
13
|
+
| Data grids | TanStack Table plus Web UI `Table` parts |
|
|
14
|
+
| App-wide client state | TanStack Store, after URL/server/local state |
|
|
15
|
+
| Schema validation | Zod at the boundary |
|
|
16
|
+
| Tests | Vitest |
|
|
17
|
+
| Format | Prettier |
|
|
18
|
+
| Lint | ESLint flat config |
|
|
19
|
+
| Types | `tsc --noEmit` |
|
|
20
|
+
| Staged files | lint-staged or nano-staged |
|
|
21
|
+
| Dead code | Knip via `verify:knip` |
|
|
26
22
|
|
|
27
23
|
## When a library owns a layer
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
Use the library that already owns the problem. Adopt Form for form rules, Query for caching and refetching, Router for URL
|
|
26
|
+
state, and Table for sorting and pagination instead of rebuilding those layers. Keep an existing project choice when it
|
|
27
|
+
works.
|
|
32
28
|
|
|
33
29
|
## Devtools
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- Check for a `-devtools` sibling on every new TanStack dependency — not every library has one yet.
|
|
38
|
-
- Once several are in, host them in one panel via `@tanstack/devtools`.
|
|
31
|
+
Add the devtools package for a TanStack library when one exists, mount it only in development, and check for a matching
|
|
32
|
+
devtools package whenever a new TanStack dependency is added.
|
|
39
33
|
|
|
40
34
|
## Docs over memory
|
|
41
35
|
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
Use `npx @tanstack/cli` for TanStack documentation. If TanStack Intent is configured, use it for the Agent Skills the
|
|
37
|
+
TanStack package provides.
|
|
44
38
|
|
|
45
39
|
## Prettier
|
|
46
40
|
|
|
47
|
-
|
|
41
|
+
Use a config file. The repo's values are:
|
|
48
42
|
|
|
49
43
|
```ts
|
|
50
44
|
import type { Config } from "prettier";
|
|
@@ -59,58 +53,36 @@ const config: Config = {
|
|
|
59
53
|
singleQuote: false,
|
|
60
54
|
proseWrap: "always"
|
|
61
55
|
};
|
|
62
|
-
```
|
|
63
56
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
## ESLint
|
|
67
|
-
|
|
68
|
-
Flat config (`eslint.config.ts`):
|
|
69
|
-
|
|
70
|
-
- `strictTypeChecked` + `stylisticTypeChecked`, `projectService: true`
|
|
71
|
-
- `as` and `!` banned outside tests (`consistent-type-assertions`)
|
|
72
|
-
- `.gitignore` as the ignore source (`includeIgnoreFile` from `@eslint/compat`)
|
|
73
|
-
- `#/` alias enforced via `no-restricted-imports` banning `../*` — parent-relative paths error, siblings (`./`) stay relative
|
|
74
|
-
([project-structure.md](project-structure.md))
|
|
75
|
-
|
|
76
|
-
## tsconfig
|
|
77
|
-
|
|
78
|
-
```jsonc
|
|
79
|
-
{
|
|
80
|
-
"compilerOptions": {
|
|
81
|
-
"strict": true,
|
|
82
|
-
"moduleResolution": "bundler",
|
|
83
|
-
"verbatimModuleSyntax": true, // import type stays import type
|
|
84
|
-
"noUnusedLocals": true,
|
|
85
|
-
"noUnusedParameters": true,
|
|
86
|
-
"noFallthroughCasesInSwitch": true,
|
|
87
|
-
"noUncheckedSideEffectImports": true,
|
|
88
|
-
"skipLibCheck": true,
|
|
89
|
-
"paths": { "#/*": ["./src/*"] }
|
|
90
|
-
}
|
|
91
|
-
}
|
|
57
|
+
export default config;
|
|
92
58
|
```
|
|
93
59
|
|
|
94
|
-
|
|
60
|
+
## ESLint and TypeScript
|
|
61
|
+
|
|
62
|
+
Use flat ESLint config with type-aware rules and the repo's ignore file. The baseline bans `as` and non-null `!` outside
|
|
63
|
+
tests, requires the `#/` alias instead of parent-relative imports, and uses project-service type information.
|
|
64
|
+
|
|
65
|
+
Use strict TypeScript with bundler module resolution, `verbatimModuleSyntax`, unused-value checks, and `#/` paths. Keep
|
|
66
|
+
`import type` for type-only imports.
|
|
95
67
|
|
|
96
68
|
## Staged files
|
|
97
69
|
|
|
98
|
-
|
|
99
|
-
|
|
70
|
+
The staged-file runner applies ESLint fixes and Prettier writes only to staged files. It also runs the relevant project
|
|
71
|
+
checks when source or config can affect the dependency graph.
|
|
100
72
|
|
|
101
73
|
## CSS class names
|
|
102
74
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
- The library ships the plugin; pass a short, app-scoped prefix:
|
|
75
|
+
Register Web UI's `readableCssModuleNames` plugin in every compiler that processes the app's CSS, including development,
|
|
76
|
+
Storybook, and production:
|
|
106
77
|
|
|
107
78
|
```ts
|
|
79
|
+
import { defineConfig } from "vite";
|
|
80
|
+
import react from "@vitejs/plugin-react";
|
|
108
81
|
import { readableCssModuleNames } from "@imfusion/web-ui/build/vite-css-module-names";
|
|
109
82
|
|
|
110
83
|
export default defineConfig({
|
|
111
|
-
plugins: [react(), readableCssModuleNames({ prefix: "
|
|
84
|
+
plugins: [react(), readableCssModuleNames({ prefix: "app" })]
|
|
112
85
|
});
|
|
113
86
|
```
|
|
114
87
|
|
|
115
|
-
|
|
116
|
-
left out generates different names for the same source, and its styles silently don't apply.
|
|
88
|
+
A compiler left out of the plugin produces different class names and styles that appear to work only in some environments.
|