@imfusion/web-ui 0.8.1-dev.3.g40eaad1c → 0.8.1-dev.36.g641eac85

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 (50) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/README.md +26 -18
  3. package/dist/{code-D2lwxHgd.js → code-DBIxhqGE.js} +3 -3
  4. package/dist/components/card/card.d.ts +15 -1
  5. package/dist/components/field/field.d.ts +5 -2
  6. package/dist/components/slider/slider.d.ts +11 -1
  7. package/dist/components/typo/typo.d.ts +20 -11
  8. package/dist/{icons-CaGCCwG-.js → icons-CM68Ql5d.js} +1 -1
  9. package/dist/icons.js +1 -1
  10. package/dist/index.js +1381 -1345
  11. package/dist/integrations/code-highlight.js +6 -4
  12. package/dist/integrations/frame-selection/frame-selection-view.d.ts +24 -0
  13. package/dist/integrations/frame-selection/frame-selection.d.ts +21 -0
  14. package/dist/integrations/frame-selection/frame-selection.meta.d.ts +2 -0
  15. package/dist/integrations/frame-selection/index.d.ts +2 -0
  16. package/dist/integrations/frame-selection.js +121 -0
  17. package/dist/integrations/image-display-options.js +71 -70
  18. package/dist/llms/evals/runner/config.d.ts +1 -0
  19. package/dist/llms/gen-tokens.d.ts +1 -1
  20. package/dist/{paths-Oi0v8x-w.js → paths-B5Ht3ETc.js} +2 -2
  21. package/dist/separator-lzz4A3x_.js +26 -0
  22. package/dist/slider-Cqpreglu.js +116 -0
  23. package/dist/style.css +1 -1
  24. package/dist/tabs-Dsfp8Ogj.js +295 -0
  25. package/dist/utils/status-glyphs.d.ts +4 -0
  26. package/dist/vite.js +2 -2
  27. package/dist/web-ui-cli.js +1 -1
  28. package/docs/user-guide/AgentTooling.mdx +8 -3
  29. package/package.json +33 -36
  30. package/src/docgen/doc.gen.json +149 -2
  31. package/src/llms/llms.gen.txt +8 -2
  32. package/src/llms/skills/imf-web-ui/SKILL.md +2 -0
  33. package/src/llms/skills/imf-web-ui-audit/SKILL.md +5 -3
  34. package/src/llms/skills/imf-web-ui-components/SKILL.md +3 -1
  35. package/src/llms/skills/imf-web-ui-conventions/templates/AUDIT_CHECKLIST.md +4 -2
  36. package/src/llms/skills/imf-web-ui-conventions/topics/authentication.md +2 -1
  37. package/src/llms/skills/imf-web-ui-conventions/topics/data.md +5 -3
  38. package/src/llms/skills/imf-web-ui-conventions/topics/git.md +2 -3
  39. package/src/llms/skills/imf-web-ui-conventions/topics/npm-project.md +3 -4
  40. package/src/llms/skills/imf-web-ui-conventions/topics/styling.md +4 -2
  41. package/src/llms/skills/imf-web-ui-conventions/topics/tooling.md +66 -28
  42. package/src/llms/skills/imf-web-ui-conventions/topics/typescript.md +14 -0
  43. package/src/llms/skills/imf-web-ui-setup/SKILL.md +2 -0
  44. package/src/llms/skills/imf-web-ui-update/SKILL.md +37 -6
  45. package/src/llms/skills/imf-web-ui-user-profile/SKILL.md +95 -0
  46. package/src/llms/skills/imf-web-ui-ux/SKILL.md +2 -0
  47. package/src/llms/tokens.gen.json +3 -3
  48. package/dist/tabs-BS6rqrrA.js +0 -398
  49. /package/dist/{meta-j-7HGrWv.js → meta-V_KyLeGn.js} +0 -0
  50. /package/dist/{chunk-DmhlhrBa.js → rolldown-runtime-Dy4uBu1J.js} +0 -0
@@ -499,6 +499,19 @@
499
499
  "description": "Width divided by height, or \"auto\" to use the image's intrinsic ratio."
500
500
  }
501
501
  ]
502
+ },
503
+ {
504
+ "name": "Card.Status",
505
+ "description": "Card.Status — a tinted slot that marks the card's status with the variant's glyph. Pass\nchildren to replace the glyph. Like `Card.Image`, place it first among the slots: a column\nbeside the body in a horizontal card, a band above it in a vertical one. Decorative\n(`aria-hidden`); the card's text carries the status.",
506
+ "props": [
507
+ {
508
+ "name": "variant",
509
+ "required": false,
510
+ "type": "| \"info\"\n| \"positive\"\n| \"warning\"\n| \"negative\"\n| null",
511
+ "defaultValue": "info",
512
+ "description": ""
513
+ }
514
+ ]
502
515
  }
503
516
  ]
504
517
  },
@@ -1963,7 +1976,7 @@
1963
1976
  },
1964
1977
  {
1965
1978
  "name": "Field.Error",
1966
- "description": "Field.Error — an inline validation message shown when its match condition fails.",
1979
+ "description": "Field.Error — an inline validation message shown when its match condition fails.\nIt announces itself with `role=\"alert\"`; pass another `role` to quiet it.",
1967
1980
  "props": [
1968
1981
  {
1969
1982
  "name": "className",
@@ -5556,7 +5569,7 @@
5556
5569
  "slider": {
5557
5570
  "root": {
5558
5571
  "name": "Slider.Root",
5559
- "description": "Slider.Root — value selector along a continuous or stepped track.",
5572
+ "description": "Slider.Root — value selector along a continuous or stepped track.\n\n`variant=\"position\"` draws a scrollbar-like trough and bar thumb for picking one position\nbetween `min` and `max`, such as a slice or frame; `onValueChange` reports the position.",
5560
5573
  "props": [
5561
5574
  {
5562
5575
  "name": "className",
@@ -5704,6 +5717,13 @@
5704
5717
  "type": "number | readonly number[]",
5705
5718
  "defaultValue": null,
5706
5719
  "description": "The value of the slider.\nFor ranged sliders, provide an array with two values."
5720
+ },
5721
+ {
5722
+ "name": "variant",
5723
+ "required": false,
5724
+ "type": "\"default\" | \"position\" | null",
5725
+ "defaultValue": "default",
5726
+ "description": ""
5707
5727
  }
5708
5728
  ]
5709
5729
  },
@@ -7787,6 +7807,13 @@
7787
7807
  "type": "| \"main\"\n| \"support\"\n| \"minor\"\n| \"brand\"\n| \"primary\"\n| \"negative\"\n| \"positive\"\n| \"warning\"\n| \"info\"\n| \"oncolor\"\n| \"accent-1\"\n| \"accent-2\"\n| \"accent-3\"\n| null",
7788
7808
  "defaultValue": "support",
7789
7809
  "description": ""
7810
+ },
7811
+ {
7812
+ "name": "weight",
7813
+ "required": false,
7814
+ "type": "\"strong\" | \"regular\" | \"medium\"",
7815
+ "defaultValue": null,
7816
+ "description": "Font weight. Omit to keep the member's default; `medium` firms up short body text on cards."
7790
7817
  }
7791
7818
  ]
7792
7819
  },
@@ -7814,6 +7841,40 @@
7814
7841
  "type": "| \"main\"\n| \"support\"\n| \"minor\"\n| \"brand\"\n| \"primary\"\n| \"negative\"\n| \"positive\"\n| \"warning\"\n| \"info\"\n| \"oncolor\"\n| \"accent-1\"\n| \"accent-2\"\n| \"accent-3\"\n| null",
7815
7842
  "defaultValue": "main",
7816
7843
  "description": ""
7844
+ },
7845
+ {
7846
+ "name": "weight",
7847
+ "required": false,
7848
+ "type": "\"strong\" | \"regular\" | \"medium\"",
7849
+ "defaultValue": null,
7850
+ "description": "Font weight. Omit to keep the member's default; `medium` firms up short body text on cards."
7851
+ }
7852
+ ]
7853
+ },
7854
+ {
7855
+ "name": "Typo.Eyebrow",
7856
+ "description": "Typo.Eyebrow — short uppercase label above a heading. Also called a kicker or overline.",
7857
+ "props": [
7858
+ {
7859
+ "name": "ref",
7860
+ "required": false,
7861
+ "type": "Ref<HTMLHeadingElement>",
7862
+ "defaultValue": null,
7863
+ "description": "Allows getting a ref to the component instance.\nOnce the component unmounts, React will set `ref.current` to `null`\n(or call the ref with `null` if you passed a callback ref).\n@see {@link https://react.dev/learn/referencing-values-with-refs#refs-and-the-dom React Docs}"
7864
+ },
7865
+ {
7866
+ "name": "render",
7867
+ "required": false,
7868
+ "type": "| ReactElement<\n unknown,\n string | JSXElementConstructor<any>\n >\n| ComponentRenderFn<HTMLProps, {}>",
7869
+ "defaultValue": null,
7870
+ "description": "Allows you to replace the component's HTML element\nwith a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
7871
+ },
7872
+ {
7873
+ "name": "variant",
7874
+ "required": false,
7875
+ "type": "| \"main\"\n| \"support\"\n| \"minor\"\n| \"brand\"\n| \"primary\"\n| \"negative\"\n| \"positive\"\n| \"warning\"\n| \"info\"\n| \"oncolor\"\n| \"accent-1\"\n| \"accent-2\"\n| \"accent-3\"\n| null",
7876
+ "defaultValue": "support",
7877
+ "description": ""
7817
7878
  }
7818
7879
  ]
7819
7880
  },
@@ -7997,6 +8058,13 @@
7997
8058
  "type": "| \"main\"\n| \"support\"\n| \"minor\"\n| \"brand\"\n| \"primary\"\n| \"negative\"\n| \"positive\"\n| \"warning\"\n| \"info\"\n| \"oncolor\"\n| \"accent-1\"\n| \"accent-2\"\n| \"accent-3\"\n| null",
7998
8059
  "defaultValue": "oncolor",
7999
8060
  "description": ""
8061
+ },
8062
+ {
8063
+ "name": "weight",
8064
+ "required": false,
8065
+ "type": "\"strong\" | \"regular\" | \"medium\"",
8066
+ "defaultValue": null,
8067
+ "description": "Font weight. Omit to keep the member's default; `medium` firms up short body text on cards."
8000
8068
  }
8001
8069
  ]
8002
8070
  },
@@ -8058,6 +8126,13 @@
8058
8126
  "type": "| \"main\"\n| \"support\"\n| \"minor\"\n| \"brand\"\n| \"primary\"\n| \"negative\"\n| \"positive\"\n| \"warning\"\n| \"info\"\n| \"oncolor\"\n| \"accent-1\"\n| \"accent-2\"\n| \"accent-3\"\n| null",
8059
8127
  "defaultValue": "main",
8060
8128
  "description": ""
8129
+ },
8130
+ {
8131
+ "name": "weight",
8132
+ "required": false,
8133
+ "type": "\"strong\" | \"regular\" | \"medium\"",
8134
+ "defaultValue": null,
8135
+ "description": "Font weight. Omit to keep the member's default; `medium` firms up short body text on cards."
8061
8136
  }
8062
8137
  ]
8063
8138
  },
@@ -8092,6 +8167,13 @@
8092
8167
  "type": "| \"main\"\n| \"support\"\n| \"minor\"\n| \"brand\"\n| \"primary\"\n| \"negative\"\n| \"positive\"\n| \"warning\"\n| \"info\"\n| \"oncolor\"\n| \"accent-1\"\n| \"accent-2\"\n| \"accent-3\"\n| null",
8093
8168
  "defaultValue": "primary",
8094
8169
  "description": ""
8170
+ },
8171
+ {
8172
+ "name": "weight",
8173
+ "required": false,
8174
+ "type": "\"strong\" | \"regular\" | \"medium\"",
8175
+ "defaultValue": null,
8176
+ "description": "Font weight. Omit to keep the member's default; `medium` firms up short body text on cards."
8095
8177
  }
8096
8178
  ]
8097
8179
  },
@@ -8119,6 +8201,13 @@
8119
8201
  "type": "| \"main\"\n| \"support\"\n| \"minor\"\n| \"brand\"\n| \"primary\"\n| \"negative\"\n| \"positive\"\n| \"warning\"\n| \"info\"\n| \"oncolor\"\n| \"accent-1\"\n| \"accent-2\"\n| \"accent-3\"\n| null",
8120
8202
  "defaultValue": "main",
8121
8203
  "description": ""
8204
+ },
8205
+ {
8206
+ "name": "weight",
8207
+ "required": false,
8208
+ "type": "\"strong\" | \"regular\" | \"medium\"",
8209
+ "defaultValue": null,
8210
+ "description": "Font weight. Omit to keep the member's default; `medium` firms up short body text on cards."
8122
8211
  }
8123
8212
  ]
8124
8213
  },
@@ -8146,6 +8235,13 @@
8146
8235
  "type": "| \"main\"\n| \"support\"\n| \"minor\"\n| \"brand\"\n| \"primary\"\n| \"negative\"\n| \"positive\"\n| \"warning\"\n| \"info\"\n| \"oncolor\"\n| \"accent-1\"\n| \"accent-2\"\n| \"accent-3\"\n| null",
8147
8236
  "defaultValue": "main",
8148
8237
  "description": ""
8238
+ },
8239
+ {
8240
+ "name": "weight",
8241
+ "required": false,
8242
+ "type": "\"strong\" | \"regular\" | \"medium\"",
8243
+ "defaultValue": null,
8244
+ "description": "Font weight. Omit to keep the member's default; `medium` firms up short body text on cards."
8149
8245
  }
8150
8246
  ]
8151
8247
  },
@@ -8173,6 +8269,13 @@
8173
8269
  "type": "| \"main\"\n| \"support\"\n| \"minor\"\n| \"brand\"\n| \"primary\"\n| \"negative\"\n| \"positive\"\n| \"warning\"\n| \"info\"\n| \"oncolor\"\n| \"accent-1\"\n| \"accent-2\"\n| \"accent-3\"\n| null",
8174
8270
  "defaultValue": "minor",
8175
8271
  "description": ""
8272
+ },
8273
+ {
8274
+ "name": "weight",
8275
+ "required": false,
8276
+ "type": "\"strong\" | \"regular\" | \"medium\"",
8277
+ "defaultValue": null,
8278
+ "description": "Font weight. Omit to keep the member's default; `medium` firms up short body text on cards."
8176
8279
  }
8177
8280
  ]
8178
8281
  },
@@ -8200,6 +8303,13 @@
8200
8303
  "type": "| \"main\"\n| \"support\"\n| \"minor\"\n| \"brand\"\n| \"primary\"\n| \"negative\"\n| \"positive\"\n| \"warning\"\n| \"info\"\n| \"oncolor\"\n| \"accent-1\"\n| \"accent-2\"\n| \"accent-3\"\n| null",
8201
8304
  "defaultValue": "main",
8202
8305
  "description": ""
8306
+ },
8307
+ {
8308
+ "name": "weight",
8309
+ "required": false,
8310
+ "type": "\"strong\" | \"regular\" | \"medium\"",
8311
+ "defaultValue": null,
8312
+ "description": "Font weight. Omit to keep the member's default; `medium` firms up short body text on cards."
8203
8313
  }
8204
8314
  ]
8205
8315
  }
@@ -8309,6 +8419,43 @@
8309
8419
  }
8310
8420
  ]
8311
8421
  },
8422
+ "frame-selection": {
8423
+ "root": {
8424
+ "name": "FrameSelection",
8425
+ "description": "FrameSelection - a position slider bound to the focused frame of the multi-frame image sets in `data`.\nEvery matching image set follows the slider, and a `{focus} | {count}` readout sits beside it.\nRenders nothing when `data` holds no multi-frame image set.\n\nMust render inside an `ImFusionProvider`.",
8426
+ "props": [
8427
+ {
8428
+ "name": "data",
8429
+ "required": true,
8430
+ "type": "HandleArray<Data>",
8431
+ "defaultValue": null,
8432
+ "description": "Datasets to scan, for example from `useVisibleData()`. Non-image data, single-frame image sets, and image sets whose\nframe count differs from the first multi-frame set are ignored."
8433
+ },
8434
+ {
8435
+ "name": "disabled",
8436
+ "required": false,
8437
+ "type": "boolean",
8438
+ "defaultValue": "false",
8439
+ "description": "Whether the slider ignores user interaction."
8440
+ },
8441
+ {
8442
+ "name": "label",
8443
+ "required": false,
8444
+ "type": "string",
8445
+ "defaultValue": "Frame",
8446
+ "description": "Accessible name of the slider thumb."
8447
+ },
8448
+ {
8449
+ "name": "orientation",
8450
+ "required": false,
8451
+ "type": "\"horizontal\" | \"vertical\"",
8452
+ "defaultValue": "horizontal",
8453
+ "description": "Direction of the slider; the vertical layout stacks the readout under it."
8454
+ }
8455
+ ]
8456
+ },
8457
+ "subComponents": []
8458
+ },
8312
8459
  "image-display-options": {
8313
8460
  "root": {
8314
8461
  "name": "ImageDisplayOptions",
@@ -38,7 +38,7 @@ Persistent, in-place status message for information, success, warnings, and erro
38
38
 
39
39
  ## Card
40
40
  - category: Layout, status: stable
41
- Surface container for related content on a tonal background. Also called a panel or tile; compose it from optional image, header, content, and footer parts.
41
+ Surface container for related content on a tonal background. Also called a panel or tile; compose it from optional image, status, header, content, and footer parts.
42
42
  - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .card (jq: jq '.card' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
43
43
 
44
44
  ## Checkbox
@@ -96,6 +96,12 @@ Semantic group for related form controls with a shared legend. Also called a for
96
96
  - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .fieldset (jq: jq '.fieldset' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
97
97
  - further reading (usage/anatomy, not props): https://base-ui.com/react/components/fieldset.md
98
98
 
99
+ ## FrameSelection
100
+ - category: Inputs, status: experimental
101
+ Frame or slice picker bound to the focused frame of ImFusion image sets, with a focus and count readout. For a position picker not tied to the SDK, use Slider with variant="position".
102
+ - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .frame-selection (jq: jq '.frame-selection' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
103
+ - further reading (usage/anatomy, not props): https://docs.imfusion.com/
104
+
99
105
  ## Icon
100
106
  - category: Display, status: stable
101
107
  Renders a Web UI icon with an optional semantic foreground color. The default inherits the surrounding currentColor.
@@ -160,7 +166,7 @@ Line that divides content horizontally or vertically. Also called a divider or r
160
166
 
161
167
  ## Slider
162
168
  - category: Inputs, status: stable
163
- Numeric input for choosing one value or a range by dragging a thumb. Also called a range input; use it when relative magnitude matters.
169
+ Numeric input for choosing one value or a range by dragging a thumb. Also called a range input; use it when relative magnitude matters. The position variant is a scrubber for picking one slice or frame.
164
170
  - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .slider (jq: jq '.slider' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
165
171
  - further reading (usage/anatomy, not props): https://base-ui.com/react/components/slider.md
166
172
 
@@ -11,6 +11,8 @@ description:
11
11
  Start here for UI work and library questions in a project that uses `@imfusion/web-ui`. This skill is a map, not a second
12
12
  copy of every convention.
13
13
 
14
+ Invoke `imf-web-ui-user-profile` first and explain at its levels throughout.
15
+
14
16
  ## 1. Decide whether guidance is needed
15
17
 
16
18
  Skip a companion when the task is explicit and small, an existing local pattern already solves it in the file being changed,
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: imf-web-ui-audit
3
3
  description:
4
- "Audit an existing ImFusion frontend against the @imfusion/web-ui conventions without changing files. Use this for a
5
- project health check, a pre-setup review, or one named topic. Report broken and missing pieces, working deviations,
4
+ "Audit an existing ImFusion frontend against the @imfusion/web-ui conventions without changing project files. Use this for
5
+ a project health check, a pre-setup review, or one named topic. Report broken and missing pieces, working deviations,
6
6
  evidence, and an ordered plan."
7
7
  argument-hint: "[full|<topic>]"
8
- allowed-tools: Read Glob Grep
8
+ allowed-tools: Read Glob Grep Skill
9
9
  ---
10
10
 
11
11
  # Audit a consumer project
@@ -21,6 +21,8 @@ it low, and do not let it crowd the sections a consumer has to act on.
21
21
 
22
22
  ## Workflow
23
23
 
24
+ Invoke `imf-web-ui-user-profile` first and explain at its levels throughout.
25
+
24
26
  1. Resolve the argument. Bare means `full`; an unknown topic is an error, not a reason to widen the scope.
25
27
  2. Enter plan mode unless this audit is being called as verification by another skill or the host has no plan mode.
26
28
  3. Read the report template and existing reviewer notes, then dispatch one investigator per in-scope topic. Tell each
@@ -4,13 +4,15 @@ description:
4
4
  "Look up @imfusion/web-ui component APIs, compound parts, defaults, and icon glyphs without reading source. Use this
5
5
  whenever a consumer task names a Web UI component or icon and you need to choose a prop, part, default, or glyph. Do not
6
6
  use it to choose between components or bootstrap a project."
7
- allowed-tools: Bash
7
+ allowed-tools: Bash Skill
8
8
  ---
9
9
 
10
10
  # Look up components
11
11
 
12
12
  Use the generated package indexes. They are smaller and more reliable than reading the library source.
13
13
 
14
+ Invoke `imf-web-ui-user-profile` first and explain at its levels throughout.
15
+
14
16
  ## Component lookup
15
17
 
16
18
  1. Read the identity index first, even when you already know the component's name: it gives the docgen key and the status.
@@ -50,6 +50,7 @@ sections one to one — change a topic's sections and this file follows in the s
50
50
 
51
51
  - [ ] Types
52
52
  - [ ] Immutability & expressions
53
+ - [ ] Functions
53
54
  - [ ] Naming
54
55
 
55
56
  ## Styling — `styling`
@@ -102,8 +103,9 @@ sections one to one — change a topic's sections and this file follows in the s
102
103
  - [ ] When a library owns a layer
103
104
  - [ ] Devtools
104
105
  - [ ] Docs over memory
105
- - [ ] Prettier
106
- - [ ] ESLint and TypeScript
106
+ - [ ] Vite+
107
+ - [ ] Oxfmt
108
+ - [ ] Oxlint and TypeScript
107
109
  - [ ] Pre-commit checks
108
110
  - [ ] Vite plugins
109
111
 
@@ -19,7 +19,8 @@ Use one server-backed current-user query as the identity source:
19
19
 
20
20
  - Put it in `api/auth/queries.ts` and define its key and schema with the [data.md](data.md) pattern.
21
21
  - Expose query options as `context.api.auth.getUser()`.
22
- - Call `ensureQueryData` in a loader or a query hook in a component.
22
+ - Call `queryClient.query({ ...options, staleTime: "static" })` in a loader (see [data.md](data.md)) or a query hook in a
23
+ component.
23
24
  - Keep credentials and access tokens out of React state.
24
25
  - Do not infer authentication from local storage, decoded tokens, route flags, or permission-gated UI.
25
26
 
@@ -85,9 +85,11 @@ export function getDetails(options?: QueryOptions<User>) {
85
85
  }
86
86
  ```
87
87
 
88
- Callers choose `ensureQueryData`, `useSuspenseQuery`, or another Query API. Mutation factories likewise return
89
- `mutationOptions`, own their mutation key and parsing, and are passed to `useMutation`; do not inline those options at the
90
- call site.
88
+ Callers choose `queryClient.query`, `useSuspenseQuery`, or another Query API. A router loader reads the cache with
89
+ `queryClient.query({ ...options, staleTime: "static" })`, not the deprecated `ensureQueryData` or `fetchQuery`. Keep
90
+ `"static"`: without it the factory's `staleTime` applies (default 0), so every loader run waits on the network and the page's
91
+ `useQuery` refetches on mount. Mutation factories likewise return `mutationOptions`, own their mutation key and parsing, and
92
+ are passed to `useMutation`; do not inline those options at the call site.
91
93
 
92
94
  ## Router context access
93
95
 
@@ -18,11 +18,10 @@ The hooks are tracked in `.githooks/`. Check both the Git setting and the direct
18
18
 
19
19
  There are two scopes:
20
20
 
21
- - **commit**: `verify:staged`, run by pre-commit. It checks whole-repository lint and formatting, then runs project checks
22
- for staged inputs.
21
+ - **commit**: the pre-commit hook, which runs `vp staged` as `tooling.md` describes under Pre-commit checks.
23
22
  - **full**: `verify:full`, the build and complete verification used by CI.
24
23
 
25
- The scripts under `scripts/verify/` own the step lists. Hooks and npm scripts only launch them.
24
+ The `staged` block and the scripts under `scripts/verify/` own the step lists. Hooks and npm scripts only launch them.
26
25
 
27
26
  ## Staleness at commit time
28
27
 
@@ -17,12 +17,11 @@ Use the same names for the same checks where the project supports them:
17
17
  | ------------------ | --------------------------------------- |
18
18
  | `dev` | Start local development. |
19
19
  | `build` | Create a production build. |
20
- | `verify:format` | Check Prettier. |
21
- | `verify:lint` | Check ESLint. |
20
+ | `verify:format` | Run `vp fmt --check`. |
21
+ | `verify:lint` | Run `vp lint`. |
22
22
  | `verify:typecheck` | Run TypeScript without emitting. |
23
- | `verify:tests` | Run tests once. |
23
+ | `verify:tests` | Run `vp test run`. |
24
24
  | `verify:knip` | Check dead code and unused exports. |
25
- | `verify:staged` | Run the pre-commit scope. |
26
25
  | `verify:full` | Run the CI scope. |
27
26
  | `format` | Rewrite formatting. |
28
27
  | `lint` | Rewrite fixable lint issues. |
@@ -7,6 +7,8 @@ Use native CSS and Web UI's public seams. The library's CSS Modules and token na
7
7
  - Use CSS Modules with native nesting. Do not add Sass, CSS-in-JS, or a utility-class framework for ordinary component
8
8
  styles.
9
9
  - Nest states, pseudo-elements, and child selectors under the root.
10
+ - Write declarations before nested rules; browsers before Chrome 130, Firefox 132, and Safari 18 move later ones above them.
11
+ - Do not nest under a selector list whose selectors differ in specificity. The nested rule takes the highest one.
10
12
  - Lift repeated values into custom properties, especially when a state or variant changes several descendants.
11
13
  - Keep structurally different rules explicit; not every repeated value needs an abstraction.
12
14
 
@@ -85,8 +87,8 @@ Foreground roles are for text, icons, borders, and focus rings. Background roles
85
87
 
86
88
  ## Browser floor
87
89
 
88
- The library targets Safari 16.2 or newer. `color-mix(in oklch, …)` is available at that floor. Relative color syntax needs a
89
- guarded fallback for older Safari:
90
+ The library targets Safari 17.5, Chrome 123, and Firefox 120 or newer, the first versions with native `light-dark()`.
91
+ `color-mix(in oklch, …)` is available at that floor. Relative color syntax needs a guarded fallback for older Safari:
90
92
 
91
93
  ```css
92
94
  .subtle {
@@ -6,18 +6,18 @@ Use the tool's current documentation rather than memory.
6
6
 
7
7
  | Need | Default |
8
8
  | --------------------- | -------------------------------------------- |
9
- | Build | Vite |
9
+ | Toolchain | Vite+ (`vite-plus`), driven through `vp` |
10
10
  | Routing and URL state | TanStack Router |
11
11
  | Server state | TanStack Query |
12
12
  | Forms | TanStack Form |
13
13
  | Data grids | TanStack Table plus Web UI `Table` parts |
14
14
  | App-wide client state | TanStack Store, after URL/server/local state |
15
15
  | Schema validation | Zod at the boundary |
16
- | Tests | Vitest |
17
- | Format | Prettier |
18
- | Lint | ESLint flat config |
16
+ | Tests | Vitest through `vp test` |
17
+ | Format | Oxfmt through `vp fmt` |
18
+ | Lint | Oxlint through `vp lint`, type-aware |
19
19
  | Types | `tsc --noEmit` |
20
- | Pre-commit checks | Whole-repository lint and format checks |
20
+ | Pre-commit checks | `vp staged` against the staged state |
21
21
  | Dead code | Knip via `verify:knip` |
22
22
 
23
23
  ## When a library owns a layer
@@ -36,47 +36,85 @@ devtools package whenever a new TanStack dependency is added.
36
36
  Use `npx @tanstack/cli` for TanStack documentation. If TanStack Intent is configured, use it for the Agent Skills the
37
37
  TanStack package provides.
38
38
 
39
- ## Prettier
39
+ ## Vite+
40
40
 
41
- Use a config file. The repo's values are:
41
+ Vite+ runs development, build, tests, format, lint, and staged checks through one `vp` binary configured in `vite.config.ts`.
42
+ Pin `vite-plus` exactly, alias `vite` to `npm:@voidzero-dev/vite-plus-core@<vite-plus version>`, pin `vitest` to the version
43
+ `vp toolchain vitest` reports, and repeat both pins under `overrides`. Import `defineConfig` from `vite-plus` and Vitest APIs
44
+ from `vite-plus/test`. It needs Node `^22.22.1 || ^24.11 || >=26` and Git 2.32 or later.
45
+
46
+ `vp <command>` is a built-in; `vp run <script>` runs a package script.
47
+
48
+ Move an existing Vite, Vitest, ESLint, or Prettier project with `vp migrate --no-hooks` (`vp help migrate` lists its
49
+ options), then check the result against this topic. The hook lives in `.githooks/`, as `git.md` describes.
50
+
51
+ ## Oxfmt
52
+
53
+ Configure formatting in the `fmt` block of `vite.config.ts`. The repo's values:
42
54
 
43
55
  ```ts
44
- import type { Config } from "prettier";
45
-
46
- const config: Config = {
47
- printWidth: 125,
48
- tabWidth: 2,
49
- useTabs: false,
50
- trailingComma: "none",
51
- arrowParens: "avoid",
52
- semi: true,
53
- singleQuote: false,
54
- proseWrap: "always"
55
- };
56
-
57
- export default config;
56
+ export default defineConfig({
57
+ fmt: {
58
+ printWidth: 125,
59
+ tabWidth: 2,
60
+ useTabs: false,
61
+ trailingComma: "none",
62
+ arrowParens: "avoid",
63
+ semi: true,
64
+ singleQuote: false,
65
+ proseWrap: "always",
66
+ sortPackageJson: false
67
+ }
68
+ });
58
69
  ```
59
70
 
60
- ## ESLint and TypeScript
71
+ ## Oxlint and TypeScript
61
72
 
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.
73
+ Configure linting in the `lint` block with type-aware rules on. The baseline bans `as` and non-null `!` outside tests and
74
+ requires the `#/` alias instead of parent-relative imports:
75
+
76
+ ```ts
77
+ export default defineConfig({
78
+ lint: {
79
+ options: { typeAware: true },
80
+ overrides: [
81
+ {
82
+ files: ["src/**/*.{ts,tsx}"],
83
+ rules: {
84
+ "typescript/consistent-type-assertions": ["error", { assertionStyle: "never" }],
85
+ "typescript/no-non-null-assertion": "error",
86
+ "no-restricted-imports": [
87
+ "error",
88
+ { patterns: [{ group: ["../*"], message: "Use the #/ alias for anything outside the current folder." }] }
89
+ ]
90
+ }
91
+ },
92
+ {
93
+ files: ["**/*.test.{ts,tsx}"],
94
+ rules: { "typescript/consistent-type-assertions": "off", "typescript/no-non-null-assertion": "off" }
95
+ }
96
+ ]
97
+ }
98
+ });
99
+ ```
64
100
 
65
101
  Use strict TypeScript with bundler module resolution, `verbatimModuleSyntax`, unused-value checks, and `#/` paths. Keep
66
102
  `import type` for type-only imports.
67
103
 
68
104
  ## Pre-commit checks
69
105
 
70
- The pre-commit hook checks the whole repository with ESLint and Prettier, then runs project checks for staged inputs that can
71
- affect generated files, types, dependencies, or TeamCity configuration. It does not rewrite files during a commit; use
72
- `npm run format` to apply Prettier changes explicitly.
106
+ `.githooks/pre-commit` runs `vp staged --hide-unstaged --fail-on-changes --revert --diff-filter=ACMRD` with `set -e`. Those
107
+ flags make the checks see only the staged state, include deletion-only commits, fail the commit when a check would change a
108
+ file, and restore the tree afterwards, so the hook never rewrites or stages files. The `staged` block in `vite.config.ts` is
109
+ a function returning path-free commands such as `npm run --silent verify:lint`, so each project-wide check runs once. Use
110
+ `npm run format` to apply formatting explicitly.
73
111
 
74
112
  ## Vite plugins
75
113
 
76
114
  Register Web UI's Vite plugins in the application configuration:
77
115
 
78
116
  ```ts
79
- import { defineConfig } from "vite";
117
+ import { defineConfig } from "vite-plus";
80
118
  import react from "@vitejs/plugin-react";
81
119
  import { imfusionBrandAssets, readableCssModuleNames } from "@imfusion/web-ui/vite";
82
120
 
@@ -30,6 +30,20 @@ input. Use `toSorted` and `toReversed` instead of their mutating counterparts.
30
30
  A loop is fine when it has a real early exit or measured hot-path benefit. A long chain or a `reduce` with several jobs wants
31
31
  named intermediate values.
32
32
 
33
+ ## Functions
34
+
35
+ Declare a named function with `function`, including a local helper inside a component. A callback passed inline as a prop or
36
+ argument stays an arrow.
37
+
38
+ ```tsx
39
+ function resetCaptcha() {
40
+ captcha.current?.reset();
41
+ }
42
+
43
+ <Button onClick={() => setOpen(true)}>Open</Button>;
44
+ items.map(item => item.id);
45
+ ```
46
+
33
47
  ## Naming
34
48
 
35
49
  Name what a value is, not how it is built. Booleans read as assertions (`isOpen`, `hasAccess`, `canSubmit`). Props use `onX`;
@@ -15,6 +15,8 @@ working choices.
15
15
 
16
16
  ## Workflow
17
17
 
18
+ Invoke `imf-web-ui-user-profile` first and explain at its levels throughout.
19
+
18
20
  1. Resolve the scope. A named topic limits the assessment to that topic. A bare invocation means `full`, unless the request
19
21
  is about one topic, in which case that topic is the scope: answer what was asked and offer the wider sweep rather than
20
22
  running it. If the argument is unknown, list the supported topics and point to `imf-web-ui-audit`.