npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jini-ai/ui

v0.4.7

Published

Generic, product-neutral React UI primitives and feature domains. Framework-free types, decision rules, catalogs, and host port contracts live behind ./core (no React, no DOM). The Excalidraw and Lexical editors live behind ./sketch-editor and ./lexical-r

Downloads

3,491

Readme

@jini-ai/ui — generic, product-neutral UI primitives

@jini-ai/ui/mcp-ui/secret-card exports the framework-free defineSecretCardTool lifecycle. Provide a spec with prepare, form, save, outcome and result, then register tool.handler({ surfaceExchanges, askThenReport }); both dependencies come from @jini-ai/daemon/surface-exchanges. The UI entry has no daemon or React runtime import. Transport ports, including emitSurface, remain in the handler's optional second object.

Secret fields use { kind: 'string', name, label, secret: true } and cannot have value. Use allowBlank: true for optional credentials or a host's preserve-on-update policy; the engine sends the blank to save, where the host implements that policy. Domain save ports receive only declared field names, exact secret bytes and the execution's abort signal. Whitespace-only input is refused even with allowBlank; nonblank secrets are never trimmed. Return safe summaries only; result and outcome projections never receive submitted values. form retains the builders' translation, locale, details, warning and app options, with an optional cancelLabel. outcome may return undefined for locally reported dismiss/expiry. Use safeError only to map known error kinds to fixed text and logFailure for fixed metadata.

The store ABI binds actor, tool and channel. The host must also enforce run scope when routing human answers (or inject a store scoped to the run); the exchange ABI does not carry a run ID. After coordinator verification, publish core 0.4.2, daemon 0.5.4 and UI 0.4.5 before adoption.

Renamed from @jini-ai/components in this session (2026-07-16) once it became clear one "components" bucket undersold the scope: this package is meant to hold more than flat presentational components — pure business logic, hooks, and injectable providers for whatever generic (non-chat, non-OD-branded) UI domains fall out of extracting OD's ~217-file apps/web/src/components/ zone.

Scope boundary (hard rule, decided 2026-07-16)

Only genuinely cross-product-reusable UI lands here — buttons, dialogs, form controls, layout primitives, icons, and any small feature-shaped domain (e.g. a generic toast/notification system) that isn't tied to Open Design's product vocabulary.

Not here: anything OD-product-specific — FileViewer, ProjectView, DesignSystemFlow, memory-extraction config UI, automations, handoff, brand/plugin/figma-specific components. Those stay in foundry/integrations/open-design/ as OD's own UI if/when that adapter needs them. SettingsDialog itself is one of these (its execution/orbit/media/ connector-provider/critiqueTheater/pet/designSystems/projectLocations/ routines/about tabs and its AMR/autosave shell state stay OD-specific) — but its reusable tabbed-dialog shell and 6 of its small, clean, generic tabs (appearance, notifications, language, instructions, integrations, privacy) shipped as src/features/settings-dialog/; see the archived provenance ledger. Also not here: chat/artifact UI — that's @jini-ai/chat's ./core and ./react subpaths (the former standalone @jini-ai/chat-core package was retired into ./core on 2026-08-03), and this package's own ./renderers subpath (the artifact-renderer registry, once planned as a separate renderers-react package) — kept as separate subpaths deliberately, not folded into this package's own React layer. See the chat-core/chat-react split discussion in ADS-memory/reports/jini-port/ session notes.

Internal structure

  • src/react/components/ — flat, presentational-only atoms. Props in, JSX out, no state/logic/fetch (same discipline as OD's own ADR 0002 slice rule). Renamed from src/components/ (2026-07-18) to keep the React layer visibly separate at the package top level too, consistent with the per-feature react/ convention below.
  • src/features/<domain>/ — anything that needs its own hooks + ports + dumb-components + barrel because it's a cohesive concern, not a single atom (mirrors the ports+dependencies+barrel discipline already proven by OD's features/memory, features/chat-pane, features/chat-composer slices — see ADS-memory/reports/jini-port/od-reference-branches.md). Within each feature (decided 2026-07-17): files with zero React import (types.ts, constants.ts, rules.ts, ports.ts, dependencies.ts, the barrel index.ts) stay at the feature's top level; anything that imports React (hooks/, components/) moves under a react/ subfolder — features/<domain>/react/{hooks,components}/. This is a deliberately lighter motivation than the @jini-ai/chat-core/@jini-ai/chat-react package split: not "prepare for a Vue consumer" (no such consumer exists or is planned), just keeping the pure layer visibly and mechanically separate from the React layer within one package, at effectively zero cost. See the archived provenance ledger's features/connectors/ section for the worked example. Retrofitted onto the flat src/components/ bucket (2026-07-18, now src/react/components/); src/hooks/ below is not — it still sits at the top level; revisit if this pattern proves worth extending there too.
  • src/providers/ — the only place allowed to import a concrete transport/DOM adapter and bind it to a features/<domain>/ports.ts interface. Everything else in this package depends on the port, never a concrete implementation.
  • src/hooks/ — generic hooks that don't belong to one feature domain specifically (feature-local hooks live inside their own features/<domain>/ instead).
  • src/utils/ — non-component pure helpers and small stateful browser-API wrappers that don't need the full ports+dependencies ceremony. Added in the i18n/observability/utils porting task (2026-07-16); see the archived provenance ledger.

./core — the framework-free half, importable without React

@jini-ai/ui/core is not a separate concern, just a separate entry point. It re-exports exactly the files described above as "zero React import" (types.ts, constants.ts, rules.ts, ports.ts, dependencies.ts) across every feature listed in the table below — the same files that already live at each feature's top level, right next to that feature's react/ folder. There is no separate source tree to keep in sync; ./core (src/core.ts) is a curated barrel over files that already exist.

This used to be a genuinely separate package, @jini-ai/ui-core, until it became clear it never earned that: it was always the framework-free half of this package's features, and its own name — "core of the UI package" — gave no hint that it was one half of a per-feature split. Folded back in here 2026-08-01, once that confusion cost real time (including for the person who built it). the archived provenance ledger has the dated entry.

Feature map

Every feature below was extracted from Open Design's SettingsDialog.tsx (~8,538 lines). That shared origin is why the set looks arbitrary: it is not a designed taxonomy, it is whatever that one dialog contained, split tab by tab. Worth knowing before you go looking for a general principle that is not there.

| Feature | What it models | Has ports? | |---|---|---| | about | version/build info panel | | | appearance | theme segmented control, density | | | connectors | OAuth integration marketplace | ✓ | | execution | local agent detection, BYOK config, model listing | ✓ | | integrations | multi-client MCP integration management | ✓ | | language | locale selection | | | media-providers | media/asset provider config | ✓ | | memory | memory slice wire + view-model types, commit guard | ✓ | | notifications | completion sounds, browser notification flow | | | privacy | telemetry/data-sharing toggles | | | project-locations | project folder registration — belongs to the fleet orchestrator; do not mount in a product's admin | ✓ | | skills | skill catalog + detail | ✓ | | source-config-list | generic "add a source by URL/key, set trust, test/refresh" list | ✓ |

Plus, at the package root and inside features/settings/dialog/ and features/notifications/:

  • features/settings/dialog/{types,rules}.ts — the settings-dialog shell itself: SettingsDialogTabMeta, resolveInitialActiveTabId, findActiveTab. A tab registry with active-item resolution.
  • features/notifications/notifications-catalog.ts — sound ids and defaults (distinct from features/notifications/{types,constants,rules}.ts, the notifications tab's own state).
  • icon-name.ts (package root), utils/uuid.ts, utils/endpoint-policy.ts.

Boundary: ./core vs the rest of ui vs admin

Three concerns, easy to confuse, so state them plainly:

| Surface | Holds | Rule of thumb | |---|---|---| | @jini-ai/ui/core | framework-free half of this package's features | importable with zero React/DOM; never mounts UI by itself | | @jini-ai/ui (root) | generic, product-neutral UI: primitives (asset-grid, command-palette, list-detail-panel) and the React half of everything under ./core | a thing a non-admin product would also want | | @jini-ai/admin | the admin application surface: panel registry, routing, transport, admin panels | only meaningful inside an admin |

Known wrinkle, recorded rather than silently tolerated: this package holds both genuine primitives and ~13 settings-screen features, which is a real conflation. They are not moved to admin because they also back Open Design's SettingsDialog reproduction, so they are not admin-exclusive. If a future consumer needs them without an admin, that is the moment to revisit.

Before building an admin panel

@jini-ai/admin panels must check ./core first. Several features here already model domains an admin panel would otherwise re-derive — execution, integrations, connectors, media-providers, notifications, appearance.

This has already gone wrong once: the reference implementation's admin API client defines AdminExecutionDetectedAgent with the comment "Mirrors @jini-ai/ui's ExecutionTab DetectedAgent shape" — a hand-maintained copy of features/execution/types.ts's DetectedAgent that has already drifted (it added authStatus/authMessage; this side has description and reasoning-effort presets). It also re-derived detectExecutionAgents / testExecutionConnection / listExecutionModels / testExecutionAgent, which is exactly the ExecutionPort already defined in features/execution/ports.ts.

Reuse the port. Do not create a third definition.

Testing the ./core surface

src/__tests__/features/**, src/__tests__/utils/**, and src/__tests__/rules.test.ts are this package's own centralized home for ./core's tests (inherited unchanged from @jini-ai/ui-core's own src/__tests__/ tree, rather than the co-located features/<domain>/__tests__/ convention the rest of this package uses — kept centralized specifically so vitest.config.ts's environmentMatchGlobs can route the whole tree to a DOM-less node environment by directory glob instead of a per-file pragma). Nothing there touches the DOM, and a DOM-dependent test landing in that tree would mean React logic crept back into the framework-free half — which is the one thing this boundary exists to prevent. Let such a test fail loudly, same as when this was its own package.

Status

Real content has landed in several parallel passes — see the archived provenance ledger for full per-section provenance:

  • src/utils/ — a framework-free DOM/pure-function layer (2026-07-16), plus a second batch (i18n/observability-adjacent utils: notifications, uuid, platform, etc., also 2026-07-16).

  • src/features/i18n/ and src/features/observability/ (2026-07-16).

  • src/components/ (now src/react/components/, see rename above) and src/hooks/ — ADS-memory/reports/jini-port/ui-extraction-plan.md section A's flat-group components and the useInView hook (2026-07-17) — the first content in these two directories, and the first task to pull in react/react-dom as real dependencies of this package.

  • src/features/connectors/ — the ConnectorsBrowser.tsx god-component canary (2026-07-17), per ADS-memory/reports/jini-port/god-components-extraction-plan.md §0: an OAuth integration marketplace UI (ports+dependencies+hooks+ components+barrel).

  • src/features/browser-chrome/ — a partial slice of DesignBrowserPanel.tsx (2026-07-17), per ADS-memory/reports/jini-port/god-components-extraction-plan.md's Section B: the generic "embeddable webview/iframe browser tab" primitive (navigation stack, address-bar normalization, history/favicon utilities, a viewport-preset switcher, and ports for onNavigate/history storage/ brand-bridge registration) — not the full file. The first feature to use the new react/{hooks,components}/ layout described above. See the archived provenance ledger for the full breakdown, including a confirmed duplicate with FileViewer.tsx's (not-yet-ported) viewport controls.

  • src/features/sketch-editor/ — SketchEditor.tsx's Excalidraw-integration shim (2026-07-17), per the god-components-extraction-plan.md Consolidation map §B: theme sync, dirty/save/export orchestration, and a DOM-enhancement toolkit for embedding @excalidraw/excalidraw. The first feature to use the new react/{hooks,components}/ layout described above, and the first to take a real third-party UI-library dependency (not just browser/DOM primitives).

  • src/features/asset-grid/ — a generic AssetGrid<TAsset> ported from LibrarySection.tsx (2026-07-17), per ADS-memory/reports/jini-port/god-components-extraction-plan.md's Consolidation map: rubber-band multi-select, day-bucketed timeline grouping, kind/source facets, debounced search, SSE live-merge, grid/timeline view toggle, bulk-delete-with-confirm, keyboard shortcuts, kind-aware thumbnail dispatch. The first feature built under the new react/{hooks,components}/ layout described above.

  • src/features/viewer-shell/ — the file-viewer's media-viewer shell family (2026-07-17), per ADS-memory/reports/jini-port/god-components-extraction-plan.md's consolidation map: a generic viewer-toolbar/body chrome, a resolved viewport-switcher-overlap pair (ViewportSwitcher/ViewportToggleGroup), the comment side-panel, and a markdown split-pane with scroll-sync. The first feature built under the new react/{hooks,components} layout described above (not yet retrofitted onto connectors/progress-card).

  • src/features/settings-dialog/ (shell) + src/features/settings-dialog/ tabs/{appearance,notifications,language,instructions,integrations,privacy}/ — extracted from SettingsDialog.tsx (2026-07-17), per ADS-memory/reports/jini-port/god-components-extraction-plan.md item 5. Uses the NEW react/{hooks,components} layout (this is the first feature built with it from scratch). See the archived provenance ledger.

  • src/features/tab-strip/ — a consolidated draggable/reorderable tab-strip primitive, published through the browser entry @jini-ai/ui/tab-strip, including TabBar, TabBarProps, TabBarTab, TabStrip, TabStripItem, and their hooks. The feature was consolidated (2026-07-18), per ADS-memory/reports/jini-port/god-components-extraction-plan.md's Consolidation map §A features/tab-strip/ row: WorkspaceTabsBar.tsx's workspace-tab strip and FileWorkspace.tsx's independently-reimplemented inline Tab component (r6 confirms these are two divergent implementations of the same interaction, not shared even within OD's own codebase) both design into ONE generic primitive (TabStrip/ TabStripItem/useTabStripDragReorder) rather than porting either verbatim — drag-to-reorder (with a 'live'-vs-'onDrop' reorder-timing option and pinned-tab drop-edge coercion), active/close-button affordances, host-injected tab content. A dual-shape test proves both source interaction shapes work correctly through the same code paths, not just that they compile. See the archived provenance ledger.

  • src/features/list-detail-panel/ — a generic ListDetailPanel<TItem> master-detail (list+preview) navigator shell, ported from DesignSystemsTab.tsx (2026-07-18), per ADS-memory/reports/jini-port/god-components-extraction-plan.md's Consolidation map. PluginsView.tsx's detail modal and ProjectView.tsx's composition were read and confirmed NOT to share this shape (a portal overlay and a resizable 2-pane split, respectively) — scoped to DesignSystemsTab.tsx alone rather than forcing a broader generalization. See the archived provenance ledger.

  • 10 more src/components/ flat atoms (2026-07-18) — the Section C bucket-A batch from NewProjectPanel.tsx (OptionCards, CompactToggle, ToggleRow), PluginsView.tsx (StatCard, Notice, ImportChoice, FileImportPanel), and EntryShell.tsx (OnboardingPanelHeader, OnboardingChipField, OnboardingDropdown). See the archived provenance ledger.

  • src/features/memory/ — ported from OD's never-merged PR #5228 (a vertical-slice decomposition of MemorySection.tsx, 2026-07-18): the saved-memory list/editor, the extraction-history stream, and the connector-sourced-suggestions flow. Carries forward every async/ state-correctness fix that PR's own long review cycle found (the bugs were independently confirmed pre-existing in OD's original monolith, not introduced by the decomposition), plus one additional fix (fetchMemoryList()'s under-validated response) made during this port. See the archived provenance ledger for the full provenance note, including why its connector-reconciliation reducers reuse features/connectors/rules.ts instead of re-deriving a third copy.

  • src/features/schedule-picker/ — RecurringSchedulePicker, a generic "cron-lite" recurring-schedule editor (2026-07-18), ported from NewAutomationModal.tsx's SchedulePopover per ADS-memory/reports/jini-port/god-components-extraction-plan.md's Consolidation map. Also added flat src/components/{PillButton,PopoverMenu,PopoverItem}.tsx and src/utils/timezone.ts, both from the same source file. See the archived provenance ledger.

  • src/features/mention-autocomplete/ — MentionAutocomplete, a generic "type a trigger character, get a filtered picker" mention/capability autocomplete (2026-07-18), also ported from NewAutomationModal.tsx, per the same Consolidation map row. Checked against QuickSwitcher.tsx and the composer/* Lexical @mention system for a possible 3-way overlap — concluded they're three distinct shapes, not one primitive done three times; see the archived provenance ledger for the full comparison (read that section before extracting either of those two).

  • src/react/components/EditorIcon.tsx (2026-07-18) — a flat icon-by-key atom ported from EditorIcon.tsx, same lookup-table shape as Icon.tsx/ AgentIcon.tsx/RemixIcon.tsx. First file under the new src/react/components/ path (the refactor/ui-flat-components-under-react rename hadn't landed on this branch's base yet, so this is a new folder alongside the still-present flat src/components/). See the archived provenance ledger.

  • src/features/iframe-pool/ — a generic, host-configurable "cap N mounted iframes, LRU-evict inactive ones, park the rest off-DOM" pool (2026-07-18), ported from IframeKeepAlivePool.tsx per ADS-memory/reports/jini-port/god-components-extraction-plan.md's Consolidation map (the pattern recurs 3 times in OD's own codebase; this is the canonical implementation). Genericizes the origin's projectId/fileName key pair into one opaque string key and drops the OD-specific OD_PREVIEW_KEEP_ALIVE env-var toggle. Fixed two real bugs found while porting (a missing px-unit append on numeric style values, and a reused parked iframe never having its hidden/inert markers undone) — see the archived provenance ledger.

  • src/features/command-palette/ — CommandPalette, a generic Cmd/Ctrl+P fuzzy file-and-item palette (2026-07-18), ported from QuickSwitcher.tsx. Collapses the origin's file/tab discriminated union into one CommandPaletteItem shape; recents persist via a real localStorage-backed CommandPaletteRecentsPort. Confirmed distinct from features/mention-autocomplete/ (already checked in that feature's source-map section) rather than re-litigated. See the archived provenance ledger.

  • src/features/tab-launcher-menu/ — TabLauncherMenu, an anchored, portal-rendered "+"-button command-palette dropdown (2026-07-18), ported from TabLauncherMenu.tsx. Generic TabLauncherResultItem shared by both the file list and the tab list; TabLauncherAction<TActionCtx> generic over whatever context a host's actions run against, replacing the origin's OD-specific LauncherContext. features/tab-strip/ does not exist on this branch despite the extraction plan describing it as already shipped — documented as a discrepancy, matching the same pattern already recorded for features/progress-card/. See the archived provenance ledger.

  • src/features/revision-review/ — RevisionDiffCard/RevisionHistoryList, a generic "proposed change review" widget (2026-07-18), ported from DesignSystemFlow.tsx's remaining pieces. Genericizes DesignSystemRevision to RevisionReviewItem<TMeta>; unifies the origin's two duplicate diff functions into one diffAddedLines. Confirmed distinct from features/progress-card/ rather than folded in. See the archived provenance ledger.

  • src/react/components/{TokenChip,ValueChip,ComponentKitPreview}.tsx (2026-07-18) — the rest of DesignSystemFlow.tsx's remaining pieces: a color-swatch chip, a plain-value chip, and the theme-toggle-driven style-guide preview panel that renders both, with the token source genericized to host-injected data (the origin's markdown-parsing pipeline is not ported). Reuses the already-shipped utils/color-math.ts rather than re-deriving its math a second time. See the archived provenance ledger.

  • src/features/file-dropzone/ — FileDropzone, a consolidated file-staging primitive (2026-07-18) ported from two independent OD file-staging zones — DesignSystemAssetDropzone.tsx (a kind-aware thumbnail grid + lightbox) and DesignSystemFlow.tsx's DropZone (a labeled zone with a file-dialog cancel-vs-still-loading detection heuristic) — read in full, confirmed the same underlying interaction, and shipped as one primitive instead of two. Also promoted the drag/drop directory-walk and clipboard utilities the two features already duplicated a third time (from features/asset-tree-browser/rules.ts) up to utils/file-transfer.ts and browser/useFileDropTarget.ts, so this package now has exactly one copy. See the archived provenance ledger for the consolidation evidence, a real infinite-render-loop bug found and fixed during this port, and full test/coverage numbers.

  • src/features/folder-path-drop/ (2026-09-14) — dropping a folder onto a host with native file access (e.g. an Electron preload exposing webUtils.getPathForFile as a FolderPathDropPort) inserts the folder's absolute path as text instead of uploading its contents. The framework-free rules are also on ./core; useFolderPathDropCapture returns a stable onDropCapture handler. See the archived provenance ledger.

  • src/utils/scroll-tabs-with-wheel.ts and src/utils/color-math.ts (2026-07-18) — two flat bucket-A atoms from ADS-memory/reports/jini-port/god-components-extraction-plan.md's Consolidation map §C: a generic wheel-to-horizontal-scroll handler for an overflowing tab strip (from FileWorkspace.tsx's scrollWorkspaceTabsWithWheel) and hex/RGB/ luminance/mix color-math primitives (from DesignSystemFlow.tsx). See the archived provenance ledger for the full writeup, including what was deliberately left behind (the OD-specific color-selection heuristic that consumes the math, not the math itself).

  • src/features/lexical-rich-text-editor/ (renamed from rich-text-input/, 2026-07-26 — the feature is Lexical specifically, not a general rich-text input, so the old name promised an engine-neutrality its Lexical-shaped API never had; see the archived provenance ledger's dated rename entry) — a real Lexical (Meta's rich-text framework) editor integration ported from OD's chat composer (2026-07-18): editor setup/config, an atomic @mention//command token node type (generic, host-injected resolveMentionColor instead of importing OD's connector-brand-color logic), a caret-floating-layer positioning hook/component, and serialize/deserialize between the editor's document model and a plain @token string. Named by three prior tasks (mention-autocomplete's own "3-way overlap" note among them) as this exact destination. See the archived provenance ledger, including four dead branches found and refactored away during the coverage-driven-refactor loop (a mention-parser.ts merge pass, a rules.ts selection catch-all, a useSeededValue StrictMode guard, and two $isRangeSelection/$isMentionNode re-checks) — all four provably unreachable given Lexical's own selection/point invariants, not padded with contrived tests.

Section B (vertical-slice features/<domain>/ work: byok-config, mcp-config) and section C (cross-package routing) of the extraction plan are not started. workspace-tabs (renamed tab-strip, see the Consolidation map's naming-reconciliation note) and rich-text-input (renamed lexical-rich-text-editor, 2026-07-26) are now landed, per above — no longer in this "not started" list. The god-components-extraction-plan.md list beyond the features enumerated above is also not started.

Integrated widgets and panel APIs

See API-CONVENTION.md for the widget, panel-kit, fetch-query, port and object-argument API shapes, including the breaking changes collected in CHANGELOG.md.

Theming the admin

Import @jini-ai/ui/styles/admin.css for the variable contract, admin aliases and widgets. For a custom layout that needs only the contract, import @jini-ai/ui/styles/variables.css. The existing admin-widgets.css, tabbed-dialog.css and settings-dialog.css subpaths remain. @jini-ai/tokens has been removed; no alias package is provided.

@jini-ai/ui/theme exports AdminTheme, defaultAdminTheme, validateAdminTheme, applyAdminTheme and resolveColorScheme. A theme supplies name, body/heading fonts, an optional mono font and font stylesheet URLs, complete light/dark palettes, and optional base radius/density. The ten palette fields are primary, primaryInk, bg, surface, text, muted, border, danger, success and warning. Existing variable names derive from this compact contract. Palettes accept concrete hex, RGB/HSL/OKLCH/OKLab/Lab/LCH values or basic color keywords; URLs, dependent variables, injected declarations and incomplete palettes fail validation. Radius is a nonnegative px/rem/em length; density is a multiplier from 0.5 to 2. The validator reports { valid, theme } or { valid, errors }; applying an invalid theme throws before any DOM mutation. Neutral defaults hold no product brand.

import '@jini-ai/ui/styles/admin.css';
import { defaultAdminTheme } from '@jini-ai/ui/theme';
import { AdminShell } from '@jini-ai/admin/react/shell';

// The host supplies its document and storage adapters, along with the existing shell props.
const themeEnvironment = {
  target: document.documentElement,
  document,
  matchMedia: window.matchMedia.bind(window),
};

<AdminShell {...hostShellProps}
  theme={defaultAdminTheme}
  themeEnvironment={themeEnvironment}
  themePreferenceStore={hostUserPreferenceStore}
/>;

The preference store implements read({ userId, workspace }) and write({ userId, workspace, preference }); reads return light, dark, system or null. The host owns persistent storage and keys. No user preference is written during mounting. Authenticated slots receive appearance, including setPreference({ preference }), so the host provides its own picker and labels. colorScheme makes the setting controlled when supplied; onColorSchemeChange({ preference }) tells that host to update it. Without a controlled prop, the shell restores the user's stored choice or follows the system. User/workspace changes discard the previous user's selection.

For other layouts call applyAdminTheme({ theme }, { target, document }); its cleanup restores the scope and releases owned font links, with deduplication across active themes. Then call resolveColorScheme({ preference }, { matchMedia }) and set the target's data-color-scheme attribute to the returned light or dark. A system preference requires the injected media port. AdminShell subscribes to system changes and cleans up on unmount. Inject the document root when components portal so dialogs share the palette. Former data-theme attributes must be migrated to this resolved attribute. Independent theme scopes can use different target elements.

The ZANA-THEME.md and the host-THEME.md files preserve product data for later host adoption; they are documentation, not runtime themes. Derived ramps/radius scales and the neutral defaults change the previous look, so review both schemes during adoption. Verification was not run in the extraction job at the owner's direction.

Design decisions

Kernel contracts

Fetch-query caches accept the shared Clock from @jini-ai/core/primitives through new FetchQueryCache({}, { clock }); clocks implement nowMs(). Freshness remains ten seconds by default and idle retention five minutes. Browser HTTP defaults now use injected native fetch ports and have no platform imports.

The A2UI interpreter uses that same core Clock with nowMs(); import the clock type directly from @jini-ai/core/primitives. The obsolete A2uiClockPort re-export is removed. Create the basic catalog with createLabCatalog({}) and inject it through createA2uiInterpreter({ catalog, clock, ids }).

Browser transport ports

useBrandFonts({ fonts }, { fetch?, manifest?, ... }) and ExportDiagnosticsButton's fetch prop accept native fetch implementations. Memory's createMemoryHttpPorts({}, { fetch? }) returns { config, entries, extractions }; pass these ports to the existing feature hooks. Omitted fetch uses globalThis.fetch. Quick requests abort after 15 seconds and archive downloads after 120 seconds; a caller signal is combined with the timeout. Browser failures retain native abort reasons, including TimeoutError, rather than platform's FetchTimeoutError. The package no longer depends on platform. Connector examples are vendor-neutral.

Optional TanStack fetch-query adapter

The default @jini-ai/ui/fetch-query entry stays dependency-free beyond React and the shared Jini foundation. Install the optional @tanstack/react-query v5 peer to select @jini-ai/ui/fetch-query/tanstack at the provider composition:

import { FetchQueryProvider } from '@jini-ai/ui/fetch-query/tanstack';
import { useFetchQuery } from '@jini-ai/ui/fetch-query';

function Records() {
  const query = useFetchQuery(
    { key: ['records'], fetch: loadRecords },
    { staleTime: 0, refetchOnWindowFocus: true },
  );
  return <span>{query.status}</span>;
}

// Existing components keep their imports; the provider binds all four hooks.
<FetchQueryProvider><Records /></FetchQueryProvider>;

Both providers own isolated caches and share the same library-free types, prefix invalidation, promise loader and mutation contracts. The default freshness is 10 seconds, window focus refresh is off, and reads/writes do not retry failures automatically. refetch() retries a failed read explicitly. Set refetchOnWindowFocus: true per query to refresh stale data on focus; choose staleTime: 0 when every focus should revalidate. The optional environment port controls offline pausing and reconnect/focus events independently per provider. Keep the selected adapter fixed for a provider's lifetime; remount to switch it.