diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2142cb5e..72d1831c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,7 @@ jobs: - run: bun install --frozen-lockfile - run: bun run typecheck - run: bun run lint + - run: bun run format:check - run: bun test - run: bun run build - run: bun run contrast-test diff --git a/.ladle/components.tsx b/.ladle/components.tsx index 7e954d10..7ca2e180 100644 --- a/.ladle/components.tsx +++ b/.ladle/components.tsx @@ -11,13 +11,20 @@ import "../src/styles.css"; // colour. const Provider: GlobalProvider = ({ globalState, children }) => { useEffect(() => { - const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches; - const isDark = globalState.theme === "dark" || (globalState.theme === "auto" && prefersDark); + const prefersDark = window.matchMedia( + "(prefers-color-scheme: dark)", + ).matches; + const isDark = + globalState.theme === "dark" || + (globalState.theme === "auto" && prefersDark); document.documentElement.classList.toggle("dark", isDark); // theme.css now follows the OS when neither class is set, so "light" must // be pinned explicitly — removing `.dark` alone still renders dark on a // dark-OS dev box. - document.documentElement.classList.toggle("light", globalState.theme === "light"); + document.documentElement.classList.toggle( + "light", + globalState.theme === "light", + ); }, [globalState.theme]); return children; diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 00000000..9af2565f --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,4 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "printWidth": 80 +} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 00000000..0cd4329b --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,6 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "categories": { + "correctness": "error" + } +} diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 55b28710..505be88b 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -23,10 +23,10 @@ points. There is no CLI, no component generator and no docs site, and that is de `bun run build` is three steps, joined by `&&`, in order: -| Step | Tool | Output | -| --- | --- | --- | -| `build:js` | `tsc -p tsconfig.build.json` | `dist/**/*.js` and `dist/**/*.d.ts`, one per source file | -| `build:css` | Tailwind v4 CLI | `dist/styles.css`, plus `dist/theme.css` copied from source | +| Step | Tool | Output | +| ----------- | ---------------------------- | ----------------------------------------------------------- | +| `build:js` | `tsc -p tsconfig.build.json` | `dist/**/*.js` and `dist/**/*.d.ts`, one per source file | +| `build:css` | Tailwind v4 CLI | `dist/styles.css`, plus `dist/theme.css` copied from source | `tsc` emits the JavaScript and the declarations in one pass. Nothing bundles, so nothing can merge two modules into one chunk. `prepack` runs the build, so npm publishes a fresh @@ -47,6 +47,7 @@ Components in this package **never fetch**, and never import a server-side packa component that fetches has chosen the consumer's data layer for them; a component that imports a server package drags a database tree into a browser bundle. Data arrives as props — the host owns its own data layer, caching and pagination. + ## `CommandPalette` `CommandPalette` is a global-search overlay with no idea what it is searching. It takes @@ -112,7 +113,7 @@ provider; every other component does not. ### Contrast is gated `contrast-test` runs in CI after the build. It parses the **built** `dist/styles.css` -for the `:root` and `.dark` custom properties and derives its pairs from token *names*, +for the `:root` and `.dark` custom properties and derives its pairs from token _names_, never from listed hexes, so a new `--warning` / `--warning-foreground` is covered on the next run with no edit. Six rules: @@ -123,7 +124,7 @@ next run with no edit. Six rules: a border; 4. `input` and `ring` clear the 3:1 UI-component threshold on both surfaces; 5. every `chart-N` clears 3:1 against both surfaces; -6. a card is separable from the page by at least 1.2:1, satisfied by *either* its fill or +6. a card is separable from the page by at least 1.2:1, satisfied by _either_ its fill or its border. Both modes run every time, with dark treated as an override layer over light so a token @@ -151,7 +152,7 @@ alongside a second, non-colour channel. That is why `seriesDash()` exists next to `seriesColor()`, and why every chart ships a legend, direct labels and a data table rather than treating them as polish: the palette is -compliant *because* they are there. Past five series `seriesColor` returns the muted ink +compliant _because_ they are there. Past five series `seriesColor` returns the muted ink rather than cycling or inventing a hue — use `foldSeries` so the label says "lumped together" too. The contrast gate re-checks the 3:1 half on every build; the colour-vision half cannot be re-derived from hex pairs, so if you re-step these tokens, re-run the @@ -173,18 +174,18 @@ and numbers out, checkable without rendering anything). ## Layout -| | | -| --- | --- | -| `src/ui/` | Components. Primitives, collection surfaces, shells, and the domain families. | -| `src/lib/` | Non-component source: `utils`, the per-domain shapes, and the internal chart palette and geometry. | -| `src/hooks/` | 7 headless hooks: `use-command-palette-navigation`, `use-controllable-state`, `use-delayed-autofocus`, `use-dismissable-popover`, `use-list-selection`, `use-prefers-reduced-motion`, `use-scroll-current-into-view`. | -| `src/blocks/` | Multi-file compositions (`login`). | -| `src/theme.css` | Tokens, keyframes, base layer. The only CSS source. | -| `src/styles.css` | Two `@import`s. The entry Tailwind compiles into `dist/styles.css`. | -| `src/index.ts` | The root barrel. Hand-written: add a public module here. | -| `scripts/contrast-test.mjs` | The theme gate. Reads `dist/styles.css`. | -| `scripts/dep-guard.mjs` | The forbidden-import gates. | -| `dist/` | Build output. Generated, not committed; the only thing `files` publishes. | +| | | +| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `src/ui/` | Components. Primitives, collection surfaces, shells, and the domain families. | +| `src/lib/` | Non-component source: `utils`, the per-domain shapes, and the internal chart palette and geometry. | +| `src/hooks/` | 7 headless hooks: `use-command-palette-navigation`, `use-controllable-state`, `use-delayed-autofocus`, `use-dismissable-popover`, `use-list-selection`, `use-prefers-reduced-motion`, `use-scroll-current-into-view`. | +| `src/blocks/` | Multi-file compositions (`login`). | +| `src/theme.css` | Tokens, keyframes, base layer. The only CSS source. | +| `src/styles.css` | Two `@import`s. The entry Tailwind compiles into `dist/styles.css`. | +| `src/index.ts` | The root barrel. Hand-written: add a public module here. | +| `scripts/contrast-test.mjs` | The theme gate. Reads `dist/styles.css`. | +| `scripts/dep-guard.mjs` | The forbidden-import gates. | +| `dist/` | Build output. Generated, not committed; the only thing `files` publishes. | ## Small-stateless-components audit @@ -216,15 +217,15 @@ families hold zero layout state internally — the parent owns it, full stop. The components below didn't follow either shape; they held `open`/`expanded` in `useState` with no way for a parent to read or drive it. -| Component | Path | Blast radius | Status | -| --- | --- | --- | --- | -| `ToolNarrative` | `src/ui/tool-narrative.tsx` | 3 importers | **Fixed** — added optional `open`/`onOpenChange` | -| `ToolBlock` | `src/ui/tool-block.tsx` | 2 importers | **Fixed** — added optional `open`/`onOpenChange` | -| `NotificationsBell` | `src/ui/notifications-bell.tsx` | 1 importer | **Fixed** — added optional `open`/`onOpenChange` | -| `TenantSelector` | `src/ui/tenant-selector.tsx` | 1 importer | **Fixed** — added optional `open`/`onOpenChange` | -| `ThreadSwitcher` | `src/ui/thread-switcher.tsx` | 1 importer | **Fixed** — added optional `open`/`onOpenChange` | -| `ReasoningBlock` (was `ActivityBlock`) | `src/ui/reasoning-block.tsx` | 1 importer | **Fixed** — added optional `open`/`onOpenChange` over the native `
` | -| `SubagentDock` (per-row disclosure) | `src/ui/subagent-dock.tsx` | 1 importer | Deferred — lifting it means a parent tracking one open flag per subagent row (a map, not a boolean); a bigger surface change than this pass covers | +| Component | Path | Blast radius | Status | +| -------------------------------------- | ------------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ToolNarrative` | `src/ui/tool-narrative.tsx` | 3 importers | **Fixed** — added optional `open`/`onOpenChange` | +| `ToolBlock` | `src/ui/tool-block.tsx` | 2 importers | **Fixed** — added optional `open`/`onOpenChange` | +| `NotificationsBell` | `src/ui/notifications-bell.tsx` | 1 importer | **Fixed** — added optional `open`/`onOpenChange` | +| `TenantSelector` | `src/ui/tenant-selector.tsx` | 1 importer | **Fixed** — added optional `open`/`onOpenChange` | +| `ThreadSwitcher` | `src/ui/thread-switcher.tsx` | 1 importer | **Fixed** — added optional `open`/`onOpenChange` | +| `ReasoningBlock` (was `ActivityBlock`) | `src/ui/reasoning-block.tsx` | 1 importer | **Fixed** — added optional `open`/`onOpenChange` over the native `
` | +| `SubagentDock` (per-row disclosure) | `src/ui/subagent-dock.tsx` | 1 importer | Deferred — lifting it means a parent tracking one open flag per subagent row (a map, not a boolean); a bigger surface change than this pass covers | All five fixes are additive: `open`/`onOpenChange` are optional, and the component still manages its own state when they're omitted, so nothing that @@ -262,11 +263,11 @@ exists to standardize — a regression, not a cleanup. ### (c) Imperative logic that belongs in a hook -| Finding | Components | Blast radius | Status | -| --- | --- | --- | --- | -| Outside-click + Escape dismissal, hand-rolled three times | `NotificationsBell`, `TenantSelector`, `ThreadSwitcher` | 3 components, 3 importers combined | **Fixed** — extracted `useDismissablePopover` (`src/hooks/use-dismissable-popover.ts`) | -| `matchMedia("(prefers-reduced-motion: reduce)")`, read once and never re-checked, duplicated three times | `AnimatedNumber`, `DitherCanvas`, `use-scroll-current-into-view` | 3 components/hooks, 5 importers combined | **Fixed** — extracted `usePrefersReducedMotion` (`src/hooks/use-prefers-reduced-motion.ts`), now reactive via `useSyncExternalStore` | -| Spotlight measurement (`getBoundingClientRect`, `scrollIntoView`, resize/scroll listeners) | `OnboardingTour` | 1 importer | Deferred — tightly coupled to tour-step semantics (target selectors, centering a step with no target); low reuse value elsewhere today | +| Finding | Components | Blast radius | Status | +| -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | +| Outside-click + Escape dismissal, hand-rolled three times | `NotificationsBell`, `TenantSelector`, `ThreadSwitcher` | 3 components, 3 importers combined | **Fixed** — extracted `useDismissablePopover` (`src/hooks/use-dismissable-popover.ts`) | +| `matchMedia("(prefers-reduced-motion: reduce)")`, read once and never re-checked, duplicated three times | `AnimatedNumber`, `DitherCanvas`, `use-scroll-current-into-view` | 3 components/hooks, 5 importers combined | **Fixed** — extracted `usePrefersReducedMotion` (`src/hooks/use-prefers-reduced-motion.ts`), now reactive via `useSyncExternalStore` | +| Spotlight measurement (`getBoundingClientRect`, `scrollIntoView`, resize/scroll listeners) | `OnboardingTour` | 1 importer | Deferred — tightly coupled to tour-step semantics (target selectors, centering a step with no target); low reuse value elsewhere today | `CommandPalette`'s keyboard/query navigation already lives in `use-command-palette-navigation.ts` — this is the pattern the fixes above @@ -305,12 +306,12 @@ The gap is JS-driven motion, which that CSS rule cannot reach: `requestAnimationFrame` loops and imperative `scrollIntoView` calls. All four such call sites were audited: -| Component | Path | Before | Status | -| --- | --- | --- | --- | -| `AnimatedNumber` | `src/ui/animated-number.tsx` | One-shot `matchMedia` check per count-up | **Fixed** — now uses `usePrefersReducedMotion`, re-runs if the OS preference flips mid-count | -| `DitherCanvas` | `src/ui/dither-canvas.tsx` | One-shot `matchMedia` check per mount | **Fixed** — same | -| `use-scroll-current-into-view` | `src/hooks/use-scroll-current-into-view.ts` | One-shot `matchMedia` check per scroll | **Fixed** — same | -| `DitherBackground` | `src/ui/dither-background.tsx` | Already listens for `change` on its own `MediaQueryList` | Reviewed, no change — already the correct, live-reactive pattern | +| Component | Path | Before | Status | +| ------------------------------ | ------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| `AnimatedNumber` | `src/ui/animated-number.tsx` | One-shot `matchMedia` check per count-up | **Fixed** — now uses `usePrefersReducedMotion`, re-runs if the OS preference flips mid-count | +| `DitherCanvas` | `src/ui/dither-canvas.tsx` | One-shot `matchMedia` check per mount | **Fixed** — same | +| `use-scroll-current-into-view` | `src/hooks/use-scroll-current-into-view.ts` | One-shot `matchMedia` check per scroll | **Fixed** — same | +| `DitherBackground` | `src/ui/dither-background.tsx` | Already listens for `change` on its own `MediaQueryList` | Reviewed, no change — already the correct, live-reactive pattern | ### Summary @@ -366,7 +367,7 @@ so that check lives in one place too. component whose job is to render caller-supplied content it has no opinion about (`NotificationsBell`'s `children`, `AuthLayout`'s `panel`) takes `children` or a named slot. A component whose job is to render a -*collection* of the library's own domain shapes (a list of `WorkflowStep`, a +_collection_ of the library's own domain shapes (a list of `WorkflowStep`, a list of chat messages) takes that collection as a data prop — that's what makes a table, a list and a step rail interchangeable consumers of one loading/empty/error contract. Don't reach for a config-object prop as a @@ -404,13 +405,14 @@ setting changes. ## Known limits - decision, not by oversight. +decision, not by oversight. + - **Tests exist but are partial.** `bun test` runs 338 tests co-located with their subjects (`src/**/*.test.tsx`), plus shared harnesses under `src/test/` — the measurable half of appearance (contrast, in both modes) is gated in the build, and a growing slice of behaviour is now covered by these, but most components still have no test.- **A rendering gallery exists but is partial.** Ladle (`bun run stories`, `bun run - stories:build`) renders 42 stories under `stories/`; most of the ~90 components have +stories:build`) renders 42 stories under `stories/`; most of the ~90 components have none, so visual review of an uncovered component still means rendering the package inside a consumer app. - **The prebuilt stylesheet restyles the consuming page** — it carries Tailwind's diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e48c664e..73b361e7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,11 +28,12 @@ consumer's data layer, not in a component — data arrives as props. ```sh bun run build # tsc (JS + types) → Tailwind bun run typecheck # tsc --noEmit -bun run lint # eslint . +bun run lint # oxlint +bun run format:check # oxfmt --check (`bun run format` rewrites) bun run dep-guard # forbidden-import checks over src/ ``` -All four must be clean before a change lands. There is no CI in this repository yet, so +All five must be clean before a change lands. There is no CI in this repository yet, so running them locally is the only thing standing between a change and a release. `typecheck` is not negotiable and `any` is not a way past it: the escapes that exist each carry a comment explaining why the type system leaves no alternative, and a new one needs @@ -46,7 +47,7 @@ Two things follow: - **Change tokens in `src/theme.css`, then run `bun run build`.** Editing `dist/` changes nothing — it is generated output and is gitignored. -- **Do not add hex pairs to the test.** The pairs are derived from token *names*. If a +- **Do not add hex pairs to the test.** The pairs are derived from token _names_. If a token you added is not covered, the fix is a naming rule in `checksFor`, not a hardcoded pair. @@ -62,7 +63,7 @@ secret is a guard nobody can comply with. 1. **No `@workbench/*`.** A published file that imports an unpublished scope is uninstallable for everyone outside the project that defines it, and the failure lands - in the *consumer's* install, where they cannot fix it. + in the _consumer's_ install, where they cannot fix it. 2. **Nothing reachable from the root barrel imports an optional peer.** Re-exporting a module that statically imports an optional peer makes that peer mandatory for every root import, so such a module must be kept out of the barrel and given its own `exports` entry. @@ -81,7 +82,7 @@ around it. 2. If it is public API, add `export * from ".//.js";` to `src/index.ts`. Machinery stays unlisted: keeping something internal is cheap; taking an export back after it ships is not. -3. If it is public but statically imports an *optional* peer, leave it out of +3. If it is public but statically imports an _optional_ peer, leave it out of `src/index.ts` and add a `.//` entry to `exports` in `package.json` instead. `dep-guard` fails if it reaches the barrel. 4. If a `ui/` or `blocks/` module calls a hook, creates a context, defines an inline @@ -166,7 +167,7 @@ not a follow-up. accessible name; an icon-only button needs `aria-label`. - **Colour is never the only channel.** Anything encoded in colour is encoded a second way too — a shape, an icon, a dash pattern, a label. Charts are the sharp case: the - palette's dark-mode separation is compliant only *because* every chart ships a legend, + palette's dark-mode separation is compliant only _because_ every chart ships a legend, direct labels and a data table. - Respect `prefers-reduced-motion` for anything that animates. @@ -180,12 +181,12 @@ Check your item in both modes and at 200% zoom before opening a change. major version. - **The minimum that works.** No wrapper that only forwards props, no config indirection, no speculative extension point. No CLI, no component generator, no docs site. -- Comments explain *why*. The code already says what. +- Comments explain _why_. The code already says what. ## Pull requests - Keep commits focused, and keep the diff to the change you are describing. -- Explain *why* in the commit message; the code already says what. +- Explain _why_ in the commit message; the code already says what. - Run the gates locally and say so in the pull request: `dep-guard`, `typecheck`, `lint`, and `build` including the contrast gate. Nothing runs them for you yet. - Contributions are accepted under the repository's LGPL-2.1-only licence. diff --git a/IMPLEMENTATION.md b/IMPLEMENTATION.md index ec982bf1..5f621f69 100644 --- a/IMPLEMENTATION.md +++ b/IMPLEMENTATION.md @@ -24,16 +24,16 @@ Why the library exists lives in [PRODUCT.md](./PRODUCT.md). Development uses **bun**. `bun run build` is three steps, joined by `&&`: -| Step | Tool | Output | -| --- | --- | --- | -| `build:js` | `tsc -p tsconfig.build.json` | `dist/**/*.js` and `dist/**/*.d.ts`, one per source file | -| `build:css` | Tailwind v4 CLI | `dist/styles.css`, plus `dist/theme.css` copied from source | +| Step | Tool | Output | +| ----------- | ---------------------------- | ----------------------------------------------------------- | +| `build:js` | `tsc -p tsconfig.build.json` | `dist/**/*.js` and `dist/**/*.d.ts`, one per source file | +| `build:css` | Tailwind v4 CLI | `dist/styles.css`, plus `dist/theme.css` copied from source | Source imports are relative and carry `.js` extensions. There is no path alias. `prepack` runs the build. CI runs `contrast-test` (reads `dist/styles.css`) and `dep-guard` after it. -Other scripts: `bun run typecheck`, `bun run lint` (eslint), +Other scripts: `bun run typecheck`, `bun run lint` (oxlint), `bun run format` / `format:check` (oxfmt), `bun run dep-guard` (`scripts/dep-guard.mjs` — fails if any file imports `@workbench/*`), `bun test`, `bun run stories` / `stories:build` ([Ladle](https://ladle.dev) over `stories/`). diff --git a/PRODUCT.md b/PRODUCT.md index e0b88882..3e269720 100644 --- a/PRODUCT.md +++ b/PRODUCT.md @@ -20,7 +20,7 @@ It is not a design-system starter, not a CLI, and not a template app. ## What a host can do - Import from the root (`@corbits/react-ui`) and bundle only what they used. -- Render collection surfaces against *their* data. Components never fetch. +- Render collection surfaces against _their_ data. Components never fetch. Data arrives as props. Pieces that take no collection need no extra host wiring. - Style with a single prebuilt sheet (`@corbits/react-ui/styles.css`) and no diff --git a/SECURITY.md b/SECURITY.md index 21eae733..2927717b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -39,13 +39,13 @@ Defects in the components this package ships: - credentials, tokens, internal hostnames or real data in a shipped file or a fixture; - a declared dependency the package does not actually need, an undeclared one it - does, or anything that runs at install time — the package has no install scripts, - and adding one would be a supply-chain change. + does, or anything that runs at install time — the package has no install scripts, + and adding one would be a supply-chain change. ## What is not - **Authentication and authorization.** This package ships no auth. The `login` and - `access-notice` blocks are *presentation*: they render a form and a message. What + `access-notice` blocks are _presentation_: they render a form and a message. What a submission does, whether a session is valid, and who may see a route are the host's, and a host that trusts a client-side check owns that decision. - **Fetching, caching and transport.** Components never fetch. A `DataPort` diff --git a/bun.lock b/bun.lock index 11af5444..6034c5c5 100644 --- a/bun.lock +++ b/bun.lock @@ -10,7 +10,6 @@ "tailwind-merge": "^3.3.1", }, "devDependencies": { - "@eslint/js": "^9.39.1", "@happy-dom/global-registrator": "^20.11.2", "@ladle/react": "^5.1.1", "@radix-ui/react-dialog": "^1.1.15", @@ -23,14 +22,14 @@ "@types/node": "^24.9.1", "@types/react": "^19.2.2", "@types/react-dom": "^19.2.1", - "eslint": "^9.39.1", "lucide-react": "^1.27.0", + "oxfmt": "^0.70.0", + "oxlint": "^1.85.0", "react": "^19.2.0", "react-dom": "^19.2.0", "sonner": "^2.0.7", "tailwindcss": "^4.3.3", "typescript": "^5.9.3", - "typescript-eslint": "^8.46.2", }, "peerDependencies": { "@radix-ui/react-dialog": "^1.1.15", @@ -143,24 +142,6 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], - "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.10.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg=="], - - "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], - - "@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="], - - "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], - - "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], - - "@eslint/eslintrc": ["@eslint/eslintrc@3.3.6", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.3.0", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA=="], - - "@eslint/js": ["@eslint/js@9.39.5", "", {}, "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A=="], - - "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], - - "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], - "@floating-ui/core": ["@floating-ui/core@1.8.0", "", { "dependencies": { "@floating-ui/utils": "^0.2.12" } }, "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ=="], "@floating-ui/dom": ["@floating-ui/dom@1.8.0", "", { "dependencies": { "@floating-ui/core": "^1.8.0", "@floating-ui/utils": "^0.2.12" } }, "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg=="], @@ -171,16 +152,6 @@ "@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.11.2", "", { "dependencies": { "@types/node": ">=20.0.0", "happy-dom": "^20.11.2" } }, "sha512-7fkpoXZWzyxaBkzRtlD0wRU5ckxbNT4j6BywzpSYh+SFPsGxEVmNR9KHaMwM2xkHB0pADQLl4Z8DSrztECJ7Ww=="], - "@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="], - - "@humanfs/node": ["@humanfs/node@0.16.8", "", { "dependencies": { "@humanfs/core": "^0.19.2", "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ=="], - - "@humanfs/types": ["@humanfs/types@0.15.0", "", {}, "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q=="], - - "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], - - "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], - "@inquirer/ansi": ["@inquirer/ansi@2.0.7", "", {}, "sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q=="], "@inquirer/confirm": ["@inquirer/confirm@6.1.1", "", { "dependencies": { "@inquirer/core": "^11.2.1", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ=="], @@ -225,6 +196,82 @@ "@open-draft/until": ["@open-draft/until@2.1.0", "", {}, "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg=="], + "@oxfmt/binding-android-arm-eabi": ["@oxfmt/binding-android-arm-eabi@0.70.0", "", { "os": "android", "cpu": "arm" }, "sha512-Xd7YO4/T2axEj6FTLcj4Why3mTBqFMg+x24xtorT4Lb2+1g82090GH0a/4U1m0pABGYiix2bq1pqkYrmV3f0Sw=="], + + "@oxfmt/binding-android-arm64": ["@oxfmt/binding-android-arm64@0.70.0", "", { "os": "android", "cpu": "arm64" }, "sha512-x9rlMYyKXdgKdYyUJzGsK1ZV8P4di/J32ipzcS6Jet6p9r9UAh28neXIMtdlSaJJycdi61Z4YkcLKLpk8ueFjg=="], + + "@oxfmt/binding-darwin-arm64": ["@oxfmt/binding-darwin-arm64@0.70.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-IUTUPvrBVYy7POh4stXzRdz4IVC/1QSaviCWoyenSlOhGu0X9j5K07vCTM9biLjAA2Zs31l0Rj5vvRpj9n95wA=="], + + "@oxfmt/binding-darwin-x64": ["@oxfmt/binding-darwin-x64@0.70.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-vw745q870oTd6J517O24asoX4/E+eK0nxYIFoedSLqgJ+nI5En7+ZS82iZSHZ69zevQrnOXiyHP01dA+t8xD8w=="], + + "@oxfmt/binding-freebsd-x64": ["@oxfmt/binding-freebsd-x64@0.70.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-NO14EgSM9dFkcg+MfGPxvsKqXYs9LKaxPrOKXpv1R0rLokGGFDcCq6dBMq18dE4wlpFOovX0UZY2uh1P30O7QA=="], + + "@oxfmt/binding-linux-arm-gnueabihf": ["@oxfmt/binding-linux-arm-gnueabihf@0.70.0", "", { "os": "linux", "cpu": "arm" }, "sha512-139OEhHarj9CYoJ/i9gXlPv4KLBGtLj2toseOWYFf09QwlhklaZk+wW3aOvlqoeZtuayvkoSNVWra7WJ21s3VQ=="], + + "@oxfmt/binding-linux-arm-musleabihf": ["@oxfmt/binding-linux-arm-musleabihf@0.70.0", "", { "os": "linux", "cpu": "arm" }, "sha512-GEh2PY3IWTE0M24eNhTduountANSbWyDmMnzFSQE/nGg/bjPugbUgiGuFu+xdqcQSd/HKSwH80/F2yVVD48yhA=="], + + "@oxfmt/binding-linux-arm64-gnu": ["@oxfmt/binding-linux-arm64-gnu@0.70.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-En5i+UJmZSPxuSf47F2Hl5YOzKB0bicQLnGQkeTCMQ35cWLtbrSwACJKfiLqRZrk05DwSnsJkhBRaM3OURtIaA=="], + + "@oxfmt/binding-linux-arm64-musl": ["@oxfmt/binding-linux-arm64-musl@0.70.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-WWOoV5W9Im3flVwOVrWn/2DUlOF8v5vcCip+kcNuaMpulRCh6nzzt1Su2vcL2F908YJIXNV3HvegbBHuyLwKHg=="], + + "@oxfmt/binding-linux-ppc64-gnu": ["@oxfmt/binding-linux-ppc64-gnu@0.70.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-YUouneIqW+5n7aE8xx/zeZ6/utr/KH7oykcGoFyd8Uz8uh591T1oKlnoWA3BsRq/ZR42oY1w4MUYvS/0e/MQOA=="], + + "@oxfmt/binding-linux-riscv64-gnu": ["@oxfmt/binding-linux-riscv64-gnu@0.70.0", "", { "os": "linux", "cpu": "none" }, "sha512-iEnMf21S5aGVa4hViDGY8sAQ/AHyCu2JPyrQF8P06wtHhSkD1YJBeT4m/KiGewgf7+a5XCYSCRIPcRQa1xwEoQ=="], + + "@oxfmt/binding-linux-riscv64-musl": ["@oxfmt/binding-linux-riscv64-musl@0.70.0", "", { "os": "linux", "cpu": "none" }, "sha512-91Sdniaj20fQzyMeCxMDzTP4c9s4RB8dGQ308xHhDR0n6U7+1Xq7N9klE7mfXq8iV3lRmIGSXi5X23Hn/0XX/g=="], + + "@oxfmt/binding-linux-s390x-gnu": ["@oxfmt/binding-linux-s390x-gnu@0.70.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-uUV30M6E+2TKKGMaKiwfeL4RZrviHXlUxsrYJ/jFBb+1EZy+pnFT+hF73eeWdzh5NqPOAnw0iiMAIqjqiLZPFg=="], + + "@oxfmt/binding-linux-x64-gnu": ["@oxfmt/binding-linux-x64-gnu@0.70.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ivMcX6kNDPhqtbOaBt/ItFlLlTlXNHLgRuNmxP6Na6UuYXRT10llpJcAPbGeRgjjb3Qzv4jwPp3fB0hui50WNQ=="], + + "@oxfmt/binding-linux-x64-musl": ["@oxfmt/binding-linux-x64-musl@0.70.0", "", { "os": "linux", "cpu": "x64" }, "sha512-w+S+fERxYmlZSyZlJK/U292FjyBoH8cCEj21/tYJX6atX5kNSn+HDkhlFQKT2zcMwUW0uAtUL/bOrlwJZwqRdA=="], + + "@oxfmt/binding-openharmony-arm64": ["@oxfmt/binding-openharmony-arm64@0.70.0", "", { "os": "none", "cpu": "arm64" }, "sha512-Zlom1Xkx257R8bk4ZI4zJsrGno2opknz1+5v5baka3nn4FPyvNSdh8JUL4CdN1S1OWRMvJ9UJQ+RfIqGGCUEfA=="], + + "@oxfmt/binding-win32-arm64-msvc": ["@oxfmt/binding-win32-arm64-msvc@0.70.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-FQgPW5R17vzt7cgrJ8eG/dqX00o2xHsqFeLfw4xzA9FRHpN/DjFo9YDonvIIXGxiEuS9F/jZPnGO+KHNKuCo4Q=="], + + "@oxfmt/binding-win32-ia32-msvc": ["@oxfmt/binding-win32-ia32-msvc@0.70.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-ZfZublNhZ+XBndMiXhkiLlPE+XyGRDa0CweeTL6t1fZypfCh1LTg7e5CvnOeTBunq15MskOcRempumSPGAaCQA=="], + + "@oxfmt/binding-win32-x64-msvc": ["@oxfmt/binding-win32-x64-msvc@0.70.0", "", { "os": "win32", "cpu": "x64" }, "sha512-HlIZEn+WzLQL0DszNzldiRl/DPRCX5R0Vkt6qeUPR1YHwy52hZZo4x6HoTOVmKRP2wUiwPGtKsihNY/f8KRaBg=="], + + "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.85.0", "", { "os": "android", "cpu": "arm" }, "sha512-q2KO/Zso9UT+OMn0NF9ywn4E4t0MI3yxiDhNyhsQ7DyQJrC4FhFE4TXOi4bktFnOWXTMds8qZSbpv2XwRaNOBg=="], + + "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.85.0", "", { "os": "android", "cpu": "arm64" }, "sha512-SxLN3ALjoT9NNdvpjEevGeHvfzTAFrF0NBYB5tzK7/GtCKMze3j1e/m/X2ozqGj2U9hfGG/dg/OG8vpVK4PiDA=="], + + "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.85.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y/Sup/J4f0f9UGsSd/xyCNTeWL+gepO63GBdEDAfue9nBsnk9zMmnIXx1O6b1V8C90vB5nucYNZ0pbMXAp8zJA=="], + + "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.85.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ApOSNC04ynpDTwvBD+//0wyfODRSbEzvRoKpX8teffmc27z8AockwSNeMXGJXn5KP85eahDgR/2llICWLkzcnw=="], + + "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.85.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-bNrVrCOA/kHky3Tu79IXWXe5bhIgLXfUuUEDHlAGOHUk96MkvDZ1ecaQF19rwstrnaqfP1o9nBTqzIr9+ZHkUg=="], + + "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.85.0", "", { "os": "linux", "cpu": "arm" }, "sha512-NUrzOJ1s/EqsVvfn2L/1D8Wro2LPIZUbihL8kOJLh5fEdGEN3rdOGUYq3HwnUIL8sjpoP+4N6RaGrgmMJnaMPw=="], + + "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.85.0", "", { "os": "linux", "cpu": "arm" }, "sha512-UJXrAT3E/RWkEqXLIs2ehETja1qfgkPb+5gwLIIS+o/6cf+grHvoOXTa5997a/YNQfcJS0DRBTOfZt95cvOI1g=="], + + "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.85.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-lK40QLjI0HxigO7CjDDshEtfYIeiYS0020v5BHFPqN4uuQBQxd2K9LNom2dW15o9F1937quSCRVp4ZsVhdbYdg=="], + + "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.85.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-c2zbdBwGKreHXwRx3gWBuFGJxLhxgsg6YlZ+3H+RgRusU/UEV9jNwJ3HGYK+nRo0LvBa7mt6Kj86xoVotUo8cw=="], + + "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.85.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tlt/Hy8lZ97/lCPmCgw/B3k/mwh+BzaIPbPkldZEly7TwLmx0xe2CQcaW2g/rR0dOgS9JNGCZsMEqLhUNMGvaw=="], + + "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.85.0", "", { "os": "linux", "cpu": "none" }, "sha512-3tNR9Xey82X0zKuY1d8hJ6Rc9gwRDurmqGLnQZa5xqOXy8/YyiqFXjAtugkKLY82obOlpK1eSiDRlgcNPuxtIg=="], + + "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.85.0", "", { "os": "linux", "cpu": "none" }, "sha512-wbGRd5PqCcjkJFHhZuZ2OBSUQY9czlQsoA/cQQB9JK/L9mC5MQgGoKAh+xd8QjA5V+0D3j+Qd1lAWn1I8zlelA=="], + + "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.85.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-3Sn0kSrE4DPZCWV/8o+n4x3aFZxI9ulMnkYlwCbJ8eUVkwRK2IerohE/A/z3SNbCwoPFOCJmGE5Avrq0rrvdvQ=="], + + "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.85.0", "", { "os": "linux", "cpu": "x64" }, "sha512-JY2pxxYfB62bAGfejljVCqc44etItehPuAyaeSAdMuEMtwNA00ggMnS66lC1oIhos6oOXUkuU6mZ9bpFh3BqWg=="], + + "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.85.0", "", { "os": "linux", "cpu": "x64" }, "sha512-5k74vZ6qJBjBHEOlBk9B/iv68Yu0F1Afw/vvT2ar6OGCqEeXLaSjXz2n/IPCbhLG22UoKoYEJTzpYraRdcp6PA=="], + + "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.85.0", "", { "os": "none", "cpu": "arm64" }, "sha512-GbAl5qt5TCkPLXTaIISZJnugrcBhra6rodcXc9jYt620UtdsTt71NlNmJmm0frxzFpd54x/G+MkitEJA8I/BoA=="], + + "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.85.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-kjmws5MK0et2swk4ND85D7NVQyDHw162i6whtZDLUA/lo6FQyBZDcmMRCMcVZcNrAhIaftVb00x9ChGDOjjNJA=="], + + "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.85.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-eSsIJx9n4yxvOqYTZyPEMyEXRmE60XH7xGAU7i0Qbsn1lf6Za3CWJ9aRd82oSFKXaxhp+sA6/yMJVRIpLpna6A=="], + + "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.85.0", "", { "os": "win32", "cpu": "x64" }, "sha512-pBebIPUpKKhWrhSMWhy8TdAZBewiXnfxmaAGxhzxM1068GagqFaTwgKlU6e+UyJ2sPR+VoHouhXuGJkQjsrDvA=="], + "@parcel/watcher": ["@parcel/watcher@2.5.1", "", { "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.1", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-freebsd-x64": "2.5.1", "@parcel/watcher-linux-arm-glibc": "2.5.1", "@parcel/watcher-linux-arm-musl": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", "@parcel/watcher-linux-arm64-musl": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", "@parcel/watcher-linux-x64-musl": "2.5.1", "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-ia32": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1" } }, "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg=="], "@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.1", "", { "os": "android", "cpu": "arm64" }, "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA=="], @@ -445,8 +492,6 @@ "@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="], - "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], "@types/mdx": ["@types/mdx@2.0.14", "", {}, "sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg=="], @@ -471,26 +516,6 @@ "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.65.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.65.0", "@typescript-eslint/type-utils": "8.65.0", "@typescript-eslint/utils": "8.65.0", "@typescript-eslint/visitor-keys": "8.65.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.65.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA=="], - - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.65.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.65.0", "@typescript-eslint/types": "8.65.0", "@typescript-eslint/typescript-estree": "8.65.0", "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA=="], - - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.65.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.65.0", "@typescript-eslint/types": "^8.65.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q=="], - - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.65.0", "", { "dependencies": { "@typescript-eslint/types": "8.65.0", "@typescript-eslint/visitor-keys": "8.65.0" } }, "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg=="], - - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.65.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg=="], - - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.65.0", "", { "dependencies": { "@typescript-eslint/types": "8.65.0", "@typescript-eslint/typescript-estree": "8.65.0", "@typescript-eslint/utils": "8.65.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g=="], - - "@typescript-eslint/types": ["@typescript-eslint/types@8.65.0", "", {}, "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg=="], - - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.65.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.65.0", "@typescript-eslint/tsconfig-utils": "8.65.0", "@typescript-eslint/types": "8.65.0", "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg=="], - - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.65.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.65.0", "@typescript-eslint/types": "8.65.0", "@typescript-eslint/typescript-estree": "8.65.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA=="], - - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.65.0", "", { "dependencies": { "@typescript-eslint/types": "8.65.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A=="], - "@ungap/structured-clone": ["@ungap/structured-clone@1.3.3", "", {}, "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg=="], "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], @@ -503,15 +528,11 @@ "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], - "ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], - "ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="], "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - - "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], @@ -521,8 +542,6 @@ "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], - "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.11.12", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA=="], "bcp-47-match": ["bcp-47-match@2.0.3", "", {}, "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ=="], @@ -531,8 +550,6 @@ "boxen": ["boxen@8.0.1", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^8.0.0", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "string-width": "^7.2.0", "type-fest": "^4.21.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0" } }, "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw=="], - "brace-expansion": ["brace-expansion@1.1.16", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw=="], - "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], "browserslist": ["browserslist@4.28.7", "", { "dependencies": { "baseline-browser-mapping": "^2.10.44", "caniuse-lite": "^1.0.30001806", "electron-to-chromium": "^1.5.393", "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw=="], @@ -549,15 +566,13 @@ "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], - "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], - "camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="], "caniuse-lite": ["caniuse-lite@1.0.30001809", "", {}, "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ=="], "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], - "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], @@ -593,8 +608,6 @@ "commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], - "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], - "content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="], "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], @@ -619,8 +632,6 @@ "deep-equal": ["deep-equal@1.0.1", "", {}, "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw=="], - "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], - "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="], "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], @@ -673,21 +684,7 @@ "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], - "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - - "eslint": ["eslint@9.39.5", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.6", "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw=="], - - "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], - - "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], - - "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], - - "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], - - "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], - - "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], "estree-util-attach-comments": ["estree-util-attach-comments@3.0.0", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw=="], @@ -703,18 +700,10 @@ "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], - "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], - "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], - "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], - - "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - "fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="], "fast-string-width": ["fast-string-width@3.0.2", "", { "dependencies": { "fast-string-truncated-width": "^3.0.2" } }, "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg=="], @@ -725,18 +714,10 @@ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], - "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], "filter-obj": ["filter-obj@5.1.0", "", {}, "sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng=="], - "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], - - "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], - - "flatted": ["flatted@3.4.3", "", {}, "sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ=="], - "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], @@ -759,9 +740,7 @@ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], - - "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "globby": ["globby@14.1.0", "", { "dependencies": { "@sindresorhus/merge-streams": "^2.1.0", "fast-glob": "^3.3.3", "ignore": "^7.0.3", "path-type": "^6.0.0", "slash": "^5.1.0", "unicorn-magic": "^0.3.0" } }, "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA=="], @@ -775,8 +754,6 @@ "happy-dom": ["happy-dom@20.11.2", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-7MB+bJLkxu3SowAfBJbjW+c55kNz5tkR45gu2qzrxznezhLeN5YIlJbwUgSzlGc+qWoZ8Ykg71H5ezz69xixrw=="], - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], @@ -817,11 +794,7 @@ "http-errors": ["http-errors@1.8.1", "", { "dependencies": { "depd": "~1.1.2", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": ">= 1.5.0 < 2", "toidentifier": "1.0.1" } }, "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g=="], - "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - - "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], - - "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + "ignore": ["ignore@7.0.6", "", {}, "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw=="], "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], @@ -863,30 +836,18 @@ "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - "js-yaml": ["js-yaml@4.3.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q=="], - "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], - "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], - - "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - - "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], - "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], "keygrip": ["keygrip@1.1.0", "", { "dependencies": { "tsscmp": "1.0.6" } }, "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ=="], - "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], - "koa": ["koa@2.16.4", "", { "dependencies": { "accepts": "^1.3.5", "cache-content-type": "^1.0.0", "content-disposition": "~0.5.2", "content-type": "^1.0.4", "cookies": "~0.9.0", "debug": "^4.3.2", "delegates": "^1.0.0", "depd": "^2.0.0", "destroy": "^1.0.4", "encodeurl": "^1.0.2", "escape-html": "^1.0.3", "fresh": "~0.5.2", "http-assert": "^1.3.0", "http-errors": "^1.6.3", "is-generator-function": "^1.0.7", "koa-compose": "^4.1.0", "koa-convert": "^2.0.0", "on-finished": "^2.3.0", "only": "~0.0.2", "parseurl": "^1.3.2", "statuses": "^1.5.0", "type-is": "^1.6.16", "vary": "^1.1.2" } }, "sha512-3An0GCLDSR34tsCO4H8Tef8Pp2ngtaZDAZnsWJYelqXUK5wyiHvGItgK/xcSkmHLSTn1Jcho1mRQs2ehRzvKKw=="], "koa-compose": ["koa-compose@4.1.0", "", {}, "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw=="], "koa-convert": ["koa-convert@2.0.0", "", { "dependencies": { "co": "^4.6.0", "koa-compose": "^4.1.0" } }, "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA=="], - "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], - "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], @@ -911,8 +872,6 @@ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], - "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], - "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], @@ -1043,8 +1002,6 @@ "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], @@ -1055,8 +1012,6 @@ "nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="], - "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], - "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], @@ -1073,15 +1028,11 @@ "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], - "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], - "outvariant": ["outvariant@1.4.3", "", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="], - "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + "oxfmt": ["oxfmt@0.70.0", "", { "dependencies": { "tinypool": "2.1.2" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.70.0", "@oxfmt/binding-android-arm64": "0.70.0", "@oxfmt/binding-darwin-arm64": "0.70.0", "@oxfmt/binding-darwin-x64": "0.70.0", "@oxfmt/binding-freebsd-x64": "0.70.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.70.0", "@oxfmt/binding-linux-arm-musleabihf": "0.70.0", "@oxfmt/binding-linux-arm64-gnu": "0.70.0", "@oxfmt/binding-linux-arm64-musl": "0.70.0", "@oxfmt/binding-linux-ppc64-gnu": "0.70.0", "@oxfmt/binding-linux-riscv64-gnu": "0.70.0", "@oxfmt/binding-linux-riscv64-musl": "0.70.0", "@oxfmt/binding-linux-s390x-gnu": "0.70.0", "@oxfmt/binding-linux-x64-gnu": "0.70.0", "@oxfmt/binding-linux-x64-musl": "0.70.0", "@oxfmt/binding-openharmony-arm64": "0.70.0", "@oxfmt/binding-win32-arm64-msvc": "0.70.0", "@oxfmt/binding-win32-ia32-msvc": "0.70.0", "@oxfmt/binding-win32-x64-msvc": "0.70.0" }, "peerDependencies": { "svelte": "^5.0.0", "vite-plus": "*" }, "optionalPeers": ["svelte", "vite-plus"], "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-IsHxZ4y0wQLLMhnrJblBJgZsLDzfULrJnAw5j/QqsTlMa/m3AqsbToi+W71uhBGaqlqq/PbbjvHc09TJwdv3Tw=="], - "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], - - "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + "oxlint": ["oxlint@1.85.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.85.0", "@oxlint/binding-android-arm64": "1.85.0", "@oxlint/binding-darwin-arm64": "1.85.0", "@oxlint/binding-darwin-x64": "1.85.0", "@oxlint/binding-freebsd-x64": "1.85.0", "@oxlint/binding-linux-arm-gnueabihf": "1.85.0", "@oxlint/binding-linux-arm-musleabihf": "1.85.0", "@oxlint/binding-linux-arm64-gnu": "1.85.0", "@oxlint/binding-linux-arm64-musl": "1.85.0", "@oxlint/binding-linux-ppc64-gnu": "1.85.0", "@oxlint/binding-linux-riscv64-gnu": "1.85.0", "@oxlint/binding-linux-riscv64-musl": "1.85.0", "@oxlint/binding-linux-s390x-gnu": "1.85.0", "@oxlint/binding-linux-x64-gnu": "1.85.0", "@oxlint/binding-linux-x64-musl": "1.85.0", "@oxlint/binding-openharmony-arm64": "1.85.0", "@oxlint/binding-win32-arm64-msvc": "1.85.0", "@oxlint/binding-win32-ia32-msvc": "1.85.0", "@oxlint/binding-win32-x64-msvc": "1.85.0" }, "peerDependencies": { "oxlint-tsgolint": ">=7.0.2001", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-bc26s97nuvPj1ViyPsqmKecVkUWFMEdtayO8MaQ6oiLfs1pj94cQlZZhrh4BPNlr9HQosjhIlwgZKsfcwmcNgg=="], "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], @@ -1089,8 +1040,6 @@ "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], - "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], - "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], @@ -1103,16 +1052,12 @@ "postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="], - "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], - "prism-react-renderer": ["prism-react-renderer@2.4.1", "", { "dependencies": { "@types/prismjs": "^1.26.0", "clsx": "^2.0.0" }, "peerDependencies": { "react": ">=16.0.0" } }, "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig=="], "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], "property-information": ["property-information@7.2.0", "", {}, "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg=="], - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - "query-string": ["query-string@9.5.0", "", { "dependencies": { "decode-uri-component": "^0.5.0", "filter-obj": "^5.1.0", "split-on-first": "^3.0.0" } }, "sha512-YlJmwNyi0RGYjlxYcuDncMsxFU7YyutbuI7gTm8ySxIGBlwx5yiBCOD5ig9ZNoHkawk/1Dey0N5mEfcUybMVAA=="], "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], @@ -1163,8 +1108,6 @@ "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], - "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], - "rettime": ["rettime@0.11.11", "", {}, "sha512-ILJRqVWBCTlg9r42fFgwVZx1gnFAcQF8mRoMkbgQfIrjEDf9nbBFDFx00oloOa+Q869FUtaYDXZvEfnecQSCoQ=="], "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], @@ -1215,14 +1158,10 @@ "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], - "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], - "style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="], "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], - "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], "tailwind-merge": ["tailwind-merge@3.6.0", "", {}, "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w=="], @@ -1233,6 +1172,8 @@ "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], + "tinypool": ["tinypool@2.1.2", "", {}, "sha512-9YodfrxS9g9IbFr/KOjE5bAeJ0p61n3bW6mqvy0jtoeKd1kTW1Cxm0oulm6KX2lyM9Gl6WIe8nEbY7LWv5ZJww=="], + "tldts": ["tldts@7.4.10", "", { "dependencies": { "tldts-core": "^7.4.10" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog=="], "tldts-core": ["tldts-core@7.4.10", "", {}, "sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw=="], @@ -1247,24 +1188,18 @@ "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], - "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], - "tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "tsscmp": ["tsscmp@1.0.6", "", {}, "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA=="], - "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], - "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "typescript-eslint": ["typescript-eslint@8.65.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.65.0", "@typescript-eslint/parser": "8.65.0", "@typescript-eslint/typescript-estree": "8.65.0", "@typescript-eslint/utils": "8.65.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA=="], - "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], @@ -1287,8 +1222,6 @@ "update-browserslist-db": ["update-browserslist-db@1.3.0", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-x/M6q3w4Ybp91CNaS4S69UnliqR3BzRpOT6LWbksjth0S/+jhfaPJsWjt/TewpT8j9eLIojUf5jr29WextHroA=="], - "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], @@ -1313,8 +1246,6 @@ "widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="], - "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], - "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], "ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="], @@ -1331,12 +1262,8 @@ "ylru": ["ylru@1.4.0", "", {}, "sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA=="], - "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], - "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - "@mswjs/interceptors/@open-draft/deferred-promise": ["@open-draft/deferred-promise@2.2.0", "", {}, "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.3", "tslib": "^2.4.0" }, "bundled": true }, "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg=="], @@ -1351,34 +1278,18 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.6", "", {}, "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw=="], - - "@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], - - "@typescript-eslint/typescript-estree/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], - - "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], - "ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "boxen/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - - "globby/ignore": ["ignore@7.0.6", "", {}, "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw=="], - "http-errors/depd": ["depd@1.1.2", "", {}, "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ=="], "lightningcss/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], - "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "msw/statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], @@ -1389,12 +1300,8 @@ "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], - "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.8", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg=="], - "ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "ansi-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -1403,12 +1310,12 @@ "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - "ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], diff --git a/eslint.config.mjs b/eslint.config.mjs deleted file mode 100644 index 10bc4b1c..00000000 --- a/eslint.config.mjs +++ /dev/null @@ -1,12 +0,0 @@ -import js from "@eslint/js"; -import tseslint from "typescript-eslint"; - -export default tseslint.config( - { ignores: ["dist/**", "scratch/**", "node_modules/**", "build/**", ".worktrees/**", "src/index.ts"] }, - js.configs.recommended, - tseslint.configs.recommended, - { - files: ["scripts/**/*.mjs"], - languageOptions: { globals: { URL: "readonly", console: "readonly", process: "readonly" } }, - }, -); diff --git a/package.json b/package.json index 36057363..292d943a 100644 --- a/package.json +++ b/package.json @@ -2,28 +2,56 @@ "name": "@corbits/react-ui", "version": "0.2.0", "description": "React components for agent and workflow surfaces.", - "author": "Sawyer Cutler ", - "license": "LGPL-2.1-only", - "engines": { - "node": ">=24", - "bun": ">=1.2.0" - }, - "type": "module", - "packageManager": "bun@1.4.0", - "sideEffects": [ - "**/*.css" - ], "homepage": "https://github.com/corbitsdev/react-ui", + "bugs": { + "url": "https://github.com/corbitsdev/react-ui/issues" + }, + "license": "LGPL-2.1-only", + "author": "Sawyer Cutler ", "repository": { "type": "git", "url": "git+https://github.com/corbitsdev/react-ui.git" }, - "bugs": { - "url": "https://github.com/corbitsdev/react-ui/issues" - }, "files": [ "dist" ], + "type": "module", + "sideEffects": [ + "**/*.css" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./ui/command-palette": { + "types": "./dist/ui/command-palette.d.ts", + "default": "./dist/ui/command-palette.js" + }, + "./ui/dialog": { + "types": "./dist/ui/dialog.d.ts", + "default": "./dist/ui/dialog.js" + }, + "./ui/menu": { + "types": "./dist/ui/menu.d.ts", + "default": "./dist/ui/menu.js" + }, + "./ui/mic-permission-dialog": { + "types": "./dist/ui/mic-permission-dialog.d.ts", + "default": "./dist/ui/mic-permission-dialog.js" + }, + "./ui/toast": { + "types": "./dist/ui/toast.d.ts", + "default": "./dist/ui/toast.js" + }, + "./ui/tooltip": { + "types": "./dist/ui/tooltip.d.ts", + "default": "./dist/ui/tooltip.js" + }, + "./styles.css": "./dist/styles.css", + "./theme.css": "./dist/theme.css", + "./package.json": "./package.json" + }, "publishConfig": { "access": "public" }, @@ -34,7 +62,9 @@ "build:css": "tailwindcss -i src/styles.css -o dist/styles.css && cp src/theme.css dist/theme.css", "typecheck": "tsc --noEmit", "test": "bun test", - "lint": "eslint .", + "lint": "oxlint", + "format": "oxfmt", + "format:check": "oxfmt --check", "dep-guard": "node scripts/dep-guard.mjs", "contrast-test": "node scripts/contrast-test.mjs", "stories": "ladle serve --host 127.0.0.1", @@ -46,6 +76,28 @@ "clsx": "^2.1.1", "tailwind-merge": "^3.3.1" }, + "devDependencies": { + "@happy-dom/global-registrator": "^20.11.2", + "@ladle/react": "^5.1.1", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-tooltip": "^1.2.8", + "@tailwindcss/cli": "^4.3.3", + "@tailwindcss/vite": "^4.3.3", + "@types/bun": "^1.3.14", + "@types/node": "^24.9.1", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.1", + "lucide-react": "^1.27.0", + "oxfmt": "^0.70.0", + "oxlint": "^1.85.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "sonner": "^2.0.7", + "tailwindcss": "^4.3.3", + "typescript": "^5.9.3" + }, "peerDependencies": { "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", @@ -70,60 +122,9 @@ "optional": true } }, - "devDependencies": { - "@eslint/js": "^9.39.1", - "@happy-dom/global-registrator": "^20.11.2", - "@ladle/react": "^5.1.1", - "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-slot": "^1.2.3", - "@radix-ui/react-tooltip": "^1.2.8", - "@tailwindcss/cli": "^4.3.3", - "@tailwindcss/vite": "^4.3.3", - "@types/bun": "^1.3.14", - "@types/node": "^24.9.1", - "@types/react": "^19.2.2", - "@types/react-dom": "^19.2.1", - "eslint": "^9.39.1", - "lucide-react": "^1.27.0", - "react": "^19.2.0", - "react-dom": "^19.2.0", - "sonner": "^2.0.7", - "tailwindcss": "^4.3.3", - "typescript": "^5.9.3", - "typescript-eslint": "^8.46.2" + "engines": { + "bun": ">=1.2.0", + "node": ">=24" }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - }, - "./ui/command-palette": { - "types": "./dist/ui/command-palette.d.ts", - "default": "./dist/ui/command-palette.js" - }, - "./ui/dialog": { - "types": "./dist/ui/dialog.d.ts", - "default": "./dist/ui/dialog.js" - }, - "./ui/menu": { - "types": "./dist/ui/menu.d.ts", - "default": "./dist/ui/menu.js" - }, - "./ui/mic-permission-dialog": { - "types": "./dist/ui/mic-permission-dialog.d.ts", - "default": "./dist/ui/mic-permission-dialog.js" - }, - "./ui/toast": { - "types": "./dist/ui/toast.d.ts", - "default": "./dist/ui/toast.js" - }, - "./ui/tooltip": { - "types": "./dist/ui/tooltip.d.ts", - "default": "./dist/ui/tooltip.js" - }, - "./styles.css": "./dist/styles.css", - "./theme.css": "./dist/theme.css", - "./package.json": "./package.json" - } + "packageManager": "bun@1.4.0" } diff --git a/scripts/contrast-test.mjs b/scripts/contrast-test.mjs index f6d6c5ce..46a63780 100644 --- a/scripts/contrast-test.mjs +++ b/scripts/contrast-test.mjs @@ -62,10 +62,15 @@ const REQUIRED = [ /** Hex custom properties declared in the first `selector { ... }` block found. */ function tokensIn(css, selector) { const start = css.indexOf(`${selector} {`); - if (start === -1) throw new Error(`contrast-test: no \`${selector}\` block in dist/styles.css`); + if (start === -1) + throw new Error( + `contrast-test: no \`${selector}\` block in dist/styles.css`, + ); const body = css.slice(start, css.indexOf("}", start)); return Object.fromEntries( - [...body.matchAll(/--([a-z0-9-]+):\s*(#[0-9a-fA-F]{6})\b/g)].map(([, name, hex]) => [name, hex]), + [...body.matchAll(/--([a-z0-9-]+):\s*(#[0-9a-fA-F]{6})\b/g)].map( + ([, name, hex]) => [name, hex], + ), ); } @@ -73,7 +78,9 @@ function tokensIn(css, selector) { function declaredIn(css, selector) { const start = css.indexOf(`${selector} {`); const body = css.slice(start, css.indexOf("}", start)); - return new Set([...body.matchAll(/--([a-z0-9-]+):/g)].map(([, name]) => name)); + return new Set( + [...body.matchAll(/--([a-z0-9-]+):/g)].map(([, name]) => name), + ); } const AA_TEXT = 4.5; // WCAG 1.4.3, normal-size text @@ -115,7 +122,9 @@ for (const [mode, declared] of [ ]) { for (const name of REQUIRED) { if (declared.has(name)) continue; - console.error(`MISSING ${mode} --${name} is not defined in dist/styles.css`); + console.error( + `MISSING ${mode} --${name} is not defined in dist/styles.css`, + ); missingTokens += 1; } } @@ -132,7 +141,8 @@ const SURFACES = ["background", "card", "popover", "muted"]; /** Every check, expressed against a mode's token record. */ function checksFor(tokens) { - const has = (name) => typeof tokens[name] === "string" && tokens[name].startsWith("#"); + const has = (name) => + typeof tokens[name] === "string" && tokens[name].startsWith("#"); const checks = []; // 1. Every `X-foreground` must be readable on its own `X`. @@ -147,7 +157,12 @@ function checksFor(tokens) { for (const text of ["foreground", "muted-foreground"]) { for (const surface of SURFACES) { if (has(text) && has(surface)) { - checks.push([`${text} on ${surface}`, tokens[text], tokens[surface], AA_TEXT]); + checks.push([ + `${text} on ${surface}`, + tokens[text], + tokens[surface], + AA_TEXT, + ]); } } } @@ -155,7 +170,12 @@ function checksFor(tokens) { // 3. Orange-as-text/border must clear text contrast on the surfaces it sits on. for (const surface of ["background", "card"]) { if (has("primary-emphasis") && has(surface)) { - checks.push([`primary-emphasis on ${surface}`, tokens["primary-emphasis"], tokens[surface], AA_TEXT]); + checks.push([ + `primary-emphasis on ${surface}`, + tokens["primary-emphasis"], + tokens[surface], + AA_TEXT, + ]); } } @@ -163,7 +183,12 @@ function checksFor(tokens) { for (const line of ["input", "ring"]) { for (const surface of ["background", "card"]) { if (has(line) && has(surface)) { - checks.push([`${line} against ${surface}`, tokens[line], tokens[surface], AA_UI]); + checks.push([ + `${line} against ${surface}`, + tokens[line], + tokens[surface], + AA_UI, + ]); } } } @@ -173,7 +198,12 @@ function checksFor(tokens) { // the 3:1 UI-component threshold (WCAG 1.4.11), not the 4.5:1 text one. for (const surface of ["background", "card"]) { if (has("destructive") && has(surface)) { - checks.push([`destructive border against ${surface}`, tokens["destructive"], tokens[surface], AA_UI]); + checks.push([ + `destructive border against ${surface}`, + tokens["destructive"], + tokens[surface], + AA_UI, + ]); } } @@ -192,7 +222,12 @@ function checksFor(tokens) { if (!/^chart-\d+$/.test(name) || !has(name)) continue; for (const surface of ["background", "card"]) { if (has(surface)) { - checks.push([`${name} against ${surface}`, tokens[name], tokens[surface], AA_UI]); + checks.push([ + `${name} against ${surface}`, + tokens[name], + tokens[surface], + AA_UI, + ]); } } } diff --git a/scripts/dep-guard.mjs b/scripts/dep-guard.mjs index 910a3d2f..97f173b9 100644 --- a/scripts/dep-guard.mjs +++ b/scripts/dep-guard.mjs @@ -23,13 +23,19 @@ import { dirname, join, relative, resolve } from "node:path"; const ROOT = new URL("../src", import.meta.url).pathname; -const PKG = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")); +const PKG = JSON.parse( + readFileSync(new URL("../package.json", import.meta.url), "utf8"), +); const OPTIONAL_PEERS = Object.keys(PKG.peerDependenciesMeta).filter( (peer) => PKG.peerDependenciesMeta[peer].optional, ); const RULES = [ - { label: "@workbench/*", pattern: /["'](@workbench\/[^"']*)["']/g, allow: () => false }, + { + label: "@workbench/*", + pattern: /["'](@workbench\/[^"']*)["']/g, + allow: () => false, + }, ]; const walk = (dir) => @@ -47,7 +53,9 @@ const id = (path) => relative(ROOT, path); const violations = sources.flatMap((path) => { const source = read(path); return RULES.filter((rule) => !rule.allow(id(path))).flatMap((rule) => - [...source.matchAll(rule.pattern)].map((match) => `${id(path)}: imports ${match[1]}`), + [...source.matchAll(rule.pattern)].map( + (match) => `${id(path)}: imports ${match[1]}`, + ), ); }); @@ -75,7 +83,9 @@ function reachableFrom(entry) { const barrel = join(ROOT, "index.ts"); if (!sources.includes(barrel)) { - console.error("dep-guard: src/index.ts is missing — the root barrel is hand-written"); + console.error( + "dep-guard: src/index.ts is missing — the root barrel is hand-written", + ); process.exit(1); } @@ -102,23 +112,30 @@ const CLIENT_ONLY = new RegExp( ].join("|"), ); const DIST = new URL("../dist", import.meta.url).pathname; -const stripComments = (source) => source.replace(/\/\*[\s\S]*?\*\/|(^|[^:])\/\/.*$/gm, "$1"); +const stripComments = (source) => + source.replace(/\/\*[\s\S]*?\*\/|(^|[^:])\/\/.*$/gm, "$1"); for (const path of sources) { if (!/^(ui|blocks)\//.test(id(path)) || /\.test\.tsx?$/.test(path)) continue; const source = read(path); const marked = source.startsWith('"use client";'); if (CLIENT_ONLY.test(stripComments(source)) !== marked) { - violations.push(`${id(path)}: ${marked ? "stateless but marked" : "stateful but missing"} "use client"`); + violations.push( + `${id(path)}: ${marked ? "stateless but marked" : "stateful but missing"} "use client"`, + ); } const built = join(DIST, id(path).replace(/\.tsx?$/, ".js")); if (marked && existsSync(built) && !read(built).startsWith('"use client";')) { - violations.push(`${id(path)}: the build dropped "use client" from ${relative(DIST, built)}`); + violations.push( + `${id(path)}: the build dropped "use client" from ${relative(DIST, built)}`, + ); } } if (violations.length > 0) { - console.error(`dep-guard: forbidden imports\n${[...new Set(violations)].join("\n")}`); + console.error( + `dep-guard: forbidden imports\n${[...new Set(violations)].join("\n")}`, + ); process.exit(1); } console.log( diff --git a/src/blocks/login/auth-layout.test.tsx b/src/blocks/login/auth-layout.test.tsx index a753c261..09aa16b1 100644 --- a/src/blocks/login/auth-layout.test.tsx +++ b/src/blocks/login/auth-layout.test.tsx @@ -36,9 +36,13 @@ describe("AuthLayout panel slot", () => { test("renders whatever node is passed as the panel", () => { const mounted = render( - custom}>Form, + custom}> + Form + , ); - expect(mounted.container.querySelector('[data-testid="custom-panel"]')).not.toBeNull(); + expect( + mounted.container.querySelector('[data-testid="custom-panel"]'), + ).not.toBeNull(); mounted.unmount(); }); }); diff --git a/src/blocks/login/auth-layout.tsx b/src/blocks/login/auth-layout.tsx index 1cc8f67d..092679a0 100644 --- a/src/blocks/login/auth-layout.tsx +++ b/src/blocks/login/auth-layout.tsx @@ -39,22 +39,37 @@ export type AuthLayoutProps = { * *without* the retracting toolbar, so a `100vh` sign-in page is reliably a * little too tall and the button sits just under the fold. */ -export function AuthLayout({ brand, children, panel, aside, className }: AuthLayoutProps) { +export function AuthLayout({ + brand, + children, + panel, + aside, + className, +}: AuthLayoutProps) { return ( -
+
{brand === undefined ? null : ( -
{brand}
+
+ {brand} +
)}
{children}
-
+
{panel} {aside === undefined ? null : ( -
{aside}
+
+ {aside} +
)}
diff --git a/src/blocks/login/login-form.test.tsx b/src/blocks/login/login-form.test.tsx index b8260f06..a68ece28 100644 --- a/src/blocks/login/login-form.test.tsx +++ b/src/blocks/login/login-form.test.tsx @@ -37,7 +37,9 @@ describe("LoginForm default (sign-in) mode", () => { test("busy reads Signing in…", () => { const mounted = render( {}} busy />); - expect(mounted.container.querySelector("button[type=submit]")?.textContent).toBe("Signing in…"); + expect( + mounted.container.querySelector("button[type=submit]")?.textContent, + ).toBe("Signing in…"); mounted.unmount(); }); @@ -51,7 +53,9 @@ describe("LoginForm default (sign-in) mode", () => { describe("LoginForm sign-up mode", () => { test("renders a name field", () => { const mounted = render( {}} />); - expect(mounted.container.querySelector('input[name="name"]')).not.toBeNull(); + expect( + mounted.container.querySelector('input[name="name"]'), + ).not.toBeNull(); mounted.unmount(); }); @@ -63,27 +67,45 @@ describe("LoginForm sign-up mode", () => { }); test("busy reads Signing up…", () => { - const mounted = render( {}} busy />); - expect(mounted.container.querySelector("button[type=submit]")?.textContent).toBe("Signing up…"); + const mounted = render( + {}} busy />, + ); + expect( + mounted.container.querySelector("button[type=submit]")?.textContent, + ).toBe("Signing up…"); mounted.unmount(); }); test("submits the name alongside email and password", () => { - const submitted: { credentials: { name?: string; email: string; password: string } | null } = { + const submitted: { + credentials: { name?: string; email: string; password: string } | null; + } = { credentials: null, }; const mounted = render( - (submitted.credentials = credentials)} />, + (submitted.credentials = credentials)} + />, ); const form = mounted.container.querySelector("form"); - const name = mounted.container.querySelector('input[name="name"]') as HTMLInputElement; - const email = mounted.container.querySelector('input[name="email"]') as HTMLInputElement; - const password = mounted.container.querySelector('input[name="password"]') as HTMLInputElement; + const name = mounted.container.querySelector( + 'input[name="name"]', + ) as HTMLInputElement; + const email = mounted.container.querySelector( + 'input[name="email"]', + ) as HTMLInputElement; + const password = mounted.container.querySelector( + 'input[name="password"]', + ) as HTMLInputElement; // React controls these inputs; set the native value then dispatch input // through the DOM so React's change handler observes it. const setValue = (input: HTMLInputElement, value: string) => { - const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set; + const setter = Object.getOwnPropertyDescriptor( + window.HTMLInputElement.prototype, + "value", + )?.set; setter?.call(input, value); input.dispatchEvent(new Event("input", { bubbles: true })); }; @@ -93,7 +115,9 @@ describe("LoginForm sign-up mode", () => { setValue(password, "correct horse battery staple"); }); act(() => { - form?.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true })); + form?.dispatchEvent( + new Event("submit", { bubbles: true, cancelable: true }), + ); }); expect(submitted.credentials).toEqual({ diff --git a/src/blocks/login/login-form.tsx b/src/blocks/login/login-form.tsx index 4213553a..4b11d42d 100644 --- a/src/blocks/login/login-form.tsx +++ b/src/blocks/login/login-form.tsx @@ -32,7 +32,11 @@ export type LoginFormProps = { readonly providers?: readonly AuthProvider[]; readonly onProvider?: (id: string) => void; /** Omit to render an SSO-only page with no email fields at all. */ - readonly onSubmit?: (credentials: { readonly name?: string; readonly email: string; readonly password: string }) => void; + readonly onSubmit?: (credentials: { + readonly name?: string; + readonly email: string; + readonly password: string; + }) => void; /** True while a sign-in or sign-up is in flight. Disables every control. */ readonly busy?: boolean; /** A failure to show. Announced, not just drawn. */ @@ -82,7 +86,9 @@ export function LoginForm({ return (
-

{heading}

+

+ {heading} +

{error === null ? null : (

{ event.preventDefault(); - onSubmit(isSignUp ? { name, email, password } : { email, password }); + onSubmit( + isSignUp ? { name, email, password } : { email, password }, + ); }} > {!isSignUp ? null : ( @@ -154,7 +162,9 @@ export function LoginForm({ {onSubmit === undefined || providers.length === 0 ? null : (

- or + + or +
)} @@ -173,7 +183,9 @@ export function LoginForm({ ))} - {footer === undefined ? null :
{footer}
} + {footer === undefined ? null : ( +
{footer}
+ )}
); } diff --git a/src/hooks/use-command-palette-navigation.test.ts b/src/hooks/use-command-palette-navigation.test.ts index 8ecc3337..e1db9598 100644 --- a/src/hooks/use-command-palette-navigation.test.ts +++ b/src/hooks/use-command-palette-navigation.test.ts @@ -11,7 +11,11 @@ type Item = { readonly id: string }; * against a DOM node, exercising the same path a consumer's keyboard listener * takes rather than calling the hook's handler function directly. */ -function mountNavigation(items: readonly Item[], onSelect: (id: string) => void, onClose: () => void) { +function mountNavigation( + items: readonly Item[], + onSelect: (id: string) => void, + onClose: () => void, +) { const container = document.createElement("div"); document.body.appendChild(container); const root = createRoot(container); @@ -20,7 +24,11 @@ function mountNavigation(items: readonly Item[], onSelect: (id: string) => void, function Host() { const nav = useCommandPaletteNavigation({ items, onSelect, onClose }); activeId = nav.activeId; - return createElement("div", { tabIndex: 0, onKeyDown: nav.onKeyDown, "data-testid": "host" }); + return createElement("div", { + tabIndex: 0, + onKeyDown: nav.onKeyDown, + "data-testid": "host", + }); } act(() => { @@ -32,7 +40,13 @@ function mountNavigation(items: readonly Item[], onSelect: (id: string) => void, return { fire(key: string) { act(() => { - host.dispatchEvent(new KeyboardEvent("keydown", { key, bubbles: true, cancelable: true })); + host.dispatchEvent( + new KeyboardEvent("keydown", { + key, + bubbles: true, + cancelable: true, + }), + ); }); }, getActiveId: () => activeId, @@ -42,12 +56,20 @@ function mountNavigation(items: readonly Item[], onSelect: (id: string) => void, describe("useCommandPaletteNavigation", () => { test("defaults to the first item", () => { - const nav = mountNavigation([{ id: "a" }, { id: "b" }], () => {}, () => {}); + const nav = mountNavigation( + [{ id: "a" }, { id: "b" }], + () => {}, + () => {}, + ); expect(nav.getActiveId()).toBe("a"); }); test("ArrowDown moves selection forward and wraps at the end", () => { - const nav = mountNavigation([{ id: "a" }, { id: "b" }, { id: "c" }], () => {}, () => {}); + const nav = mountNavigation( + [{ id: "a" }, { id: "b" }, { id: "c" }], + () => {}, + () => {}, + ); nav.fire("ArrowDown"); expect(nav.getActiveId()).toBe("b"); nav.fire("ArrowDown"); @@ -56,7 +78,11 @@ describe("useCommandPaletteNavigation", () => { }); test("ArrowUp moves selection backward and wraps at the start", () => { - const nav = mountNavigation([{ id: "a" }, { id: "b" }, { id: "c" }], () => {}, () => {}); + const nav = mountNavigation( + [{ id: "a" }, { id: "b" }, { id: "c" }], + () => {}, + () => {}, + ); nav.fire("ArrowUp"); expect(nav.getActiveId()).toBe("c"); }); @@ -90,7 +116,11 @@ describe("useCommandPaletteNavigation", () => { test("an empty item list has no active id and ignores Enter", () => { const selected: string[] = []; - const nav = mountNavigation([], (id) => selected.push(id), () => {}); + const nav = mountNavigation( + [], + (id) => selected.push(id), + () => {}, + ); expect(nav.getActiveId()).toBeUndefined(); nav.fire("Enter"); expect(selected).toEqual([]); @@ -104,11 +134,23 @@ describe("useCommandPaletteNavigation", () => { let setItems: (items: readonly Item[]) => void = () => {}; function Host() { - const [items, setState] = useState([{ id: "a" }, { id: "b" }, { id: "c" }]); + const [items, setState] = useState([ + { id: "a" }, + { id: "b" }, + { id: "c" }, + ]); setItems = setState; - const nav = useCommandPaletteNavigation({ items, onSelect: () => {}, onClose: () => {} }); + const nav = useCommandPaletteNavigation({ + items, + onSelect: () => {}, + onClose: () => {}, + }); activeId = nav.activeId; - return createElement("div", { tabIndex: 0, onKeyDown: nav.onKeyDown, "data-testid": "host" }); + return createElement("div", { + tabIndex: 0, + onKeyDown: nav.onKeyDown, + "data-testid": "host", + }); } act(() => { @@ -116,10 +158,22 @@ describe("useCommandPaletteNavigation", () => { }); const host = container.querySelector("[data-testid='host']") as HTMLElement; act(() => { - host.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowDown", bubbles: true, cancelable: true })); + host.dispatchEvent( + new KeyboardEvent("keydown", { + key: "ArrowDown", + bubbles: true, + cancelable: true, + }), + ); }); act(() => { - host.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowDown", bubbles: true, cancelable: true })); + host.dispatchEvent( + new KeyboardEvent("keydown", { + key: "ArrowDown", + bubbles: true, + cancelable: true, + }), + ); }); expect(activeId).toBe("c"); diff --git a/src/hooks/use-command-palette-navigation.ts b/src/hooks/use-command-palette-navigation.ts index 7fb19679..7b330ac3 100644 --- a/src/hooks/use-command-palette-navigation.ts +++ b/src/hooks/use-command-palette-navigation.ts @@ -1,7 +1,9 @@ import { useMemo, useState } from "react"; import type { KeyboardEvent as ReactKeyboardEvent } from "react"; -export type UseCommandPaletteNavigationOptions = { +export type UseCommandPaletteNavigationOptions< + TItem extends { readonly id: string }, +> = { /** The flat, visible result order — grouping is a display concern only. */ readonly items: readonly TItem[]; readonly onSelect: (id: string) => void; @@ -22,7 +24,9 @@ export type UseCommandPaletteNavigationResult = { * reordering that happens every keystroke (fresh search results land at * different positions) — an index would silently point at the wrong row. */ -export function useCommandPaletteNavigation({ +export function useCommandPaletteNavigation< + TItem extends { readonly id: string }, +>({ items, onSelect, onClose, @@ -31,13 +35,20 @@ export function useCommandPaletteNavigation { if (items.length === 0) return undefined; - if (requestedId !== undefined && items.some((item) => item.id === requestedId)) return requestedId; + if ( + requestedId !== undefined && + items.some((item) => item.id === requestedId) + ) + return requestedId; return items[0]?.id; }, [items, requestedId]); function moveBy(delta: number) { if (items.length === 0) return; - const currentIndex = activeId === undefined ? -1 : items.findIndex((item) => item.id === activeId); + const currentIndex = + activeId === undefined + ? -1 + : items.findIndex((item) => item.id === activeId); const nextIndex = (currentIndex + delta + items.length) % items.length; setRequestedId(items[nextIndex]?.id); } diff --git a/src/hooks/use-controllable-state.test.ts b/src/hooks/use-controllable-state.test.ts index baf9ecba..ea80b546 100644 --- a/src/hooks/use-controllable-state.test.ts +++ b/src/hooks/use-controllable-state.test.ts @@ -2,13 +2,18 @@ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test"; import { act, createElement } from "react"; import { createRoot } from "react-dom/client"; -import { useControllableState, type UseControllableStateOptions } from "./use-controllable-state.js"; +import { + useControllableState, + type UseControllableStateOptions, +} from "./use-controllable-state.js"; function mount(options: UseControllableStateOptions) { const container = document.createElement("div"); document.body.appendChild(container); const root = createRoot(container); - let result: readonly [T, (action: T | ((previous: T) => T)) => void] | undefined; + let result: + | readonly [T, (action: T | ((previous: T) => T)) => void] + | undefined; function Host(props: UseControllableStateOptions) { result = useControllableState(props); @@ -20,7 +25,8 @@ function mount(options: UseControllableStateOptions) { }); return { - get: () => result as readonly [T, (action: T | ((previous: T) => T)) => void], + get: () => + result as readonly [T, (action: T | ((previous: T) => T)) => void], rerender: (props: UseControllableStateOptions) => { act(() => { root.render(createElement(Host, props)); @@ -48,18 +54,33 @@ describe("useControllableState", () => { test("controlled: the value tracks the prop, not internal state", () => { let changed: boolean | undefined; - const handle = mount({ value: false, defaultValue: false, name: "test", onChange: (v) => (changed = v) }); + const handle = mount({ + value: false, + defaultValue: false, + name: "test", + onChange: (v) => (changed = v), + }); act(() => handle.get()[1](true)); expect(changed).toBe(true); expect(handle.get()[0]).toBe(false); - handle.rerender({ value: true, defaultValue: false, name: "test", onChange: (v) => (changed = v) }); + handle.rerender({ + value: true, + defaultValue: false, + name: "test", + onChange: (v) => (changed = v), + }); expect(handle.get()[0]).toBe(true); handle.unmount(); }); test("controlled: the setter's functional updater sees the controlled value", () => { let changed: number | undefined; - const handle = mount({ value: 5, defaultValue: 0, name: "test", onChange: (v) => (changed = v) }); + const handle = mount({ + value: 5, + defaultValue: 0, + name: "test", + onChange: (v) => (changed = v), + }); act(() => handle.get()[1]((previous) => previous + 10)); expect(changed).toBe(15); handle.unmount(); @@ -79,9 +100,16 @@ describe("useControllableState", () => { }); test("logs once when an uncontrolled caller starts passing a value", () => { - const handle = mount({ defaultValue: false, name: "test-warn-a" }); + const handle = mount({ + defaultValue: false, + name: "test-warn-a", + }); expect(errorSpy).not.toHaveBeenCalled(); - handle.rerender({ value: true, defaultValue: false, name: "test-warn-a" }); + handle.rerender({ + value: true, + defaultValue: false, + name: "test-warn-a", + }); expect(errorSpy).toHaveBeenCalledTimes(1); expect(errorSpy.mock.calls[0]?.[0]).toContain("test-warn-a"); expect(errorSpy.mock.calls[0]?.[0]).toContain("uncontrolled"); @@ -90,18 +118,37 @@ describe("useControllableState", () => { }); test("logs once when a controlled caller stops passing a value", () => { - const handle = mount({ value: true, defaultValue: false, name: "test-warn-b" }); + const handle = mount({ + value: true, + defaultValue: false, + name: "test-warn-b", + }); expect(errorSpy).not.toHaveBeenCalled(); - handle.rerender({ value: undefined, defaultValue: false, name: "test-warn-b" }); + handle.rerender({ + value: undefined, + defaultValue: false, + name: "test-warn-b", + }); expect(errorSpy).toHaveBeenCalledTimes(1); handle.unmount(); }); test("does not log when staying uncontrolled or staying controlled", () => { - const handle = mount({ defaultValue: false, name: "test-warn-c" }); + const handle = mount({ + defaultValue: false, + name: "test-warn-c", + }); handle.rerender({ defaultValue: false, name: "test-warn-c" }); - const controlled = mount({ value: false, defaultValue: false, name: "test-warn-d" }); - controlled.rerender({ value: true, defaultValue: false, name: "test-warn-d" }); + const controlled = mount({ + value: false, + defaultValue: false, + name: "test-warn-d", + }); + controlled.rerender({ + value: true, + defaultValue: false, + name: "test-warn-d", + }); expect(errorSpy).not.toHaveBeenCalled(); handle.unmount(); controlled.unmount(); diff --git a/src/hooks/use-controllable-state.ts b/src/hooks/use-controllable-state.ts index 9130afd1..c21330df 100644 --- a/src/hooks/use-controllable-state.ts +++ b/src/hooks/use-controllable-state.ts @@ -12,7 +12,8 @@ export type UseControllableStateOptions = { readonly name: string; }; -const isProduction = typeof process !== "undefined" && process.env?.NODE_ENV === "production"; +const isProduction = + typeof process !== "undefined" && process.env?.NODE_ENV === "production"; /** * The controlled-or-uncontrolled resolution every prop pair shaped like @@ -53,7 +54,10 @@ export function useControllableState( const setValue = useCallback( (action: ControllableStateAction) => { - const resolved = typeof action === "function" ? (action as (previous: T) => T)(value) : action; + const resolved = + typeof action === "function" + ? (action as (previous: T) => T)(value) + : action; if (!isControlled) setUncontrolledValue(resolved); onChange?.(resolved); }, diff --git a/src/hooks/use-dismissable-popover.test.ts b/src/hooks/use-dismissable-popover.test.ts index d667ceaf..37a3b0c2 100644 --- a/src/hooks/use-dismissable-popover.test.ts +++ b/src/hooks/use-dismissable-popover.test.ts @@ -2,7 +2,10 @@ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test"; import { act, createElement } from "react"; import { createRoot } from "react-dom/client"; -import { useDismissablePopover, type UseDismissablePopoverOptions } from "./use-dismissable-popover.js"; +import { + useDismissablePopover, + type UseDismissablePopoverOptions, +} from "./use-dismissable-popover.js"; function mount(options: UseDismissablePopoverOptions = {}) { const container = document.createElement("div"); @@ -13,7 +16,11 @@ function mount(options: UseDismissablePopoverOptions = {}) { container.appendChild(trigger); const reactRoot = createRoot(container); - let result: ReturnType> | undefined; + let result: + | ReturnType< + typeof useDismissablePopover + > + | undefined; function Host(props: UseDismissablePopoverOptions) { result = useDismissablePopover(props); @@ -26,11 +33,17 @@ function mount(options: UseDismissablePopoverOptions = {}) { // Attach the hook's refs to real, already-mounted nodes since Host renders // no DOM of its own — the hook only reads `.current`, it never expects // React to have set it via a ref prop. - (result as { rootRef: { current: HTMLDivElement | null } }).rootRef.current = root; - (result as { triggerRef: { current: HTMLButtonElement | null } }).triggerRef.current = trigger; + (result as { rootRef: { current: HTMLDivElement | null } }).rootRef.current = + root; + ( + result as { triggerRef: { current: HTMLButtonElement | null } } + ).triggerRef.current = trigger; return { - get: () => result as ReturnType>, + get: () => + result as ReturnType< + typeof useDismissablePopover + >, rerender: (props: UseDismissablePopoverOptions) => { act(() => { reactRoot.render(createElement(Host, props)); @@ -68,7 +81,10 @@ describe("useDismissablePopover", () => { test("controlled: open tracks the prop, not internal state", () => { let changed: boolean | undefined; - const handle = mount({ open: false, onOpenChange: (value) => (changed = value) }); + const handle = mount({ + open: false, + onOpenChange: (value) => (changed = value), + }); act(() => handle.get().setOpen(true)); expect(changed).toBe(true); expect(handle.get().open).toBe(false); @@ -84,7 +100,9 @@ describe("useDismissablePopover", () => { focused = true; }; act(() => { - document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true })); + document.dispatchEvent( + new KeyboardEvent("keydown", { key: "Escape", bubbles: true }), + ); }); expect(handle.get().open).toBe(false); expect(focused).toBe(true); @@ -94,7 +112,9 @@ describe("useDismissablePopover", () => { test("closeOnEscape: false leaves Escape handling to the caller", () => { const handle = mount({ defaultOpen: true, closeOnEscape: false }); act(() => { - document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true })); + document.dispatchEvent( + new KeyboardEvent("keydown", { key: "Escape", bubbles: true }), + ); }); expect(handle.get().open).toBe(true); handle.unmount(); @@ -115,7 +135,9 @@ describe("useDismissablePopover", () => { test("pointerdown inside the root does not close it", () => { const handle = mount({ defaultOpen: true }); act(() => { - handle.root.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true })); + handle.root.dispatchEvent( + new PointerEvent("pointerdown", { bubbles: true }), + ); }); expect(handle.get().open).toBe(true); handle.unmount(); diff --git a/src/hooks/use-dismissable-popover.ts b/src/hooks/use-dismissable-popover.ts index 59a7000a..633ec513 100644 --- a/src/hooks/use-dismissable-popover.ts +++ b/src/hooks/use-dismissable-popover.ts @@ -1,6 +1,9 @@ import { useCallback, useEffect, useRef, type RefObject } from "react"; -import { useControllableState, type ControllableStateAction } from "./use-controllable-state.js"; +import { + useControllableState, + type ControllableStateAction, +} from "./use-controllable-state.js"; export type UseDismissablePopoverOptions = { /** Controlled open flag. Supply this with `onOpenChange` to lift state to a parent. */ @@ -12,7 +15,10 @@ export type UseDismissablePopoverOptions = { readonly closeOnEscape?: boolean; }; -export type UseDismissablePopoverResult = { +export type UseDismissablePopoverResult< + Root extends HTMLElement, + Trigger extends HTMLElement, +> = { readonly open: boolean; readonly setOpen: (action: ControllableStateAction) => void; readonly rootRef: RefObject; @@ -41,8 +47,15 @@ export type UseDismissablePopoverResult(options: UseDismissablePopoverOptions = {}): UseDismissablePopoverResult { - const { open: controlledOpen, onOpenChange, defaultOpen = false, closeOnEscape = true } = options; +>( + options: UseDismissablePopoverOptions = {}, +): UseDismissablePopoverResult { + const { + open: controlledOpen, + onOpenChange, + defaultOpen = false, + closeOnEscape = true, + } = options; const [open, setOpen] = useControllableState({ value: controlledOpen, defaultValue: defaultOpen, diff --git a/src/hooks/use-list-selection.test.ts b/src/hooks/use-list-selection.test.ts index 53b2b7ce..1dd938eb 100644 --- a/src/hooks/use-list-selection.test.ts +++ b/src/hooks/use-list-selection.test.ts @@ -24,12 +24,14 @@ function mountSelection(ids: readonly string[] = IDS) { return { get: () => result, - toggle: (id: string, modifiers?: { shiftKey?: boolean }) => act(() => result.toggle(id, modifiers)), + toggle: (id: string, modifiers?: { shiftKey?: boolean }) => + act(() => result.toggle(id, modifiers)), selectAll: () => act(() => result.selectAll()), clear: () => act(() => result.clear()), // Re-renders with a fresh `ids` array — exercises reconciliation and the // ref-backed callback identity independently of any state change. - rerenderWithIds: (nextIds: readonly string[]) => act(() => root.render(createElement(Host, { ids: nextIds }))), + rerenderWithIds: (nextIds: readonly string[]) => + act(() => root.render(createElement(Host, { ids: nextIds }))), unmount: () => act(() => root.unmount()), }; } diff --git a/src/hooks/use-list-selection.ts b/src/hooks/use-list-selection.ts index 6fe5bdc0..df5b20c3 100644 --- a/src/hooks/use-list-selection.ts +++ b/src/hooks/use-list-selection.ts @@ -51,14 +51,23 @@ type State = { }; type Action = - | { readonly type: "toggle"; readonly id: TId; readonly shiftKey: boolean; readonly ids: readonly TId[] } + | { + readonly type: "toggle"; + readonly id: TId; + readonly shiftKey: boolean; + readonly ids: readonly TId[]; + } | { readonly type: "selectAll"; readonly ids: readonly TId[] } | { readonly type: "clear" }; function reducer(state: State, action: Action): State { switch (action.type) { case "clear": - return { selectedIds: new Set(), anchorId: undefined, baseSelection: new Set() }; + return { + selectedIds: new Set(), + anchorId: undefined, + baseSelection: new Set(), + }; case "selectAll": { const all = new Set(action.ids); return { selectedIds: all, anchorId: state.anchorId, baseSelection: all }; @@ -75,7 +84,10 @@ function reducer(state: State, action: Action): State { const next = toggleOne(state.selectedIds, id); return { selectedIds: next, anchorId: id, baseSelection: next }; } - const [start, end] = anchorIndex <= targetIndex ? [anchorIndex, targetIndex] : [targetIndex, anchorIndex]; + const [start, end] = + anchorIndex <= targetIndex + ? [anchorIndex, targetIndex] + : [targetIndex, anchorIndex]; const range = ids.slice(start, end + 1); const selectedIds = new Set(state.baseSelection); for (const rangeId of range) selectedIds.add(rangeId); @@ -89,7 +101,11 @@ function reducer(state: State, action: Action): State { } function createInitialState(): State { - return { selectedIds: new Set(), anchorId: undefined, baseSelection: new Set() }; + return { + selectedIds: new Set(), + anchorId: undefined, + baseSelection: new Set(), + }; } /** @@ -124,7 +140,11 @@ function createInitialState(): State { export function useListSelection({ ids, }: UseListSelectionOptions): UseListSelectionResult { - const [state, dispatch] = useReducer(reducer, undefined, createInitialState); + const [state, dispatch] = useReducer( + reducer, + undefined, + createInitialState, + ); // Updated on every render, read only inside event handlers — never during // render — so `toggle`/`selectAll` can stay referentially stable while @@ -134,11 +154,19 @@ export function useListSelection({ const toggle = useCallback( (id: TId, modifiers?: ToggleModifiers) => - dispatch({ type: "toggle", id, shiftKey: modifiers?.shiftKey ?? false, ids: idsRef.current }), + dispatch({ + type: "toggle", + id, + shiftKey: modifiers?.shiftKey ?? false, + ids: idsRef.current, + }), [], ); - const selectAll = useCallback(() => dispatch({ type: "selectAll", ids: idsRef.current }), []); + const selectAll = useCallback( + () => dispatch({ type: "selectAll", ids: idsRef.current }), + [], + ); const clear = useCallback(() => dispatch({ type: "clear" }), []); @@ -149,10 +177,20 @@ export function useListSelection({ return reconciled; }, [state.selectedIds, ids]); - const isSelected = useCallback((id: TId) => selectedIds.has(id), [selectedIds]); + const isSelected = useCallback( + (id: TId) => selectedIds.has(id), + [selectedIds], + ); return useMemo( - () => ({ selectedIds, selectedCount: selectedIds.size, isSelected, toggle, selectAll, clear }), + () => ({ + selectedIds, + selectedCount: selectedIds.size, + isSelected, + toggle, + selectAll, + clear, + }), [selectedIds, isSelected, toggle, selectAll, clear], ); } diff --git a/src/hooks/use-prefers-reduced-motion.test.ts b/src/hooks/use-prefers-reduced-motion.test.ts index 800d6d40..234e4353 100644 --- a/src/hooks/use-prefers-reduced-motion.test.ts +++ b/src/hooks/use-prefers-reduced-motion.test.ts @@ -15,8 +15,10 @@ function stubMatchMedia(initialMatches: boolean) { ({ matches, media: query, - addEventListener: (_type: string, listener: Listener) => listeners.add(listener), - removeEventListener: (_type: string, listener: Listener) => listeners.delete(listener), + addEventListener: (_type: string, listener: Listener) => + listeners.add(listener), + removeEventListener: (_type: string, listener: Listener) => + listeners.delete(listener), }) as unknown as MediaQueryList) as typeof window.matchMedia; return { diff --git a/src/hooks/use-prefers-reduced-motion.ts b/src/hooks/use-prefers-reduced-motion.ts index 2da0b3b3..df36daa4 100644 --- a/src/hooks/use-prefers-reduced-motion.ts +++ b/src/hooks/use-prefers-reduced-motion.ts @@ -10,7 +10,9 @@ function subscribe(callback: () => void): () => void { } function getSnapshot(): boolean { - return typeof window === "undefined" ? false : window.matchMedia(QUERY).matches; + return typeof window === "undefined" + ? false + : window.matchMedia(QUERY).matches; } function getServerSnapshot(): boolean { diff --git a/src/hooks/use-scroll-current-into-view.ts b/src/hooks/use-scroll-current-into-view.ts index a6f6a359..71d05f7b 100644 --- a/src/hooks/use-scroll-current-into-view.ts +++ b/src/hooks/use-scroll-current-into-view.ts @@ -19,7 +19,9 @@ import { usePrefersReducedMotion } from "./use-prefers-reduced-motion.js"; * theme's global reduced-motion block, so a `prefers-reduced-motion: reduce` * check here falls back to an instant jump. */ -export function useScrollCurrentIntoView(currentKey: string | number): RefObject { +export function useScrollCurrentIntoView( + currentKey: string | number, +): RefObject { const currentRef = useRef(null); const prefersReducedMotion = usePrefersReducedMotion(); diff --git a/src/icon-tokens.test.ts b/src/icon-tokens.test.ts index 4b54528d..8adb8c97 100644 --- a/src/icon-tokens.test.ts +++ b/src/icon-tokens.test.ts @@ -15,7 +15,11 @@ describe("icon-size tokens", () => { }); test("declares the tokens once, on :root, not inside a dark-mode block", () => { - const rootBlock = theme.slice(theme.indexOf(":root {"), theme.indexOf(":root {") + theme.slice(theme.indexOf(":root {")).indexOf("\n}")); + const rootBlock = theme.slice( + theme.indexOf(":root {"), + theme.indexOf(":root {") + + theme.slice(theme.indexOf(":root {")).indexOf("\n}"), + ); expect(rootBlock).toContain("--icon-size-nav"); expect(rootBlock).toContain("--icon-size-topbar"); expect(rootBlock).toContain("--icon-size-button"); @@ -26,6 +30,8 @@ describe("icon-size tokens", () => { expect(theme).toMatch(/--text-icon-nav:\s*var\(--icon-size-nav\)/); expect(theme).toMatch(/--text-icon-topbar:\s*var\(--icon-size-topbar\)/); expect(theme).toMatch(/--text-icon-button:\s*var\(--icon-size-button\)/); - expect(theme).toMatch(/--text-icon-checkbox:\s*var\(--icon-size-checkbox\)/); + expect(theme).toMatch( + /--text-icon-checkbox:\s*var\(--icon-size-checkbox\)/, + ); }); }); diff --git a/src/lib/artifact.ts b/src/lib/artifact.ts index 087a9734..ce25a24b 100644 --- a/src/lib/artifact.ts +++ b/src/lib/artifact.ts @@ -58,7 +58,10 @@ const FORM_BY_SUFFIX: readonly (readonly [RegExp, ArtifactForm])[] = [ [/(^|-)csv(-|$)|(^|-)sheet(-|$)|(^|-)table(-|$)/, "table"], [/(^|-)comparison(-|$)|(^|-)compare(-|$)|^ab-/, "comparison"], [/(^|-)research(-|$)|(^|-)brief(-|$)/, "research"], - [/(^|-)deck(-|$)|(^|-)presentation(-|$)|(^|-)slides(-|$)|(^|-)embed(-|$)/, "embed"], + [ + /(^|-)deck(-|$)|(^|-)presentation(-|$)|(^|-)slides(-|$)|(^|-)embed(-|$)/, + "embed", + ], [/(^|-)image(-|$)|(^|-)photo(-|$)|(^|-)screenshot(-|$)/, "image"], [/(^|-)file(-|$)|(^|-)upload(-|$)|(^|-)binary(-|$)/, "download"], ]; diff --git a/src/lib/chart-geometry.ts b/src/lib/chart-geometry.ts index 63af6a96..f5609ceb 100644 --- a/src/lib/chart-geometry.ts +++ b/src/lib/chart-geometry.ts @@ -30,7 +30,9 @@ export function niceTicks(max: number, count = 4): readonly number[] { const rough = max / count; const magnitude = 10 ** Math.floor(Math.log10(rough)); const normalized = rough / magnitude; - const step = (normalized <= 1 ? 1 : normalized <= 2 ? 2 : normalized <= 5 ? 5 : 10) * magnitude; + const step = + (normalized <= 1 ? 1 : normalized <= 2 ? 2 : normalized <= 5 ? 5 : 10) * + magnitude; const top = Math.ceil(max / step) * step; const ticks: number[] = []; @@ -50,7 +52,11 @@ export function niceTicks(max: number, count = 4): readonly number[] { * attribute makes the entire path vanish, so a flat line at the baseline is both * correct and the only readable failure mode. */ -export function scaleLinear(value: number, domainMax: number, rangeMax: number): number { +export function scaleLinear( + value: number, + domainMax: number, + rangeMax: number, +): number { if (domainMax <= 0) return 0; return (value / domainMax) * rangeMax; } @@ -66,7 +72,9 @@ export type Point = { readonly x: number; readonly y: number }; */ export function linePath(points: readonly Point[]): string { if (points.length === 0) return ""; - return points.map((point, index) => `${index === 0 ? "M" : "L"}${point.x} ${point.y}`).join(" "); + return points + .map((point, index) => `${index === 0 ? "M" : "L"}${point.x} ${point.y}`) + .join(" "); } /** @@ -96,4 +104,3 @@ export function formatCompact(value: number): string { if (magnitude >= 10_000) return `${(value / 1000).toFixed(1)}K`; return value.toLocaleString(); } - diff --git a/src/lib/chat-parts.ts b/src/lib/chat-parts.ts index 2ae5c7f9..4d72e9b1 100644 --- a/src/lib/chat-parts.ts +++ b/src/lib/chat-parts.ts @@ -75,4 +75,10 @@ export type PartEvent = { readonly data?: unknown; }; -export type Part = PartText | PartReasoning | PartToolTrace | PartBlock | PartFile | PartEvent; +export type Part = + | PartText + | PartReasoning + | PartToolTrace + | PartBlock + | PartFile + | PartEvent; diff --git a/src/lib/csv.ts b/src/lib/csv.ts index bb8df92c..facf57e7 100644 --- a/src/lib/csv.ts +++ b/src/lib/csv.ts @@ -128,9 +128,11 @@ export function capCsv(parsed: ParsedCsv): CappedCsv { const rowsTruncated = totalRows > CSV_ROW_CAP; return { - headers: columnsTruncated ? parsed.headers.slice(0, CSV_COLUMN_CAP) : parsed.headers, - rows: (rowsTruncated ? parsed.rows.slice(0, CSV_ROW_CAP) : parsed.rows).map((row) => - columnsTruncated ? row.slice(0, CSV_COLUMN_CAP) : row, + headers: columnsTruncated + ? parsed.headers.slice(0, CSV_COLUMN_CAP) + : parsed.headers, + rows: (rowsTruncated ? parsed.rows.slice(0, CSV_ROW_CAP) : parsed.rows).map( + (row) => (columnsTruncated ? row.slice(0, CSV_COLUMN_CAP) : row), ), totalRows, totalColumns, diff --git a/src/lib/metrics.ts b/src/lib/metrics.ts index c068abf2..cdd8e667 100644 --- a/src/lib/metrics.ts +++ b/src/lib/metrics.ts @@ -28,12 +28,16 @@ export type DeltaResult = { * meaningful baseline to divide by, so callers render the absolute delta rather * than a misleading "∞%". */ -export function computeDelta(current: number, previous: number | null | undefined): DeltaResult { +export function computeDelta( + current: number, + previous: number | null | undefined, +): DeltaResult { if (previous === null || previous === undefined) { return { direction: "flat", pct: null, delta: 0, comparable: false }; } const delta = current - previous; - const direction: DeltaDirection = delta > 0 ? "up" : delta < 0 ? "down" : "flat"; + const direction: DeltaDirection = + delta > 0 ? "up" : delta < 0 ? "down" : "flat"; if (previous === 0) { return { direction, pct: null, delta, comparable: true }; } @@ -53,7 +57,11 @@ export type SparklineGeometry = { * a box this small. A flat series renders along the vertical centre and a * single point is centred horizontally. */ -export function buildSparkline(values: readonly number[], width: number, height: number): SparklineGeometry { +export function buildSparkline( + values: readonly number[], + width: number, + height: number, +): SparklineGeometry { if (values.length === 0) { return { points: "", coords: [] }; } @@ -79,7 +87,9 @@ export type MosaicSegment = { }; /** Shares of a whole, as percentage widths for a stacked strip. */ -export function buildMosaic(parts: readonly { label: string; value: number }[]): MosaicSegment[] { +export function buildMosaic( + parts: readonly { label: string; value: number }[], +): MosaicSegment[] { const total = parts.reduce((sum, part) => sum + part.value, 0); return parts.map((part) => ({ label: part.label, diff --git a/src/lib/now-item.ts b/src/lib/now-item.ts index 69d230fb..b5d89035 100644 --- a/src/lib/now-item.ts +++ b/src/lib/now-item.ts @@ -49,13 +49,24 @@ export type NowItem = readonly read: boolean; }); -const PRIORITY_RANK: Record = { now: 0, next: 1, later: 2 }; +const PRIORITY_RANK: Record = { + now: 0, + next: 1, + later: 2, +}; /** Short band label for the queue's priority column. */ -export const PRIORITY_LABEL: Record = { now: "HIGH", next: "MED", later: "LOW" }; +export const PRIORITY_LABEL: Record = { + now: "HIGH", + next: "MED", + later: "LOW", +}; /** Human label for the status marker. */ -export const STATUS_LABEL: Record = { "needs-action": "Needs action", done: "Done" }; +export const STATUS_LABEL: Record = { + "needs-action": "Needs action", + done: "Done", +}; /** * Priority first, then newest. Sorts a copy — a component handed a frozen or @@ -78,15 +89,22 @@ export type NowGroup = { * Sorted items split into priority bands. Empty bands are dropped rather than * returned empty, so a caller can map straight to headings without checking. */ -export function groupNowItemsByPriority(items: readonly NowItem[]): readonly NowGroup[] { +export function groupNowItemsByPriority( + items: readonly NowItem[], +): readonly NowGroup[] { const sorted = sortNowItems(items); const order: readonly NowPriority[] = ["now", "next", "later"]; return order - .map((priority) => ({ priority, items: sorted.filter((item) => item.priority === priority) })) + .map((priority) => ({ + priority, + items: sorted.filter((item) => item.priority === priority), + })) .filter((group) => group.items.length > 0); } /** The classifications present, in first-appearance order — for a filter row. */ -export function nowItemClassifications(items: readonly NowItem[]): readonly string[] { +export function nowItemClassifications( + items: readonly NowItem[], +): readonly string[] { return [...new Set(sortNowItems(items).map((item) => item.classification))]; } diff --git a/src/lib/relative-time.ts b/src/lib/relative-time.ts index fae0880d..f9ce762d 100644 --- a/src/lib/relative-time.ts +++ b/src/lib/relative-time.ts @@ -15,13 +15,19 @@ const UNITS: readonly (readonly [Intl.RelativeTimeFormatUnit, number])[] = [ * is untestable and re-renders to a different string than the server produced. * Pass the server's render time when hydrating. */ -export function formatRelativeTime(iso: string, now: number = Date.now()): string { +export function formatRelativeTime( + iso: string, + now: number = Date.now(), +): string { const then = Date.parse(iso); if (Number.isNaN(then)) return ""; const delta = then - now; const magnitude = Math.abs(delta); - const format = new Intl.RelativeTimeFormat(undefined, { numeric: "auto", style: "narrow" }); + const format = new Intl.RelativeTimeFormat(undefined, { + numeric: "auto", + style: "narrow", + }); for (const [unit, ms] of UNITS) { if (magnitude >= ms) return format.format(Math.round(delta / ms), unit); diff --git a/src/lib/theme.test.ts b/src/lib/theme.test.ts index d3e2a580..a99faba7 100644 --- a/src/lib/theme.test.ts +++ b/src/lib/theme.test.ts @@ -136,7 +136,9 @@ describe("zero-JS prefers-color-scheme dark", () => { test("the media-query :root block matches .dark (tokens + color-scheme) so they cannot drift", () => { const dark = declarations(ruleBodyAfter(theme, "\n.dark {")); const media = ruleBodyAfter(theme, "@media (prefers-color-scheme: dark)"); - const mediaRoot = declarations(ruleBodyAfter(media, ":root:not(.dark):not(.light)")); + const mediaRoot = declarations( + ruleBodyAfter(media, ":root:not(.dark):not(.light)"), + ); expect(mediaRoot).toEqual(dark); expect(mediaRoot["color-scheme"]).toBe("dark"); }); diff --git a/src/lib/theme.ts b/src/lib/theme.ts index 13d91951..affbfc38 100644 --- a/src/lib/theme.ts +++ b/src/lib/theme.ts @@ -15,7 +15,11 @@ export type ResolvedThemeMode = "light" | "dark"; export type ThemePreset = "default" | "warm" | "cool"; export const THEME_MODES: readonly ThemeMode[] = ["system", "light", "dark"]; -export const THEME_PRESETS: readonly ThemePreset[] = ["default", "warm", "cool"]; +export const THEME_PRESETS: readonly ThemePreset[] = [ + "default", + "warm", + "cool", +]; export const DEFAULT_THEME_STORAGE_KEY = "corbits-theme"; @@ -52,7 +56,9 @@ export function parseThemePreference( if (typeof parsed !== "object" || parsed === null) return fallback; const record = parsed as Record; const mode = isThemeMode(record.mode) ? record.mode : fallback.mode; - const preset = isThemePreset(record.preset) ? record.preset : fallback.preset; + const preset = isThemePreset(record.preset) + ? record.preset + : fallback.preset; return { mode, preset }; } catch { // Legacy: a bare mode string from an earlier client. diff --git a/src/lib/url.ts b/src/lib/url.ts index 2a1307a2..0f058f0b 100644 --- a/src/lib/url.ts +++ b/src/lib/url.ts @@ -16,7 +16,10 @@ const DEFAULT_ALLOWED_PROTOCOLS: readonly string[] = ["http:", "https:"]; * an embed — so there is no in-app page a bare path could sensibly mean, and * inventing a base to resolve against would only guess wrong. */ -export function isSafeUrl(url: string, allowedProtocols: readonly string[] = DEFAULT_ALLOWED_PROTOCOLS): boolean { +export function isSafeUrl( + url: string, + allowedProtocols: readonly string[] = DEFAULT_ALLOWED_PROTOCOLS, +): boolean { try { return allowedProtocols.includes(new URL(url).protocol); } catch { diff --git a/src/lib/workflow-display-flow.ts b/src/lib/workflow-display-flow.ts index 0a52e35b..9ee0afd8 100644 --- a/src/lib/workflow-display-flow.ts +++ b/src/lib/workflow-display-flow.ts @@ -88,7 +88,9 @@ function characterOf(primitive: WorkflowStepPrimitive): DisplayStepCharacter { * published library, where the tag vocabulary would become part of the * public surface. */ -export function deriveDisplayFlow(primitives: readonly WorkflowStepPrimitive[]): DisplayFlow { +export function deriveDisplayFlow( + primitives: readonly WorkflowStepPrimitive[], +): DisplayFlow { return { steps: primitives.map((primitive) => ({ stepId: primitive.stepId, @@ -142,7 +144,9 @@ const CHARACTER_PRIORITY: readonly DisplayStepCharacter[] = [ "other", ]; -function aggregateCharacter(characters: readonly DisplayStepCharacter[]): DisplayStepCharacter { +function aggregateCharacter( + characters: readonly DisplayStepCharacter[], +): DisplayStepCharacter { for (const candidate of CHARACTER_PRIORITY) { if (characters.includes(candidate)) return candidate; } @@ -155,14 +159,19 @@ function aggregateCharacter(characters: readonly DisplayStepCharacter[]): Displa * unknown id throws immediately rather than silently producing an empty or * partial group. */ -export function applyDisplayOverlay(flow: DisplayFlow, overlay: DisplayOverlay): OverlaidDisplayFlow { +export function applyDisplayOverlay( + flow: DisplayFlow, + overlay: DisplayOverlay, +): OverlaidDisplayFlow { const byId = new Map(flow.steps.map((step) => [step.stepId, step])); const groups: OverlaidDisplayStep[] = overlay.groups.map((group) => { const characters: DisplayStepCharacter[] = []; for (const stepId of group.steps) { const step = byId.get(stepId); if (step === undefined) { - throw new Error(`display overlay group ${JSON.stringify(group.key)} references unknown step ${JSON.stringify(stepId)}`); + throw new Error( + `display overlay group ${JSON.stringify(group.key)} references unknown step ${JSON.stringify(stepId)}`, + ); } characters.push(step.character); } @@ -171,7 +180,9 @@ export function applyDisplayOverlay(flow: DisplayFlow, overlay: DisplayOverlay): label: group.label, character: aggregateCharacter(characters), stepIds: group.steps, - ...(group.activityLabel !== undefined ? { activityLabel: group.activityLabel } : {}), + ...(group.activityLabel !== undefined + ? { activityLabel: group.activityLabel } + : {}), }; }); return { groups }; @@ -184,13 +195,16 @@ export function applyDisplayOverlay(flow: DisplayFlow, overlay: DisplayOverlay): */ export function stepListFromDisplayFlow( steps: readonly DisplayFlowStep[], - statusById?: ReadonlyMap | Record, + statusById?: + | ReadonlyMap + | Record, ): StepListItem[] { const lookup = statusById instanceof Map ? (id: string) => statusById.get(id) : statusById !== undefined - ? (id: string) => (statusById as Record)[id] + ? (id: string) => + (statusById as Record)[id] : () => undefined; return steps.map((step) => ({ diff --git a/src/lib/workflow-registry.ts b/src/lib/workflow-registry.ts index 4a29f470..6216f00a 100644 --- a/src/lib/workflow-registry.ts +++ b/src/lib/workflow-registry.ts @@ -16,7 +16,12 @@ import type { DisplayStepCharacter } from "./workflow-display-flow.js"; */ export type WorkflowScope = "personal" | "tenant"; -export type WorkflowStatusTone = "running" | "awaiting" | "done" | "paused" | "fail"; +export type WorkflowStatusTone = + | "running" + | "awaiting" + | "done" + | "paused" + | "fail"; export const WORKFLOW_STATUS_LABEL: Record = { running: "Running", @@ -27,16 +32,20 @@ export const WORKFLOW_STATUS_LABEL: Record = { }; /** Badge tone for a status chip composed as `{label}`. */ -export const WORKFLOW_STATUS_BADGE_TONE: Record = { - running: "info", - awaiting: "accent", - done: "success", - paused: "neutral", - fail: "danger", -}; +export const WORKFLOW_STATUS_BADGE_TONE: Record = + { + running: "info", + awaiting: "accent", + done: "success", + paused: "neutral", + fail: "danger", + }; /** Dot tone for the same chip — see `status-dot.tsx` for why only three exist. */ -export const WORKFLOW_STATUS_DOT_TONE: Record = { +export const WORKFLOW_STATUS_DOT_TONE: Record< + WorkflowStatusTone, + StatusDotTone +> = { running: "emphasis", awaiting: "emphasis", done: "neutral", diff --git a/src/lib/workflow-run-error.ts b/src/lib/workflow-run-error.ts index 4a07f2c4..7526fcad 100644 --- a/src/lib/workflow-run-error.ts +++ b/src/lib/workflow-run-error.ts @@ -26,7 +26,8 @@ export type ClassifiedRunError = { readonly raw: string; }; -const PROVIDER_API_ERROR = /^([A-Za-z][A-Za-z0-9]*(?: [A-Za-z0-9]+){0,2}) API error: (\d{3})/; +const PROVIDER_API_ERROR = + /^([A-Za-z][A-Za-z0-9]*(?: [A-Za-z0-9]+){0,2}) API error: (\d{3})/; const ANY_API_ERROR = /\bAPI error: (\d{3})/; const GENERIC_PROVIDER_LABEL = "An external service"; @@ -44,10 +45,18 @@ const NETWORK_PATTERNS = [ const INTERNAL_MESSAGE = "Something went wrong inside this workflow run. Try running it again; if it keeps failing, contact your workspace admin."; -const NETWORK_MESSAGE = "A service this workflow depends on couldn't be reached. Try running it again in a moment."; +const NETWORK_MESSAGE = + "A service this workflow depends on couldn't be reached. Try running it again in a moment."; -function classifyProviderError(provider: string, status: number, raw: string): ClassifiedRunError { - const credentialRef = provider === GENERIC_PROVIDER_LABEL ? "credential's" : `${provider} credential's`; +function classifyProviderError( + provider: string, + status: number, + raw: string, +): ClassifiedRunError { + const credentialRef = + provider === GENERIC_PROVIDER_LABEL + ? "credential's" + : `${provider} credential's`; if (status === 401 || status === 403) { return { kind: "external-auth", @@ -79,11 +88,19 @@ function classifyProviderError(provider: string, status: number, raw: string): C export function classifyRunError(raw: string): ClassifiedRunError { const providerMatch = PROVIDER_API_ERROR.exec(raw); if (providerMatch?.[1] !== undefined && providerMatch[2] !== undefined) { - return classifyProviderError(providerMatch[1], Number(providerMatch[2]), raw); + return classifyProviderError( + providerMatch[1], + Number(providerMatch[2]), + raw, + ); } const anyApiErrorMatch = ANY_API_ERROR.exec(raw); if (anyApiErrorMatch?.[1] !== undefined) { - return classifyProviderError(GENERIC_PROVIDER_LABEL, Number(anyApiErrorMatch[1]), raw); + return classifyProviderError( + GENERIC_PROVIDER_LABEL, + Number(anyApiErrorMatch[1]), + raw, + ); } if (NETWORK_PATTERNS.some((pattern) => pattern.test(raw))) { return { kind: "network", userMessage: NETWORK_MESSAGE, raw }; diff --git a/src/lib/workflow-run-progress.ts b/src/lib/workflow-run-progress.ts index 616e5360..3a1f9a6f 100644 --- a/src/lib/workflow-run-progress.ts +++ b/src/lib/workflow-run-progress.ts @@ -13,7 +13,13 @@ import { classifyRunError } from "./workflow-run-error.js"; * rule encoded here: a step counts as passed once its own phase is completed * OR any later step has progressed at all. */ -export type StepPhase = "pending" | "in-flight" | "awaiting-signal" | "awaiting-timer" | "completed" | "failed"; +export type StepPhase = + | "pending" + | "in-flight" + | "awaiting-signal" + | "awaiting-timer" + | "completed" + | "failed"; export type RunPhase = "pending" | "in-flight" | "completed" | "failed"; @@ -45,7 +51,10 @@ export function workflowStepLabelClass(status: WorkflowStepStatus): string { } /** Mark glyph for a step's status: a checkmark, an exclamation, or its number. */ -export function workflowStepGlyph(status: WorkflowStepStatus, number: number): string { +export function workflowStepGlyph( + status: WorkflowStepStatus, + number: number, +): string { if (status === "completed") return "✓"; if (status === "failed") return "!"; return String(number); @@ -68,19 +77,33 @@ export type DisplayStep = { readonly activityLabel?: string; }; -export function getStepPhase(progress: RunProgress | null, stepId: string): StepPhase | undefined { +export function getStepPhase( + progress: RunProgress | null, + stepId: string, +): StepPhase | undefined { return progress?.steps.get(stepId)?.phase; } export function isStepRunning(phase: StepPhase | undefined): boolean { - return phase === "in-flight" || phase === "awaiting-signal" || phase === "awaiting-timer"; + return ( + phase === "in-flight" || + phase === "awaiting-signal" || + phase === "awaiting-timer" + ); } /** The aggregate phase of a display step from its runtime steps. */ -export function displayStepPhase(progress: RunProgress | null, stepIds: readonly string[]): StepPhase | undefined { +export function displayStepPhase( + progress: RunProgress | null, + stepIds: readonly string[], +): StepPhase | undefined { if (stepIds.length === 0) return undefined; const terminal = stepIds[stepIds.length - 1]; - if (terminal !== undefined && getStepPhase(progress, terminal) === "completed") return "completed"; + if ( + terminal !== undefined && + getStepPhase(progress, terminal) === "completed" + ) + return "completed"; for (const id of stepIds) { const phase = getStepPhase(progress, id); if (isStepRunning(phase)) return phase; @@ -94,7 +117,10 @@ export function displayStepPhase(progress: RunProgress | null, stepIds: readonly return undefined; } -function hasAnyProgress(progress: RunProgress | null, step: DisplayStep): boolean { +function hasAnyProgress( + progress: RunProgress | null, + step: DisplayStep, +): boolean { return step.stepIds.some((id) => getStepPhase(progress, id) !== undefined); } @@ -105,21 +131,29 @@ function hasAnyProgress(progress: RunProgress | null, step: DisplayStep): boolea * a later step's progress says nothing about whether this one failed. * Returns the last index once every step is complete. */ -export function activeDisplayStepIndex(progress: RunProgress | null, steps: readonly DisplayStep[]): number { +export function activeDisplayStepIndex( + progress: RunProgress | null, + steps: readonly DisplayStep[], +): number { for (let i = 0; i < steps.length; i++) { const step = steps[i]; if (step === undefined) continue; const phase = displayStepPhase(progress, step.stepIds); if (phase === "completed") continue; if (phase === "failed") return i; - const laterProgressed = steps.slice(i + 1).some((later) => hasAnyProgress(progress, later)); + const laterProgressed = steps + .slice(i + 1) + .some((later) => hasAnyProgress(progress, later)); if (laterProgressed) continue; return i; } return Math.max(0, steps.length - 1); } -export function buildStepperSteps(progress: RunProgress | null, steps: readonly DisplayStep[]): WorkflowStep[] { +export function buildStepperSteps( + progress: RunProgress | null, + steps: readonly DisplayStep[], +): WorkflowStep[] { const activeIdx = activeDisplayStepIndex(progress, steps); // A completed run marks every entry done: the final step's output can lag // behind the run's own terminal phase, and without this it would linger @@ -133,14 +167,22 @@ export function buildStepperSteps(progress: RunProgress | null, steps: readonly status = "pending"; } else { const phase = displayStepPhase(progress, step.stepIds); - status = phase === "completed" ? "completed" : phase === "failed" ? "failed" : "current"; + status = + phase === "completed" + ? "completed" + : phase === "failed" + ? "failed" + : "current"; } return { number: i + 1, label: step.label, status }; }); } /** The active display step, or `null` while the run is terminal. */ -export function activeDisplayStep(progress: RunProgress | null, steps: readonly DisplayStep[]): DisplayStep | null { +export function activeDisplayStep( + progress: RunProgress | null, + steps: readonly DisplayStep[], +): DisplayStep | null { if (steps.length === 0) return null; return steps[activeDisplayStepIndex(progress, steps)] ?? null; } @@ -153,9 +195,13 @@ export function activeDisplayStep(progress: RunProgress | null, steps: readonly * carries no `activityLabel` at all (a gate or intake step): silence there is * correct, never a stepper noun standing in for it. */ -export function liveStatusLabel(progress: RunProgress | null, steps: readonly DisplayStep[]): string | null { +export function liveStatusLabel( + progress: RunProgress | null, + steps: readonly DisplayStep[], +): string | null { if (progress === null) return null; - if (progress.phase === "failed" || progress.phase === "completed") return null; + if (progress.phase === "failed" || progress.phase === "completed") + return null; const idx = activeDisplayStepIndex(progress, steps); if (idx === 0) return null; const step = steps[idx]; @@ -168,10 +214,14 @@ export function liveStatusLabel(progress: RunProgress | null, steps: readonly Di /** Label of the first display step whose aggregate phase is `failed`. Does * not require `progress.phase === "failed"` — a caller flagging failure from * a single failed step ahead of the run's own phase flip still gets a name. */ -export function failedDisplayStepLabel(progress: RunProgress | null, steps: readonly DisplayStep[]): string | null { +export function failedDisplayStepLabel( + progress: RunProgress | null, + steps: readonly DisplayStep[], +): string | null { if (progress === null) return null; for (const step of steps) { - if (displayStepPhase(progress, step.stepIds) === "failed") return step.label; + if (displayStepPhase(progress, step.stepIds) === "failed") + return step.label; } return null; } @@ -180,10 +230,13 @@ export function failedDisplayStepLabel(progress: RunProgress | null, steps: read * `null` if the run has not failed or no step carries an error. Uses the * first error found in map-iteration order — a run design that attaches an * error to more than one step at once is a design this cannot disambiguate. */ -export function failedRunErrorMessage(progress: RunProgress | null): string | null { +export function failedRunErrorMessage( + progress: RunProgress | null, +): string | null { if (progress?.phase !== "failed") return null; for (const step of progress.steps.values()) { - if (step.lastErrorMessage !== undefined) return classifyRunError(step.lastErrorMessage).userMessage; + if (step.lastErrorMessage !== undefined) + return classifyRunError(step.lastErrorMessage).userMessage; } return null; } @@ -196,7 +249,10 @@ export function failedRunErrorMessage(progress: RunProgress | null): string | nu * shape regardless of phase — in that case whether it started is unknown, so * this returns `false` rather than making a false "never started" claim. */ -export function runNeverStarted(progress: RunProgress | null, logRead: boolean): boolean { +export function runNeverStarted( + progress: RunProgress | null, + logRead: boolean, +): boolean { return progress !== null && logRead && progress.steps.size === 0; } @@ -204,6 +260,7 @@ export function runNeverStarted(progress: RunProgress | null, logRead: boolean): * recorded any activity: queued (`pending`, or no progress yet) reads * differently from a runtime already booting that first step. */ export function runStartLabel(progress: RunProgress | null): string { - if (progress === null || progress.phase === "pending") return "Starting your workflow…"; + if (progress === null || progress.phase === "pending") + return "Starting your workflow…"; return "Preparing your workflow…"; } diff --git a/src/lib/workflow-run.ts b/src/lib/workflow-run.ts index 5151f2f5..b9d639d8 100644 --- a/src/lib/workflow-run.ts +++ b/src/lib/workflow-run.ts @@ -89,7 +89,9 @@ const ATTENTION_RANK: Record = { }; /** Attention order, newest first within a band. Sorts a copy. */ -export function sortRunsByAttention(runs: readonly WorkflowRunSummary[]): readonly WorkflowRunSummary[] { +export function sortRunsByAttention( + runs: readonly WorkflowRunSummary[], +): readonly WorkflowRunSummary[] { return [...runs].sort((a, b) => { const byAttention = ATTENTION_RANK[a.status] - ATTENTION_RANK[b.status]; if (byAttention !== 0) return byAttention; diff --git a/src/theme.css b/src/theme.css index 1c628752..f3c835c5 100644 --- a/src/theme.css +++ b/src/theme.css @@ -274,7 +274,11 @@ a `.dark` override of their own. */ :root { --sidebar-row-radius: var(--radius); - --sidebar-row-selected-bg: color-mix(in srgb, var(--primary) 10%, transparent); + --sidebar-row-selected-bg: color-mix( + in srgb, + var(--primary) 10%, + transparent + ); } @theme inline { @@ -603,26 +607,59 @@ /* Ridge-study motion: keep geometry and timing in the original 100 x 70 space. */ @keyframes corbits-thinking-sweep { - 0% { transform: translateX(8px); opacity: 0; } - 8%, 86% { opacity: 1; } - 100% { transform: translateX(130px); opacity: 0; } + 0% { + transform: translateX(8px); + opacity: 0; + } + 8%, + 86% { + opacity: 1; + } + 100% { + transform: translateX(130px); + opacity: 0; + } } @keyframes corbits-thinking-rise { - 0% { transform: translateY(88px); } - 100% { transform: translateY(-30px); } + 0% { + transform: translateY(88px); + } + 100% { + transform: translateY(-30px); + } } @keyframes corbits-thinking-wave { - 0% { transform: scale(.03); opacity: 0; } - 10% { opacity: 1; } - 75% { opacity: .9; } - 100% { transform: scale(1.35); opacity: 0; } + 0% { + transform: scale(0.03); + opacity: 0; + } + 10% { + opacity: 1; + } + 75% { + opacity: 0.9; + } + 100% { + transform: scale(1.35); + opacity: 0; + } } @media (prefers-reduced-motion: reduce) { /* Static poses land the gradient mid-stroke. These offsets mirror the SVG geometry in thinking-mark.tsx — change the rect/circle geometry there and these poses have to be re-picked to match. */ - .corbits-thinking-silk, .corbits-thinking-strata, .corbits-thinking-echo { animation: none; } - .corbits-thinking-silk { transform: translateX(44px); } - .corbits-thinking-strata { transform: translateY(30px); } - .corbits-thinking-echo { transform: scale(.6); } + .corbits-thinking-silk, + .corbits-thinking-strata, + .corbits-thinking-echo { + animation: none; + } + .corbits-thinking-silk { + transform: translateX(44px); + } + .corbits-thinking-strata { + transform: translateY(30px); + } + .corbits-thinking-echo { + transform: scale(0.6); + } } diff --git a/src/ui/agent-turn.tsx b/src/ui/agent-turn.tsx index 35e598df..c47dd88c 100644 --- a/src/ui/agent-turn.tsx +++ b/src/ui/agent-turn.tsx @@ -68,7 +68,11 @@ export function AgentTurn({
{hasWork ? (
- + {tools.map((part) => ( ))} diff --git a/src/ui/animated-number.tsx b/src/ui/animated-number.tsx index 9fb517f4..57cc0c92 100644 --- a/src/ui/animated-number.tsx +++ b/src/ui/animated-number.tsx @@ -66,14 +66,17 @@ export function AnimatedNumber({ return () => cancelAnimationFrame(frameRef.current); }, [value, durationMs, reduce]); - const text = format === undefined ? display.toFixed(decimals) : format(display); + const text = + format === undefined ? display.toFixed(decimals) : format(display); return ( {/* The settled value is what assistive tech reads — announcing every interpolated frame would be unusable. */} {text} - {format === undefined ? value.toFixed(decimals) : format(value)} + + {format === undefined ? value.toFixed(decimals) : format(value)} + ); } diff --git a/src/ui/approval-card.tsx b/src/ui/approval-card.tsx index c03dac75..fd4ddf83 100644 --- a/src/ui/approval-card.tsx +++ b/src/ui/approval-card.tsx @@ -27,7 +27,11 @@ export type ApprovalRequest = { readonly actionKey?: string; }; -export type ApprovalState = "idle" | "approving" | "rejecting" | "allowing-always"; +export type ApprovalState = + | "idle" + | "approving" + | "rejecting" + | "allowing-always"; export type ApprovalCardProps = { readonly request: ApprovalRequest; @@ -68,16 +72,22 @@ export function ApprovalCard({ children, }: ApprovalCardProps) { const busy = state !== "idle"; - const canAllowAlways = onAllowAlways !== undefined && request.actionKey !== undefined; + const canAllowAlways = + onAllowAlways !== undefined && request.actionKey !== undefined; return (
Needs your approval - Requested by {request.requestedBy} + + Requested by {request.requestedBy} +

{request.headline}

@@ -129,7 +139,12 @@ function ApprovalDetailRow({ detail }: { detail: ApprovalDetail }) {
{detail.label}
- + {detail.value} {long ? ( diff --git a/src/ui/artifact-body.tsx b/src/ui/artifact-body.tsx index 7c9205a7..c1e23f83 100644 --- a/src/ui/artifact-body.tsx +++ b/src/ui/artifact-body.tsx @@ -1,6 +1,10 @@ import { Download } from "lucide-react"; -import { type Artifact, type ArtifactForm, artifactForm } from "../lib/artifact.js"; +import { + type Artifact, + type ArtifactForm, + artifactForm, +} from "../lib/artifact.js"; import { toSafeHref } from "../lib/url.js"; import { cn } from "../lib/utils.js"; import { ArtifactNotice } from "./artifact-notice.js"; @@ -48,13 +52,22 @@ export function ArtifactBody({ artifact, form, className }: ArtifactBodyProps) { // The thumbnail URL, not the content: an image artifact's bytes live // behind a route, and `content` for one is either empty or a caption. const src = toSafeHref(artifact.thumbnailUrl ?? artifact.downloadUrl); - if (src === undefined) return ; + if (src === undefined) + return ; return (
{/* A plain , not `next/image`: this file is copied into whatever app installs it, and half of those are not Next apps. */} - {artifact.title} - {blank ? null :
{content}
} + {artifact.title} + {blank ? null : ( +
+ {content} +
+ )}
); } @@ -62,10 +75,16 @@ export function ArtifactBody({ artifact, form, className }: ArtifactBodyProps) { case "download": { const downloadUrl = toSafeHref(artifact.downloadUrl); if (downloadUrl === undefined) { - return ; + return ( + + ); } return ( - + Download {artifact.title} @@ -77,7 +96,9 @@ export function ArtifactBody({ artifact, form, className }: ArtifactBodyProps) { ); @@ -90,7 +111,10 @@ export function ArtifactBody({ artifact, form, className }: ArtifactBodyProps) { Download @@ -102,7 +126,14 @@ export function ArtifactBody({ artifact, form, className }: ArtifactBodyProps) { } case "comparison": - return ; + return ( + + ); case "research": { if (blank) return ; @@ -110,7 +141,12 @@ export function ArtifactBody({ artifact, form, className }: ArtifactBodyProps) { // Unparseable is not a failure worth an error card — the payload is // still the artifact, so fall through to prose and let it be read. if (parsed === null) return ; - return ; + return ( + + ); } case "prose": @@ -132,5 +168,9 @@ export function ArtifactBody({ artifact, form, className }: ArtifactBodyProps) { * on a wide screen is unreadable regardless of how good the type is. */ function Prose({ text }: { readonly text: string }) { - return
{text}
; + return ( +
+ {text} +
+ ); } diff --git a/src/ui/artifact-notice.tsx b/src/ui/artifact-notice.tsx index bb71fe62..3f121165 100644 --- a/src/ui/artifact-notice.tsx +++ b/src/ui/artifact-notice.tsx @@ -25,7 +25,13 @@ export function ArtifactNotice({ readonly message?: string; }) { if (message !== undefined) { - return } title="Nothing to preview" description={message} />; + return ( + } + title="Nothing to preview" + description={message} + /> + ); } if (pending) { return ( @@ -36,5 +42,11 @@ export function ArtifactNotice({ /> ); } - return } title="No content" description="This artifact is empty." />; + return ( + } + title="No content" + description="This artifact is empty." + /> + ); } diff --git a/src/ui/avatar.tsx b/src/ui/avatar.tsx index 32f665f4..d304a06a 100644 --- a/src/ui/avatar.tsx +++ b/src/ui/avatar.tsx @@ -93,18 +93,29 @@ export type AvatarStackProps = { /** * Overlapping avatar stack for thread participants and channel members. */ -export function AvatarStack({ items, max = 4, size = "sm", className }: AvatarStackProps) { +export function AvatarStack({ + items, + max = 4, + size = "sm", + className, +}: AvatarStackProps) { const visible = items.slice(0, max); const overflow = items.length - visible.length; return ( - + {visible.map((item, index) => ( 0 && "-ml-1.5")} /> diff --git a/src/ui/badge.tsx b/src/ui/badge.tsx index 83c62d53..9077300f 100644 --- a/src/ui/badge.tsx +++ b/src/ui/badge.tsx @@ -13,14 +13,17 @@ const badgeVariants = cva( variants: { tone: { neutral: "border-border bg-muted text-muted-foreground", - accent: "border-primary-emphasis/40 bg-primary/10 text-primary-emphasis", + accent: + "border-primary-emphasis/40 bg-primary/10 text-primary-emphasis", info: "border-accent bg-accent text-accent-foreground", success: "border-success bg-success text-success-foreground", // Warning reuses accent amber until a dedicated warning pair lands in theme. - warning: "border-primary-emphasis/40 bg-primary/10 text-primary-emphasis", + warning: + "border-primary-emphasis/40 bg-primary/10 text-primary-emphasis", danger: "border-destructive bg-destructive text-destructive-foreground", shared: "border-accent bg-accent text-accent-foreground", - access: "border-primary-emphasis/40 bg-primary/10 text-primary-emphasis", + access: + "border-primary-emphasis/40 bg-primary/10 text-primary-emphasis", }, }, defaultVariants: { tone: "neutral" }, @@ -30,10 +33,17 @@ const badgeVariants = cva( /** The tone names, exported so callers can type a state → tone lookup table. */ export type BadgeTone = NonNullable["tone"]>; -export type BadgeProps = React.ComponentProps<"span"> & VariantProps; +export type BadgeProps = React.ComponentProps<"span"> & + VariantProps; export function Badge({ className, tone, ...props }: BadgeProps) { - return ; + return ( + + ); } export { badgeVariants }; diff --git a/src/ui/bar-chart.tsx b/src/ui/bar-chart.tsx index 0e634e1d..5eebef8d 100644 --- a/src/ui/bar-chart.tsx +++ b/src/ui/bar-chart.tsx @@ -1,4 +1,8 @@ -import { formatCompact, niceTicks, scaleLinear } from "../lib/chart-geometry.js"; +import { + formatCompact, + niceTicks, + scaleLinear, +} from "../lib/chart-geometry.js"; import { seriesColor } from "../lib/chart-palette.js"; import { cn } from "../lib/utils.js"; import { ChartFrame } from "./chart-frame.js"; @@ -48,7 +52,14 @@ const RADIUS = 4; * the exact values — which is what anyone reading non-visually wants — are in * the table, in order, with headers. */ -export function BarChart({ title, description, data, valueLabel = "Value", format = formatCompact, className }: BarChartProps) { +export function BarChart({ + title, + description, + data, + valueLabel = "Value", + format = formatCompact, + className, +}: BarChartProps) { const max = Math.max(0, ...data.map((datum) => datum.value)); const ticks = niceTicks(max); const axisMax = ticks[ticks.length - 1] ?? 1; @@ -79,7 +90,15 @@ export function BarChart({ title, description, data, valueLabel = "Value", forma return ( // Hairline, solid, one step off the surface: present enough to // measure against, quiet enough that the bars stay the loud thing. - + ); })} diff --git a/src/ui/block-card.test.tsx b/src/ui/block-card.test.tsx index 24cac880..f0be0af9 100644 --- a/src/ui/block-card.test.tsx +++ b/src/ui/block-card.test.tsx @@ -28,17 +28,24 @@ function render(node: React.ReactElement): Mounted { describe("BlockCard", () => { test("renders zero-radius bordered frame with title and body", () => { - const mounted = render(Body content); + const mounted = render( + Body content, + ); const card = mounted.container.querySelector("[data-slot='block-card']"); expect(card?.getAttribute("class") ?? "").toContain("rounded-none"); - expect(mounted.container.querySelector("[data-slot='block-card-title']")?.textContent).toBe("Cancel order"); + expect( + mounted.container.querySelector("[data-slot='block-card-title']") + ?.textContent, + ).toBe("Cancel order"); expect(mounted.container.textContent).toContain("Body content"); mounted.unmount(); }); test("header pulse dot is decorative and not announced twice", () => { const mounted = render(Body); - const header = mounted.container.querySelector("[data-slot='block-card-header']"); + const header = mounted.container.querySelector( + "[data-slot='block-card-header']", + ); const dot = header?.firstElementChild; expect(dot?.getAttribute("aria-hidden")).toBe("true"); mounted.unmount(); @@ -59,7 +66,9 @@ describe("RiskBadge", () => { }); test("renders the label as real text, and an optional note alongside it", () => { - const mounted = render(); + const mounted = render( + , + ); expect(mounted.container.textContent).toContain("High risk"); expect(mounted.container.textContent).toContain("No undo"); mounted.unmount(); diff --git a/src/ui/block-card.tsx b/src/ui/block-card.tsx index b2062de5..080a5858 100644 --- a/src/ui/block-card.tsx +++ b/src/ui/block-card.tsx @@ -19,7 +19,13 @@ export type BlockCardProps = { */ export function BlockCard({ title, children, className }: BlockCardProps) { return ( -
+
{label} - {note === undefined ? null : {note}} + {note === undefined ? null : ( + {note} + )} ); } diff --git a/src/ui/boot-screen.tsx b/src/ui/boot-screen.tsx index 8835483d..4545d7f0 100644 --- a/src/ui/boot-screen.tsx +++ b/src/ui/boot-screen.tsx @@ -27,28 +27,46 @@ export type BootScreenProps = { * to a still frame under the theme's reduced-motion rule with nothing to do * here. */ -export function BootScreen({ message, brand, footer, className }: BootScreenProps) { +export function BootScreen({ + message, + brand, + footer, + className, +}: BootScreenProps) { const messageId = useId(); return (
- {brand === undefined ? null :
{brand}
} + {brand === undefined ? null : ( +
{brand}
+ )}
- + -

+

{message}

{footer === undefined ? null : ( -
{footer}
+
+ {footer} +
)}
); diff --git a/src/ui/bulk-action-bar.test.tsx b/src/ui/bulk-action-bar.test.tsx index 4c1b9bcf..1d9b6d56 100644 --- a/src/ui/bulk-action-bar.test.tsx +++ b/src/ui/bulk-action-bar.test.tsx @@ -9,7 +9,13 @@ function mount(count: number, onClear: () => void) { document.body.appendChild(container); const root = createRoot(container); act(() => { - root.render(createElement(BulkActionBar, { count, onClear, children: createElement("button", null, "Archive") })); + root.render( + createElement(BulkActionBar, { + count, + onClear, + children: createElement("button", null, "Archive"), + }), + ); }); return { group: () => container.querySelector("[role='group']"), @@ -21,7 +27,11 @@ function mount(count: number, onClear: () => void) { rerender: (nextCount: number) => act(() => { root.render( - createElement(BulkActionBar, { count: nextCount, onClear, children: createElement("button", null, "Archive") }), + createElement(BulkActionBar, { + count: nextCount, + onClear, + children: createElement("button", null, "Archive"), + }), ); }), unmount: () => act(() => root.unmount()), @@ -62,15 +72,17 @@ describe("BulkActionBar", () => { }), ); }); - expect(container.querySelector("[role='group']")?.getAttribute("aria-label")).toBe( - "Actions for 1 selected artifact", - ); + expect( + container.querySelector("[role='group']")?.getAttribute("aria-label"), + ).toBe("Actions for 1 selected artifact"); act(() => root.unmount()); }); test("the count label is announced via aria-live", () => { const { container, unmount } = mount(2, () => {}); - expect(container.querySelector("[aria-live='polite']")?.textContent).toBe("2 selected"); + expect(container.querySelector("[aria-live='polite']")?.textContent).toBe( + "2 selected", + ); unmount(); }); @@ -82,7 +94,9 @@ describe("BulkActionBar", () => { // The centering class is unconditional — not gated behind `motion-safe:` // — so a reduced-motion viewer (who never runs the entrance animation // at all) still ends up centered rather than pinned at the midpoint. - expect(positioningWrapper()?.className).not.toContain("motion-safe:-translate-x-1/2"); + expect(positioningWrapper()?.className).not.toContain( + "motion-safe:-translate-x-1/2", + ); expect(group()?.className).not.toContain("translate-x"); unmount(); }); @@ -93,7 +107,9 @@ describe("BulkActionBar", () => { cleared = true; }); act(() => { - window.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true })); + window.dispatchEvent( + new KeyboardEvent("keydown", { key: "Escape", bubbles: true }), + ); }); expect(cleared).toBe(true); unmount(); @@ -106,7 +122,9 @@ describe("BulkActionBar", () => { }); rerender(0); act(() => { - window.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true })); + window.dispatchEvent( + new KeyboardEvent("keydown", { key: "Escape", bubbles: true }), + ); }); expect(cleared).toBe(false); unmount(); @@ -118,10 +136,17 @@ describe("BulkActionBar", () => { cleared = true; }); act(() => { - const event = new KeyboardEvent("keydown", { key: "Escape", bubbles: true, cancelable: true }); + const event = new KeyboardEvent("keydown", { + key: "Escape", + bubbles: true, + cancelable: true, + }); // Simulates a dialog/input above the bar that already claimed this // Escape press before it reaches the bar's window listener. - window.addEventListener("keydown", (e) => e.preventDefault(), { once: true, capture: true }); + window.addEventListener("keydown", (e) => e.preventDefault(), { + once: true, + capture: true, + }); window.dispatchEvent(event); }); expect(cleared).toBe(false); diff --git a/src/ui/bulk-action-bar.tsx b/src/ui/bulk-action-bar.tsx index 7d3b5f20..e2b77d26 100644 --- a/src/ui/bulk-action-bar.tsx +++ b/src/ui/bulk-action-bar.tsx @@ -41,7 +41,13 @@ export type BulkActionBarProps = { * already handled its own Escape wins instead of also clearing the * selection underneath it. */ -export function BulkActionBar({ count, onClear, children, label = "Bulk actions", className }: BulkActionBarProps) { +export function BulkActionBar({ + count, + onClear, + children, + label = "Bulk actions", + className, +}: BulkActionBarProps) { useEffect(() => { if (count === 0) return; function onKeyDown(event: KeyboardEvent) { @@ -54,7 +60,9 @@ export function BulkActionBar({ count, onClear, children, label = "Bulk actions" if (count === 0) return null; return ( -
+
- + {count} selected
{children}
diff --git a/src/ui/button.test.tsx b/src/ui/button.test.tsx index 76a339c6..e7fb72f4 100644 --- a/src/ui/button.test.tsx +++ b/src/ui/button.test.tsx @@ -29,7 +29,8 @@ function render(node: React.ReactElement): Mounted { describe("Button link variant", () => { test("carries the underline treatment and no fill or border", () => { const mounted = render(); - const className = mounted.container.querySelector("button")?.getAttribute("class") ?? ""; + const className = + mounted.container.querySelector("button")?.getAttribute("class") ?? ""; expect(className).toContain("text-primary-emphasis"); expect(className).toContain("hover:underline"); expect(className).not.toContain("bg-primary"); diff --git a/src/ui/button.tsx b/src/ui/button.tsx index 9aed40c6..03ace2d3 100644 --- a/src/ui/button.tsx +++ b/src/ui/button.tsx @@ -28,7 +28,8 @@ const buttonVariantsBase = cva( outline: "border border-input bg-transparent hover:bg-muted", ghost: "hover:bg-muted", link: "text-primary-emphasis underline-offset-4 hover:underline", - destructive: "bg-destructive text-destructive-foreground hover:opacity-90", + destructive: + "bg-destructive text-destructive-foreground hover:opacity-90", }, size: { sm: "h-8 px-3", @@ -39,7 +40,13 @@ const buttonVariantsBase = cva( }, // link reads as inline text, not a control — it opts out of every size's // fixed height/padding rather than getting a box of its own. - compoundVariants: [{ variant: "link", size: ["sm", "md", "lg", "icon"], class: "h-auto px-0" }], + compoundVariants: [ + { + variant: "link", + size: ["sm", "md", "lg", "icon"], + class: "h-auto px-0", + }, + ], defaultVariants: { variant: "primary", size: "md" }, }, ); @@ -48,16 +55,30 @@ const buttonVariantsBase = cva( // strings with no dedup, so the compound override above only wins once // tailwind-merge resolves the h-*/px-* conflicts — hence the wrapper instead // of exporting buttonVariantsBase directly. -function buttonVariants(props?: VariantProps): string { +function buttonVariants( + props?: VariantProps, +): string { return twMerge(buttonVariantsBase(props)); } export type ButtonProps = React.ComponentProps<"button"> & VariantProps & { asChild?: boolean }; -export function Button({ className, variant, size, asChild = false, ...props }: ButtonProps) { +export function Button({ + className, + variant, + size, + asChild = false, + ...props +}: ButtonProps) { const Comp = asChild ? Slot : "button"; - return ; + return ( + + ); } export { buttonVariants }; diff --git a/src/ui/card.tsx b/src/ui/card.tsx index 6fc57b58..9c0321ba 100644 --- a/src/ui/card.tsx +++ b/src/ui/card.tsx @@ -6,24 +6,60 @@ export function Card({ className, ...props }: React.ComponentProps<"div">) { return (
); } -export function CardHeader({ className, ...props }: React.ComponentProps<"div">) { - return
; +export function CardHeader({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ); } export function CardTitle({ className, ...props }: React.ComponentProps<"h3">) { - return

; + return ( +

+ ); } -export function CardDescription({ className, ...props }: React.ComponentProps<"p">) { - return

; +export function CardDescription({ + className, + ...props +}: React.ComponentProps<"p">) { + return ( +

+ ); } -export function CardFooter({ className, ...props }: React.ComponentProps<"div">) { - return

; +export function CardFooter({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ); } diff --git a/src/ui/category-bars.tsx b/src/ui/category-bars.tsx index 1e3a00aa..c569d170 100644 --- a/src/ui/category-bars.tsx +++ b/src/ui/category-bars.tsx @@ -52,7 +52,10 @@ export function CategoryBars({ {data.map((datum) => { const pct = (datum.value / axisMax) * 100; return ( -
+

{datum.label}

{format(datum.value)}

{datum.secondaryLabel === undefined ? null : ( -

{datum.secondaryLabel}

+

+ {datum.secondaryLabel} +

)}
diff --git a/src/ui/chart-frame.tsx b/src/ui/chart-frame.tsx index b50f32dd..9385cee4 100644 --- a/src/ui/chart-frame.tsx +++ b/src/ui/chart-frame.tsx @@ -1,7 +1,14 @@ import type { ReactNode } from "react"; import { cn } from "../lib/utils.js"; -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./table.js"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "./table.js"; export type ChartLegendEntry = { readonly label: string; @@ -58,7 +65,14 @@ export type ChartFrameProps = { * cross-reference. Text never wears the series colour: a mid-lightness hue that * is correct for a 24px bar is unreadable at 12px. */ -export function ChartFrame({ title, description, legend, table, children, className }: ChartFrameProps) { +export function ChartFrame({ + title, + description, + legend, + table, + children, + className, +}: ChartFrameProps) { const showLegend = legend !== undefined && legend.length > 1; return ( @@ -73,11 +87,22 @@ export function ChartFrame({ title, description, legend, table, children, classN {showLegend ? (
    {legend.map((entry) => ( -
  • +
  • {entry.dash === undefined ? ( - + ) : ( - + - Show the data + + Show the data +
    {table.columns.map((column, index) => ( - + {column} ))} @@ -117,7 +148,11 @@ export function ChartFrame({ title, description, legend, table, children, classN {row.map((cell, cellIndex) => ( {cell} diff --git a/src/ui/chat-composer.tsx b/src/ui/chat-composer.tsx index d6aa1ed4..fb278927 100644 --- a/src/ui/chat-composer.tsx +++ b/src/ui/chat-composer.tsx @@ -8,7 +8,10 @@ import { cn } from "../lib/utils.js"; import { ChatInput, type ChatInputProps } from "./chat-input.js"; import { QuickReplyChips } from "./quick-reply-chips.js"; -export type ChatComposerProps = Omit & { +export type ChatComposerProps = Omit< + ChatInputProps, + "working" | "disabled" | "textareaRef" +> & { /** Suggestion chips shown above the input while the conversation is empty. */ readonly suggestions?: readonly QuickReply[]; readonly onSuggestionSelect?: (reply: QuickReply) => void; @@ -41,14 +44,20 @@ export function ChatComposer({ const textareaRef = useRef(null); useDelayedAutofocus(textareaRef, autoFocusOn); - const showSuggestions = suggestions.length > 0 && onSuggestionSelect !== undefined; + const showSuggestions = + suggestions.length > 0 && onSuggestionSelect !== undefined; return (
    {showSuggestions ? ( ) : null} - +
    ); } diff --git a/src/ui/chat-input.tsx b/src/ui/chat-input.tsx index bb67bb98..0fad33f6 100644 --- a/src/ui/chat-input.tsx +++ b/src/ui/chat-input.tsx @@ -80,7 +80,10 @@ export function ChatInput({ return (
    { event.preventDefault(); submit(); diff --git a/src/ui/chat-panel.tsx b/src/ui/chat-panel.tsx index f5b55be0..1cd63c0a 100644 --- a/src/ui/chat-panel.tsx +++ b/src/ui/chat-panel.tsx @@ -12,11 +12,17 @@ import { cn } from "../lib/utils.js"; * and each wants something different in the header — a thread switcher here, a * close button there. Composition is what makes one frame serve three shells. */ -export function ChatPanel({ className, ...props }: React.ComponentProps<"div">) { +export function ChatPanel({ + className, + ...props +}: React.ComponentProps<"div">) { return (
    ); @@ -31,7 +37,10 @@ export function ChatPanelHeader({ return (

    {identity.name}

    {identity.tagline === undefined ? null : ( -

    {identity.tagline}

    +

    + {identity.tagline} +

    )}
    {children}
    @@ -52,11 +63,20 @@ export function ChatPanelHeader({ } /** The composer area: quick replies, the input, anything else pinned to the bottom. */ -export function ChatPanelFooter({ className, children }: { className?: string; children: ReactNode }) { +export function ChatPanelFooter({ + className, + children, +}: { + className?: string; + children: ReactNode; +}) { return (
    {children}
    diff --git a/src/ui/chat-thread.tsx b/src/ui/chat-thread.tsx index 9322f027..27e45f4d 100644 --- a/src/ui/chat-thread.tsx +++ b/src/ui/chat-thread.tsx @@ -53,7 +53,16 @@ export function ChatThread({ }, [messages]); if (messages.length === 0 && empty !== undefined) { - return
    {empty}
    ; + return ( +
    + {empty} +
    + ); } return ( @@ -63,18 +72,27 @@ export function ChatThread({ const node = event.currentTarget; // A few pixels of slack: sub-pixel layout means an exact comparison is // false on plenty of screens that are, visually, at the bottom. - pinnedRef.current = node.scrollHeight - node.scrollTop - node.clientHeight < 32; + pinnedRef.current = + node.scrollHeight - node.scrollTop - node.clientHeight < 32; }} // `log` + polite: new turns are announced without interrupting, which is // what an arriving reply is. role="log" aria-live="polite" aria-label="Conversation" - className={cn("flex min-h-0 flex-1 flex-col gap-5 overflow-y-auto p-4", className)} + className={cn( + "flex min-h-0 flex-1 flex-col gap-5 overflow-y-auto p-4", + className, + )} > {messages.map((message) => message.role === "agent" ? ( - + {renderBody?.(message)} ) : ( diff --git a/src/ui/checkbox.test.tsx b/src/ui/checkbox.test.tsx index 398a015d..956d10d9 100644 --- a/src/ui/checkbox.test.tsx +++ b/src/ui/checkbox.test.tsx @@ -22,7 +22,11 @@ function mount(props: ComponentProps) { describe("Checkbox", () => { test("renders a native checkbox labelled by its own label text", () => { - const { input, label, unmount } = mount({ label: "Auto-approve", checked: false, onCheckedChange: () => {} }); + const { input, label, unmount } = mount({ + label: "Auto-approve", + checked: false, + onCheckedChange: () => {}, + }); expect(input().type).toBe("checkbox"); expect(label().textContent).toBe("Auto-approve"); expect(label().getAttribute("for")).toBe(input().id); @@ -60,7 +64,11 @@ describe("Checkbox", () => { }); test("omits aria-describedby when there is no description", () => { - const { input, unmount } = mount({ label: "Auto-approve", checked: false, onCheckedChange: () => {} }); + const { input, unmount } = mount({ + label: "Auto-approve", + checked: false, + onCheckedChange: () => {}, + }); expect(input().getAttribute("aria-describedby")).toBeNull(); unmount(); }); @@ -78,13 +86,23 @@ describe("Checkbox", () => { }); test("disabled disables the control", () => { - const { input, unmount } = mount({ label: "Auto-approve", checked: false, onCheckedChange: () => {}, disabled: true }); + const { input, unmount } = mount({ + label: "Auto-approve", + checked: false, + onCheckedChange: () => {}, + disabled: true, + }); expect(input().disabled).toBe(true); unmount(); }); test("invalid marks the control as aria-invalid", () => { - const { input, unmount } = mount({ label: "Agree", checked: false, onCheckedChange: () => {}, invalid: true }); + const { input, unmount } = mount({ + label: "Agree", + checked: false, + onCheckedChange: () => {}, + invalid: true, + }); expect(input().getAttribute("aria-invalid")).toBe("true"); unmount(); }); @@ -100,7 +118,9 @@ describe("Checkbox", () => { }, }); const describedBy = input().getAttribute("aria-describedby"); - const description = container.querySelector(`#${describedBy}`) as HTMLElement; + const description = container.querySelector( + `#${describedBy}`, + ) as HTMLElement; act(() => { description.dispatchEvent(new MouseEvent("click", { bubbles: true })); }); @@ -109,7 +129,11 @@ describe("Checkbox", () => { }); test("without a label, renders just the bare control", () => { - const { container, input, unmount } = mount({ checked: false, onCheckedChange: () => {}, id: "bare-checkbox" }); + const { container, input, unmount } = mount({ + checked: false, + onCheckedChange: () => {}, + id: "bare-checkbox", + }); expect(container.querySelector("label")).toBeNull(); expect(input().id).toBe("bare-checkbox"); unmount(); @@ -122,7 +146,9 @@ describe("Checkbox", () => { id: "bare-checkbox", describedBy: "external-description", }); - expect(input().getAttribute("aria-describedby")).toBe("external-description"); + expect(input().getAttribute("aria-describedby")).toBe( + "external-description", + ); unmount(); }); }); diff --git a/src/ui/checkbox.tsx b/src/ui/checkbox.tsx index ccc43029..9f29c3c1 100644 --- a/src/ui/checkbox.tsx +++ b/src/ui/checkbox.tsx @@ -30,7 +30,8 @@ export type CheckboxProps = { readonly className?: string; }; -const CONTROL_CLASS = "relative mt-0.5 flex size-4 shrink-0 items-center justify-center"; +const CONTROL_CLASS = + "relative mt-0.5 flex size-4 shrink-0 items-center justify-center"; export function Checkbox({ checked, @@ -46,7 +47,8 @@ export function Checkbox({ }: CheckboxProps) { const generatedId = useId(); const checkboxId = id ?? generatedId; - const descriptionId = description === undefined ? undefined : `${checkboxId}-description`; + const descriptionId = + description === undefined ? undefined : `${checkboxId}-description`; const ref = useRef(null); const isBare = label === undefined; @@ -87,13 +89,20 @@ export function Checkbox({ return (
    {notes.length === 0 ? null : ( -

    Showing {notes.join(" and ")} — download for the full file.

    +

    + Showing {notes.join(" and ")} — download for the full file. +

    )}
); diff --git a/src/ui/dialog.test.tsx b/src/ui/dialog.test.tsx index 618f8500..0407b620 100644 --- a/src/ui/dialog.test.tsx +++ b/src/ui/dialog.test.tsx @@ -103,7 +103,9 @@ describe("Dialog craft defaults", () => { const body = document.querySelector('[data-slot="dialog-body"]'); const footer = document.querySelector('[data-slot="dialog-footer"]'); const title = document.querySelector('[data-slot="dialog-content"] h2'); - const description = document.querySelector('[data-slot="dialog-content"] p'); + const description = document.querySelector( + '[data-slot="dialog-content"] p', + ); expect(header?.className).toContain("shrink-0"); expect(header?.className).toContain("gap-1"); @@ -133,7 +135,9 @@ describe("Dialog craft defaults", () => { test("close control is a stable hit target with hover and focus chrome", () => { const mounted = openDialog(); - const close = document.querySelector('[data-slot="dialog-content"] > button[aria-label="Close"]'); + const close = document.querySelector( + '[data-slot="dialog-content"] > button[aria-label="Close"]', + ); expect(close).not.toBeNull(); const className = close?.getAttribute("class") ?? ""; expect(className).toContain("size-[1.8rem]"); @@ -142,7 +146,9 @@ describe("Dialog craft defaults", () => { expect(className).toContain("hover:bg-foreground/5"); expect(className).toContain("hover:text-foreground"); expect(className).toContain("focus-visible:outline-none"); - expect(className).toContain("focus-visible:shadow-[0_0_0_3px_color-mix(in_srgb,var(--primary)_22%,transparent)]"); + expect(className).toContain( + "focus-visible:shadow-[0_0_0_3px_color-mix(in_srgb,var(--primary)_22%,transparent)]", + ); // Legacy opacity-only hover is gone — polish is color/background. expect(className).not.toContain("opacity-70"); expect(className).not.toContain("hover:opacity-100"); diff --git a/src/ui/dialog.tsx b/src/ui/dialog.tsx index d6166fbf..538c4cfa 100644 --- a/src/ui/dialog.tsx +++ b/src/ui/dialog.tsx @@ -53,7 +53,9 @@ export function DialogContent({ children, side = "center", ...props -}: React.ComponentProps & { side?: DialogSide }) { +}: React.ComponentProps & { + side?: DialogSide; +}) { return ( @@ -89,24 +91,40 @@ export function DialogContent({ } /** `pr-8` keeps the title clear of the close control in the corner. */ -export function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { +export function DialogHeader({ + className, + ...props +}: React.ComponentProps<"div">) { return ( -
+
); } /** Scrolls; the header and footer stay put. Needed by tall centre dialogs and side sheets. */ -export function DialogBody({ className, ...props }: React.ComponentProps<"div">) { +export function DialogBody({ + className, + ...props +}: React.ComponentProps<"div">) { return (
); } -export function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { +export function DialogFooter({ + className, + ...props +}: React.ComponentProps<"div">) { return (
) { +export function DialogTitle({ + className, + ...props +}: React.ComponentProps) { return ( ); } -export function DialogDescription({ className, ...props }: React.ComponentProps) { +export function DialogDescription({ + className, + ...props +}: React.ComponentProps) { return ( ); } /** Uppercase field label used inside dialog forms. */ -export function DialogFieldLabel({ className, ...props }: React.ComponentProps<"label">) { +export function DialogFieldLabel({ + className, + ...props +}: React.ComponentProps<"label">) { return (