@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 fromsrc/components/(2026-07-18) to keep the React layer visibly separate at the package top level too, consistent with the per-featurereact/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'sfeatures/memory,features/chat-pane,features/chat-composerslices — seeADS-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 barrelindex.ts) stay at the feature's top level; anything that imports React (hooks/,components/) moves under areact/subfolder —features/<domain>/react/{hooks,components}/. This is a deliberately lighter motivation than the@jini-ai/chat-core/@jini-ai/chat-reactpackage 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'sfeatures/connectors/section for the worked example. Retrofitted onto the flatsrc/components/bucket (2026-07-18, nowsrc/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 afeatures/<domain>/ports.tsinterface. 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 ownfeatures/<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 fromfeatures/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/andsrc/features/observability/(2026-07-16).src/components/(nowsrc/react/components/, see rename above) andsrc/hooks/—ADS-memory/reports/jini-port/ui-extraction-plan.mdsection A's flat-group components and theuseInViewhook (2026-07-17) — the first content in these two directories, and the first task to pull inreact/react-domas real dependencies of this package.src/features/connectors/— theConnectorsBrowser.tsxgod-component canary (2026-07-17), perADS-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 ofDesignBrowserPanel.tsx(2026-07-17), perADS-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 foronNavigate/history storage/ brand-bridge registration) — not the full file. The first feature to use the newreact/{hooks,components}/layout described above. See the archived provenance ledger for the full breakdown, including a confirmed duplicate withFileViewer.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 newreact/{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 genericAssetGrid<TAsset>ported fromLibrarySection.tsx(2026-07-17), perADS-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 newreact/{hooks,components}/layout described above.src/features/viewer-shell/— the file-viewer's media-viewer shell family (2026-07-17), perADS-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 newreact/{hooks,components}layout described above (not yet retrofitted ontoconnectors/progress-card).src/features/settings-dialog/(shell) +src/features/settings-dialog/ tabs/{appearance,notifications,language,instructions,integrations,privacy}/— extracted fromSettingsDialog.tsx(2026-07-17), perADS-memory/reports/jini-port/god-components-extraction-plan.mditem 5. Uses the NEWreact/{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, includingTabBar,TabBarProps,TabBarTab,TabStrip,TabStripItem, and their hooks. The feature was consolidated (2026-07-18), perADS-memory/reports/jini-port/god-components-extraction-plan.md's Consolidation map §Afeatures/tab-strip/row:WorkspaceTabsBar.tsx's workspace-tab strip andFileWorkspace.tsx's independently-reimplemented inlineTabcomponent (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 genericListDetailPanel<TItem>master-detail (list+preview) navigator shell, ported fromDesignSystemsTab.tsx(2026-07-18), perADS-memory/reports/jini-port/god-components-extraction-plan.md's Consolidation map.PluginsView.tsx's detail modal andProjectView.tsx's composition were read and confirmed NOT to share this shape (a portal overlay and a resizable 2-pane split, respectively) — scoped toDesignSystemsTab.tsxalone 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 fromNewProjectPanel.tsx(OptionCards,CompactToggle,ToggleRow),PluginsView.tsx(StatCard,Notice,ImportChoice,FileImportPanel), andEntryShell.tsx(OnboardingPanelHeader,OnboardingChipField,OnboardingDropdown). See the archived provenance ledger.src/features/memory/— ported from OD's never-merged PR #5228 (a vertical-slice decomposition ofMemorySection.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 reusefeatures/connectors/rules.tsinstead of re-deriving a third copy.src/features/schedule-picker/—RecurringSchedulePicker, a generic "cron-lite" recurring-schedule editor (2026-07-18), ported fromNewAutomationModal.tsx'sSchedulePopoverperADS-memory/reports/jini-port/god-components-extraction-plan.md's Consolidation map. Also added flatsrc/components/{PillButton,PopoverMenu,PopoverItem}.tsxandsrc/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 fromNewAutomationModal.tsx, per the same Consolidation map row. Checked againstQuickSwitcher.tsxand thecomposer/*Lexical@mentionsystem 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 fromEditorIcon.tsx, same lookup-table shape asIcon.tsx/AgentIcon.tsx/RemixIcon.tsx. First file under the newsrc/react/components/path (therefactor/ui-flat-components-under-reactrename hadn't landed on this branch's base yet, so this is a new folder alongside the still-present flatsrc/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 fromIframeKeepAlivePool.tsxperADS-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'sprojectId/fileNamekey pair into one opaque string key and drops the OD-specificOD_PREVIEW_KEEP_ALIVEenv-var toggle. Fixed two real bugs found while porting (a missingpx-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 fromQuickSwitcher.tsx. Collapses the origin's file/tab discriminated union into oneCommandPaletteItemshape; recents persist via a reallocalStorage-backedCommandPaletteRecentsPort. Confirmed distinct fromfeatures/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 fromTabLauncherMenu.tsx. GenericTabLauncherResultItemshared 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-specificLauncherContext.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 forfeatures/progress-card/. See the archived provenance ledger.src/features/revision-review/—RevisionDiffCard/RevisionHistoryList, a generic "proposed change review" widget (2026-07-18), ported fromDesignSystemFlow.tsx's remaining pieces. GenericizesDesignSystemRevisiontoRevisionReviewItem<TMeta>; unifies the origin's two duplicate diff functions into onediffAddedLines. Confirmed distinct fromfeatures/progress-card/rather than folded in. See the archived provenance ledger.src/react/components/{TokenChip,ValueChip,ComponentKitPreview}.tsx(2026-07-18) — the rest ofDesignSystemFlow.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-shippedutils/color-math.tsrather 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) andDesignSystemFlow.tsx'sDropZone(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 (fromfeatures/asset-tree-browser/rules.ts) up toutils/file-transfer.tsandbrowser/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 exposingwebUtils.getPathForFileas aFolderPathDropPort) inserts the folder's absolute path as text instead of uploading its contents. The framework-free rules are also on./core;useFolderPathDropCapturereturns a stableonDropCapturehandler. See the archived provenance ledger.src/utils/scroll-tabs-with-wheel.tsandsrc/utils/color-math.ts(2026-07-18) — two flat bucket-A atoms fromADS-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 (fromFileWorkspace.tsx'sscrollWorkspaceTabsWithWheel) and hex/RGB/ luminance/mix color-math primitives (fromDesignSystemFlow.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 fromrich-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//commandtoken node type (generic, host-injectedresolveMentionColorinstead 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@tokenstring. 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 (amention-parser.tsmerge pass, arules.tsselection catch-all, auseSeededValueStrictMode guard, and two$isRangeSelection/$isMentionNodere-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.
