From 60ad31bfdb1056c172a521acad334bb558097bf9 Mon Sep 17 00:00:00 2001 From: Naffah Abdulla Rasheed Date: Sun, 1 Nov 2026 10:40:04 +0500 Subject: [PATCH 01/47] F1: Say where to branch from in the setup section Co-Authored-By: Claude Opus 5.5 --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f6c04d14..d40543c6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,6 +10,8 @@ pnpm install pnpm dev # web app at http://localhost:5173 ``` +Branch from `dev` and target `dev` in PRs; `main` is release-only. + Maps in the web app use Google Maps when `VITE_GOOGLE_MAPS_API_KEY` (and optionally `VITE_GOOGLE_MAPS_MAP_ID`) is set, and the keyless placeholder otherwise. Until a Map ID is set (locally or as the `GOOGLE_MAPS_MAP_ID` secret of the demo build), the examples use Google's `DEMO_MAP_ID`, so advanced markers work. The map demos open on Javaabu's office in Malé (`JAVAABU_OFFICE` in `web/src/showcase/maps/_data.ts`). To try the real Google adapter without a key (development mode, watermarked), set `VITE_GOOGLE_MAPS_FORCE=1 VITE_GOOGLE_MAPS_MAP_ID=DEMO_MAP_ID`. See [MapProvider](docs/maps/map-provider.md#verified-against-google-maps-js-api). ## Definition of done From 324d0e820275466081aa0852e66b623a34bd8e6c Mon Sep 17 00:00:00 2001 From: Naffah Abdulla Rasheed Date: Sun, 1 Nov 2026 10:41:36 +0500 Subject: [PATCH 02/47] A1: Require React 19 Co-Authored-By: Claude Opus 5.5 --- .changeset/react-19-only.md | 5 +++++ REQUIREMENTS.md | 4 ++-- docs/guides/inertia-and-nextjs.md | 2 +- docs/installation-and-setup.md | 2 +- docs/requirements.md | 2 +- packages/onu/package.json | 4 ++-- 6 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 .changeset/react-19-only.md diff --git a/.changeset/react-19-only.md b/.changeset/react-19-only.md new file mode 100644 index 00000000..26b0686b --- /dev/null +++ b/.changeset/react-19-only.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": minor +--- + +Onu now requires React 19; React 18 is no longer in the peer range. diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index dee9bb8d..d8d75c59 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -86,7 +86,7 @@ This document captures the requirements, constraints and full build list for Onu - `@javaabu/onu/styles.css` — theme tokens + base styles - `@javaabu/onu/tailwind.css` — Tailwind `@theme` / `@source` entry for consumers - **R2.6** Heavy or licensed third-party dependencies (map SDKs, chart libs, editors, scanners, FontAwesome, fonts) are **optional peer dependencies**, required only when the matching subpath is imported. -- **R2.7** Peer dependencies: `react` and `react-dom` `^18.3 || ^19`, `tailwindcss` `^4`. +- **R2.7** Peer dependencies: `react` and `react-dom` `^19`, `tailwindcss` `^4`. - **R2.8** Ship TypeScript declarations; source written in TypeScript `strict` mode. No `any` in public APIs. - **R2.9** Every client component file carries a `"use client"` directive so it works in React Server Component environments (Next.js App Router). - **R2.10** Semantic versioning. Releases managed with [Changesets](https://github.com/changesets/changesets); `CHANGELOG.md` generated from them. @@ -181,7 +181,7 @@ A dependency must meet **all** of these criteria: - **Maintained**: a release or meaningful commit within the last 12 months, a responsive issue tracker, and not archived or deprecated. - **Reputable**: widely adopted, and backed by an organisation or established maintainers. - **Typed**: first-party TypeScript types. -- **Compatible**: React 18 and 19, SSR-safe, tree-shakable. +- **Compatible**: React 19, SSR-safe, tree-shakable. - **Accessible and RTL-capable**, or can be made so by wrapping. - **Reasonable bundle size** for what it provides. diff --git a/docs/guides/inertia-and-nextjs.md b/docs/guides/inertia-and-nextjs.md index e587ff4a..c92ec006 100644 --- a/docs/guides/inertia-and-nextjs.md +++ b/docs/guides/inertia-and-nextjs.md @@ -3,7 +3,7 @@ title: Inertia & Next.js sidebar_position: 13 --- -Onu is plain React, so it works in Vite apps, Laravel + Inertia (React) and Next.js. +Onu is plain React (React 19), so it works in Vite apps, Laravel + Inertia (React) and Next.js. # Links diff --git a/docs/installation-and-setup.md b/docs/installation-and-setup.md index bf6c52a9..409006b4 100644 --- a/docs/installation-and-setup.md +++ b/docs/installation-and-setup.md @@ -5,7 +5,7 @@ sidebar_position: 1.2 # Installation -First install the NPM package +Onu needs React 19. First install the NPM package ```bash npm install @javaabu/onu --save diff --git a/docs/requirements.md b/docs/requirements.md index 3c03366d..e8d9e90e 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -5,7 +5,7 @@ sidebar_position: 1.1 This package requires: -- [React](https://react.dev) 18.3+ or 19 +- [React](https://react.dev) 19 - [Tailwind CSS](https://tailwindcss.com) 4 Onu supports every browser Tailwind CSS 4 supports: Chrome and Edge 111+, Safari 16.4+, Firefox 128+, and their mobile versions. diff --git a/packages/onu/package.json b/packages/onu/package.json index 64884fc4..d78544ce 100644 --- a/packages/onu/package.json +++ b/packages/onu/package.json @@ -142,8 +142,8 @@ "barcode-detector": "^3.2.2", "lowlight": "^3.3.0", "media-chrome": "^4.19.3", - "react": "^18.3.0 || ^19.0.0", - "react-dom": "^18.3.0 || ^19.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", "react-easy-crop": "^6.2.4", "recharts": "^3.10.1", "shiki": "^4.5.0", From d05c72d625e43f0a70262a1e7ef84fcd566853b7 Mon Sep 17 00:00:00 2001 From: Naffah Abdulla Rasheed Date: Sun, 1 Nov 2026 10:42:21 +0500 Subject: [PATCH 03/47] A2: Release every package at one version The changesets fixed group keeps @javaabu/onu and @javaabu/onu-mcp together, and scripts/sync-plugin-version.mjs keeps the plugin manifest in step; the release workflow fails if it differs. Co-Authored-By: Claude Opus 5.5 --- .changeset/config.json | 2 +- .github/workflows/release.yml | 1 + CONTRIBUTING.md | 4 ++-- scripts/sync-plugin-version.mjs | 28 ++++++++++++++++++++++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 scripts/sync-plugin-version.mjs diff --git a/.changeset/config.json b/.changeset/config.json index fcfcf83e..a1571f74 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -2,7 +2,7 @@ "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", "changelog": "@changesets/cli/changelog", "commit": false, - "fixed": [], + "fixed": [["@javaabu/onu", "@javaabu/onu-mcp"]], "linked": [], "access": "public", "baseBranch": "main", diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 202f2020..5041de7e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,6 +41,7 @@ jobs: exit 1 fi done + node scripts/sync-plugin-version.mjs --check - name: Check the npm token run: | if [ -z "$NPM_TOKEN" ]; then diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f6c04d14..60585066 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -85,10 +85,10 @@ Every change that affects a published package adds a changeset (`pnpm changeset` To release: -1. On `dev`, run `pnpm changeset version`. It bumps every package with pending changesets, writes their `CHANGELOG.md` and deletes the changesets. All packages share one version, so check that `packages/onu` and `packages/onu-mcp` match. +1. On `dev`, run `pnpm changeset version && node scripts/sync-plugin-version.mjs`. `@javaabu/onu` and `@javaabu/onu-mcp` are a fixed group in `.changeset/config.json`, so they always move to the same version; the script copies that version into `plugins/onu/.claude-plugin/plugin.json`, which changesets can't see. `changeset version` also writes their `CHANGELOG.md` and deletes the changesets. 2. Commit (`Release v0.1.0`) and merge `dev` into `main`. Wait for CI to pass on `main`. 3. Tag that commit on `main` and push the tag: `git tag v0.1.0 && git push origin v0.1.0`. -The **Release** workflow runs only for `v*` tags. It checks that the tagged commit is on `main` and that every package's version matches the tag, then builds and publishes to npm (versions already on npm are skipped). Tags with a pre-release suffix (`v1.0.0-beta.1`) publish under the `next` dist-tag. +The **Release** workflow runs only for `v*` tags. It checks that the tagged commit is on `main` and that every package's version and the plugin's match the tag, then builds and publishes to npm (versions already on npm are skipped). Tags with a pre-release suffix (`v1.0.0-beta.1`) publish under the `next` dist-tag. Publishing needs an `NPM_TOKEN` repository secret: an npm granular access token with read and write access to the `@javaabu` scope and "bypass 2FA" allowed, from an account that can publish to `@javaabu`. Write tokens expire after about 90 days. Provenance is signed only once the repository is public. After the first release, you can switch to npm trusted publishing (each package's settings on npmjs.com, pointing at `release.yml`) and remove the token. diff --git a/scripts/sync-plugin-version.mjs b/scripts/sync-plugin-version.mjs new file mode 100644 index 00000000..93679148 --- /dev/null +++ b/scripts/sync-plugin-version.mjs @@ -0,0 +1,28 @@ +#!/usr/bin/env node +/** + * Copies the @javaabu/onu version into the Claude Code plugin manifest, which is + * not a workspace package, so changesets never bump it. + * + * node scripts/sync-plugin-version.mjs # write it + * node scripts/sync-plugin-version.mjs --check # fail if it differs (release workflow) + */ +import { readFileSync, writeFileSync } from "node:fs" +import { resolve } from "node:path" + +const root = resolve(import.meta.dirname, "..") +const manifestPath = resolve(root, "plugins/onu/.claude-plugin/plugin.json") +const { version } = JSON.parse(readFileSync(resolve(root, "packages/onu/package.json"), "utf8")) +const manifest = JSON.parse(readFileSync(manifestPath, "utf8")) + +if (manifest.version === version) { + console.log(`plugin.json is at ${version}`) +} else if (process.argv.includes("--check")) { + console.error( + `plugin.json is at ${manifest.version}, but @javaabu/onu is at ${version}. Run node scripts/sync-plugin-version.mjs.`, + ) + process.exit(1) +} else { + manifest.version = version + writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`) + console.log(`plugin.json is now at ${version}`) +} From 85509ba9d420d46d3af014e37ec5836cf52580a7 Mon Sep 17 00:00:00 2001 From: Naffah Abdulla Rasheed Date: Sun, 1 Nov 2026 10:43:15 +0500 Subject: [PATCH 04/47] A3: Load the Thaana fonts without the optional font pack The @font-face rules move to fonts-thaana.css, imported by tailwind.css, styles.css and fonts.css. New budget: fonts-thaana.css 400 B (327 B measured). tailwind.css stays within 8.5 kB (7.97 kB) and styles.css within 4.5 kB (4.38 kB). fonts.css drops to 400 B now the font faces moved out (306 B). Co-Authored-By: Claude Opus 5.5 --- .changeset/thaana-fonts-always.md | 5 +++++ README.md | 2 +- docs/guides/dhivehi-and-locales.md | 2 +- docs/guides/fonts.md | 6 ++--- docs/installation-and-setup.md | 2 +- packages/onu/README.md | 2 +- packages/onu/package.json | 10 ++++++++- packages/onu/src/styles/fonts-thaana.css | 23 +++++++++++++++++++ packages/onu/src/styles/fonts.css | 28 +++--------------------- packages/onu/src/styles/styles.css | 1 + packages/onu/src/styles/tailwind.css | 1 + 11 files changed, 48 insertions(+), 34 deletions(-) create mode 100644 .changeset/thaana-fonts-always.md create mode 100644 packages/onu/src/styles/fonts-thaana.css diff --git a/.changeset/thaana-fonts-always.md b/.changeset/thaana-fonts-always.md new file mode 100644 index 00000000..134cc89d --- /dev/null +++ b/.changeset/thaana-fonts-always.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": patch +--- + +Faruma and MV Faseyha now load from `tailwind.css` and `styles.css`, so Thaana text works without the optional font pack. diff --git a/README.md b/README.md index 10a9fbe8..a5892f85 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ See [`REQUIREMENTS.md`](REQUIREMENTS.md) for the full specification and [`docs/` ```bash npm install @javaabu/onu -# optional font pack (Inter, JetBrains Mono, Noto Sans Thaana; Faruma and MV Faseyha are bundled) +# optional font pack (Inter, JetBrains Mono, Noto Sans Thaana; Faruma and MV Faseyha come with tailwind.css and styles.css) npm install @fontsource-variable/inter @fontsource-variable/jetbrains-mono @fontsource-variable/noto-sans-thaana ``` diff --git a/docs/guides/dhivehi-and-locales.md b/docs/guides/dhivehi-and-locales.md index b7f38287..696e5743 100644 --- a/docs/guides/dhivehi-and-locales.md +++ b/docs/guides/dhivehi-and-locales.md @@ -101,4 +101,4 @@ Charts have their own `useChartFormat` in `@javaabu/onu/charts`, with axis and p # Fonts -Dhivehi text uses Faruma, with MV Faseyha for headings and bold, both bundled with `fonts.css`. See [Fonts](./fonts). +Dhivehi text uses Faruma, with MV Faseyha for headings and bold, both loaded by `tailwind.css`. See [Fonts](./fonts). diff --git a/docs/guides/fonts.md b/docs/guides/fonts.md index d9b07b20..0e7ee4bb 100644 --- a/docs/guides/fonts.md +++ b/docs/guides/fonts.md @@ -29,13 +29,11 @@ All of these are self-hosted from npm, so no requests go to a font CDN. # Thaana fonts -Onu ships Faruma and MV Faseyha with `fonts.css`, as `woff2` files subset to Thaana and its punctuation (about 12 kB each). Their `unicode-range` means a browser only downloads them when a page shows Thaana text, and Latin text in a mixed sentence stays in Inter. +`tailwind.css` and `styles.css` load Faruma and MV Faseyha, so they work without `fonts.css` and its packages. They ship as `woff2` files subset to Thaana and its punctuation (about 12 kB each). Their `unicode-range` means a browser only downloads them when a page shows Thaana text, and Latin text in a mixed sentence stays in Inter. - **Faruma** is used for Dhivehi body text (`--onu-font-thaana`). - **MV Faseyha** is used for Dhivehi headings and ``/`` (`--onu-font-thaana-bold`). Both fonts have a single weight and Onu turns off faux bold for Dhivehi, so emphasis uses the second face instead. -- Characters neither font covers fall back to Noto Sans Thaana. - -If you don't import `fonts.css`, the same names still apply to copies installed on the device. +- Characters neither font covers fall back to Noto Sans Thaana when you import `fonts.css`. # Mixed Dhivehi and English diff --git a/docs/installation-and-setup.md b/docs/installation-and-setup.md index 409006b4..44cf601f 100644 --- a/docs/installation-and-setup.md +++ b/docs/installation-and-setup.md @@ -25,7 +25,7 @@ npm install @fontsource-variable/inter @fontsource-variable/jetbrains-mono @font @import "@javaabu/onu/fonts.css"; /* optional: needs the font packages above */ ``` -Without the font pack, leave out the last line. Faruma and MV Faseyha (Thaana) are inside `@javaabu/onu`; see [Fonts](./guides/fonts). +Without the font pack, leave out the last line. `tailwind.css` already loads Faruma and MV Faseyha (Thaana); see [Fonts](./guides/fonts). `tailwind.css` adds Onu's design tokens, the dark mode variant, and tells Tailwind to scan Onu's components for classes. diff --git a/packages/onu/README.md b/packages/onu/README.md index 90817113..557f40ea 100644 --- a/packages/onu/README.md +++ b/packages/onu/README.md @@ -17,7 +17,7 @@ Every component and the sample pages: [onu.on-forge.com](https://onu.on-forge.co ```bash npm install @javaabu/onu -# optional font pack (Inter, JetBrains Mono, Noto Sans Thaana; Faruma and MV Faseyha are bundled) +# optional font pack (Inter, JetBrains Mono, Noto Sans Thaana; Faruma and MV Faseyha come with tailwind.css and styles.css) npm install @fontsource-variable/inter @fontsource-variable/jetbrains-mono @fontsource-variable/noto-sans-thaana ``` diff --git a/packages/onu/package.json b/packages/onu/package.json index d78544ce..d8581fbb 100644 --- a/packages/onu/package.json +++ b/packages/onu/package.json @@ -518,6 +518,7 @@ "path": [ "src/styles/styles.css", "src/styles/tokens.css", + "src/styles/fonts-thaana.css", "src/styles/theme.css" ], "rolldown": false, @@ -529,17 +530,24 @@ "src/styles/tailwind.css", "src/styles/shadcn.css", "src/styles/tokens.css", + "src/styles/fonts-thaana.css", "src/styles/theme.css", "src/styles/base.css" ], "rolldown": false, "limit": "8.5 kB" }, + { + "name": "fonts-thaana.css", + "path": "src/styles/fonts-thaana.css", + "rolldown": false, + "limit": "400 B" + }, { "name": "fonts.css", "path": "src/styles/fonts.css", "rolldown": false, - "limit": "600 B" + "limit": "400 B" }, { "name": "fonts-optional.css", diff --git a/packages/onu/src/styles/fonts-thaana.css b/packages/onu/src/styles/fonts-thaana.css new file mode 100644 index 00000000..6db0a999 --- /dev/null +++ b/packages/onu/src/styles/fonts-thaana.css @@ -0,0 +1,23 @@ +/* + * Thaana fonts (R7.3), bundled with Onu: Faruma for Dhivehi body text and + * MV Faseyha for Dhivehi headings and bold text (`--onu-font-thaana-bold`). + * Subset to Thaana and its punctuation; `unicode-range` keeps Latin text in + * Inter, and browsers only download a file when a page shows Thaana text. + */ +@font-face { + font-family: "Faruma"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url("./fonts/faruma.woff2") format("woff2"); + unicode-range: U+0780-07BF, U+FDF2, U+060C, U+061B, U+061F, U+25CC; +} + +@font-face { + font-family: "MV Faseyha"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url("./fonts/mv-faseyha.woff2") format("woff2"); + unicode-range: U+0780-07BF, U+FDF2, U+060C, U+061B, U+061F, U+25CC; +} diff --git a/packages/onu/src/styles/fonts.css b/packages/onu/src/styles/fonts.css index d242bc0c..4d8f5096 100644 --- a/packages/onu/src/styles/fonts.css +++ b/packages/onu/src/styles/fonts.css @@ -4,34 +4,12 @@ * * @import "@javaabu/onu/fonts.css"; * - * It loads Inter, JetBrains Mono and Noto Sans Thaana from npm, so install them first: + * It loads Inter, JetBrains Mono and Noto Sans Thaana from npm, so install them first + * (the bundled Thaana fonts come with `tailwind.css` and `styles.css` already): * npm install @fontsource-variable/inter @fontsource-variable/jetbrains-mono @fontsource-variable/noto-sans-thaana */ @import "@fontsource-variable/inter"; @import "@fontsource-variable/jetbrains-mono"; /* Fallback Thaana face for code points the bundled fonts don't cover (R7.3). */ @import "@fontsource-variable/noto-sans-thaana"; - -/* - * Thaana fonts (R7.3), bundled with Onu: Faruma for Dhivehi body text and - * MV Faseyha for Dhivehi headings and bold text (`--onu-font-thaana-bold`). - * Subset to Thaana and its punctuation; `unicode-range` keeps Latin text in - * Inter, and browsers only download a file when a page shows Thaana text. - */ -@font-face { - font-family: "Faruma"; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url("./fonts/faruma.woff2") format("woff2"); - unicode-range: U+0780-07BF, U+FDF2, U+060C, U+061B, U+061F, U+25CC; -} - -@font-face { - font-family: "MV Faseyha"; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url("./fonts/mv-faseyha.woff2") format("woff2"); - unicode-range: U+0780-07BF, U+FDF2, U+060C, U+061B, U+061F, U+25CC; -} +@import "./fonts-thaana.css"; diff --git a/packages/onu/src/styles/styles.css b/packages/onu/src/styles/styles.css index a6099777..4aa3ee85 100644 --- a/packages/onu/src/styles/styles.css +++ b/packages/onu/src/styles/styles.css @@ -3,4 +3,5 @@ * Tailwind projects should import `@javaabu/onu/tailwind.css` instead. */ @import "./tokens.css"; +@import "./fonts-thaana.css"; @import "./theme.css"; diff --git a/packages/onu/src/styles/tailwind.css b/packages/onu/src/styles/tailwind.css index e5c859c8..92054037 100644 --- a/packages/onu/src/styles/tailwind.css +++ b/packages/onu/src/styles/tailwind.css @@ -7,6 +7,7 @@ @import "tw-animate-css"; @import "./shadcn.css"; @import "./tokens.css"; +@import "./fonts-thaana.css"; @import "./theme.css"; @import "./base.css" layer(base); From c4de3d527f66f2bfd74a2446c28d0db940f277c9 Mon Sep 17 00:00:00 2001 From: Naffah Abdulla Rasheed Date: Sun, 1 Nov 2026 10:45:36 +0500 Subject: [PATCH 05/47] A4: Make react-hook-form and TanStack Query peers Both share React context with the app, so a second copy silently disconnects them. @tanstack/react-query is a required peer, not optional as first planned: the core entry imports ApiSelect statically, so an app without the package would fail to build. The fallback QueryClient moves from a module singleton (shared across SSR requests) to each OnuProvider, or the component when there is no provider. getOnuQueryClient is gone, and invalidateApiOptions takes the client first. Co-Authored-By: Claude Opus 5.5 --- .changeset/peer-form-and-query.md | 5 + DEPENDENCIES.md | 6 +- README.md | 2 +- docs/choice-inputs/api-select.md | 4 +- docs/installation-and-setup.md | 2 +- docs/requirements.md | 1 + packages/onu/README.md | 2 +- packages/onu/package.json | 6 +- .../onu/src/components/choice/api-options.ts | 26 +- .../src/components/choice/api-select.test.tsx | 27 +- packages/onu/src/providers/onu-provider.tsx | 7 +- pnpm-lock.yaml | 340 ++++++++++-------- templates/laravel-inertia-react/package.json | 4 +- templates/nextjs/package.json | 4 +- web/package.json | 2 + 15 files changed, 261 insertions(+), 177 deletions(-) create mode 100644 .changeset/peer-form-and-query.md diff --git a/.changeset/peer-form-and-query.md b/.changeset/peer-form-and-query.md new file mode 100644 index 00000000..e44f976d --- /dev/null +++ b/.changeset/peer-form-and-query.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": minor +--- + +`react-hook-form` and `@tanstack/react-query` are now peer dependencies. `getOnuQueryClient` is gone: the fallback client belongs to each `OnuProvider`, and `invalidateApiOptions` takes the client first. diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index 5a236288..e6a6942e 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -22,15 +22,17 @@ Selection order: shadcn/ui component → reputable library (shadcn ecosystem fir | `@internationalized/date` | Hijri (Umm al-Qura) calendar maths, date/time fields | Apache-2.0 | Adobe | | `react-aria-components` | Tree, colour picker, date/time segment fields | Apache-2.0 | Adobe; fills Radix gaps (§4 step 3) | | `embla-carousel-react` | Carousel | MIT | shadcn `carousel` | -| `react-hook-form` | Form state | MIT | | | `@tanstack/react-table`, `@tanstack/react-virtual` | Tables, data grid, virtualised lists | MIT | | -| `@tanstack/react-query` | API select caching and de-duplication | MIT | R16.5 | | `@dnd-kit/react`, `@dnd-kit/helpers` | Drag and drop (kanban, sortable lists, row reordering) with touch sensors | MIT | The maintained successor of `@dnd-kit/core` | | `@dnd-kit/dom` | dnd-kit's `Accessibility` plugin, configured with translated announcements | MIT | Already installed by `@dnd-kit/react`; imported directly so drag announcements follow the locale | | `react-phone-number-input` | Phone input (libphonenumber) | MIT | Default region MV | | `@sindresorhus/slugify` | Slug generator | MIT | | | `uqr` | QR code encoding for `QrCode` (media entry); Onu renders the SVG | MIT | unjs; zero-dep, typed. Picked over `qrcode` (Node deps), `qrcode-generator` (stale) | +## Peers (core) + +`react-hook-form` (form state, MIT) and `@tanstack/react-query` (API select caching and de-duplication, R16.5, MIT) are required peers, alongside `react` and `react-dom`. Onu reads their React context, so the app and Onu must resolve the same copy. They stay required because the core entry imports them statically. + ## Optional peers (entry points) | Entry | Packages | Licence | diff --git a/README.md b/README.md index a5892f85..fae96379 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ See [`REQUIREMENTS.md`](REQUIREMENTS.md) for the full specification and [`docs/` ## Install ```bash -npm install @javaabu/onu +npm install @javaabu/onu react-hook-form @tanstack/react-query # optional font pack (Inter, JetBrains Mono, Noto Sans Thaana; Faruma and MV Faseyha come with tailwind.css and styles.css) npm install @fontsource-variable/inter @fontsource-variable/jetbrains-mono @fontsource-variable/noto-sans-thaana ``` diff --git a/docs/choice-inputs/api-select.md b/docs/choice-inputs/api-select.md index 860304b4..ce8f32e6 100644 --- a/docs/choice-inputs/api-select.md +++ b/docs/choice-inputs/api-select.md @@ -35,7 +35,7 @@ import { ApiSelect } from '@javaabu/onu'; - **Paging**: when the response says there is a next page (`meta.last_page`, `last_page`, `next_page_url`, `links.next`, `next_cursor`), the next page loads as the list scrolls to the end. - **Preloading**: options load when the list opens, or immediately with `preload`. - **States**: loading, empty ("No results for …"), and an error with a **Retry** button. All are announced in a polite live region. -- **Caching**: per endpoint + params + query, for `staleTime` (5 minutes). After creating a record elsewhere, call `invalidateApiOptions(url)` (or `queryClient.invalidateQueries({ queryKey: apiOptionsQueryKey(url) })` with your own client). Quick-create (`onCreate`) invalidates automatically. +- **Caching**: per endpoint + params + query, for `staleTime` (5 minutes). After creating a record elsewhere, call `invalidateApiOptions(client, url)` with the client from `useOnuQueryClient()` (or `queryClient.invalidateQueries({ queryKey: apiOptionsQueryKey(url) })` with your own). Quick-create (`onCreate`) invalidates automatically. - **No requests without a URL or fetcher** you supply; CSRF and auth headers are yours to add (`headers`, `credentials`). ## Props @@ -70,7 +70,7 @@ Values are strings (`id: 12` becomes `"12"`), so they round-trip through forms u ## Data layer -The helpers behind it are exported for Autocomplete and your own components: `fetchApiOptions`, `parseApiResponse`, `parseApiItem`, `mapApiItem`, `detectHasMore`, `buildUrl`, `requestJson`, `useOnuQueryClient`, `getOnuQueryClient`, `invalidateApiOptions`, `apiOptionsQueryKey`. +The helpers behind it are exported for Autocomplete and your own components: `fetchApiOptions`, `parseApiResponse`, `parseApiItem`, `mapApiItem`, `detectHasMore`, `buildUrl`, `requestJson`, `useOnuQueryClient`, `invalidateApiOptions`, `apiOptionsQueryKey`. ## Accessibility diff --git a/docs/installation-and-setup.md b/docs/installation-and-setup.md index 44cf601f..7ab316ee 100644 --- a/docs/installation-and-setup.md +++ b/docs/installation-and-setup.md @@ -8,7 +8,7 @@ sidebar_position: 1.2 Onu needs React 19. First install the NPM package ```bash -npm install @javaabu/onu --save +npm install @javaabu/onu react-hook-form @tanstack/react-query --save ``` # Styles diff --git a/docs/requirements.md b/docs/requirements.md index e8d9e90e..6307e2c3 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -7,6 +7,7 @@ This package requires: - [React](https://react.dev) 19 - [Tailwind CSS](https://tailwindcss.com) 4 +- [React Hook Form](https://react-hook-form.com) 7 and [TanStack Query](https://tanstack.com/query) 5, which share React context with your app Onu supports every browser Tailwind CSS 4 supports: Chrome and Edge 111+, Safari 16.4+, Firefox 128+, and their mobile versions. diff --git a/packages/onu/README.md b/packages/onu/README.md index 557f40ea..d5a26503 100644 --- a/packages/onu/README.md +++ b/packages/onu/README.md @@ -16,7 +16,7 @@ Every component and the sample pages: [onu.on-forge.com](https://onu.on-forge.co ## Install ```bash -npm install @javaabu/onu +npm install @javaabu/onu react-hook-form @tanstack/react-query # optional font pack (Inter, JetBrains Mono, Noto Sans Thaana; Faruma and MV Faseyha come with tailwind.css and styles.css) npm install @fontsource-variable/inter @fontsource-variable/jetbrains-mono @fontsource-variable/noto-sans-thaana ``` diff --git a/packages/onu/package.json b/packages/onu/package.json index d8581fbb..f4a38546 100644 --- a/packages/onu/package.json +++ b/packages/onu/package.json @@ -124,6 +124,7 @@ "@fullcalendar/timegrid": "^6.1.21", "@googlemaps/markerclusterer": "^2.6.2", "@iconify/react": "^6.0.2", + "@tanstack/react-query": "^5.104.1", "@tiptap/core": "^3.31.4", "@tiptap/extension-code-block-lowlight": "^3.31.4", "@tiptap/extension-drag-handle-react": "^3.31.4", @@ -145,6 +146,7 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-easy-crop": "^6.2.4", + "react-hook-form": "^7.89.0", "recharts": "^3.10.1", "shiki": "^4.5.0", "signature_pad": "^5.1.4", @@ -180,6 +182,7 @@ "@iconify/react": "^6.0.2", "@size-limit/preset-small-lib": "^14.1.0", "@tailwindcss/vite": "^4.3.3", + "@tanstack/react-query": "^5.104.1", "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.3", "@testing-library/user-event": "^14.6.7", @@ -212,6 +215,7 @@ "react": "^19.3.0", "react-dom": "^19.3.0", "react-easy-crop": "^6.2.4", + "react-hook-form": "^7.89.0", "recharts": "^3.10.1", "shadcn": "^4.21.4", "shiki": "^4.5.0", @@ -234,7 +238,6 @@ "@dnd-kit/react": "^0.5.0", "@internationalized/date": "^3.12.4", "@sindresorhus/slugify": "^3.0.1", - "@tanstack/react-query": "^5.104.1", "@tanstack/react-table": "^9.2.6", "@tanstack/react-virtual": "^3.14.13", "class-variance-authority": "^0.7.1", @@ -247,7 +250,6 @@ "radix-ui": "^1.7.0", "react-aria-components": "^1.21.1", "react-day-picker": "^10.0.2", - "react-hook-form": "^7.89.0", "react-phone-number-input": "^3.4.18", "sonner": "^2.0.8", "tailwind-merge": "^3.7.0", diff --git a/packages/onu/src/components/choice/api-options.ts b/packages/onu/src/components/choice/api-options.ts index e7fe0bcf..7225b919 100644 --- a/packages/onu/src/components/choice/api-options.ts +++ b/packages/onu/src/components/choice/api-options.ts @@ -3,6 +3,7 @@ import * as React from "react" import { QueryClient, QueryClientContext } from "@tanstack/react-query" +import { useOnu } from "../../providers/onu-provider" import type { ChoiceOption } from "./options" /* @@ -245,27 +246,28 @@ export function apiOptionsQueryKey(url?: string) { return url ? (["onu-api-options", url] as const) : (["onu-api-options"] as const) } -let fallbackClient: QueryClient | undefined +function fallbackClient(store: { client?: QueryClient }) { + store.client ??= new QueryClient({ defaultOptions: { queries: { retry: false, refetchOnWindowFocus: false } } }) + return store.client +} /** - * Onu's own QueryClient, used when the app has no `QueryClientProvider`, so API - * selects work out of the box. Apps with a provider share their client instead. + * The app's QueryClient if there is a `QueryClientProvider`, else one owned by the + * nearest `OnuProvider` (or this component, without one), so API selects work out of + * the box and a server never shares a cache between requests. */ -export function getOnuQueryClient() { - fallbackClient ??= new QueryClient({ defaultOptions: { queries: { retry: false, refetchOnWindowFocus: false } } }) - return fallbackClient -} - -/** The app's QueryClient if there is a provider, else Onu's fallback client. */ export function useOnuQueryClient(explicit?: QueryClient) { const context = React.useContext(QueryClientContext) - return explicit ?? context ?? getOnuQueryClient() + const [own] = React.useState<{ client?: QueryClient }>({}) + const store = useOnu().queryClientStore ?? own + return explicit ?? context ?? fallbackClient(store) } /** * Clears cached API options (e.g. after creating a record elsewhere) so open - * selects refetch. Pass a URL to limit it to one endpoint. + * selects refetch. Pass a URL to limit it to one endpoint; `useOnuQueryClient()` + * returns the client `ApiSelect` uses. */ -export function invalidateApiOptions(url?: string, client: QueryClient = getOnuQueryClient()) { +export function invalidateApiOptions(client: QueryClient, url?: string) { return client.invalidateQueries({ queryKey: apiOptionsQueryKey(url) }) } diff --git a/packages/onu/src/components/choice/api-select.test.tsx b/packages/onu/src/components/choice/api-select.test.tsx index 2c41ac59..510ee50d 100644 --- a/packages/onu/src/components/choice/api-select.test.tsx +++ b/packages/onu/src/components/choice/api-select.test.tsx @@ -9,8 +9,8 @@ import { FormField } from "../form/form-field" import { buildUrl, detectHasMore, - getOnuQueryClient, invalidateApiOptions, + useOnuQueryClient, parseApiItem, parseApiResponse, valueUrlFor, @@ -335,13 +335,34 @@ describe("ApiSelect", () => { expect(fetchMock).toHaveBeenCalledTimes(1) }) + it("gives each OnuProvider its own fallback client, and invalidates it", async () => { + const clients: QueryClient[] = [] + function Capture({ children }: { children?: React.ReactNode }) { + const client = useOnuQueryClient() + React.useEffect(() => void clients.push(client), [client]) + return children + } + renderWithOnu() + mockFetch(() => ({ data: islands })) + renderWithOnu( + + + , + ) + expect(clients).toHaveLength(2) + expect(clients[0]).not.toBe(clients[1]) + + await open() + expect(await screen.findByRole("option", { name: "Malé" })).toBeInTheDocument() + expect(clients[1]?.getQueryCache().getAll().length).toBeGreaterThan(0) + await invalidateApiOptions(clients[1]!, "/api/fallback-own") + }) + it("works without a QueryClientProvider (fallback client) and with one", async () => { mockFetch(() => ({ data: islands })) const { unmount } = renderWithOnu() await open() expect(await screen.findByRole("option", { name: "Malé" })).toBeInTheDocument() - expect(getOnuQueryClient().getQueryCache().getAll().length).toBeGreaterThan(0) - await invalidateApiOptions("/api/fallback") unmount() const appClient = newClient() diff --git a/packages/onu/src/providers/onu-provider.tsx b/packages/onu/src/providers/onu-provider.tsx index 4fec5580..3dd521e5 100644 --- a/packages/onu/src/providers/onu-provider.tsx +++ b/packages/onu/src/providers/onu-provider.tsx @@ -1,5 +1,6 @@ "use client" +import type { QueryClient } from "@tanstack/react-query" import * as React from "react" import { Direction } from "radix-ui" @@ -34,6 +35,8 @@ export interface OnuContextValue { labelLayout: LabelLayout linkComponent: LinkComponent maps: MapsConfig + /** Holds this provider's fallback QueryClient, created on first use by `ApiSelect`. */ + queryClientStore?: { client?: QueryClient } } const fallbackContext: OnuContextValue = { @@ -146,6 +149,7 @@ export function OnuProvider({ [base, messages], ) const dir = dirProp ?? locale.dir + const [queryClientStore] = React.useState<{ client?: QueryClient }>({}) const value = React.useMemo( () => ({ @@ -156,8 +160,9 @@ export function OnuProvider({ labelLayout, linkComponent, maps: maps ?? {}, + queryClientStore, }), - [locale, dir, density, labelLayout, linkComponent, maps], + [locale, dir, density, labelLayout, linkComponent, maps, queryClientStore], ) React.useEffect(() => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 064450f2..c95f5803 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,19 +16,19 @@ importers: version: 3.0.3 '@eslint/js': specifier: ^10.0.1 - version: 10.0.1(eslint@10.12.0(jiti@2.7.0)) + version: 10.0.1(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2)) '@playwright/test': specifier: ^1.63.0 version: 1.63.0 eslint: specifier: ^10.12.0 - version: 10.12.0(jiti@2.7.0) + version: 10.12.0(jiti@2.7.0)(supports-color@10.2.2) eslint-plugin-jsx-a11y: specifier: ^6.10.2 - version: 6.10.2(eslint@10.12.0(jiti@2.7.0)) + version: 6.10.2(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2)) eslint-plugin-react-hooks: specifier: ^7.1.1 - version: 7.1.1(eslint@10.12.0(jiti@2.7.0)) + version: 7.1.1(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2) globals: specifier: ^17.13.0 version: 17.13.0 @@ -43,7 +43,7 @@ importers: version: 6.0.3 typescript-eslint: specifier: ^8.71.1 - version: 8.71.1(eslint@10.12.0(jiti@2.7.0))(typescript@6.0.3) + version: 8.71.1(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) figma: devDependencies: @@ -86,9 +86,6 @@ importers: '@sindresorhus/slugify': specifier: ^3.0.1 version: 3.0.1 - '@tanstack/react-query': - specifier: ^5.104.1 - version: 5.104.1(react@19.3.0) '@tanstack/react-table': specifier: ^9.2.6 version: 9.2.6(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -125,9 +122,6 @@ importers: react-day-picker: specifier: ^10.0.2 version: 10.0.2(@types/react@19.3.0)(react@19.3.0) - react-hook-form: - specifier: ^7.89.0 - version: 7.89.0(@types/react@19.3.0)(react@19.3.0) react-phone-number-input: specifier: ^3.4.18 version: 3.4.18(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -222,6 +216,9 @@ importers: '@tailwindcss/vite': specifier: ^4.3.3 version: 4.3.3(vite@8.3.4(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + '@tanstack/react-query': + specifier: ^5.104.1 + version: 5.104.1(react@19.3.0) '@testing-library/jest-dom': specifier: ^7.0.1 version: 7.0.1(@testing-library/dom@10.4.2)(vitest@5.0.3) @@ -318,12 +315,15 @@ importers: react-easy-crop: specifier: ^6.2.4 version: 6.2.4(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + react-hook-form: + specifier: ^7.89.0 + version: 7.89.0(@types/react@19.3.0)(react@19.3.0) recharts: specifier: ^3.10.1 - version: 3.10.1(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react-is@17.0.2)(react@19.3.0)(redux@5.0.1) + version: 3.10.1(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react-is@16.13.1)(react@19.3.0)(redux@5.0.1) shadcn: specifier: ^4.21.4 - version: 4.21.4 + version: 4.21.4(supports-color@10.2.2) shiki: specifier: ^4.5.0 version: 4.5.0 @@ -365,7 +365,7 @@ importers: dependencies: '@modelcontextprotocol/sdk': specifier: ^1.32.1 - version: 1.32.1(zod@4.6.7) + version: 1.32.1(supports-color@10.2.2)(zod@4.6.7) zod: specifier: ^4.6.7 version: 4.6.7 @@ -400,12 +400,18 @@ importers: '@javaabu/onu': specifier: workspace:* version: link:../../packages/onu + '@tanstack/react-query': + specifier: ^5.104.1 + version: 5.104.1(react@19.3.0) react: specifier: ^19.3.0 version: 19.3.0 react-dom: specifier: ^19.3.0 version: 19.3.0(react@19.3.0) + react-hook-form: + specifier: ^7.89.0 + version: 7.89.0(@types/react@19.3.0)(react@19.3.0) devDependencies: '@tailwindcss/vite': specifier: ^4.3.3 @@ -456,15 +462,21 @@ importers: '@javaabu/onu': specifier: workspace:* version: link:../../packages/onu + '@tanstack/react-query': + specifier: ^5.104.1 + version: 5.104.1(react@19.3.0) next: specifier: ^16.4.0 - version: 16.4.0(@babel/core@7.29.7)(@playwright/test@1.63.0)(@types/node@26.6.4)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + version: 16.4.0(@babel/core@7.29.7(supports-color@10.2.2))(@playwright/test@1.63.0)(@types/node@26.6.4)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) react: specifier: ^19.3.0 version: 19.3.0 react-dom: specifier: ^19.3.0 version: 19.3.0(react@19.3.0) + react-hook-form: + specifier: ^7.89.0 + version: 7.89.0(@types/react@19.3.0)(react@19.3.0) devDependencies: '@tailwindcss/postcss': specifier: ^4.3.3 @@ -562,6 +574,9 @@ importers: '@javaabu/onu': specifier: workspace:* version: link:../packages/onu + '@tanstack/react-query': + specifier: ^5.104.1 + version: 5.104.1(react@19.3.0) '@tiptap/core': specifier: ^3.31.4 version: 3.31.4(@tiptap/pm@3.31.4) @@ -625,6 +640,9 @@ importers: react-easy-crop: specifier: ^6.2.4 version: 6.2.4(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + react-hook-form: + specifier: ^7.89.0 + version: 7.89.0(@types/react@19.3.0)(react@19.3.0) react-router: specifier: ^8.4.0 version: 8.4.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -6759,20 +6777,20 @@ snapshots: '@babel/compat-data@7.29.7': {} - '@babel/core@7.29.7': + '@babel/core@7.29.7(supports-color@10.2.2)': dependencies: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.8 '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2) '@babel/helpers': 7.29.10 '@babel/parser': 7.29.9 '@babel/template': 7.29.7 - '@babel/traverse': 7.29.10 + '@babel/traverse': 7.29.10(supports-color@10.2.2) '@babel/types': 7.29.8 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -6799,41 +6817,41 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)': + '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@10.2.2) '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7(supports-color@10.2.2) '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - '@babel/traverse': 7.29.10 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2) + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@10.2.2) + '@babel/traverse': 7.29.10(supports-color@10.2.2) semver: 6.3.1 transitivePeerDependencies: - supports-color '@babel/helper-globals@7.29.7': {} - '@babel/helper-member-expression-to-functions@7.29.7': + '@babel/helper-member-expression-to-functions@7.29.7(supports-color@10.2.2)': dependencies: - '@babel/traverse': 7.29.10 + '@babel/traverse': 7.29.10(supports-color@10.2.2) '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color - '@babel/helper-module-imports@7.29.7': + '@babel/helper-module-imports@7.29.7(supports-color@10.2.2)': dependencies: - '@babel/traverse': 7.29.10 + '@babel/traverse': 7.29.10(supports-color@10.2.2) '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-imports': 7.29.7 + '@babel/core': 7.29.7(supports-color@10.2.2) + '@babel/helper-module-imports': 7.29.7(supports-color@10.2.2) '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.10 + '@babel/traverse': 7.29.10(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -6843,18 +6861,18 @@ snapshots: '@babel/helper-plugin-utils@7.29.7': {} - '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)': + '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/core': 7.29.7(supports-color@10.2.2) + '@babel/helper-member-expression-to-functions': 7.29.7(supports-color@10.2.2) '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/traverse': 7.29.10 + '@babel/traverse': 7.29.10(supports-color@10.2.2) transitivePeerDependencies: - supports-color - '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + '@babel/helper-skip-transparent-expression-wrappers@7.29.7(supports-color@10.2.2)': dependencies: - '@babel/traverse': 7.29.10 + '@babel/traverse': 7.29.10(supports-color@10.2.2) '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color @@ -6874,43 +6892,43 @@ snapshots: dependencies: '@babel/types': 7.29.8 - '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@10.2.2) '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@10.2.2) '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/core': 7.29.7(supports-color@10.2.2) + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2) '@babel/helper-plugin-utils': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-typescript@7.29.9(@babel/core@7.29.7)': + '@babel/plugin-transform-typescript@7.29.9(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@10.2.2) '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2) '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@10.2.2) + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2)) transitivePeerDependencies: - supports-color - '@babel/preset-typescript@7.29.7(@babel/core@7.29.7)': + '@babel/preset-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@10.2.2) '@babel/helper-plugin-utils': 7.29.7 '@babel/helper-validator-option': 7.29.7 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-typescript': 7.29.9(@babel/core@7.29.7) + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2)) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2) + '@babel/plugin-transform-typescript': 7.29.9(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -6922,7 +6940,7 @@ snapshots: '@babel/parser': 7.29.9 '@babel/types': 7.29.8 - '@babel/traverse@7.29.10': + '@babel/traverse@7.29.10(supports-color@10.2.2)': dependencies: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.8 @@ -6930,7 +6948,7 @@ snapshots: '@babel/parser': 7.29.9 '@babel/template': 7.29.7 '@babel/types': 7.29.8 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -7251,17 +7269,17 @@ snapshots: tslib: 2.8.1 optional: true - '@eslint-community/eslint-utils@4.10.1(eslint@10.12.0(jiti@2.7.0))': + '@eslint-community/eslint-utils@4.10.1(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2))': dependencies: - eslint: 10.12.0(jiti@2.7.0) + eslint: 10.12.0(jiti@2.7.0)(supports-color@10.2.2) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.23.5': + '@eslint/config-array@0.23.5(supports-color@10.2.2)': dependencies: '@eslint/object-schema': 3.0.5 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) minimatch: 10.2.6 transitivePeerDependencies: - supports-color @@ -7274,9 +7292,9 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/js@10.0.1(eslint@10.12.0(jiti@2.7.0))': + '@eslint/js@10.0.1(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2))': optionalDependencies: - eslint: 10.12.0(jiti@2.7.0) + eslint: 10.12.0(jiti@2.7.0)(supports-color@10.2.2) '@eslint/object-schema@3.0.5': {} @@ -7703,18 +7721,18 @@ snapshots: dependencies: '@math.gl/core': 4.1.0 - '@modelcontextprotocol/sdk@1.32.1(zod@3.25.76)': + '@modelcontextprotocol/sdk@1.32.1(supports-color@10.2.2)(zod@3.25.76)': dependencies: '@hono/node-server': 2.1.3(hono@4.13.13) ajv: 8.20.0 - ajv-formats: 3.0.1(ajv@8.20.0) + ajv-formats: 3.0.1 content-type: 1.0.5 cors: 2.8.6 cross-spawn: 7.0.6 eventsource: 3.0.7 eventsource-parser: 3.1.1 - express: 5.2.1 - express-rate-limit: 8.7.1(express@5.2.1) + express: 5.2.1(supports-color@10.2.2) + express-rate-limit: 8.7.1(express@5.2.1(supports-color@10.2.2))(supports-color@10.2.2) hono: 4.13.13 jose: 6.2.12 json-schema-typed: 8.0.2 @@ -7725,18 +7743,18 @@ snapshots: transitivePeerDependencies: - supports-color - '@modelcontextprotocol/sdk@1.32.1(zod@4.6.7)': + '@modelcontextprotocol/sdk@1.32.1(supports-color@10.2.2)(zod@4.6.7)': dependencies: '@hono/node-server': 2.1.3(hono@4.13.13) ajv: 8.20.0 - ajv-formats: 3.0.1(ajv@8.20.0) + ajv-formats: 3.0.1 content-type: 1.0.5 cors: 2.8.6 cross-spawn: 7.0.6 eventsource: 3.0.7 eventsource-parser: 3.1.1 - express: 5.2.1 - express-rate-limit: 8.7.1(express@5.2.1) + express: 5.2.1(supports-color@10.2.2) + express-rate-limit: 8.7.1(express@5.2.1(supports-color@10.2.2))(supports-color@10.2.2) hono: 4.13.13 jose: 6.2.12 json-schema-typed: 8.0.2 @@ -8621,12 +8639,12 @@ snapshots: '@sec-ant/readable-stream@0.4.1': {} - '@shadcn/registry@0.1.3': + '@shadcn/registry@0.1.3(supports-color@10.2.2)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@10.2.2) '@babel/parser': 7.29.9 - '@babel/plugin-transform-typescript': 7.29.9(@babel/core@7.29.7) - '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.29.9(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2) + '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2) '@dotenvx/dotenvx': 1.75.1 cn: 0.2.6 code-block-writer: 13.0.3 @@ -10351,15 +10369,15 @@ snapshots: '@types/validate-npm-package-name@4.0.2': {} - '@typescript-eslint/eslint-plugin@8.71.1(@typescript-eslint/parser@8.71.1(eslint@10.12.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.12.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.71.1(@typescript-eslint/parser@8.71.1(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.71.1(eslint@10.12.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.71.1(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.71.1 - '@typescript-eslint/type-utils': 8.71.1(eslint@10.12.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/utils': 8.71.1(eslint@10.12.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/type-utils': 8.71.1(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.1(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.71.1 - eslint: 10.12.0(jiti@2.7.0) + eslint: 10.12.0(jiti@2.7.0)(supports-color@10.2.2) ignore: 7.0.12 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -10367,23 +10385,23 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.71.1(eslint@10.12.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/parser@8.71.1(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)': dependencies: '@typescript-eslint/scope-manager': 8.71.1 '@typescript-eslint/types': 8.71.1 - '@typescript-eslint/typescript-estree': 8.71.1(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.71.1(supports-color@10.2.2)(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.71.1 - debug: 4.4.3 - eslint: 10.12.0(jiti@2.7.0) + debug: 4.4.3(supports-color@10.2.2) + eslint: 10.12.0(jiti@2.7.0)(supports-color@10.2.2) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.71.1(typescript@6.0.3)': + '@typescript-eslint/project-service@8.71.1(supports-color@10.2.2)(typescript@6.0.3)': dependencies: '@typescript-eslint/tsconfig-utils': 8.71.1(typescript@6.0.3) '@typescript-eslint/types': 8.71.1 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -10397,13 +10415,13 @@ snapshots: dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.71.1(eslint@10.12.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.71.1(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)': dependencies: '@typescript-eslint/types': 8.71.1 - '@typescript-eslint/typescript-estree': 8.71.1(typescript@6.0.3) - '@typescript-eslint/utils': 8.71.1(eslint@10.12.0(jiti@2.7.0))(typescript@6.0.3) - debug: 4.4.3 - eslint: 10.12.0(jiti@2.7.0) + '@typescript-eslint/typescript-estree': 8.71.1(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.1(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + debug: 4.4.3(supports-color@10.2.2) + eslint: 10.12.0(jiti@2.7.0)(supports-color@10.2.2) ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: @@ -10411,13 +10429,13 @@ snapshots: '@typescript-eslint/types@8.71.1': {} - '@typescript-eslint/typescript-estree@8.71.1(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.71.1(supports-color@10.2.2)(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.71.1(typescript@6.0.3) + '@typescript-eslint/project-service': 8.71.1(supports-color@10.2.2)(typescript@6.0.3) '@typescript-eslint/tsconfig-utils': 8.71.1(typescript@6.0.3) '@typescript-eslint/types': 8.71.1 '@typescript-eslint/visitor-keys': 8.71.1 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) minimatch: 10.2.6 semver: 7.8.5 tinyglobby: 0.2.17 @@ -10426,13 +10444,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.71.1(eslint@10.12.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/utils@8.71.1(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2)) '@typescript-eslint/scope-manager': 8.71.1 '@typescript-eslint/types': 8.71.1 - '@typescript-eslint/typescript-estree': 8.71.1(typescript@6.0.3) - eslint: 10.12.0(jiti@2.7.0) + '@typescript-eslint/typescript-estree': 8.71.1(supports-color@10.2.2)(typescript@6.0.3) + eslint: 10.12.0(jiti@2.7.0)(supports-color@10.2.2) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -10559,12 +10577,12 @@ snapshots: acorn@8.19.0: {} - ajv-formats@2.1.1(ajv@8.20.0): - optionalDependencies: + ajv-formats@2.1.1: + dependencies: ajv: 8.20.0 - ajv-formats@3.0.1(ajv@8.20.0): - optionalDependencies: + ajv-formats@3.0.1: + dependencies: ajv: 8.20.0 ajv@6.15.0: @@ -10707,11 +10725,11 @@ snapshots: bignumber.js@9.3.1: {} - body-parser@2.3.0: + body-parser@2.3.0(supports-color@10.2.2): dependencies: bytes: 3.1.2 content-type: 2.1.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) http-errors: 2.0.1 iconv-lite: 0.7.3 on-finished: 2.4.1 @@ -10880,7 +10898,7 @@ snapshots: conf@10.2.0: dependencies: ajv: 8.20.0 - ajv-formats: 2.1.1(ajv@8.20.0) + ajv-formats: 2.1.1 atomically: 1.7.0 debounce-fn: 4.0.0 dot-prop: 6.0.1 @@ -11014,9 +11032,11 @@ snapshots: dependencies: mimic-fn: 3.1.0 - debug@4.4.3: + debug@4.4.3(supports-color@10.2.2): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 10.2.2 decimal.js-light@2.5.1: {} @@ -11229,7 +11249,7 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-plugin-jsx-a11y@6.10.2(eslint@10.12.0(jiti@2.7.0)): + eslint-plugin-jsx-a11y@6.10.2(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2)): dependencies: aria-query: 5.3.2 array-includes: 3.2.0 @@ -11239,7 +11259,7 @@ snapshots: axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 10.12.0(jiti@2.7.0) + eslint: 10.12.0(jiti@2.7.0)(supports-color@10.2.2) hasown: 2.0.4 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 @@ -11248,11 +11268,11 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-react-hooks@7.1.1(eslint@10.12.0(jiti@2.7.0)): + eslint-plugin-react-hooks@7.1.1(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2): dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@10.2.2) '@babel/parser': 7.29.9 - eslint: 10.12.0(jiti@2.7.0) + eslint: 10.12.0(jiti@2.7.0)(supports-color@10.2.2) hermes-parser: 0.25.1 zod: 4.6.5 zod-validation-error: 4.0.2(zod@4.6.5) @@ -11270,11 +11290,11 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.12.0(jiti@2.7.0): + eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2)) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.23.5 + '@eslint/config-array': 0.23.5(supports-color@10.2.2) '@eslint/config-helpers': 0.7.0 '@eslint/core': 1.2.1 '@eslint/plugin-kit': 0.7.3 @@ -11284,7 +11304,7 @@ snapshots: '@types/estree': 1.0.9 ajv: 6.15.0 cross-spawn: 7.0.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) escape-string-regexp: 4.0.0 eslint-scope: 9.1.2 eslint-visitor-keys: 5.0.1 @@ -11370,28 +11390,28 @@ snapshots: expect-type@1.4.0: {} - express-rate-limit@8.7.1(express@5.2.1): + express-rate-limit@8.7.1(express@5.2.1(supports-color@10.2.2))(supports-color@10.2.2): dependencies: - debug: 4.4.3 - express: 5.2.1 + debug: 4.4.3(supports-color@10.2.2) + express: 5.2.1(supports-color@10.2.2) ip-address: 10.7.3 transitivePeerDependencies: - supports-color - express@5.2.1: + express@5.2.1(supports-color@10.2.2): dependencies: accepts: 2.0.0 - body-parser: 2.3.0 + body-parser: 2.3.0(supports-color@10.2.2) content-disposition: 1.1.0 content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 2.1.1 + finalhandler: 2.1.1(supports-color@10.2.2) fresh: 2.0.0 http-errors: 2.0.1 merge-descriptors: 2.0.0 @@ -11402,9 +11422,9 @@ snapshots: proxy-addr: 2.0.8 qs: 6.16.0 range-parser: 1.3.0 - router: 2.2.0 - send: 1.2.1 - serve-static: 2.2.1 + router: 2.2.0(supports-color@10.2.2) + send: 1.2.1(supports-color@10.2.2) + serve-static: 2.2.1(supports-color@10.2.2) statuses: 2.0.2 type-is: 2.1.0 vary: 1.1.2 @@ -11463,9 +11483,9 @@ snapshots: dependencies: to-regex-range: 5.0.1 - finalhandler@2.1.1: + finalhandler@2.1.1(supports-color@10.2.2): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 @@ -12281,7 +12301,7 @@ snapshots: dependencies: content-type: 2.1.0 - next@16.4.0(@babel/core@7.29.7)(@playwright/test@1.63.0)(@types/node@26.6.4)(react-dom@19.3.0(react@19.3.0))(react@19.3.0): + next@16.4.0(@babel/core@7.29.7(supports-color@10.2.2))(@playwright/test@1.63.0)(@types/node@26.6.4)(react-dom@19.3.0(react@19.3.0))(react@19.3.0): dependencies: '@next/env': 16.4.0 '@swc/helpers': 0.5.23 @@ -12290,7 +12310,7 @@ snapshots: postcss: 8.5.23 react: 19.3.0 react-dom: 19.3.0(react@19.3.0) - styled-jsx: 5.1.6(@babel/core@7.29.7)(react@19.3.0) + styled-jsx: 5.1.6(@babel/core@7.29.7(supports-color@10.2.2))(react@19.3.0) optionalDependencies: '@next/swc-darwin-arm64': 16.4.0 '@next/swc-darwin-x64': 16.4.0 @@ -12865,6 +12885,26 @@ snapshots: tiny-invariant: 1.3.3 tslib: 2.8.1 + recharts@3.10.1(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react-is@16.13.1)(react@19.3.0)(redux@5.0.1): + dependencies: + '@reduxjs/toolkit': 2.13.0(react-redux@9.3.0(@types/react@19.3.0)(react@19.3.0)(redux@5.0.1))(react@19.3.0) + clsx: 2.1.1 + decimal.js-light: 2.5.1 + es-toolkit: 1.52.0 + eventemitter3: 5.0.4 + immer: 11.1.21 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + react-is: 16.13.1 + react-redux: 9.3.0(@types/react@19.3.0)(react@19.3.0)(redux@5.0.1) + reselect: 5.2.0 + tiny-invariant: 1.3.3 + use-sync-external-store: 1.7.0(react@19.3.0) + victory-vendor: 37.3.6 + transitivePeerDependencies: + - '@types/react' + - redux + recharts@3.10.1(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react-is@17.0.2)(react@19.3.0)(redux@5.0.1): dependencies: '@reduxjs/toolkit': 2.13.0(react-redux@9.3.0(@types/react@19.3.0)(react@19.3.0)(redux@5.0.1))(react@19.3.0) @@ -12970,9 +13010,9 @@ snapshots: rope-sequence@1.3.4: {} - router@2.2.0: + router@2.2.0(supports-color@10.2.2): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 @@ -13024,9 +13064,9 @@ snapshots: semver@7.8.5: {} - send@1.2.1: + send@1.2.1(supports-color@10.2.2): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -13040,12 +13080,12 @@ snapshots: transitivePeerDependencies: - supports-color - serve-static@2.2.1: + serve-static@2.2.1(supports-color@10.2.2): dependencies: encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 1.2.1 + send: 1.2.1(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -13073,15 +13113,15 @@ snapshots: setprototypeof@1.2.0: {} - shadcn@4.21.4: + shadcn@4.21.4(supports-color@10.2.2): dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@10.2.2) '@babel/parser': 7.29.9 - '@babel/plugin-transform-typescript': 7.29.9(@babel/core@7.29.7) - '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.29.9(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2) + '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2) '@dotenvx/dotenvx': 1.75.1 - '@modelcontextprotocol/sdk': 1.32.1(zod@3.25.76) - '@shadcn/registry': 0.1.3 + '@modelcontextprotocol/sdk': 1.32.1(supports-color@10.2.2)(zod@3.25.76) + '@shadcn/registry': 0.1.3(supports-color@10.2.2) '@types/validate-npm-package-name': 4.0.2 browserslist: 4.29.3 cn: 0.2.6 @@ -13324,12 +13364,12 @@ snapshots: dependencies: min-indent: 1.0.1 - styled-jsx@5.1.6(@babel/core@7.29.7)(react@19.3.0): + styled-jsx@5.1.6(@babel/core@7.29.7(supports-color@10.2.2))(react@19.3.0): dependencies: client-only: 0.0.1 react: 19.3.0 optionalDependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@10.2.2) supercluster@8.0.1: dependencies: @@ -13476,13 +13516,13 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.71.1(eslint@10.12.0(jiti@2.7.0))(typescript@6.0.3): + typescript-eslint@8.71.1(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.71.1(@typescript-eslint/parser@8.71.1(eslint@10.12.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.12.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/parser': 8.71.1(eslint@10.12.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.71.1(typescript@6.0.3) - '@typescript-eslint/utils': 8.71.1(eslint@10.12.0(jiti@2.7.0))(typescript@6.0.3) - eslint: 10.12.0(jiti@2.7.0) + '@typescript-eslint/eslint-plugin': 8.71.1(@typescript-eslint/parser@8.71.1(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/parser': 8.71.1(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.71.1(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.1(eslint@10.12.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + eslint: 10.12.0(jiti@2.7.0)(supports-color@10.2.2) typescript: 6.0.3 transitivePeerDependencies: - supports-color diff --git a/templates/laravel-inertia-react/package.json b/templates/laravel-inertia-react/package.json index b95b515f..df77f687 100644 --- a/templates/laravel-inertia-react/package.json +++ b/templates/laravel-inertia-react/package.json @@ -15,8 +15,10 @@ "@inertiajs/core": "^3.9.1", "@inertiajs/react": "^3.9.1", "@javaabu/onu": "workspace:*", + "@tanstack/react-query": "^5.104.1", "react": "^19.3.0", - "react-dom": "^19.3.0" + "react-dom": "^19.3.0", + "react-hook-form": "^7.89.0" }, "devDependencies": { "@tailwindcss/vite": "^4.3.3", diff --git a/templates/nextjs/package.json b/templates/nextjs/package.json index 492b1ab7..cdc2b4b2 100644 --- a/templates/nextjs/package.json +++ b/templates/nextjs/package.json @@ -15,9 +15,11 @@ "@fontsource-variable/jetbrains-mono": "^5.3.0", "@fontsource-variable/noto-sans-thaana": "^5.3.0", "@javaabu/onu": "workspace:*", + "@tanstack/react-query": "^5.104.1", "next": "^16.4.0", "react": "^19.3.0", - "react-dom": "^19.3.0" + "react-dom": "^19.3.0", + "react-hook-form": "^7.89.0" }, "devDependencies": { "@tailwindcss/postcss": "^4.3.3", diff --git a/web/package.json b/web/package.json index 99eedded..8d4f2261 100644 --- a/web/package.json +++ b/web/package.json @@ -35,6 +35,7 @@ "@iconify-json/tabler": "^1.2.41", "@iconify/react": "^6.0.2", "@javaabu/onu": "workspace:*", + "@tanstack/react-query": "^5.104.1", "@tiptap/core": "^3.31.4", "@tiptap/extension-code-block-lowlight": "^3.31.4", "@tiptap/extension-drag-handle-react": "^3.31.4", @@ -56,6 +57,7 @@ "react": "^19.3.0", "react-dom": "^19.3.0", "react-easy-crop": "^6.2.4", + "react-hook-form": "^7.89.0", "react-router": "^8.4.0", "recharts": "^3.10.1", "shiki": "^4.5.0", From f78af776a2c197c1f35c9da0d8b07e205891d8a3 Mon Sep 17 00:00:00 2001 From: Naffah Abdulla Rasheed Date: Sun, 1 Nov 2026 10:47:19 +0500 Subject: [PATCH 06/47] A5: Remove the unused base-ui Combobox primitive Nothing in src, web or templates used it; every Onu select is built on cmdk. The primitives budget drops from 165 kB to 130 kB (124.25 kB measured). Co-Authored-By: Claude Opus 5.5 --- .changeset/remove-base-ui-combobox.md | 5 + DEPENDENCIES.md | 1 - packages/onu/package.json | 3 +- packages/onu/src/styles/base.css | 1 - packages/onu/src/ui/combobox.tsx | 305 -------------------------- packages/onu/src/ui/index.ts | 1 - pnpm-lock.yaml | 55 ----- 7 files changed, 6 insertions(+), 365 deletions(-) create mode 100644 .changeset/remove-base-ui-combobox.md delete mode 100644 packages/onu/src/ui/combobox.tsx diff --git a/.changeset/remove-base-ui-combobox.md b/.changeset/remove-base-ui-combobox.md new file mode 100644 index 00000000..d3b40b2b --- /dev/null +++ b/.changeset/remove-base-ui-combobox.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": minor +--- + +Removed the unused `Combobox` primitive from `@javaabu/onu/primitives`, and with it the `@base-ui/react` dependency. diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index e6a6942e..daad31e1 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -9,7 +9,6 @@ Selection order: shadcn/ui component → reputable library (shadcn ecosystem fir | Package | Purpose | Licence | Notes | | -------------------------------------------------- | -------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------ | | `radix-ui` | Accessible primitives behind the shadcn components | MIT | shadcn default base | -| `@base-ui/react` | Combobox primitive (shadcn's combobox) | MIT | From the Radix / MUI team | | `class-variance-authority` | Variants (`buttonVariants`, …) | Apache-2.0 | Stable and feature-complete; last release 2024 (exception to the 12-month rule, used by shadcn) | | `clsx`, `tailwind-merge` | `cn()` class merging so consumer classes win | MIT | | | `tw-animate-css` | Enter/exit animations | MIT | shadcn default | diff --git a/packages/onu/package.json b/packages/onu/package.json index f4a38546..8c0f663f 100644 --- a/packages/onu/package.json +++ b/packages/onu/package.json @@ -232,7 +232,6 @@ "yet-another-react-lightbox": "^3.32.2" }, "dependencies": { - "@base-ui/react": "^1.8.0", "@dnd-kit/dom": "^0.5.0", "@dnd-kit/helpers": "^0.5.0", "@dnd-kit/react": "^0.5.0", @@ -453,7 +452,7 @@ "name": "primitives", "path": "dist/ui/index.js", "import": "*", - "limit": "165 kB" + "limit": "130 kB" }, { "name": "thaana", diff --git a/packages/onu/src/styles/base.css b/packages/onu/src/styles/base.css index a4fb3e81..29fcc661 100644 --- a/packages/onu/src/styles/base.css +++ b/packages/onu/src/styles/base.css @@ -54,7 +54,6 @@ body { [data-slot="tabs-trigger"], [data-slot="input"], [data-slot="input-group"], - [data-slot="combobox-trigger"], [data-slot="pagination-link"], [data-slot="dropdown-menu-item"], [data-slot="command-item"], diff --git a/packages/onu/src/ui/combobox.tsx b/packages/onu/src/ui/combobox.tsx deleted file mode 100644 index 2408b90a..00000000 --- a/packages/onu/src/ui/combobox.tsx +++ /dev/null @@ -1,305 +0,0 @@ -"use client" - -// Vendored from shadcn/ui (radix base, nova style, RTL) by scripts/sync-shadcn.mjs. -// Onu changes are marked with "onu:" comments; keep them when re-syncing. -// onu: Ant popups: 8px corners (rounded-xl), a colorBorderSecondary hairline (ring-border) and the subtle popup shadow (shadow-popup). -// onu: Ant menu items: 32px tall, 12px padding, 4px corners (rounded-sm). - -import * as React from "react" -import { Combobox as ComboboxPrimitive } from "@base-ui/react" -import { cn } from "../lib/utils" - -import { Button } from "./button" -import { - InputGroup, - InputGroupAddon, - InputGroupButton, - InputGroupInput, -} from "./input-group" -// onu: fixed glyphs passed as Icon fallbacks, so primitives work without OnuProvider and the full icon map. -import { CheckIcon, ChevronDownIcon, XIcon } from "lucide-react" -import { Icon } from "../icons/icon" - -const Combobox = ComboboxPrimitive.Root - -function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) { - return -} - -function ComboboxTrigger({ - className, - children, - ...props -}: ComboboxPrimitive.Trigger.Props) { - return ( - - {children} - - - ) -} - -function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) { - return ( - } - className={cn(className)} - {...props} - > - - - ) -} - -function ComboboxInput({ - className, - children, - disabled = false, - showTrigger = true, - showClear = false, - ...props -}: ComboboxPrimitive.Input.Props & { - showTrigger?: boolean - showClear?: boolean -}) { - return ( - - } - {...props} - /> - - {showTrigger && ( - - - - )} - {showClear && } - - {children} - - ) -} - -function ComboboxContent({ - className, - side = "bottom", - sideOffset = 6, - align = "start", - alignOffset = 0, - anchor, - ...props -}: ComboboxPrimitive.Popup.Props & - Pick< - ComboboxPrimitive.Positioner.Props, - "side" | "align" | "sideOffset" | "alignOffset" | "anchor" - >) { - return ( - - - - - - ) -} - -function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) { - return ( - - ) -} - -function ComboboxItem({ - className, - children, - ...props -}: ComboboxPrimitive.Item.Props) { - return ( - - {children} - } - > - - - - ) -} - -function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) { - return ( - - ) -} - -function ComboboxLabel({ - className, - ...props -}: ComboboxPrimitive.GroupLabel.Props) { - return ( - - ) -} - -function ComboboxCollection({ ...props }: ComboboxPrimitive.Collection.Props) { - return ( - - ) -} - -function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) { - return ( -