@zephytiju/prism-query-box
v0.3.0
Published
Platform Prism query-box micro-UI (component id "query-box"): GeoVision v7 search panel — panel header, search row, collapsible filter panel (geo pills, catalog tag chips, threat/conf sliders, time span) — bound to interfaces/ISearchable/search through th
Readme
PrismQueryBoxMicroUI
Platform Prism query-box micro-UI. Component id: query-box.
Published to npm as @zephytiju/prism-query-box.
The query box renders the CONFIRMED GeoVision v7 entity-search panel: the panel header (title / subtitle / LIVE pill), the search row (mono input with glyph + clear ×, FILTERS toggle with an active-count badge), and the collapsible filter panel — geo pills + BBOX line (static, config-driven), a catalog hashtag input whose chips render inside the input (per-tag semantic colors, individual × removal, no clear-all), a THREAT slider whose min/max/threshold are component configuration (never a hardcoded 1–5 band), a CONF ≥ slider with a % pill, and a two-field calendar timespan joined by →. There is no sources filter, and APPLY FILTERS carries no match count. Submitting (Enter in the search input or APPLY FILTERS) runs a search through the Common bundle's searchable interface via the host Lattice transport and publishes the bounded results, the structured filter context, and the search lifecycle on Prism channels. Composed applications (for example Guanlan) consume it as-is; the component is platform-owned.
Configuration keys
| Prop | Meaning |
| --- | --- |
| locale | UI locale for the component-fixed strings: "en" \| "zh-CN" (default "en") — see i18n |
| title | Panel header title (default ENTITY SEARCH) |
| subtitle | Panel header mono subtitle (default FUSED INDEX) |
| liveLabel | LIVE pill copy in the header (defaults to the locale's live string) |
| tagPalette | Catalog tag → semantic color token for the chips (e.g. { MILITARY: "threat" }) |
| threat | { min, max, threshold } for the threat slider — the range is configuration, not fixed |
| confMin | Initial CONF ≥ slider position in percent (default 60) |
| geo | Optional { label, bbox } for the geo pill row and static BBOX line |
| sort | Interface-declared sort key forwarded with every request (default label) |
| placeholder | Search input placeholder (defaults to the locale's searchPlaceholder) |
Channel contract
| Direction | Kind | Id | Payload |
| --- | --- | --- | --- |
| publishes | state | query-box.filter-context | SearchFilterContext — structured: { query: string, catalogTags: string[], threatThreshold?: number, confMin?: number, timeSpan?: { start: string, end: string } } (ISO-8601 instants) |
| publishes | state | query-box.entity-results | EntityResultsProjection \| null |
| publishes | state | query-box.search-status | SearchStatus — { phase: "idle" } \| { phase: "busy" } \| { phase: "error", message: string } |
| consumes | event | query-box.search-retry | re-runs the last search request (kept in a ref) |
Channel ids are string literals at every call-site so the build-time channel-graph scanner can
derive the graph. All channel bindings are setter-only (usePrismStateSetter), so the component
never re-renders from its own publications.
Lattice binding
createSearchableClient(useLatticeTransport()) from @zephytiju/lattice-common-interfaces —
every search goes through the per-method route interfaces/ISearchable/search with full
server-side validation. No URLs, credentials, or generic invokes. The structured filters are
serialized onto stable SearchRequest.filterRefs — catalog:TAG, threat:>=N, conf:>=N,
time:<start>/<end> — with sort: "label" (serializeFilterRefs is exported).
Audit rule
Entity search is NOT audit-worthy. This component emits NO audit event and never emits a
query-box.search-submitted event. The only event in this contract family is the internal
query-box.search-retry (consumed here; emitted by the search-results micro-UI's retry button).
Internationalization (i18n)
The component ships en and zh-CN locale bundles — src/locales/en.json / src/locales/zh-CN.json —
and every component-fixed UI string is resolved from them (FILTERS, APPLY FILTERS, the filter-row
labels GEO FILTER / BBOX / CATALOG / THREAT / CONF ≥ / TIME, DRAW ON MAP, the tag placeholder, and the
live / searchPlaceholder defaults). The component renders no hardcoded copy.
{
"query-box": {
"searchPlaceholder": "search entities…",
"filters": "FILTERS",
"apply": "APPLY FILTERS",
"tagPlaceholder": "tag…",
"catalogLabel": "CATALOG",
"threatLabel": "THREAT",
"confLabel": "CONF ≥",
"timeLabel": "TIME",
"geoLabel": "GEO FILTER",
"bboxLabel": "BBOX",
"drawOnMap": "DRAW ON MAP",
"live": "LIVE"
}
}locale?: "en" | "zh-CN"prop (default"en") selects the string table per instance — it is a per-instance prop, so two instances in one host may render differing languages.- Explicit
liveLabel/placeholderprops override the locale strings. - Composition-authored strings are localized by the composer; component-fixed strings live in the
locale JSONs.
title/subtitle(and any explicitliveLabel/placeholder) are configuration-authored: a host with a localized header passes its own strings per locale. - Locale bundles are namespaced under the component id (
"query-box") so a composer can deep-merge every component's bundle into ONE UI language bundle without collisions:
import { locales as queryBoxLocales } from "@zephytiju/prism-query-box";
// queryBoxLocales.en -> { "query-box": { … } }
// queryBoxLocales["zh-CN"] -> { "query-box": { … } }
const uiBundle = deepMerge(hostStrings, queryBoxLocales.en, searchResultsLocales.en);The parsed bundles are exported from the package entry (locales, en, zhCN,
stringsForLocale), and the raw JSONs are also served by the ./locales/* exports subpath
(e.g. @zephytiju/prism-query-box/locales/zh-CN.json); files ships both dist and locales.
Theme
No palette is hardcoded. Every color resolves to SEMANTIC theme tokens (ok, accent,
threat, warn, signal, card, card-dark, input, border, line, text, muted,
deep, panel) consumed as CSS variables, plus --mantine-font-family-monospace for the mono
typography — the palette is supplied entirely by the host's MantineProvider. The local demo
ships TWO themes, both defined in src/demo.tsx: geovisionTheme (dark), mapping each
semantic token onto the exact :root variables of the CONFIRMED design (deep bg, panel black,
card, mint, red, blue, amber, purple, muted, border), and the contrasting latticeLightTheme
(light), mapping the SAME semantic token keys onto a different palette — the component is
skinned purely through the surrounding MantineProvider.
Source layout
src/ is strictly two parts:
- Component source (what the package compiles):
QueryBox.tsx,types.ts,index.ts(public entry),src/locales/(en.json,zh-CN.json,index.ts— the i18n string bundles and their resolver), andsrc/shims/node-crypto.ts(browser-build shim for the digest import — build infrastructure). - Demo: exactly ONE file,
src/demo.tsx— the two host themes (GeoVision v7 + Lattice Light), the mock host action executor (createDemoExecutor) and all demo test data (sample projection, tag palette, threat/geo configuration, seeded query/catalog tags), and the demo page rendering multipleQueryBoxinstances side by side behind a global EN | 中文 language switcher (plus per-instance switches).
The npm package ships dist (compiled component + type declarations + locale JSONs) and the
top-level locales/ directory (the raw JSON bundles, served by the ./locales/* exports
subpath); no demo code is published. scripts/copy-locales.mjs copies the JSON bundles into
both locations during npm run build.
Local development
npm install
npm run typecheck
npm test
npm run dev
npm run shot-demonpm install pulls the platform peers (@zephytiju/prism-react,
@zephytiju/lattice-common-interfaces) from the npm registry, along with the host-side
peer dependencies (react, react-dom, @mantine/core, @mantine/dates, dayjs).
When consuming the published package, install it directly
(npm install @zephytiju/prism-query-box) and provide those peer dependencies in the
host application.
The demo (npm run dev, entry src/demo.tsx) renders TWO QueryBox instances side by side,
each inside its own MantineProvider with a different theme (GeoVision v7 dark left, Lattice
Light right), behind an EN | 中文 segmented control — the GLOBAL switch sets the locale prop of
BOTH instances at once, and each instance carries its own per-instance control so the two hosts
can render DIFFERING locales simultaneously — and installs a mock host action executor answering
interfaces/ISearchable/search with a fixed sample projection. Prism channels are GLOBAL —
interacting with either instance publishes on the same channels, so the shared readouts (filter
context, search status, entity results) update from both, demonstrating theme-swap, locale-swap
and cross-instance sync at once. Include “fail” in a query to exercise the error path, and the
“Emit query-box.search-retry” button to prove the retry contract re-runs the last request.
npm run shot-demo boots the vite dev server, drives both instances in headless Chrome (opens
FILTERS in both, seeds catalog chips in the first, sets the LEFT instance to en and the RIGHT
instance to zh-CN), and captures the language switcher plus both instances in one shot to
/tmp/guanlan-review/demo-query-box-i18n.png.
Design record
https://qcnwge0wy4s0.feishu.cn/wiki/K40nwA5TZiUE7Nk5hK6cE0W8nTe — §7 (channel contracts) and
§8 (Lattice transport and interface clients). Visual reference: CONFIRMED GeoVision v7
design (nexus-geovision-v7-standalone.html, .search-panel region).
