basalt-ui 1.29.0 → 1.29.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +10 -0
- package/MIGRATING.md +20 -0
- package/README.md +43 -39
- package/agent/rules/basalt-batteries.md +1 -1
- package/agent/templates/query-client.ts.tpl +1 -1
- package/configs/oxlint-plugin.js +16 -89
- package/configs/oxlint-plugin.test.ts +3 -80
- package/configs/oxlint.json +0 -1
- package/dist/dev-dock.d.ts +1 -3
- package/dist/dev-dock.d.ts.map +1 -1
- package/dist/forms/use-form-submit.d.ts +1 -1
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +38 -7
- package/dist/query-client.d.ts +16 -12
- package/dist/query-client.d.ts.map +1 -1
- package/dist/query-client.js +3 -1
- package/dist/surfaces.d.ts +2 -2
- package/dist/surfaces.d.ts.map +1 -1
- package/dist/surfaces.js +6 -5
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/src/dev-dock.tsx +10 -7
- package/src/forms/use-form-submit.ts +1 -1
- package/src/guard/index.ts +56 -19
- package/src/query-client.ts +26 -21
- package/src/surfaces.ts +6 -5
package/AGENTS.md
CHANGED
|
@@ -31,7 +31,7 @@ Toolchain: Bun runtime, oxlint + oxfmt, conventional commits (empty scope), `mas
|
|
|
31
31
|
## Hard rules
|
|
32
32
|
|
|
33
33
|
**Mantine-free boundary**: `basalt-ui/charts`, `basalt-ui/tokens`, `basalt-ui/guard`,
|
|
34
|
-
`basalt-ui/
|
|
34
|
+
`basalt-ui/router-tanstack`, `basalt-ui/agent`, and `basalt-ui/state` must
|
|
35
35
|
import zero `@mantine/*`. `@visx/*` is permitted only inside `src/charts/**`. `@visx/*`
|
|
36
36
|
(`basalt/visx-boundary`) is enforced by an oxlint plugin rule in both the repo and the shipped
|
|
37
37
|
consumer preset.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## [1.29.1](https://github.com/jkrumm/basalt-ui/compare/v1.29.0...v1.29.1) (2026-09-03)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* collapse unwrap's two overloads into one conditional generic ([80152d7](https://github.com/jkrumm/basalt-ui/commit/80152d7d8030ce3455d8ba4f314e6aa59a88feab))
|
|
7
|
+
* let BasaltDevDock's router prop accept a real TanStack Router ([955cdfc](https://github.com/jkrumm/basalt-ui/commit/955cdfca02d28e04e4665e652f1b0a53eb92cdfb))
|
|
8
|
+
* make chart-missing-aria-label's tag scan brace-depth aware ([0d6c122](https://github.com/jkrumm/basalt-ui/commit/0d6c12239e99dc26b15d202050810c7788317acf))
|
|
9
|
+
* retire basalt/query-dual-import — its premise is unreachable post-C1 ([372bc1f](https://github.com/jkrumm/basalt-ui/commit/372bc1fa9a32008a1cde2f13e0a2f826262cbe39))
|
|
10
|
+
|
|
1
11
|
# [1.29.0](https://github.com/jkrumm/basalt-ui/compare/v1.28.0...v1.29.0) (2026-09-02)
|
|
2
12
|
|
|
3
13
|
|
package/MIGRATING.md
CHANGED
|
@@ -48,6 +48,16 @@ see § The shell scrollport below.
|
|
|
48
48
|
|
|
49
49
|
What is new, one line each — nothing here renames or removes anything:
|
|
50
50
|
|
|
51
|
+
- **`BasaltDevDock`'s `router` prop now accepts a real TanStack `Router` instance with no cast** —
|
|
52
|
+
the prior structural stand-in rejected a class instance under TS weak-type detection (no property
|
|
53
|
+
overlap, no index signature), forcing `router={router as never}`. It is now `router?: object`:
|
|
54
|
+
any non-null object is assignable, and the devtools panel remains the only reader.
|
|
55
|
+
- **`unwrap` collapses to ONE generic signature with a conditional return**, replacing the two
|
|
56
|
+
overloads this same minor introduced. The overload pair resolved `unwrap` to `unknown` whenever it
|
|
57
|
+
was passed as a bare callback (`api.threads.get().then(unwrap)`) — `.then()` reads the declared
|
|
58
|
+
type of the reference rather than applying an overload, and TS cannot propagate generic inference
|
|
59
|
+
across an overload set the way it can for a single signature. `.then(unwrap)` now infers `TData`
|
|
60
|
+
again, alongside `unwrap(await p)` and `unwrap(p)` — nothing about the runtime behaviour changed.
|
|
51
61
|
- **`scrollParentOf` / `SCROLLPORT_ATTRIBUTE`** (`basalt-ui`) — resolve which box actually scrolls an
|
|
52
62
|
element (`null` = the document). The seam behind the shell scrollport change below.
|
|
53
63
|
- **`PageAside`** (`basalt-ui`) — the right-hand aside REGION a route claims, with the panel filter
|
|
@@ -730,6 +740,16 @@ half is deliberately NOT exempted the same way — a local `StatCard` beside an
|
|
|
730
740
|
`import { StatCard as Base }` kept the name AND a piece of the original, which is the fork shape this
|
|
731
741
|
rule most wants to see.
|
|
732
742
|
|
|
743
|
+
### Guards — `basalt/query-dual-import` removed
|
|
744
|
+
|
|
745
|
+
**Retired, not merely relaxed** — its `RETIRED_RULE_IDS` entry (kept recognized, so an existing
|
|
746
|
+
`theme-allow query-dual-import` still reads as a dead waiver rather than a typo) and its
|
|
747
|
+
`PLUGIN_RULE_GRACE` ledger row are both deleted. Its whole premise — a raw `@tanstack/react-query`
|
|
748
|
+
import beside `basalt-ui/query` — went unreachable the moment `./query` dropped (see § Consolidation
|
|
749
|
+
above): a consumer now MUST import `@tanstack/react-query` directly, and the rule warned on every
|
|
750
|
+
one of those imports instead of the drift it was written to catch (90+ false positives on a single
|
|
751
|
+
consumer). `query-fn-unwrap` is unaffected — it is a different rule, still `warn` (grace → 1.30.0).
|
|
752
|
+
|
|
733
753
|
### `basalt-ui/data` — a typed facet id, and a required `getItemKey`
|
|
734
754
|
|
|
735
755
|
**`BasaltDataTable`: a mistyped `facets[].columnId` used to render no pill, silently — `if
|
package/README.md
CHANGED
|
@@ -143,8 +143,8 @@ The canonical stack, once a router and a query client join the example above:
|
|
|
143
143
|
```
|
|
144
144
|
|
|
145
145
|
The order is load-bearing, not stylistic: `BasaltProvider` auto-mounts `ConnectivityProvider`
|
|
146
|
-
(
|
|
147
|
-
client; `BasaltOverlays` must sit **inside** the theme, since Modals/Spotlight/Notifications
|
|
146
|
+
(C1: absorbed onto the root barrel from the dropped `./connectivity`), which aggregates
|
|
147
|
+
React-Query's own status and so must sit **above** the query client; `BasaltOverlays` must sit **inside** the theme, since Modals/Spotlight/Notifications
|
|
148
148
|
resolve `--vx-*` tokens through Mantine's context; the router goes **last** so every route
|
|
149
149
|
component renders with the theme, the data layer and the overlay layer already mounted above it.
|
|
150
150
|
`agent/rules/basalt-mantine.md` points here rather than restating the tree.
|
|
@@ -322,31 +322,31 @@ deleted rather than silently surviving into the next real skew.
|
|
|
322
322
|
|
|
323
323
|
## Subpath exports
|
|
324
324
|
|
|
325
|
-
| Subpath | Mantine? | Purpose
|
|
326
|
-
| ------------------- | -------- |
|
|
327
|
-
| `.` | coupled | `BasaltProvider`, `createBasaltTheme` / `baseTheme` / `cssVariablesResolver`, `BasaltShell` + sidebar / mobile nav / breadcrumbs / `PageBar`, `Section` / `WidgetHeader`, `NavCountBadge`, `SidebarAccount` + the provider-agnostic account contract (`BasaltAccountProps`/`State`/`Actions`), `ThreadWorkspace` + thread-chat components, dashboard composites (`DeltaBadge`, `StatCard`, `EmptyState`, `SettingsSection`/`SettingsRow`/`DangerZone`)
|
|
328
|
-
| `./charts` | **free** | `CartesianChart` + visx primitives, kinds, sparklines, hooks, token re-exports
|
|
329
|
-
| `./tokens` | **free** | `VX` token refs, `buildPaletteCss`, `defineSeries`, `seriesTokens`, `groupTokens`, `alpha`, `ColorPair` / `SeriesMap` types
|
|
330
|
-
| `./theme-lab` | coupled | `ThemeLabControls`, `applyOverrides`, `loadOverrides`, `COLOR_GROUPS` for live theme inspection
|
|
331
|
-
| `./vite` | — | `basaltViteConfig(opts)` — Vite preset for basalt-ui consumer apps; `basaltAppPlugin(opts)` — PWA head, manifest, and icon metadata derived from the token palette
|
|
332
|
-
| `./guard` | **free** | `checkSource`, `GUARD_RULES`, `Finding` types — the headless theme-guard core; plus the annotation reader `--audit-allows` is built on: `findAllowAnnotations`, `neutralizeAllowAnnotation`, `NEUTRALIZED_ALLOW_TOKEN`, `PLUGIN_RULE_IDS`, `AllowAnnotationSite`
|
|
333
|
-
| `./
|
|
334
|
-
| `./router-tanstack` | **free** | TanStack Router bridge: `defineNav`/`navGroup`/`navTarget`/`flattenNav` + `useNav` (→ `{ sections, mobileNav }`), `useBasaltNav` (active route), `useRouterBreadcrumbs`, and `createSearchStore` + the `field.*` vocabulary
|
|
335
|
-
| `./forms` | coupled | Mantine form adapter: `useBasaltForm`, `inputProps`, `FormErrorSummary`, `useFormDraft` (Standard Schema)
|
|
336
|
-
| `./notifications` | coupled | Mantine notifications: `notify` helpers, typed registry, persisted history, `NotificationBell`
|
|
337
|
-
| `./commands` | coupled | Typed command bus + overlay controller, `toSpotlightActions`, `ShortcutsHelp`, `BasaltOverlays`
|
|
338
|
-
| `./data`
|
|
339
|
-
| `./data/
|
|
340
|
-
| `./
|
|
341
|
-
| `./agent`
|
|
342
|
-
| `./
|
|
343
|
-
| `./
|
|
344
|
-
| `./
|
|
345
|
-
| `./
|
|
346
|
-
| `./styles.css` | — | `@layer basalt` base styles, iOS input safety net, font stack
|
|
347
|
-
| `./tokens.css` | **free** | Prebuilt `--vx-*` stylesheet — the default `buildPaletteCss()` output as a plain file, for a consumer with a bundler but no React or Mantine. **With no bundler, use the `basalt-ui tokens:css` CLI instead** and commit the emitted file: importing this subpath needs bare-specifier resolution. See [`docs/FRAMEWORK-FREE.md`](https://github.com/jkrumm/basalt-ui/blob/master/docs/FRAMEWORK-FREE.md)
|
|
348
|
-
| `./configs/*` | — | Raw toolchain presets — oxlint, oxfmt, tsconfig (base/react-app/node), lefthook
|
|
349
|
-
| `./llms.txt` | — | Machine-readable surface map — one entry per published subpath with import specifier, description, layer, and optional peers
|
|
325
|
+
| Subpath | Mantine? | Purpose |
|
|
326
|
+
| ------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
327
|
+
| `.` | coupled | `BasaltProvider`, `createBasaltTheme` / `baseTheme` / `cssVariablesResolver`, `BasaltShell` + sidebar / mobile nav / breadcrumbs / `PageBar` / `PageAside`, `Section` / `WidgetHeader`, `NavCountBadge`, `SidebarAccount` + the provider-agnostic account contract (`BasaltAccountProps`/`State`/`Actions`), `ThreadWorkspace` + thread-chat components, dashboard composites (`DeltaBadge`, `StatCard`, `EmptyState`, `SettingsSection`/`SettingsRow`/`DangerZone`), the query-branch trio `QueryState` / `LoadingState` / `ErrorState`, `createBasaltQueryClient` / `unwrap` / `toErrorMessage` / `errorStatus` / `BasaltQueryDevtools` (C1: absorbed from the dropped `./query`), `ConnectivityProvider` (auto-mounted) / `useConnectivity` / `ConnectivityIndicator` (C1: absorbed from the dropped `./connectivity`), shell types — the root entry still re-exports the thread-chat components; `./agent-chat` takes the same components standalone, without `BasaltProvider`, the shell, or the dashboard composites |
|
|
328
|
+
| `./charts` | **free** | `CartesianChart` + visx primitives, kinds, sparklines, hooks, token re-exports |
|
|
329
|
+
| `./tokens` | **free** | `VX` token refs, `buildPaletteCss`, `defineSeries`, `seriesTokens`, `groupTokens`, `alpha`, `ColorPair` / `SeriesMap` types |
|
|
330
|
+
| `./theme-lab` | coupled | `ThemeLabControls`, `applyOverrides`, `loadOverrides`, `COLOR_GROUPS` for live theme inspection |
|
|
331
|
+
| `./vite` | — | `basaltViteConfig(opts)` — Vite preset for basalt-ui consumer apps; `basaltAppPlugin(opts)` — PWA head, manifest, and icon metadata derived from the token palette |
|
|
332
|
+
| `./guard` | **free** | `checkSource`, `GUARD_RULES`, `Finding` types — the headless theme-guard core; plus the annotation reader `--audit-allows` is built on: `findAllowAnnotations`, `neutralizeAllowAnnotation`, `NEUTRALIZED_ALLOW_TOKEN`, `PLUGIN_RULE_IDS`, `AllowAnnotationSite` |
|
|
333
|
+
| `./format` | **free** | `Intl`-backed, Mantine-free, React-free formatting: `money`/`percent`/`integer`/`compact`/`deltaPct`, `duration`/`durationClock`/`clock`/`weekday`/`relativeTime`, `km`/`kcal` |
|
|
334
|
+
| `./router-tanstack` | **free** | TanStack Router bridge: `defineNav`/`navGroup`/`navTarget`/`flattenNav` + `useNav` (→ `{ sections, mobileNav }`), `useBasaltNav` (active route), `useRouterBreadcrumbs`, and `createSearchStore` + the `field.*` vocabulary |
|
|
335
|
+
| `./forms` | coupled | Mantine form adapter: `useBasaltForm`, `inputProps`, `FormErrorSummary`, `useFormDraft` (Standard Schema) |
|
|
336
|
+
| `./notifications` | coupled | Mantine notifications: `notify` helpers, typed registry, persisted history, `NotificationBell` |
|
|
337
|
+
| `./commands` | coupled | Typed command bus + overlay controller, `toSpotlightActions`, `ShortcutsHelp`, `BasaltOverlays` |
|
|
338
|
+
| `./data/table` | coupled | `BasaltDataTable` — sortable data table over TanStack Table, rendered with Mantine |
|
|
339
|
+
| `./data/virtual` | coupled | `BasaltVirtualList` — windowed virtual list over TanStack Virtual, rendered with Mantine |
|
|
340
|
+
| `./agent` | **free** | Headless streaming layer (`useAgentStream`, `PartList`) + multi-thread `createThreadsStore` / `useAgentThreadRuns` / outcome seam |
|
|
341
|
+
| `./agent-chat` | coupled | Mantine-styled thread-chat components over `./agent`: `ThreadWorkspace`, `ThreadFeed` (`variant`/`renderRow`), `ThreadFeedRow` (inline-expanding Slack row, lazily mounted + kept mounted), `ThreadOutcomeCard`, `ThreadDetailPanel`, `Composer`, `ThreadTranscript` (`groupConsecutive`/`affordances`/`virtualize` — windowed transcripts open at the newest message unless `initialScroll: 'start'`; `virtualize` is enabled by the optional `@tanstack/react-virtual` peer, absent it degrades to an unwindowed, height-bound pane), `threadPartRenderers` — **requires `motion`**, not merely an optional peer; see [Requirements](#requirements) |
|
|
342
|
+
| `./content` | coupled | `Prose`, `CodeBlock`, `Callout`, `TableOfContents`, `ReadingProgress`, `Markdown`, `MermaidDiagram`, `ArticleLayout`, `ArticleCard` / `ArticleGrid`, `GuideLink` / `GuideDrawer`, `mdxComponents` |
|
|
343
|
+
| `./controls` | coupled | The control tier: `FilterSet` + the `FieldHandle`-bound filters (`RangeFilter`/`CompareFilter`/`SelectFilter`/`MultiSelectFilter`/`NumberFilter`/`SearchFilter`/`ToggleFilter`), `ViewTabs`, `ActionGroup`/`OverflowMenu`/`SyncButton`, plus the aside's `PanelRow`/`SliderControl` row primitives — `pill`/`sheet`/`panel` surfaces, picked by the home, never a prop |
|
|
344
|
+
| `./controls-dates` | coupled | `DateRangePicker` — the `@mantine/dates` implementation of `RangeFilter`'s `customPicker` seam; its own subpath so `./controls` resolves and renders with no `@mantine/dates` installed |
|
|
345
|
+
| `./state` | **free** | createPersistedState (versioned localStorage) + the store field vocabulary (field.enum/multi/range/number/boolean/string, FieldHandle, lanes) + createLocalStore, the router-free store — Mantine-free state primitives |
|
|
346
|
+
| `./styles.css` | — | `@layer basalt` base styles, iOS input safety net, font stack |
|
|
347
|
+
| `./tokens.css` | **free** | Prebuilt `--vx-*` stylesheet — the default `buildPaletteCss()` output as a plain file, for a consumer with a bundler but no React or Mantine. **With no bundler, use the `basalt-ui tokens:css` CLI instead** and commit the emitted file: importing this subpath needs bare-specifier resolution. See [`docs/FRAMEWORK-FREE.md`](https://github.com/jkrumm/basalt-ui/blob/master/docs/FRAMEWORK-FREE.md) |
|
|
348
|
+
| `./configs/*` | — | Raw toolchain presets — oxlint, oxfmt, tsconfig (base/react-app/node), lefthook |
|
|
349
|
+
| `./llms.txt` | — | Machine-readable surface map — one entry per published subpath with import specifier, description, layer, and optional peers |
|
|
350
350
|
|
|
351
351
|
Named exports only — no default exports.
|
|
352
352
|
|
|
@@ -496,28 +496,32 @@ It resolves ahead of the `xTicks` COUNT, which is unchanged; omit both and ticks
|
|
|
496
496
|
Reach for values on a dense time axis — the tick chooser appends the final key unconditionally, so
|
|
497
497
|
a count that misses the last index collides two labels at the right edge at every count.
|
|
498
498
|
|
|
499
|
-
###
|
|
499
|
+
### Query — `createBasaltQueryClient` / `unwrap` (root barrel)
|
|
500
|
+
|
|
501
|
+
`./query` was dropped in 1.29.0 (C1 consolidation) — its two exports moved onto the root barrel,
|
|
502
|
+
which already requires `@tanstack/react-query` as a peer. TanStack itself
|
|
503
|
+
(`QueryClientProvider`, hooks, `queryOptions`) is imported straight from `@tanstack/react-query`,
|
|
504
|
+
never re-exported by basalt.
|
|
500
505
|
|
|
501
506
|
```bash
|
|
502
507
|
bun add @tanstack/react-query @tanstack/react-query-devtools
|
|
503
508
|
```
|
|
504
509
|
|
|
505
510
|
```tsx
|
|
506
|
-
import { createBasaltQueryClient
|
|
511
|
+
import { createBasaltQueryClient } from 'basalt-ui'
|
|
512
|
+
import { QueryClientProvider } from '@tanstack/react-query'
|
|
507
513
|
|
|
508
514
|
const [client] = useState(() => createBasaltQueryClient())
|
|
509
515
|
// queryFn: () => unwrap(api.users.get())
|
|
510
516
|
```
|
|
511
517
|
|
|
512
|
-
`toErrorMessage(err, fallback?)` and `errorStatus(err)` also live
|
|
513
|
-
value into something renderable, folding an HTTP status into the fallback when the
|
|
514
|
-
nothing usable (`''`, `'{}'`, `'[object Object]'`, …).
|
|
518
|
+
`toErrorMessage(err, fallback?)` and `errorStatus(err)` also live on the root barrel — decode an
|
|
519
|
+
unknown thrown value into something renderable, folding an HTTP status into the fallback when the
|
|
520
|
+
body decodes to nothing usable (`''`, `'{}'`, `'[object Object]'`, …). `BasaltQueryDevtools` (lazy)
|
|
521
|
+
is there too.
|
|
515
522
|
|
|
516
523
|
#### Rendering a query — `QueryState`
|
|
517
524
|
|
|
518
|
-
Imported from **`basalt-ui`**, not `basalt-ui/query`: it renders Mantine, and `./query` is a
|
|
519
|
-
guard-enforced Mantine-free entry.
|
|
520
|
-
|
|
521
525
|
```tsx
|
|
522
526
|
import { QueryState } from 'basalt-ui'
|
|
523
527
|
|
|
@@ -634,17 +638,17 @@ export const COMMANDS = defineCommands({
|
|
|
634
638
|
runCommand('file:save')
|
|
635
639
|
```
|
|
636
640
|
|
|
637
|
-
### `./data` — TanStack Table + Virtual
|
|
641
|
+
### `./data/table` + `./data/virtual` — TanStack Table + Virtual
|
|
638
642
|
|
|
639
|
-
|
|
640
|
-
|
|
643
|
+
Two subpaths, one per peer group (C1: there is no bare `./data` barrel) — install only what you
|
|
644
|
+
use.
|
|
641
645
|
|
|
642
646
|
```bash
|
|
643
647
|
bun add @tanstack/react-table @tanstack/react-virtual
|
|
644
648
|
```
|
|
645
649
|
|
|
646
650
|
```tsx
|
|
647
|
-
import { BasaltDataTable,
|
|
651
|
+
import { BasaltDataTable, createColumnHelper } from 'basalt-ui/data/table'
|
|
648
652
|
|
|
649
653
|
const col = createColumnHelper<Row>()
|
|
650
654
|
const columns = [
|
|
@@ -10,7 +10,7 @@ paths:
|
|
|
10
10
|
|
|
11
11
|
<!-- basalt:coverage -->
|
|
12
12
|
<!-- GENERATED from src/surfaces.ts — `bun scripts/check-coverage.ts --write`. Do not hand-edit. -->
|
|
13
|
-
<!-- backed by: guard kinds — none · oxlint rules — basalt/agent-no-raw-usechat, basalt/agent-resume-guard, basalt/ai-sdk-major, basalt/forms-field-key, basalt/query-
|
|
13
|
+
<!-- backed by: guard kinds — none · oxlint rules — basalt/agent-no-raw-usechat, basalt/agent-resume-guard, basalt/ai-sdk-major, basalt/forms-field-key, basalt/query-fn-unwrap -->
|
|
14
14
|
<!-- not guarded: — -->
|
|
15
15
|
<!-- /basalt:coverage -->
|
|
16
16
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Scaffold written by `basalt-ui init`. This file is yours — `basalt-ui sync` will not overwrite it.
|
|
6
6
|
*/
|
|
7
|
-
import { createBasaltQueryClient } from 'basalt-ui
|
|
7
|
+
import { createBasaltQueryClient } from 'basalt-ui'
|
|
8
8
|
|
|
9
9
|
export const queryClient = createBasaltQueryClient({
|
|
10
10
|
// staleTime: 60_000, // optional: global cache freshness window (ms)
|
package/configs/oxlint-plugin.js
CHANGED
|
@@ -91,8 +91,16 @@ import { fileURLToPath } from 'node:url'
|
|
|
91
91
|
* - `duplicate-notifications-mount` — its only case, `<BasaltNotifications />` mounted beside
|
|
92
92
|
* `<BasaltOverlays notifications>`, is unreachable now that `BasaltNotifications` no longer
|
|
93
93
|
* exists as a standalone export; the rule had nothing left to fire on.
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
* - `query-dual-import` — its whole premise (a raw `@tanstack/react-query` import beside
|
|
95
|
+
* `basalt-ui/query`) is unreachable now that C1 (1.29.0) dropped the `./query` subpath: a
|
|
96
|
+
* consumer MUST import `@tanstack/react-query` directly, so the rule warned on every such
|
|
97
|
+
* import instead of the drift it was written to catch.
|
|
98
|
+
*/
|
|
99
|
+
export const RETIRED_RULE_IDS = new Set([
|
|
100
|
+
'unframed-chart',
|
|
101
|
+
'duplicate-notifications-mount',
|
|
102
|
+
'query-dual-import',
|
|
103
|
+
])
|
|
96
104
|
|
|
97
105
|
/**
|
|
98
106
|
* Rule ids a `theme-allow` may name — this plugin's own rules plus `src/guard`'s kinds. Duplicated
|
|
@@ -3354,83 +3362,15 @@ const providerAboveRouter = {
|
|
|
3354
3362
|
},
|
|
3355
3363
|
}
|
|
3356
3364
|
|
|
3357
|
-
//
|
|
3365
|
+
// Rule 27 — query-dual-import — RETIRED. 1.29.0 dropped every raw TanStack re-export from
|
|
3366
|
+
// `basalt-ui/query` (the subpath itself is gone), so "dual-importing `@tanstack/react-query`
|
|
3367
|
+
// beside `basalt-ui/query`" is no longer a thing a consumer can even do — a consumer MUST import
|
|
3368
|
+
// `@tanstack/react-query` directly now, and this rule warned on every such import (90+ false
|
|
3369
|
+
// positives on argo alone). See `RETIRED_RULE_IDS` below.
|
|
3358
3370
|
|
|
3359
|
-
/**
|
|
3360
|
-
const TANSTACK_QUERY_SOURCE = /^@tanstack\/react-query(?:\/|$)/
|
|
3361
|
-
|
|
3362
|
-
/** The `basalt-ui/query` subpath, exactly — the seam the hooks are supposed to arrive through. */
|
|
3371
|
+
/** The `basalt-ui/query` subpath, exactly — kept for `query-fn-unwrap`'s own gate below. */
|
|
3363
3372
|
const BASALT_QUERY_SOURCE = 'basalt-ui/query'
|
|
3364
3373
|
|
|
3365
|
-
/** The root barrel — where `QueryState` and the dashboard composites live. */
|
|
3366
|
-
const BASALT_ROOT_SOURCE = 'basalt-ui'
|
|
3367
|
-
|
|
3368
|
-
const QUERY_DUAL_IMPORT_MESSAGE =
|
|
3369
|
-
'Dual query import — this file imports from @tanstack/react-query AND from basalt-ui/query. ' +
|
|
3370
|
-
'Take the hooks, the client factory and `unwrap` from basalt-ui/query only: two entry points to ' +
|
|
3371
|
-
'one library is how a second QueryClient, a second devtools bundle and a duplicated ' +
|
|
3372
|
-
'`toErrorMessage` reach an app (agent/rules/basalt-batteries.md). ' +
|
|
3373
|
-
'(basalt/query-dual-import)'
|
|
3374
|
-
|
|
3375
|
-
const QUERY_ROOT_IMPORT_MESSAGE =
|
|
3376
|
-
'Raw @tanstack/react-query import in a basalt file — the hooks come through basalt-ui/query, ' +
|
|
3377
|
-
'which re-exports them beside `createBasaltQueryClient`, `unwrap`, `toErrorMessage` and the lazy ' +
|
|
3378
|
-
'devtools (agent/rules/basalt-batteries.md). Import from basalt-ui/query instead. ' +
|
|
3379
|
-
'(basalt/query-dual-import)'
|
|
3380
|
-
|
|
3381
|
-
/**
|
|
3382
|
-
* The `basalt-batteries.md` law "import query hooks from `basalt-ui/query`, never dual-import
|
|
3383
|
-
* `@tanstack/react-query`" — F5, and one of the two `./query` surfaces that shipped advisory.
|
|
3384
|
-
*
|
|
3385
|
-
* TWO forms, one id, because they are one law read at two strengths. The DUAL form (both
|
|
3386
|
-
* specifiers present) is the law verbatim. The softer form — a raw `@tanstack/react-query` import
|
|
3387
|
-
* in a file that also imports the basalt root barrel — is a heuristic: it says "this file is
|
|
3388
|
-
* already a basalt file, so the query seam is right there", and it is the form that actually
|
|
3389
|
-
* catches the drift, since a file that has never heard of `basalt-ui/query` cannot dual-import.
|
|
3390
|
-
* The softer form is why the whole rule lands at `warn` rather than `error`.
|
|
3391
|
-
*
|
|
3392
|
-
* Gated on the LITERAL specifier, never on `ownTree` relative resolution: basalt's own
|
|
3393
|
-
* `src/query/**` and `src/dashboard/query-state.tsx` import `@tanstack/react-query` because they
|
|
3394
|
-
* ARE the seam, and they reach their siblings relatively. A file that writes `basalt-ui/query` is
|
|
3395
|
-
* by definition a consumer of the published surface.
|
|
3396
|
-
*/
|
|
3397
|
-
// Ships: warn (grace → 1.30.0)
|
|
3398
|
-
const queryDualImport = {
|
|
3399
|
-
meta: {
|
|
3400
|
-
type: 'suggestion',
|
|
3401
|
-
docs: { description: 'Warn on a raw @tanstack/react-query import beside a basalt import.' },
|
|
3402
|
-
schema: [],
|
|
3403
|
-
},
|
|
3404
|
-
create(context) {
|
|
3405
|
-
if (isTestFile(context)) return {}
|
|
3406
|
-
const raw = []
|
|
3407
|
-
let basaltQuery = false
|
|
3408
|
-
let basaltRoot = false
|
|
3409
|
-
|
|
3410
|
-
return {
|
|
3411
|
-
ImportDeclaration(node) {
|
|
3412
|
-
const source = node.source?.value
|
|
3413
|
-
if (typeof source !== 'string') return
|
|
3414
|
-
if (source === BASALT_QUERY_SOURCE) basaltQuery = true
|
|
3415
|
-
else if (source === BASALT_ROOT_SOURCE) basaltRoot = true
|
|
3416
|
-
// `import type { UseQueryResult } from '@tanstack/react-query'` is erased at compile time
|
|
3417
|
-
// — there is no runtime import to route through the seam, and typing against the library's
|
|
3418
|
-
// own result type is exactly what `QueryStateLike` documents as legitimate. Same skip
|
|
3419
|
-
// `agent-no-raw-usechat` and `ai-sdk-major` take.
|
|
3420
|
-
else if (TANSTACK_QUERY_SOURCE.test(source) && node.importKind !== 'type') raw.push(node)
|
|
3421
|
-
},
|
|
3422
|
-
'Program:exit'() {
|
|
3423
|
-
if (!basaltQuery && !basaltRoot) return
|
|
3424
|
-
const message = basaltQuery ? QUERY_DUAL_IMPORT_MESSAGE : QUERY_ROOT_IMPORT_MESSAGE
|
|
3425
|
-
for (const node of raw) {
|
|
3426
|
-
if (hasThemeAllow(context, node, 'query-dual-import')) continue
|
|
3427
|
-
context.report({ node, message })
|
|
3428
|
-
}
|
|
3429
|
-
},
|
|
3430
|
-
}
|
|
3431
|
-
},
|
|
3432
|
-
}
|
|
3433
|
-
|
|
3434
3374
|
// ── Rule 28 — query-fn-unwrap ───────────────────────────────────────────────────────────────────
|
|
3435
3375
|
|
|
3436
3376
|
const QUERY_FN_UNWRAP_MESSAGE =
|
|
@@ -3904,18 +3844,6 @@ export const PLUGIN_RULE_GRACE = {
|
|
|
3904
3844
|
'image-gen and the playground is measured — every one of them mounts a router, so the ' +
|
|
3905
3845
|
'false-positive question has a real sample.',
|
|
3906
3846
|
},
|
|
3907
|
-
'query-dual-import': {
|
|
3908
|
-
since: '1.28.0',
|
|
3909
|
-
promote: '1.30.0',
|
|
3910
|
-
why:
|
|
3911
|
-
'F5 — the basalt-batteries.md law with no guard. TWO forms under one id: the dual import ' +
|
|
3912
|
-
'(both `@tanstack/react-query` and `basalt-ui/query` in one file) is the law verbatim, and ' +
|
|
3913
|
-
'the softer form — a raw `@tanstack/react-query` import in a file that also imports the ' +
|
|
3914
|
-
'basalt root barrel — is the heuristic that actually catches the drift, since a file that ' +
|
|
3915
|
-
'never names `basalt-ui/query` cannot dual-import. The soft form is what holds the whole ' +
|
|
3916
|
-
'rule at `warn`: "this file is already a basalt file" is a claim about intent. 1.30.0 is ' +
|
|
3917
|
-
'when the consumer count is measured and the soft form is either kept or split off.',
|
|
3918
|
-
},
|
|
3919
3847
|
'forms-field-key': {
|
|
3920
3848
|
since: '1.28.0',
|
|
3921
3849
|
promote: '1.30.0',
|
|
@@ -4000,7 +3928,6 @@ export default {
|
|
|
4000
3928
|
'search-literal-link': searchLiteralLink,
|
|
4001
3929
|
'use-search-from-literal': useSearchFromLiteral,
|
|
4002
3930
|
'provider-above-router': providerAboveRouter,
|
|
4003
|
-
'query-dual-import': queryDualImport,
|
|
4004
3931
|
'query-fn-unwrap': queryFnUnwrap,
|
|
4005
3932
|
'deprecated-export': deprecatedExport,
|
|
4006
3933
|
'forms-field-key': formsFieldKey,
|
|
@@ -38,7 +38,6 @@ beforeEach(() => {
|
|
|
38
38
|
jsPlugins: [PLUGIN_PATH],
|
|
39
39
|
rules: {
|
|
40
40
|
'basalt/provider-above-router': 'error',
|
|
41
|
-
'basalt/query-dual-import': 'error',
|
|
42
41
|
'basalt/query-fn-unwrap': 'error',
|
|
43
42
|
'basalt/deprecated-export': 'error',
|
|
44
43
|
'basalt/forms-field-key': 'error',
|
|
@@ -3050,85 +3049,9 @@ describe('basalt/provider-above-router', () => {
|
|
|
3050
3049
|
})
|
|
3051
3050
|
})
|
|
3052
3051
|
|
|
3053
|
-
//
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
it('flags a file importing from both @tanstack/react-query and basalt-ui/query', () => {
|
|
3057
|
-
const { code, rules, output } = run(
|
|
3058
|
-
`import { useQuery } from '@tanstack/react-query'\n` +
|
|
3059
|
-
`import { unwrap } from 'basalt-ui/query'\n` +
|
|
3060
|
-
`export const useThing = () => useQuery({ queryKey: ['t'], queryFn: () => unwrap(get()) })\n`,
|
|
3061
|
-
)
|
|
3062
|
-
expect(code).toBe(1)
|
|
3063
|
-
expect(rules).toContain('query-dual-import')
|
|
3064
|
-
expect(output).toContain('Dual query import')
|
|
3065
|
-
})
|
|
3066
|
-
|
|
3067
|
-
// The softer half — the one that actually catches the drift, and why the rule is `warn`.
|
|
3068
|
-
it('flags a raw @tanstack/react-query import beside a basalt root import', () => {
|
|
3069
|
-
const { code, rules, output } = run(
|
|
3070
|
-
`import { useQuery } from '@tanstack/react-query'\n` +
|
|
3071
|
-
`import { QueryState } from 'basalt-ui'\n` +
|
|
3072
|
-
`export const useThing = () => useQuery({ queryKey: ['t'], queryFn: get })\n`,
|
|
3073
|
-
)
|
|
3074
|
-
expect(code).toBe(1)
|
|
3075
|
-
expect(rules).toContain('query-dual-import')
|
|
3076
|
-
expect(output).toContain('Raw @tanstack/react-query import')
|
|
3077
|
-
})
|
|
3078
|
-
|
|
3079
|
-
it('does NOT flag basalt-ui/query on its own', () => {
|
|
3080
|
-
const { code, rules } = run(
|
|
3081
|
-
`import { useQuery, unwrap } from 'basalt-ui/query'\n` +
|
|
3082
|
-
`export const useThing = () => useQuery({ queryKey: ['t'], queryFn: () => unwrap(get()) })\n`,
|
|
3083
|
-
)
|
|
3084
|
-
expect(code).toBe(0)
|
|
3085
|
-
expect(rules).not.toContain('query-dual-import')
|
|
3086
|
-
})
|
|
3087
|
-
|
|
3088
|
-
// `import type` is erased at compile time — there is no runtime import to route through the
|
|
3089
|
-
// seam, and typing against the library's own result type is what `QueryStateLike` documents as
|
|
3090
|
-
// legitimate. Same skip `agent-no-raw-usechat` and `ai-sdk-major` take.
|
|
3091
|
-
it('does NOT flag a type-only @tanstack/react-query import beside basalt-ui/query', () => {
|
|
3092
|
-
const { code, rules } = run(
|
|
3093
|
-
`import type { UseQueryResult } from '@tanstack/react-query'\n` +
|
|
3094
|
-
`import { unwrap } from 'basalt-ui/query'\n` +
|
|
3095
|
-
`export const pick = (q: UseQueryResult<number>) => unwrap(q.data)\n`,
|
|
3096
|
-
)
|
|
3097
|
-
expect(code).toBe(0)
|
|
3098
|
-
expect(rules).not.toContain('query-dual-import')
|
|
3099
|
-
})
|
|
3100
|
-
|
|
3101
|
-
it('does NOT flag @tanstack/react-query in a file with no basalt import', () => {
|
|
3102
|
-
const { code, rules } = run(
|
|
3103
|
-
`import { useQuery } from '@tanstack/react-query'\n` +
|
|
3104
|
-
`export const useThing = () => useQuery({ queryKey: ['t'], queryFn: get })\n`,
|
|
3105
|
-
)
|
|
3106
|
-
expect(code).toBe(0)
|
|
3107
|
-
expect(rules).not.toContain('query-dual-import')
|
|
3108
|
-
})
|
|
3109
|
-
|
|
3110
|
-
// The devtools package is a different package, not a subpath.
|
|
3111
|
-
it('does NOT flag @tanstack/react-query-devtools', () => {
|
|
3112
|
-
const { code, rules } = run(
|
|
3113
|
-
`import { ReactQueryDevtools } from '@tanstack/react-query-devtools'\n` +
|
|
3114
|
-
`import { unwrap } from 'basalt-ui/query'\n` +
|
|
3115
|
-
`export const D = () => <ReactQueryDevtools />\n`,
|
|
3116
|
-
)
|
|
3117
|
-
expect(code).toBe(0)
|
|
3118
|
-
expect(rules).not.toContain('query-dual-import')
|
|
3119
|
-
})
|
|
3120
|
-
|
|
3121
|
-
it('honours a theme-allow naming the rule', () => {
|
|
3122
|
-
const { code, rules } = run(
|
|
3123
|
-
`// theme-allow query-dual-import — the persister API has no basalt re-export yet\n` +
|
|
3124
|
-
`import { useQuery } from '@tanstack/react-query'\n` +
|
|
3125
|
-
`import { unwrap } from 'basalt-ui/query'\n` +
|
|
3126
|
-
`export const useThing = () => useQuery({ queryKey: ['t'], queryFn: () => unwrap(g()) })\n`,
|
|
3127
|
-
)
|
|
3128
|
-
expect(code).toBe(0)
|
|
3129
|
-
expect(rules).not.toContain('query-dual-import')
|
|
3130
|
-
})
|
|
3131
|
-
})
|
|
3052
|
+
// `basalt/query-dual-import` is RETIRED (`RETIRED_RULE_IDS`) — its own describe block was removed
|
|
3053
|
+
// along with the rule; see `KNOWN_RULE_IDS` below for the coverage that remains: it is still a
|
|
3054
|
+
// valid (dead) `theme-allow` id, never a typo.
|
|
3132
3055
|
|
|
3133
3056
|
// ── query-fn-unwrap (F5) ─────────────────────────────────────────────────────
|
|
3134
3057
|
|
package/configs/oxlint.json
CHANGED
|
@@ -42,7 +42,6 @@
|
|
|
42
42
|
"basalt/search-literal-link": "error",
|
|
43
43
|
"basalt/use-search-from-literal": "error",
|
|
44
44
|
"basalt/provider-above-router": "warn",
|
|
45
|
-
"basalt/query-dual-import": "warn",
|
|
46
45
|
"basalt/query-fn-unwrap": "warn",
|
|
47
46
|
"basalt/deprecated-export": "warn",
|
|
48
47
|
"basalt/forms-field-key": "warn",
|
package/dist/dev-dock.d.ts
CHANGED
|
@@ -3,9 +3,7 @@ import type { BasaltProps } from './common/props.js';
|
|
|
3
3
|
import type { ThemeLabControlsProps } from './theme-lab/index.js';
|
|
4
4
|
/** Which panel is open. `null` renders nothing (the dock is unmounted, not hidden). */
|
|
5
5
|
export type BasaltDevDockTool = 'router' | 'query' | 'theme';
|
|
6
|
-
type DevDockRouter =
|
|
7
|
-
__routerHasBeenSetup?: unknown;
|
|
8
|
-
} | Record<string, unknown>;
|
|
6
|
+
type DevDockRouter = object;
|
|
9
7
|
export type BasaltDevDockProps = BasaltProps & {
|
|
10
8
|
/** Which panel to show. `null` renders nothing. */
|
|
11
9
|
tool: BasaltDevDockTool | null;
|
package/dist/dev-dock.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-dock.d.ts","sourceRoot":"","sources":["../src/dev-dock.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACjD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAExD,uFAAuF;AACvF,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"dev-dock.d.ts","sourceRoot":"","sources":["../src/dev-dock.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACjD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAExD,uFAAuF;AACvF,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAA;AAe5D,KAAK,aAAa,GAAG,MAAM,CAAA;AAuB3B,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG;IAC7C,mDAAmD;IACnD,IAAI,EAAE,iBAAiB,GAAG,IAAI,CAAA;IAC9B;oFACgF;IAChF,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB;8DAC0D;IAC1D,MAAM,CAAC,EAAE,aAAa,CAAA;IACtB,iGAAiG;IACjG,QAAQ,CAAC,EAAE,qBAAqB,CAAA;CACjC,CAAA;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,OAAO,EACP,MAAM,EACN,QAAQ,EACR,SAAS,EACT,KAAK,GACN,EAAE,kBAAkB,GAAG,SAAS,CA8DhC"}
|
|
@@ -28,7 +28,7 @@ export type UseFormSubmitOptions<Values extends Record<string, unknown>> = {
|
|
|
28
28
|
onError?: (err: unknown) => void;
|
|
29
29
|
/**
|
|
30
30
|
* Turns a thrown value into the message `submitError` carries. Defaults to `toErrorMessage`
|
|
31
|
-
* (
|
|
31
|
+
* (root barrel), which already unwraps `Error`, `{ message }` and Eden's `{ status, value }`.
|
|
32
32
|
* Override it to brand the copy or to branch on `errorStatus(err)`.
|
|
33
33
|
*/
|
|
34
34
|
mapError?: (err: unknown) => string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/guard/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE7E,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/guard/index.ts"],"names":[],"mappings":"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"}
|
package/dist/guard/index.js
CHANGED
|
@@ -107,8 +107,33 @@ const AXIS_WRAPPER_FILE = /(?:^|\/)Axes\.tsx$/;
|
|
|
107
107
|
const RADIUS_PROP_RE = /\bradius=(?:\{[0-9]+\}|"[0-9]+")/g;
|
|
108
108
|
const MOTION_TRANSITION_NUMERIC = /\btransition\s*=\s*\{\{[^}]*\b(?:duration|stiffness|damping|mass)\s*:\s*-?\d/g;
|
|
109
109
|
const MOTION_TRANSITION_EASE_ARRAY = /\btransition\s*=\s*\{\{[^}]*\bease\s*:\s*\[/g;
|
|
110
|
-
const
|
|
110
|
+
const CHART_ENTRY_POINT_TAG_START = /<(MultiLine|Bars|BandStrip|Donut|DualPanel|Heatmap|MirroredBars|ZonedLine|StackedArea|LineSparkline|BarSparkline)\b(?:<[^<>]*>)?/g;
|
|
111
111
|
const HAS_ARIA_LABEL_PROP = /\bariaLabel\s*=/;
|
|
112
|
+
function scanChartTagClose(codeText, start) {
|
|
113
|
+
let depth = 0;
|
|
114
|
+
let i = start;
|
|
115
|
+
while (i < codeText.length) {
|
|
116
|
+
const ch = codeText[i];
|
|
117
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
118
|
+
const quote = ch;
|
|
119
|
+
i++;
|
|
120
|
+
while (i < codeText.length && codeText[i] !== quote) {
|
|
121
|
+
i += codeText[i] === "\\" ? 2 : 1;
|
|
122
|
+
}
|
|
123
|
+
i++;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (ch === "{") {
|
|
127
|
+
depth++;
|
|
128
|
+
} else if (ch === "}") {
|
|
129
|
+
depth = Math.max(0, depth - 1);
|
|
130
|
+
} else if (ch === ">" && depth === 0) {
|
|
131
|
+
return i;
|
|
132
|
+
}
|
|
133
|
+
i++;
|
|
134
|
+
}
|
|
135
|
+
return -1;
|
|
136
|
+
}
|
|
112
137
|
const LOCAL_COMPONENT_DEF = /(?:^|[\n;}])\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?(?:function|class|const|let|var)\s+([A-Z]\w*)/g;
|
|
113
138
|
const BASALT_NAMED_IMPORT = /import\s+(?:type\s+)?\{([^}]*)\}\s+from\s+['"]basalt-ui(?:\/[^'"]*)?['"]/g;
|
|
114
139
|
function localComponentNames(codeText) {
|
|
@@ -265,7 +290,6 @@ const PLUGIN_RULE_IDS = /* @__PURE__ */ new Set([
|
|
|
265
290
|
// `checkSource`'s reach: provenance here is an import graph and a JSX ancestry, neither of which
|
|
266
291
|
// a 12-line regex window has. Same reason `bound-control-outside-home` has no text-lane twin.
|
|
267
292
|
"provider-above-router",
|
|
268
|
-
"query-dual-import",
|
|
269
293
|
"query-fn-unwrap",
|
|
270
294
|
"deprecated-export",
|
|
271
295
|
// Same lane: `{...inputProps(…)}` without a sibling `key` is a JSX-ancestry claim (does this
|
|
@@ -291,7 +315,11 @@ const PLUGIN_RULE_IDS = /* @__PURE__ */ new Set([
|
|
|
291
315
|
// Retired plugin rule — `<BasaltNotifications />` mounted beside `<BasaltOverlays
|
|
292
316
|
// notifications>` is unreachable now that `BasaltNotifications` no longer exists as a
|
|
293
317
|
// standalone export. Mirrored by the plugin's own `RETIRED_RULE_IDS`.
|
|
294
|
-
"duplicate-notifications-mount"
|
|
318
|
+
"duplicate-notifications-mount",
|
|
319
|
+
// Retired plugin rule — its whole premise (a raw `@tanstack/react-query` import beside
|
|
320
|
+
// `basalt-ui/query`) is unreachable now that C1 (1.29.0) dropped the `./query` subpath.
|
|
321
|
+
// Mirrored by the plugin's own `RETIRED_RULE_IDS`.
|
|
322
|
+
"query-dual-import"
|
|
295
323
|
]);
|
|
296
324
|
const MIN_ALLOW_REASON_LENGTH = 4;
|
|
297
325
|
function parseAllowAnnotation(rest, scope) {
|
|
@@ -778,7 +806,7 @@ const GUARD_RULES = {
|
|
|
778
806
|
},
|
|
779
807
|
"chart-missing-aria-label": {
|
|
780
808
|
kind: "chart-missing-aria-label",
|
|
781
|
-
pattern:
|
|
809
|
+
pattern: CHART_ENTRY_POINT_TAG_START,
|
|
782
810
|
// handled inline (full-text tag-scoped scan); entry keeps registry complete
|
|
783
811
|
enabled: (cfg) => cfg.chartMissingAriaLabel,
|
|
784
812
|
// JSX-tag-shaped (`<MultiLine …>`) — never appears in CSS text.
|
|
@@ -1061,11 +1089,14 @@ function checkSource(text, relPath, cfg) {
|
|
|
1061
1089
|
const basaltNames = ariaLabelRuns ? basaltImportedNames(codeText) : /* @__PURE__ */ new Set();
|
|
1062
1090
|
const isShippedTag = (tag) => basaltNames.has(tag) || !localNames.has(tag);
|
|
1063
1091
|
if (ariaLabelRuns) {
|
|
1064
|
-
for (const m of codeText.matchAll(
|
|
1065
|
-
const
|
|
1092
|
+
for (const m of codeText.matchAll(CHART_ENTRY_POINT_TAG_START)) {
|
|
1093
|
+
const matchStart = m.index ?? 0;
|
|
1066
1094
|
if (!isShippedTag(m[1])) continue;
|
|
1095
|
+
const closeIdx = scanChartTagClose(codeText, matchStart + m[0].length);
|
|
1096
|
+
if (closeIdx === -1) continue;
|
|
1097
|
+
const tagText = codeText.slice(matchStart, closeIdx + 1);
|
|
1067
1098
|
if (HAS_ARIA_LABEL_PROP.test(tagText)) continue;
|
|
1068
|
-
const startLine = codeText.slice(0,
|
|
1099
|
+
const startLine = codeText.slice(0, matchStart).split("\n").length;
|
|
1069
1100
|
const endLine = startLine + (tagText.split("\n").length - 1);
|
|
1070
1101
|
if (isAllowedInRange(startLine, endLine, "chart-missing-aria-label")) continue;
|
|
1071
1102
|
findings.push({
|
package/dist/query-client.d.ts
CHANGED
|
@@ -34,11 +34,13 @@ export declare function createBasaltQueryClient(config?: QueryClientConfig): Que
|
|
|
34
34
|
* wrapper that leaves `data` unset on a miss — rather than explicitly `null` — is caught the same
|
|
35
35
|
* way; the two envelope shapes argo's own pre-basalt `unwrap` and basalt's ONLY differed on.
|
|
36
36
|
*
|
|
37
|
-
* **
|
|
38
|
-
* `queryFn: () => unwrap(api.x.get())`)
|
|
39
|
-
* (`unwrap(await api.x.get())
|
|
40
|
-
*
|
|
41
|
-
*
|
|
37
|
+
* **One generic signature, a conditional return** — a Promise of the envelope (basalt's original
|
|
38
|
+
* shape, `queryFn: () => unwrap(api.x.get())`) or the ALREADY-RESOLVED envelope itself
|
|
39
|
+
* (`unwrap(await api.x.get())`), both resolve `TData` off the same conditional. The two-overload
|
|
40
|
+
* form this replaced (1.29.0) resolved `unwrap` to `unknown` whenever it was passed as a bare
|
|
41
|
+
* callback — `.then(unwrap)` never applies an overload, it just reads the declared type of the
|
|
42
|
+
* `unwrap` reference — so `api.threads.get().then(unwrap)` silently widened. A single generic
|
|
43
|
+
* signature has exactly one type to read regardless of how the reference is used.
|
|
42
44
|
*
|
|
43
45
|
* @example
|
|
44
46
|
* import { unwrap } from 'basalt-ui'
|
|
@@ -48,16 +50,18 @@ export declare function createBasaltQueryClient(config?: QueryClientConfig): Que
|
|
|
48
50
|
* // mutation:
|
|
49
51
|
* mutationFn: (body) => unwrap(api.resource.post({ body }))
|
|
50
52
|
*
|
|
51
|
-
* // resolved-envelope — either shape:
|
|
53
|
+
* // resolved-envelope — either shape, including a bare `.then(unwrap)` callback:
|
|
52
54
|
* const rows = unwrap(await api.users.get())
|
|
53
55
|
* const rows2 = await api.users.get().then(unwrap)
|
|
54
56
|
*/
|
|
55
|
-
export declare function unwrap<
|
|
57
|
+
export declare function unwrap<R extends Envelope | Promise<Envelope>>(response: R): UnwrapResult<R>;
|
|
58
|
+
/** Transport-agnostic `{ data, error }` result shape — Eden Treaty, a raw fetch wrapper, etc. */
|
|
59
|
+
type Envelope<TData = unknown> = {
|
|
56
60
|
data: TData | null | undefined;
|
|
57
61
|
error: unknown;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
62
|
+
};
|
|
63
|
+
/** `unwrap`'s conditional return: a `Promise<TData>` when `R` is a promised envelope, `TData` when
|
|
64
|
+
* it is an already-resolved one — inferred off the SAME `R` the overload used to split on. */
|
|
65
|
+
type UnwrapResult<R> = R extends Promise<Envelope<infer TData>> ? Promise<TData> : R extends Envelope<infer TData> ? TData : never;
|
|
66
|
+
export {};
|
|
63
67
|
//# sourceMappingURL=query-client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-client.d.ts","sourceRoot":"","sources":["../src/query-client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAW9D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,CAAC,EAAE,iBAAiB,GAAG,WAAW,CAQ/E;AAID
|
|
1
|
+
{"version":3,"file":"query-client.d.ts","sourceRoot":"","sources":["../src/query-client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAW9D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,CAAC,EAAE,iBAAiB,GAAG,WAAW,CAQ/E;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAK3F;AAED,iGAAiG;AACjG,KAAK,QAAQ,CAAC,KAAK,GAAG,OAAO,IAAI;IAAE,IAAI,EAAE,KAAK,GAAG,IAAI,GAAG,SAAS,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAA;AAEnF;8FAC8F;AAC9F,KAAK,YAAY,CAAC,CAAC,IACjB,CAAC,SAAS,OAAO,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,GACpC,OAAO,CAAC,KAAK,CAAC,GACd,CAAC,SAAS,QAAQ,CAAC,MAAM,KAAK,CAAC,GAC7B,KAAK,GACL,KAAK,CAAA"}
|
package/dist/query-client.js
CHANGED
|
@@ -15,7 +15,9 @@ function createBasaltQueryClient(config) {
|
|
|
15
15
|
});
|
|
16
16
|
}
|
|
17
17
|
function unwrap(response) {
|
|
18
|
-
if (response instanceof Promise)
|
|
18
|
+
if (response instanceof Promise) {
|
|
19
|
+
return response.then((envelope) => unwrapEnvelope(envelope));
|
|
20
|
+
}
|
|
19
21
|
return unwrapEnvelope(response);
|
|
20
22
|
}
|
|
21
23
|
function unwrapEnvelope(envelope) {
|
package/dist/surfaces.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export type RuleName = 'tokens' | 'charts' | 'mantine' | 'state' | 'controls' |
|
|
|
32
32
|
* const id: PluginRuleId = 'hand-rolled-filter' // ok
|
|
33
33
|
* // const bad: PluginRuleId = 'hand-rolled-filters' // tsc error
|
|
34
34
|
*/
|
|
35
|
-
export declare const PLUGIN_RULE_ID_LIST: readonly ["no-raw-font-size", "raw-size-literal", "card-inset", "chart-in-raw-surface", "hand-rolled-plot", "chart-legend-literal", "shadow-basalt-export", "hand-rolled-shell", "raw-scroll-container", "hand-rolled-filter", "control-outside-home", "bound-control-outside-home", "control-size-literal", "page-bar-budget", "in-body-page-title", "responsive-twin", "search-literal-link", "use-search-from-literal", "provider-above-router", "query-
|
|
35
|
+
export declare const PLUGIN_RULE_ID_LIST: readonly ["no-raw-font-size", "raw-size-literal", "card-inset", "chart-in-raw-surface", "hand-rolled-plot", "chart-legend-literal", "shadow-basalt-export", "hand-rolled-shell", "raw-scroll-container", "hand-rolled-filter", "control-outside-home", "bound-control-outside-home", "control-size-literal", "page-bar-budget", "in-body-page-title", "responsive-twin", "search-literal-link", "use-search-from-literal", "provider-above-router", "query-fn-unwrap", "deprecated-export", "forms-field-key", "visx-boundary", "visx-tooltip", "token-layer-boundary", "agent-resume-guard", "agent-no-raw-usechat", "ai-sdk-major", "no-import-meta-env"];
|
|
36
36
|
/** One registered oxlint plugin rule id — the literal union of {@link PLUGIN_RULE_ID_LIST}. */
|
|
37
37
|
export type PluginRuleId = (typeof PLUGIN_RULE_ID_LIST)[number];
|
|
38
38
|
/** The 3 shipped skill names (agent/skills/basalt-{name}/SKILL.md, placed into a consumer's
|
|
@@ -424,7 +424,7 @@ export declare const SURFACES: {
|
|
|
424
424
|
readonly rule: "batteries";
|
|
425
425
|
readonly skill: readonly ["basalt-app"];
|
|
426
426
|
readonly guardKinds: readonly [];
|
|
427
|
-
readonly pluginRules: readonly ["query-
|
|
427
|
+
readonly pluginRules: readonly ["query-fn-unwrap"];
|
|
428
428
|
readonly globs: {
|
|
429
429
|
readonly shipped: readonly ["**/*.{ts,tsx}"];
|
|
430
430
|
readonly repo: readonly ["packages/basalt-ui/src/**"];
|
package/dist/surfaces.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"surfaces.d.ts","sourceRoot":"","sources":["../src/surfaces.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAI9C;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,GAAG,WAAW,CAAA;AAE3F;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,mBAAmB,
|
|
1
|
+
{"version":3,"file":"surfaces.d.ts","sourceRoot":"","sources":["../src/surfaces.ts"],"names":[],"mappings":"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"}
|
package/dist/surfaces.js
CHANGED
|
@@ -18,7 +18,6 @@ const PLUGIN_RULE_ID_LIST = [
|
|
|
18
18
|
"search-literal-link",
|
|
19
19
|
"use-search-from-literal",
|
|
20
20
|
"provider-above-router",
|
|
21
|
-
"query-dual-import",
|
|
22
21
|
"query-fn-unwrap",
|
|
23
22
|
"deprecated-export",
|
|
24
23
|
"forms-field-key",
|
|
@@ -456,10 +455,12 @@ const SURFACES = {
|
|
|
456
455
|
rule: "batteries",
|
|
457
456
|
skill: ["basalt-app"],
|
|
458
457
|
guardKinds: [],
|
|
459
|
-
// query-
|
|
460
|
-
//
|
|
461
|
-
//
|
|
462
|
-
|
|
458
|
+
// query-fn-unwrap: reassigned here from the now-dropped `./query` surface (C1 consolidation —
|
|
459
|
+
// createBasaltQueryClient/unwrap/BasaltQueryDevtools are root barrel exports now, but their
|
|
460
|
+
// doctrine stays `batteries` — "use the shipped thing" — not `mantine`). `query-dual-import`
|
|
461
|
+
// was retired alongside it — its premise (a raw `@tanstack/react-query` import beside
|
|
462
|
+
// `basalt-ui/query`) is unreachable now that the `./query` subpath is gone.
|
|
463
|
+
pluginRules: ["query-fn-unwrap"],
|
|
463
464
|
globs: {
|
|
464
465
|
// Shipped is a catch-all (not just src/**+app/**) so consumer code under components/, lib/,
|
|
465
466
|
// features/, etc. is also covered. The @visx/*-only-in-charts and Mantine-free charts/tokens
|
package/llms.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# basalt-ui v1.29.
|
|
1
|
+
# basalt-ui v1.29.1
|
|
2
2
|
# Opinionated framework for Mantine-based React apps — theme, app shell, visx charts, toolchain presets
|
|
3
3
|
#
|
|
4
4
|
# basalt-ui is an opinionated framework for Mantine-based React apps.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
# ./tokens.css subpath. Guide: docs/FRAMEWORK-FREE.md in the repo.
|
|
20
20
|
|
|
21
21
|
Package: basalt-ui
|
|
22
|
-
Version: 1.29.
|
|
22
|
+
Version: 1.29.1
|
|
23
23
|
|
|
24
24
|
## basalt-ui
|
|
25
25
|
Import: import { ... } from 'basalt-ui'
|
package/package.json
CHANGED
package/src/dev-dock.tsx
CHANGED
|
@@ -42,11 +42,14 @@ const TOOL_TITLE: Record<BasaltDevDockTool, string> = {
|
|
|
42
42
|
theme: 'Theme Lab',
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
|
|
45
|
+
// Any non-null object — avoids a hard, eager type dependency on `@tanstack/react-router` for a
|
|
46
|
+
// consumer who never renders the `'router'` tool, while still accepting a REAL `Router` class
|
|
47
|
+
// instance with no cast. A narrower structural type (matching a `Router` field, or falling back
|
|
48
|
+
// to `Record<string, unknown>`) used to reject that instance under TS weak-type detection: a
|
|
49
|
+
// class instance declares no index signature, so it has no excess-property overlap with either
|
|
50
|
+
// branch. `TanStackRouterDevtoolsPanel` itself takes the real (much wider) `AnyRouter` type; the
|
|
51
|
+
// devtools panel is the only reader of this prop, so `object` is exactly as wide as it needs to be.
|
|
52
|
+
type DevDockRouter = object
|
|
50
53
|
|
|
51
54
|
const LazyRouterDevtoolsPanel = lazy(() =>
|
|
52
55
|
import('@tanstack/react-router-devtools').then((m) => ({
|
|
@@ -135,8 +138,8 @@ export function BasaltDevDock({
|
|
|
135
138
|
{tool === 'router' && router !== undefined && (
|
|
136
139
|
<LazyRouterDevtoolsPanel
|
|
137
140
|
// The devtools panel's own `router` prop takes the real `AnyRouter` type;
|
|
138
|
-
// `DevDockRouter` is deliberately
|
|
139
|
-
// never eagerly imports the router's types.
|
|
141
|
+
// `DevDockRouter` is deliberately the widest structural stand-in (`object`) so this
|
|
142
|
+
// module never eagerly imports the router's types.
|
|
140
143
|
// oxlint-disable-next-line typescript/no-explicit-any -- see above
|
|
141
144
|
router={router as any}
|
|
142
145
|
isOpen
|
|
@@ -82,7 +82,7 @@ export type UseFormSubmitOptions<Values extends Record<string, unknown>> = {
|
|
|
82
82
|
onError?: (err: unknown) => void
|
|
83
83
|
/**
|
|
84
84
|
* Turns a thrown value into the message `submitError` carries. Defaults to `toErrorMessage`
|
|
85
|
-
* (
|
|
85
|
+
* (root barrel), which already unwraps `Error`, `{ message }` and Eden's `{ status, value }`.
|
|
86
86
|
* Override it to brand the copy or to branch on `errorStatus(err)`.
|
|
87
87
|
*/
|
|
88
88
|
mapError?: (err: unknown) => string
|
package/src/guard/index.ts
CHANGED
|
@@ -154,8 +154,8 @@ const INLINE_FONT_SIZE =
|
|
|
154
154
|
// A `Card` / `Paper` opening tag carrying `withBorder`. Card depth is `--vx-shadow-card`, whose 1px
|
|
155
155
|
// ring lives INSIDE the shadow value; `withBorder` therefore adds a SECOND, real `border` property
|
|
156
156
|
// on top of it (the theme's `styles.root` pins bg/shadow/radius but never clears `border`), and the
|
|
157
|
-
// card reads heavy/boxed. Bounded full-text tag scan, same shape as
|
|
158
|
-
// a multi-line-formatted tag still resolves to one match.
|
|
157
|
+
// card reads heavy/boxed. Bounded full-text tag scan, same shape as CHART_ENTRY_POINT_TAG_START below,
|
|
158
|
+
// so a multi-line-formatted tag still resolves to one match.
|
|
159
159
|
//
|
|
160
160
|
// Two deliberate non-matches:
|
|
161
161
|
// • `<Card.Section withBorder>` — a section DIVIDER, not card depth. Excluded by the `(?![\w.])`
|
|
@@ -409,18 +409,49 @@ const MOTION_TRANSITION_NUMERIC =
|
|
|
409
409
|
/\btransition\s*=\s*\{\{[^}]*\b(?:duration|stiffness|damping|mass)\s*:\s*-?\d/g
|
|
410
410
|
const MOTION_TRANSITION_EASE_ARRAY = /\btransition\s*=\s*\{\{[^}]*\bease\s*:\s*\[/g
|
|
411
411
|
|
|
412
|
-
// A chart entry-point JSX tag (the 7 kinds + 2 sparklines) —
|
|
413
|
-
//
|
|
414
|
-
//
|
|
415
|
-
//
|
|
416
|
-
// (
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
// match (accepted limitation of the bounded scan).
|
|
420
|
-
const CHART_ENTRY_POINT_TAG =
|
|
421
|
-
/<(MultiLine|Bars|BandStrip|Donut|DualPanel|Heatmap|MirroredBars|ZonedLine|StackedArea|LineSparkline|BarSparkline)\b(?:<[^<>]*>)?(?:=>|[^>])*?>/g
|
|
412
|
+
// A chart entry-point JSX tag (the 7 kinds + 2 sparklines) — matches the opening `<Name` plus an
|
|
413
|
+
// optional explicit JSX generic argument (`<MultiLine<Point>`); the rest of the tag (up to its
|
|
414
|
+
// closing `>`) is resolved by `scanChartTagClose` below, brace-depth aware, so it survives every
|
|
415
|
+
// `>`-shaped decoy inside a prop expression — arrow functions (`=>`), comparisons (`>=`, `<=`),
|
|
416
|
+
// shifts (`>>`) — without needing to enumerate them: a `>` only closes the tag outside every `{…}`.
|
|
417
|
+
const CHART_ENTRY_POINT_TAG_START =
|
|
418
|
+
/<(MultiLine|Bars|BandStrip|Donut|DualPanel|Heatmap|MirroredBars|ZonedLine|StackedArea|LineSparkline|BarSparkline)\b(?:<[^<>]*>)?/g
|
|
422
419
|
const HAS_ARIA_LABEL_PROP = /\bariaLabel\s*=/
|
|
423
420
|
|
|
421
|
+
/**
|
|
422
|
+
* Scans forward from `start` (immediately after a chart tag's name/generic-args) for the `>` that
|
|
423
|
+
* closes the JSX tag. Curly-brace depth aware: a `>` only closes the tag at depth 0, so anything
|
|
424
|
+
* living inside a `{…}` prop expression — `=>`, `>=`, `<=`, `>>`, a nested JSX child's own `>` —
|
|
425
|
+
* never ends the tag early. String/template literals are skipped verbatim so a quoted `>`
|
|
426
|
+
* cannot perturb the depth count either. Returns the index of the closing `>`, or -1 if the tag
|
|
427
|
+
* never closes (malformed/truncated source — the caller treats that as no match).
|
|
428
|
+
*/
|
|
429
|
+
function scanChartTagClose(codeText: string, start: number): number {
|
|
430
|
+
let depth = 0
|
|
431
|
+
let i = start
|
|
432
|
+
while (i < codeText.length) {
|
|
433
|
+
const ch = codeText[i]
|
|
434
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
435
|
+
const quote = ch
|
|
436
|
+
i++
|
|
437
|
+
while (i < codeText.length && codeText[i] !== quote) {
|
|
438
|
+
i += codeText[i] === '\\' ? 2 : 1
|
|
439
|
+
}
|
|
440
|
+
i++
|
|
441
|
+
continue
|
|
442
|
+
}
|
|
443
|
+
if (ch === '{') {
|
|
444
|
+
depth++
|
|
445
|
+
} else if (ch === '}') {
|
|
446
|
+
depth = Math.max(0, depth - 1)
|
|
447
|
+
} else if (ch === '>' && depth === 0) {
|
|
448
|
+
return i
|
|
449
|
+
}
|
|
450
|
+
i++
|
|
451
|
+
}
|
|
452
|
+
return -1
|
|
453
|
+
}
|
|
454
|
+
|
|
424
455
|
// ── Tag provenance, shared by chart-missing-aria-label ────────────────────────────────────────────
|
|
425
456
|
//
|
|
426
457
|
// `chart-missing-aria-label` keys on a JSX tag NAME, which is the whole of its signal — so a
|
|
@@ -472,13 +503,13 @@ function basaltImportedNames(codeText: string): ReadonlySet<string> {
|
|
|
472
503
|
const RAW_FORM_CONTROL = /<(?:input|select|textarea)\b/g
|
|
473
504
|
|
|
474
505
|
// A raw form-control's own opening tag — bounded full-text scan (same shape as CARD_SURFACE_TAG /
|
|
475
|
-
//
|
|
506
|
+
// CHART_ENTRY_POINT_TAG_START), used ONLY by sub-16-input-font to search the tag's own inline `style`
|
|
476
507
|
// for a sub-floor fontSize.
|
|
477
508
|
const RAW_FORM_CONTROL_TAG = /<(?:input|select|textarea)\b(?:=>|[^>])*?>/g
|
|
478
509
|
|
|
479
510
|
// ── Control-home patterns (docs/CONTROLS-SPEC.md §6 — the text lane of laws C1/C8) ──────────────
|
|
480
511
|
//
|
|
481
|
-
// Bounded full-text tag scans, the CARD_SURFACE_TAG /
|
|
512
|
+
// Bounded full-text tag scans, the CARD_SURFACE_TAG / CHART_ENTRY_POINT_TAG_START shape: a `<Title>` and a
|
|
482
513
|
// `<Select>` are both routinely formatted across lines, so a per-line regex would see neither.
|
|
483
514
|
|
|
484
515
|
/** A `<Title …>` opening tag, however it is wrapped. `order` is judged separately, on the tag text. */
|
|
@@ -877,7 +908,6 @@ export const PLUGIN_RULE_IDS: ReadonlySet<string> = new Set([
|
|
|
877
908
|
// `checkSource`'s reach: provenance here is an import graph and a JSX ancestry, neither of which
|
|
878
909
|
// a 12-line regex window has. Same reason `bound-control-outside-home` has no text-lane twin.
|
|
879
910
|
'provider-above-router',
|
|
880
|
-
'query-dual-import',
|
|
881
911
|
'query-fn-unwrap',
|
|
882
912
|
'deprecated-export',
|
|
883
913
|
// Same lane: `{...inputProps(…)}` without a sibling `key` is a JSX-ancestry claim (does this
|
|
@@ -904,6 +934,10 @@ export const PLUGIN_RULE_IDS: ReadonlySet<string> = new Set([
|
|
|
904
934
|
// notifications>` is unreachable now that `BasaltNotifications` no longer exists as a
|
|
905
935
|
// standalone export. Mirrored by the plugin's own `RETIRED_RULE_IDS`.
|
|
906
936
|
'duplicate-notifications-mount',
|
|
937
|
+
// Retired plugin rule — its whole premise (a raw `@tanstack/react-query` import beside
|
|
938
|
+
// `basalt-ui/query`) is unreachable now that C1 (1.29.0) dropped the `./query` subpath.
|
|
939
|
+
// Mirrored by the plugin's own `RETIRED_RULE_IDS`.
|
|
940
|
+
'query-dual-import',
|
|
907
941
|
])
|
|
908
942
|
|
|
909
943
|
/** The shortest string accepted as a written reason — enough to exclude a stray separator. */
|
|
@@ -1901,7 +1935,7 @@ export const GUARD_RULES = {
|
|
|
1901
1935
|
},
|
|
1902
1936
|
'chart-missing-aria-label': {
|
|
1903
1937
|
kind: 'chart-missing-aria-label',
|
|
1904
|
-
pattern:
|
|
1938
|
+
pattern: CHART_ENTRY_POINT_TAG_START, // handled inline (full-text tag-scoped scan); entry keeps registry complete
|
|
1905
1939
|
enabled: (cfg: GuardConfig) => cfg.chartMissingAriaLabel,
|
|
1906
1940
|
// JSX-tag-shaped (`<MultiLine …>`) — never appears in CSS text.
|
|
1907
1941
|
appliesTo: (relPath) => !relPath.endsWith('.css'),
|
|
@@ -2349,12 +2383,15 @@ export function checkSource(text: string, relPath: string, cfg: GuardConfig): Fi
|
|
|
2349
2383
|
// that is missing the prop. The allow-comment is honored anywhere in the tag's span, so a
|
|
2350
2384
|
// `theme-allow` that used to sit on the closing line keeps working.
|
|
2351
2385
|
if (ariaLabelRuns) {
|
|
2352
|
-
for (const m of codeText.matchAll(
|
|
2353
|
-
const
|
|
2386
|
+
for (const m of codeText.matchAll(CHART_ENTRY_POINT_TAG_START)) {
|
|
2387
|
+
const matchStart = m.index ?? 0
|
|
2354
2388
|
// A tag this file defines itself is not the shipped kind — it does not take `ariaLabel`.
|
|
2355
2389
|
if (!isShippedTag(m[1] as string)) continue
|
|
2390
|
+
const closeIdx = scanChartTagClose(codeText, matchStart + m[0].length)
|
|
2391
|
+
if (closeIdx === -1) continue
|
|
2392
|
+
const tagText = codeText.slice(matchStart, closeIdx + 1)
|
|
2356
2393
|
if (HAS_ARIA_LABEL_PROP.test(tagText)) continue
|
|
2357
|
-
const startLine = codeText.slice(0,
|
|
2394
|
+
const startLine = codeText.slice(0, matchStart).split('\n').length
|
|
2358
2395
|
const endLine = startLine + (tagText.split('\n').length - 1)
|
|
2359
2396
|
if (isAllowedInRange(startLine, endLine, 'chart-missing-aria-label')) continue
|
|
2360
2397
|
findings.push({
|
package/src/query-client.ts
CHANGED
|
@@ -55,11 +55,13 @@ export function createBasaltQueryClient(config?: QueryClientConfig): QueryClient
|
|
|
55
55
|
* wrapper that leaves `data` unset on a miss — rather than explicitly `null` — is caught the same
|
|
56
56
|
* way; the two envelope shapes argo's own pre-basalt `unwrap` and basalt's ONLY differed on.
|
|
57
57
|
*
|
|
58
|
-
* **
|
|
59
|
-
* `queryFn: () => unwrap(api.x.get())`)
|
|
60
|
-
* (`unwrap(await api.x.get())
|
|
61
|
-
*
|
|
62
|
-
*
|
|
58
|
+
* **One generic signature, a conditional return** — a Promise of the envelope (basalt's original
|
|
59
|
+
* shape, `queryFn: () => unwrap(api.x.get())`) or the ALREADY-RESOLVED envelope itself
|
|
60
|
+
* (`unwrap(await api.x.get())`), both resolve `TData` off the same conditional. The two-overload
|
|
61
|
+
* form this replaced (1.29.0) resolved `unwrap` to `unknown` whenever it was passed as a bare
|
|
62
|
+
* callback — `.then(unwrap)` never applies an overload, it just reads the declared type of the
|
|
63
|
+
* `unwrap` reference — so `api.threads.get().then(unwrap)` silently widened. A single generic
|
|
64
|
+
* signature has exactly one type to read regardless of how the reference is used.
|
|
63
65
|
*
|
|
64
66
|
* @example
|
|
65
67
|
* import { unwrap } from 'basalt-ui'
|
|
@@ -69,27 +71,30 @@ export function createBasaltQueryClient(config?: QueryClientConfig): QueryClient
|
|
|
69
71
|
* // mutation:
|
|
70
72
|
* mutationFn: (body) => unwrap(api.resource.post({ body }))
|
|
71
73
|
*
|
|
72
|
-
* // resolved-envelope — either shape:
|
|
74
|
+
* // resolved-envelope — either shape, including a bare `.then(unwrap)` callback:
|
|
73
75
|
* const rows = unwrap(await api.users.get())
|
|
74
76
|
* const rows2 = await api.users.get().then(unwrap)
|
|
75
77
|
*/
|
|
76
|
-
export function unwrap<
|
|
77
|
-
response
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
response:
|
|
82
|
-
| Promise<{ data: TData | null | undefined; error: unknown }>
|
|
83
|
-
| { data: TData | null | undefined; error: unknown },
|
|
84
|
-
): TData | Promise<TData> {
|
|
85
|
-
if (response instanceof Promise) return response.then((envelope) => unwrapEnvelope(envelope))
|
|
86
|
-
return unwrapEnvelope(response)
|
|
78
|
+
export function unwrap<R extends Envelope | Promise<Envelope>>(response: R): UnwrapResult<R> {
|
|
79
|
+
if (response instanceof Promise) {
|
|
80
|
+
return response.then((envelope) => unwrapEnvelope(envelope)) as unknown as UnwrapResult<R>
|
|
81
|
+
}
|
|
82
|
+
return unwrapEnvelope(response as Envelope) as unknown as UnwrapResult<R>
|
|
87
83
|
}
|
|
88
84
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
85
|
+
/** Transport-agnostic `{ data, error }` result shape — Eden Treaty, a raw fetch wrapper, etc. */
|
|
86
|
+
type Envelope<TData = unknown> = { data: TData | null | undefined; error: unknown }
|
|
87
|
+
|
|
88
|
+
/** `unwrap`'s conditional return: a `Promise<TData>` when `R` is a promised envelope, `TData` when
|
|
89
|
+
* it is an already-resolved one — inferred off the SAME `R` the overload used to split on. */
|
|
90
|
+
type UnwrapResult<R> =
|
|
91
|
+
R extends Promise<Envelope<infer TData>>
|
|
92
|
+
? Promise<TData>
|
|
93
|
+
: R extends Envelope<infer TData>
|
|
94
|
+
? TData
|
|
95
|
+
: never
|
|
96
|
+
|
|
97
|
+
function unwrapEnvelope<TData>(envelope: Envelope<TData>): TData {
|
|
93
98
|
const { data, error } = envelope
|
|
94
99
|
if (error) throw error
|
|
95
100
|
if (data === null || data === undefined)
|
package/src/surfaces.ts
CHANGED
|
@@ -56,7 +56,6 @@ export const PLUGIN_RULE_ID_LIST = [
|
|
|
56
56
|
'search-literal-link',
|
|
57
57
|
'use-search-from-literal',
|
|
58
58
|
'provider-above-router',
|
|
59
|
-
'query-dual-import',
|
|
60
59
|
'query-fn-unwrap',
|
|
61
60
|
'deprecated-export',
|
|
62
61
|
'forms-field-key',
|
|
@@ -672,10 +671,12 @@ export const SURFACES = {
|
|
|
672
671
|
rule: 'batteries',
|
|
673
672
|
skill: ['basalt-app'],
|
|
674
673
|
guardKinds: [],
|
|
675
|
-
// query-
|
|
676
|
-
//
|
|
677
|
-
//
|
|
678
|
-
|
|
674
|
+
// query-fn-unwrap: reassigned here from the now-dropped `./query` surface (C1 consolidation —
|
|
675
|
+
// createBasaltQueryClient/unwrap/BasaltQueryDevtools are root barrel exports now, but their
|
|
676
|
+
// doctrine stays `batteries` — "use the shipped thing" — not `mantine`). `query-dual-import`
|
|
677
|
+
// was retired alongside it — its premise (a raw `@tanstack/react-query` import beside
|
|
678
|
+
// `basalt-ui/query`) is unreachable now that the `./query` subpath is gone.
|
|
679
|
+
pluginRules: ['query-fn-unwrap'],
|
|
679
680
|
globs: {
|
|
680
681
|
// Shipped is a catch-all (not just src/**+app/**) so consumer code under components/, lib/,
|
|
681
682
|
// features/, etc. is also covered. The @visx/*-only-in-charts and Mantine-free charts/tokens
|