Przejdź do głównej zawartości

@overvis/hmi-workspace-ui

Ta treść nie jest jeszcze dostępna w Twoim języku.

Shared React library for the workspace shell, setup UI, forms, tRPC client factory, and dashboard mount. Two hosts consume it:

HostAuth / entry
Overvis HMI cloud applicationCookie session; mounts createWorkspaceRoutes
CLI-scaffolded local appsLocalWorkspaceApp with apiUrl / apiKey / workspaceId

Hosts pass credentials and URLs. The package supplies translated UI text.

Local pull workflow: Pull and scaffold a local workspace. Contracts: @overvis/hmi-shared.

Build tooling (hmi build) runs on Node.js 22.13+ on the 22.x line or 23.4+. The browser library has these peer dependencies, in the ranges the package declares: react, react-dom, @tanstack/react-form, @tanstack/react-query, @tanstack/react-router, @trpc/client, @trpc/react-query, and @trpc/server. Dashboard builds run in @overvis/hmi-cli (hmi build), which needs Vite, Tailwind CSS, @vitejs/plugin-react, @tailwindcss/vite, and TypeScript; a pulled workspace lists them in its package.json. Browser-only consumers do not need them.

LocalWorkspaceApp creates its own router, query, theme, and workspace providers. Its apiUrl is the API base URL without /trpc. The API accepts browser requests only from its own web app, so a host proxies /trpc and /static (published dashboards, rendered reports) on its own origin and passes /. The apiKey is readable by everyone who can open the host. The steps to mount it are in Embed the workspace UI in a React app.

ExportRole
.Dashboard kit: trpc, live parameters, forms, report runtime, graphics
./uiUI primitive catalog and cn
./hostApplication shell: workspace app, layout, pages, chrome, theme, tRPC factory
./i18nLocale initialization, selection, and subscribing translation hooks
./parameter-valuesLive-value state and pure projections without React UI
./dashboard-bindingsMissing dashboard binding checks without the full package entry point
./styles.cssTailwind 4 + design tokens, which the host imports once
./styles-reference.cssClass tokens without preflight or fonts, read by the dashboard build

Peers: React 19, TanStack Form/Query/Router, tRPC client / react-query / server. Depends on @overvis/hmi-shared for PublicAppRouter, dashboard contracts, and schemas.

Dashboards import the kit (.), the primitives (./ui), and translation hooks (./i18n), which the host supplies at run time; ./host is for host applications only. The full list of what a dashboard can import is in the dashboard host contract.

  • API: trpc, the typed React Query client bound to the host’s providers.
  • Live parameters: ParameterValuesProvider, useParameterValues, useParameterValue, useParameterValueActions, useParameterValueHistory, useParameterValueHistories, useStoredParameterHistory, storedHistoryWindows, withLiveTail, liveTailPoints, useStoredWindowExtremes, rangeExtremes, useDistinctParameterNames, useParameterMetadata, parameterMetadata, isParameterRecorded, isCurrentParameterValue, isParameterValuePending, parameterValueNumber, parameterValueBoolean, parameterValueFlag, parameterValueComparable, parameterValueTime, parameterStatesStaleSince, combineParameterValuesDemands, ParameterLiveControls, ParameterValueDisplay, ParameterBooleanBadge, ParameterDiagnostics, ParameterWriteReviewDialog, and parameterWriteOutcome.
  • Dashboard pages: useDashboardNavigation, DashboardLink, DashboardNavigationLink (a DashboardLink that takes the navigation as a prop), DashboardNavigateOptions (replace, and keepScroll to keep the scroll position on another path), DashboardPageTabs, DashboardPageNotFound, dashboardPageAt, dashboardPathMatches, useAnchorScroll, and usePageRotation with nextPageId. useAnchorScroll(container, anchor, { key }) scrolls to the element whose id is anchor and keeps it in view while content above it loads, until the user scrolls or five seconds pass. DashboardPageTabs keeps an optional search on its links; a tab’s current marks the current page where its path alone cannot tell. usePageRotation({ rotateSeconds, currentId, paused, onRotate }) calls onRotate every rotateSeconds for wall displays, except while paused, while a dialog is open and for a minute after user input.
  • Drag and drop: SortableGrid, SortableGroup, and SortableItem order items within and between groups, such as tiles in the sections of a page. SortableGrid takes the groups as item ID lists and calls onMove once on drop with where the item came from and went, and before, the order shown when the drag began (the baseline to check the move against); the dropped order stays until groups changes, or until a promise returned by onMove rejects; children renders the groups in their order during a drag. SortableItem passes handleRef for its drag handle. Keyboard users pick an item up by its handle with Space or Enter, move it with the arrow keys and drop it with Space or Enter; each step is announced with itemLabel and groupLabel. They are in the import map, so a dashboard that uses them bundles no drag-and-drop library.
  • Device API: useDeviceApi (the device operations the viewer may call, with canCall for per-key roles), useDeviceApiRead (shared, rate-aware reads of read operations; refreshMs of at least DEVICE_API_MIN_REFRESH_MS), useDeviceApiAction (calls, usually of write operations), and deviceApiErrorText; see device API.
  • Device dashboards: DevicePresetLayout, useDevicePresetValues, splitEndpointDiagnostic, useParameterUnit, useDeviceReadings, DevicePresetHeader, DeviceEndpointUnavailable, DeviceCommandButton, DeviceRelayTest, ParameterStateText, ParameterTrendChart, parameterTrendSeriesList, DeviceSettingsSection, DeviceSettingsPage, deviceSettingNumber, deviceSettingChoice, defaultDeviceSettingsWritePlan, DeviceSettingOutcome, ThresholdGauge, WindowGauge, formatQuantity, formatFixedNumber, isUnsupportedParameterValue, isUnsupportedParameterDiagnostic, DevicePanel, DeviceStatusBar, DeviceStatusBadge, DeviceKeyFigure, DevicePhaseTable, DeviceReadingList, DeviceSeriesSwatch, and the vendor-neutral ColdRoomCard with coldRoomBindingsType, coldRoomParameterIds, coldRoomBoundParameterIds, mapColdRoomBindings (DroppedColdRoomBinding), its coldRoom* model functions, ColdRoomCommands, coldRoomStatusLabel and coldRoomCommandLabel.
  • Reports: ReportDataProvider, ReportView, ReportWidget, ReportTimeNav, ReportCommentsFooter, useReportSession, ChartRepresentation, TableRepresentation, TimeBucketsRepresentation, HistogramRepresentation, CalendarRepresentation, HourlyMatrixRepresentation, StateDurationRepresentation, SankeyRepresentation, and GeneratedReport, GeneratedReportSection, GeneratedReportPending and useFrozenReportParameters for charts a dashboard generates (report components).
  • Counters: useCounterTotals, CounterTotal, CounterChange, CounterBars, CounterPeriodSelect, useCounterPeriod, useResolvedCounterPeriod, useDashboardTimeZone, useClosedMinute, useReadingPeak, counterBucketCadence, counterPeriodLabel, counterComparisonLabel, counterTotalValue, counterSlotUnit, counterShare, formatCounterShare, commonCounterUnit, rankByCounterTotal, CounterTotalsNotice, CounterBindingWarnings, PeriodSettingsFields, counterBindingError and numericBindingError (see Counter totals).
  • Dashboard actions: useQuickReport and useDashboardQuickReport for temporary history reports, useDashboardConfigurationMutation for validated read-modify-write configuration updates, useDashboardName for the dashboard’s own name in the UI language (null while loading), parameterStaleSinceText and parameterStaleNote for the “Stale · time” label of a value kept from an earlier read, DashboardConfigurationRequired, DashboardConfigurationRenameDialog, and useWorkspace.
  • Documents: DocumentView renders a document from @overvis/hmi-shared/document. It takes document, workspaceId, locale, and dashboardId, the dashboard showing it, which serves the document’s images. It shows the pages as dashboard subpages with their inputs, and values read from parameters, history, devices and the HMI API. It evaluates formulas in the browser and shows ”—” for values not known. DashboardDocument, DocumentLead, DocumentSection, DocumentColumns, DocumentProse, DocumentMetric, DocumentFigure, DocumentTable, DocumentChart and DocumentChartDataType compose documents in code; see document components.
  • Forms: useAppForm, withForm, withFieldGroup, FormRoot, mutationFormValidators, field helpers (including ParameterPickerField and DashboardPickerField), mapTrpcToFormErrors, formOnlyError.
  • API errors: apiErrorMessage (translated text for any failed call) and apiErrorCode; parameterRuntimeErrorText for the error of one parameter read or write result; formulaDiagnosticText / formulaProblemText (with the place in the formula) and automationDiagnosticText for formula and automation diagnostics.
  • Graphics: DashboardGraphics, defineGraphicsSymbol, useGraphicsSymbolViewport, useGraphicsSymbolAction, builtinGraphicsSymbols.
  • Maps: readLatLng, useMapsBrowserConfig, useMapsReverseGeocode. The Maps hooks and DashboardLocationPicker accept dashboardId: null for workspace settings (editor/admin/owner access); a dashboard ID retains dashboard access checks and usage attribution. The picker uses a global view when empty; defaultCenter and defaultZoom override that fallback. Saved locations and context markers take precedence.
  • Alarms: AlarmList shows a workspace’s active alarms live, unacknowledged first, filtered by sourceIds, keyPrefix or parameterIds (parameters the raising rule or automation reads now); operators and above acknowledge and shelve from it. compact drops message bodies and source names. Inside the workspace it shares the host’s single alarm subscription. See Alarms.
  • Fleets and sites: FleetOverview, FleetMap, FleetAlarmList, SiteStatusBadge, SiteReadings, SiteReadingMetric, SiteReadingValue, PhaseValues, SiteReadingCell, SiteModeBadge, SiteCommandButtons, SiteGroupCommandConfirmation, SiteGroupCommandResults, buildSiteGroupCommand, siteControlMode, EquipmentOperations, SiteEquipmentView, SiteAlarmList, SiteActiveAlarmBadges, SiteEquipmentSetup, SiteItemActions, overdueEquipment, dueDateParameterIds, FleetRoutes, matchSitePath, SitePageHeader, useSiteSetup, newSiteLocation, useSiteEditor, SiteSection, SiteBindingEditor, SiteSlotFields, SitePlacementFields, SiteStarterFields, CommandStepsField, BoardEditor, BoardSettingsFields, FleetOrderEditor, SiteDuplicateDialog, SiteFigure, SiteIndicatorLights, useParameterNames, evaluateSiteStatus, siteStatusParameterIds, siteAttentionParameterIds, activeAlarmsBySeverity, useNow, useIsMobile, useMediaQuery, and the generated-drawing helpers; see Fleet and site blocks.
  • Assistant page summary: usePublishDashboardSummary, usePublishSiteSummary, buildFleetSummary, buildSiteSummary, activeSiteAlarms, useReadParameterValuesOnce; see Page summary.

The shadcn/Base UI component catalog, including form controls, navigation, responsive layout, overlays, messaging, the shared PageTitle, Chart, composed DataTable, DateTimePicker, the native SVG Sparkline, GaugeBar and Gauge, SvgDiagram, Toaster, useToast, MessageHistoryProvider, MessageScopeProvider, MessageHistoryBell, and showNotificationToast, and the cn class-merging utility. Sparkline’s fixed-window mode buckets readings by time, disconnects sampling gaps above a caller-provided threshold, and supports a fixed vertical range and reference line. Gauge is a half-circle scale from 0 to max with an optional marker tick, such as a peak; gaugeScaleMax rounds a value up to 1, 2 or 5 times a power of ten for its scale. It takes already formatted texts.

./ui also re-exports the Recharts parts that compose with the shadcn ChartContainer: BarChart, LineChart, AreaChart, ComposedChart, Bar, Line, Area, XAxis, YAxis, CartesianGrid, ReferenceLine, and Rectangle (for a custom Bar shape), with their prop types BarProps, BarShapeProps, LineProps, AreaProps, XAxisProps, YAxisProps, CartesianGridProps, ReferenceLineProps, and RectangleProps. They come from the exact recharts version pinned in this package’s dependencies, so their props follow that version and a Recharts upgrade is a change to this public API. Dashboards build charts from these exports; the dashboard runtime does not serve recharts itself, and Recharts parts outside this list are not available to dashboards.

NoticeCard is the one problem and status card for dashboards and pages. severity (error, warning, info) sets a tinted icon badge, an accent edge and the --destructive, --warning or --info color token; an error is announced as role="alert", the others as role="status". title is a short headline and description a plain-language explanation; subject names what the notice concerns (a device) above the title. details ({ label, value }[]) lists technical facts under a collapsed “Technical details” disclosure (detailsOpen expands it). primaryAction is the one filled button; secondaryActions are outlined. An action with href or render (such as a router link) renders as a link; while disabled or pending, it renders as a disabled button. onRetry adds Retry (with retryLabel to rename it), which leads unless primaryAction is set; it shows a pending state while retrying is true or while the promise onRetry returns is unsettled. actions appends custom elements such as a DeviceCommandButton, and children adds body content under the description. size="compact" fits panels and narrow sections.

<NoticeCard
title={copy.loadError}
description={copy.loadErrorHint}
onRetry={() => query.refetch()}
retrying={query.isFetching}
/>

Hosts with their own router pass parseRouterSearch and stringifyRouterSearch from ./host to it, so dashboard subpage search parameters round-trip. The host provides the maps API keys.

  • Workspace: LocalWorkspaceApp, WorkspaceLayout, WorkspaceHostProvider, useWorkspaceHost, DashboardPage, DashboardNavigationProvider (the dashboard scope of useDashboardNavigation for a dashboard component rendered outside DashboardPage), ReportPage, setup pages and their route dialogs, NoAccessPage, WorkspaceSuspendedPage (given the suspended workspace, it explains each of its suspensionReasons), error helpers, mobile nav, WorkspacePullInstructions, AuditHistoryPage, CheckpointRevertPreview (the preview of reverting a workspace to a checkpoint, with the on-site MC252 confirmations; its onChange gets a CheckpointRevertControl whose revert() reverts exactly the previewed setup once nothing blocks it, and null while something does). SetupParametersPage and ReportPage publish the current parameter-tree selection and report session (UiPageState) to the page-state store that each WorkspaceHostProvider owns. DashboardPage publishes the mounted dashboard: its ID, title, location inside the dashboard, and the summary its current page publishes, or null. Inside the provider, useUiPageStateReader() returns a stable async reader: it resolves to the live state of the one publishing page, or null when no page or more than one page publishes. Outside a WorkspaceHostProvider, pages publish nothing and the reader always yields null.
  • Routes: createWorkspaceRoutes(workspaceRoute, options?) returns the TanStack Router code routes of one workspace; see Workspace routes.
  • Setup editors: create and edit forms use dialogs: SetupParameterFormDialog, SetupEndpointCreateDialog, SetupEndpointEditDialog, SetupDashboardCreateDialog, SetupDashboardCreateFromReportDialog, SetupDashboardSettingsDialog, and SetupReportFormDialog. Closing navigates back to the section list; SetupParameterFormDialog, SetupEndpointEditDialog, SetupDashboardSettingsDialog, and SetupReportFormDialog accept onClose to replace that. DashboardPage opens settings over the dashboard and stays there after saving or closing. DashboardCopyDialog({ workspaceId, dashboard, onClose }) accepts the source dashboard’s id and localized name, asks for a copy name, and navigates to the copy after creation; onClose dismisses the dialog on cancel or success.
  • Clients: createTrpcClient and createQueryClient.
  • Caches: invalidateWorkspaceCaches(utils, change) marks the cached queries an entity change makes stale, so open pages refetch them. utils is trpc.useUtils() of the host’s client; change is { entity, workspaceId?, id? } with entity one of workspace (name, suspension, access), workspaceList, member, cliKey, checkpoint, branding, file, endpoint, edgeProgram, automation (with alarm rules and automation limits), parameter, dashboard (with its drawings), report, reportComment, storage (stored-data usage and retention), or setup (everything the setup revision covers). Without workspaceId the entity refreshes in every workspace; with id, get queries of other rows stay cached. The package’s own mutations refresh through the same table. invalidateAfterApiCall(utils, queryClient, path) refreshes after a successful call of procedure path made on the user’s behalf and reloads open dashboards after setup-wide changes.
  • Tab icon: WorkspaceFavicon loads a workspace image with session credentials and restores the host favicon on failure, removal, or unmount. It takes the active workspace’s favicon URL or null.
  • Chrome / brand: BrandLink, OvervisLogo, LanguageSwitcher, ModeToggle, ThemeProvider. BrandLink uses the Overvis HMI wordmark by default; hosts can replace its full and compact content through props when custom branding is needed. WorkspaceBrandLink selects light/dark image URLs using the resolved theme, at 20 pixels high with proportional width and Overvis fallback on image failure. Undefined URLs hide the brand while loading; null URLs select the default brand. LocalWorkspaceApp reads branding metadata from the scaffold’s /__hmi/branding endpoint and images from local branding/ files. It reads them again whenever subscribeBrandingChanges reports a change (the scaffold wires the dev server’s branding event), so unpushed changes appear; without it branding is read once.
  • Development: DevFatalErrorBoundary and installDevFatalCrash for the scaffold’s development error overlay.
Host SPA / LocalWorkspaceApp
│
├─ ThemeProvider, Toaster, MobileNavProvider
├─ createTrpcClient ──► /trpc (cookie or x-api-key)
│
└─ WorkspaceHostProvider (cloud | local)
│
└─ WorkspaceLayout (host route /workspace/$workspaceId)
│ createWorkspaceRoutes:
├─ / → first dashboard or empty state
├─ /d/$dashboardId → DashboardPage
├─ /reports/$presetId → ReportPage
├─ /alarms (active alarms and alarm log; ?tab=history)
├─ /setup/dashboards… (list + create/settings dialogs)
├─ /setup/endpoints… (list + create/edit dialogs)
├─ /setup/endpoints/$endpointId/status (full page)
├─ /setup/parameters… (tree + create/edit dialogs)
├─ /setup/reports… (list + create/edit dialogs)
├─ /setup/automations… (list + full-page editor)
├─ /setup/automations/alarm-rules… (list + form dialogs)
├─ /setup/files (uploads/ catalog)
├─ /setup/storage (stored data and retention)
└─ /audit (activity history and checkpoints;
?view=checkpoints, ?kinds=all)

The /setup/… and /audit pages are for editors, admins and owners.

createWorkspaceRoutes(workspaceRoute, options?) from ./host builds the workspace pages as TanStack Router code routes. They are children of the host’s route at /workspace/$workspaceId, whose component renders WorkspaceLayout:

const workspaceRoute = createRoute({
getParentRoute: () => rootRoute,
path: "workspace/$workspaceId",
component: WorkspaceLayoutRoute, // renders <WorkspaceLayout workspaceId={…} />
});
const routeTree = rootRoute.addChildren([
workspaceRoute.addChildren(createWorkspaceRoutes(workspaceRoute)),
]);

LocalWorkspaceApp and the cloud application mount the same subtree, so paths, path params, and search params are identical in both. The route objects keep their types: a host that registers its router gets typed links to every workspace page, including the ones the package renders. options.resolveDeviceUiOrigin maps the configured Device UI origin to one the host can open (development proxies). Search params: endpoint status tab (device-ui; omitted for the default connection tab) and new parameter branchId.

<WorkspaceHostProvider mode="cloud" userId={user.id} onWorkspaceLoaded={setActiveWorkspaceId}>
<WorkspaceLayout workspaceId={workspaceId} />
</WorkspaceHostProvider>
  • Default dashboard entrypoint: dashboardEntrypointUrl(id) → /static/dashboard/<id>/index.js.
  • Cloud (and local remote-JS fallback) also attach dashboardStylesheetUrl(id) → /static/dashboard/<id>/index.css. Local with sources does not (Vite already scans src/dashboards/**).
  • createTrpcClient() defaults: url: "/trpc", credentials: "include", no isolated paths.
  • Transport: queries are batched into GET requests, and a batch is split before its URL passes 8,000 characters, because servers and proxies reject longer request lines. A query whose URL-encoded input passes 2,000 characters, such as readings for 100 parameters, is sent as POST with the input in the request body instead. Mutations are always POST. A proxy in front of the API therefore receives POST requests to /trpc for queries as well as mutations.
  • mintDeviceUiTicket (optional): mints the ticket that opens a VPN endpoint’s device web UI through the proxy. Without it the endpoint status page has no Device UI tab.
  • renderSuspendedBilling (optional): renders what the host offers on the page of a workspace suspended because its billing account is below its negative limit, such as the balance and a way to top it up. It gets the workspace (WorkspaceGetOutput).
  • renderNavFooter (optional): renders host content at the foot of the workspace navigation, above the Setup menu, in the sidebar and in the mobile navigation. It receives the workspace ID, whether the sidebar is collapsed to icons, and onNavigate, which closes the mobile navigation after a link is followed. LocalWorkspaceApp does not take it.
  • userId (optional): the signed-in user. Preferences kept in the browser, such as favorite and recently inserted graphics symbols, are stored per user. Without it they last until the page reloads.
  • openAssistant (optional): opens the host’s assistant panel. With a draft text it opens a new chat with that message written in the composer for the user to send, as Ask the assistant on an automation problem does. Without it, pages offer no assistant.

Props: apiUrl, apiKey, workspaceId, optional getDashboardEntrypointUrl (default /src/dashboards/<id>/index.tsx).

  • In-package TanStack router: the same createWorkspaceRoutes subtree as the cloud application.
  • Browser preferences use one fixed local user identity.
  • tRPC: credentials: "omit", getHeaders → x-api-key, client → x-hmi-client: workspace-ui/<version>, and parameter.readValuesStream as an isolated path.
  • Dashboards load from the Vite source path; when it is missing (Accept probe), from the remote /static/dashboard/… build, with a warning. Setup settings load the dashboard module from that same resolved URL.
  • Endpoint status ?tab=connection|device-ui is validated by the shared route. The Device UI tab needs a host that opens device web UIs (mintDeviceUiTicket on WorkspaceHostProvider), editor/admin/owner access, and a VPN public key. LocalWorkspaceApp does not open device web UIs, so it never shows the tab.

Scaffold entry (generated by CLI):

import { LocalWorkspaceApp } from "@overvis/hmi-workspace-ui/host";
import { initializeLocale } from "@overvis/hmi-workspace-ui/i18n";
import "@overvis/hmi-workspace-ui/styles.css";
await initializeLocale();
<LocalWorkspaceApp apiUrl={…} apiKey={…} workspaceId={…} />

DashboardPage imports the dashboard’s entry URL and requires a default export React component accepting DashboardRootProps; the props and exports are in the dashboard host contract.

Loading is a skeleton. A failed load is an error NoticeCard with Retry. A configuration that fails validation offers editors, admins and owners a link to the dashboard’s settings. For a built bundle (…/index.js), the host first reads the sibling hmi-dashboard.json; a missing manifest or an abi other than DASHBOARD_ABI_VERSION shows “Dashboard needs a rebuild” instead of importing the module. Local source entries compiled by Vite skip this check. After load, the host validates configuration against named-export TypeBox dashboardConfigType. A missing export or a failed check does not mount the root. A successful root is wrapped in a dormant ParameterValuesProvider; no live demand means no read request or polling timer.

useParameterValues registers parameter cohorts with their cadence, useParameterValue reads one discriminated state, and useParameterValueActions submits batches. The coordinator deduplicates due IDs, consumes progressive stream results, prevents overlapping reads, pauses hidden tabs, backs off failures per ID, preserves stale last-success values, and performs targeted reads after writes. A nested provider isolates the scheduling of part of a dashboard. A dashboard that registers no demand makes no live requests. The steps are in Show live values in a dashboard.

combineParameterValuesDemands joins cohort summaries. ParameterLiveControls, ParameterValueDisplay, ParameterBooleanBadge, and ParameterDiagnostics render their shared states. ParameterDiagnostics shows one NoticeCard per grouped diagnostic: the device as its subject, the error, affected parameter count and register addresses as technical details, and Retry (pending while the parameters are being read) or, when a Setup change fixes the problem, the endpoint, parameter or dashboard settings link as the primary action. DashboardConfigurationRequired shows a dashboard whose parameters are not bound yet without starting live demand. A dashboard for one device model renders its pages in DevicePresetLayout, builds them on ParameterStateText, ParameterTrendChart, useParameterUnit, DeviceCommandButton, and the overview cards (DevicePanel, DeviceKeyFigure, DevicePhaseTable, DeviceStatusBar), tests its relays with DeviceRelayTest, and edits device settings with DeviceSettingsSection, its write plans, and its gauges; see device dashboard components. ParameterWriteReviewDialog submits one reviewed batch and passes onSettled the failed change keys and each answered change’s result by key (ok or the runtime error; a change the batch did not answer has none). parameterRuntimeErrorText(result.error) is the text of a result’s error. The dialog neither retries nor rolls back. parameterWriteOutcome(result) sorts a write result into done (acknowledged), no-response (a device or gateway timeout, a unit the gateway could not reach, a dropped connection, or no result: the write may or may not have taken effect) and failed with its error. Both write dialogs read the written parameters back afterwards, except writeOnlyParameterIds, such as command registers a device cannot read; their effect shows only in separate feedback parameters. ParameterWriteSequenceDialog writes ordered steps and reports the step that stopped them to onSettled; prepare runs after its checks, right before the first write, and a message it returns stops the sequence with nothing written; onStart then runs once, so a caller can record what it must undo before anything reaches the device. Its optional secret (ParameterWriteSequenceSecret) asks for a password and writes it in a step before the others. DeviceCommandButton and DeviceSettingsSection take the same value as unlock. A settings section reads its written settings again and reports those the device did not keep (DeviceSettingsVerification); its layout can render custom controls (DeviceSettingCustomOptions, DeviceSettingControlProps) and check rules between settings (DeviceSettingsValidator). ColdRoomCard is a ready room card for refrigeration controllers, bound through coldRoomBindingsType; see cold room card. coldRoomBoundParameterIds lists every parameter the bindings name, command steps included. mapColdRoomBindings copies bindings to another controller through a parameter map: it reports each binding it leaves out, drops a command when any of its steps has no match, and gives no copy without the room probe and the setpoint. ColdRoomCommands renders the card’s commands on their own, for dashboards that lay a controller out themselves: Switch on while in stand-by and Stand-by while running, each through a DeviceCommandButton (which takes an optional size). Every bound command stays mounted while it is not offered, so a confirmation open when the state changes still reports its result.

SvgDiagram renders a same-origin SVG asset as a dashboard surface. Callers provide stable data-svg-target bindings with prepared text, classes, accessible labels, and activation handlers. The component removes executable and external SVG content, scopes IDs, fragment references, and embedded CSS selectors, and reports missing targets. It preserves the asset’s view box and fits the drawing into the container. Text bindings replace only the targeted element’s children; other bindings on container groups keep their artwork. Binding classes take precedence over declarations in the asset’s embedded stylesheet. Parameter lookup and state interpretation stay in the dashboard module.

The diagram has a min-h-80 default. A Tailwind minimum height or aspect-ratio class in className sizes it differently, such as aspect-video min-h-0 for a responsive fixed-aspect floor plan. The diagram does not add pan or zoom behavior.

In the cloud, and when the local host falls back to a published build, DashboardPage also loads the dashboard’s stylesheet, scoped to the dashboard element. Publishing dashboard changes through the assistant reloads the open dashboard without a page refresh. The packages a dashboard shares with the host are listed in the dashboard host contract.

Dashboards compose history reports from pieces exported from the package root. The report page itself, with its title, Edit, and back links, is not exported. The steps are in Embedding reports in dashboards.

PieceRole
ReportDataProviderHolds the session (window, visible parameters) and loads readings. Optional sessionScope keeps a separate remembered window
ReportViewAll layout rows of the preset in a 12-column grid. Reads the preset from the provider
ReportWidgetOne view by its key. Without column, it fills its parent; ReportView passes the layout widths
ReportTimeNavFrom/To, Reset, quick ranges, CSV, Share snapshot, Parameters. Checks the viewer’s role itself
ReportCommentsFooterComment, Sign, and the comment lists. Checks the viewer’s role itself
useReportSessionRead and set the window and visible parameters from host code

CSV export and snapshot sharing are available only through ReportTimeNav. The view components (ChartRepresentation, TableRepresentation, and the others) are also exported; ReportWidget picks the right one from the recipe. Two providers for the same preset on one page can show different windows until the page reloads, and they share the remembered window unless sessionScope is set.

Session. By default, a provider shares the window and visible parameters that the browser remembers for the report page. With sessionScope, it keeps a separate memory, so a dashboard tile does not change the window of the report page. With Lock time range (immutableTimeRange), the remembered window is ignored.

Sizing. Plot height comes from the recipe: height on charts, time bars, and histograms; tables use maxHeight, or about one screen tall when it is empty. A widget needs a parent with a definite height. A ReportWidget placed by the host applies no column span. When the same view key is mounted twice, such as in ReportView and in a separate ReportWidget, a snapshot uses only the first one.

CSV and snapshots. ReportTimeNav shows CSV and Share snapshot under the same conditions as the report page: the viewer is an operator, editor, admin, or owner, and the preset enables them. A snapshot contains the views mounted under that provider (a ReportWidget without column becomes full width), not the preset’s Setup layout.

Generated presets. A dashboard can run a ReportPreset built in memory. saved={false} keeps the provider from loading comments. trpc.report.preset.freezeFromLive freezes parameter setups for any workspace member, up to 100 IDs per call. validateReportPresetRecipe from @overvis/hmi-shared/report checks a recipe. timeRange={{ from, to }} makes the window the host’s: the session follows it whenever it changes, From and To are disabled, Reset and quick ranges are hidden, and the range is not remembered. The window is also the report’s data range, so daily bars follow it as it advances, including the current day. The window must not be empty; a calendar period such as the current day is empty until its first minute closes.

Generated reports. These helpers wrap a recipe built in code:

  • useFrozenReportParameters(workspaceId, parameterIds) freezes the parameters’ current metadata with report.preset.freezeFromLive, in as many requests as its limit needs; parameters stays null until every request has answered, and retry requests failed batches again.
  • GeneratedReport runs the preset over a counter period with timeRange and saved={false}. It keeps one session per recipe content, so a recipe rebuilt on every render keeps its session, and shows a note instead of charts while the period is still empty.
  • GeneratedReportSection renders one representation with a title and an optional note; wide content scrolls inside it. GeneratedReportPending stands in while parameters freeze, or explains that they failed with a Retry that calls onRetry.

Dashboards that watch many similar sites (stations, substations, lighting cabinets, cold rooms) build on these blocks and keep only their domain: site types, equipment, drawings, and labels. Configuration schemas and pure helpers come from @overvis/hmi-shared/dashboard (see shared).

Site status comes only from configured conditions, never from measurements. evaluateSiteStatus({ activity, alarms }, states) returns one of alarm, warning, offline, active, inactive, normal, unknown, loading, unconfigured:

StatusWhen
alarmA critical alarm condition is known to be true
warningA warning alarm is known to be true and no critical one
offlineNo feedback is current (missing or stale); lastData is the newest value
activeEvery activity condition is known and at least one is true
inactiveEvery activity condition is known and all are false
normalNo activity conditions, no alarm true, and every alarm known
unknownSome feedback is missing or stale while the rest is current
loadingFeedback is still being read for the first time
unconfiguredNo activity or alarm conditions are bound

Stale, missing, or failed feedback is never read as inactive or zero. SiteStatusBadge shows the status, an activity count, the last data time while offline, and a note when alarm feedback is unavailable. A fleet dashboard relabels states through labels, such as Running and Stopped for pumps. Blocks take each site’s evaluated state through stateOf; evaluateSites from @overvis/hmi-shared/dashboard builds such a lookup for every site.

siteStatusParameterIds(feedback) lists the parameters evaluateSiteStatus reads, for the overview’s live demand, and siteAttentionParameterIds(feedback, equipment) adds the due dates of equipment, for what a page summary reads. activeAlarmsBySeverity(alarms, states) keeps the alarms whose condition is known to be true, critical first and otherwise in their order; an alarm without a condition, such as an unbound fault slot, is never active, so a dashboard can list every possible alarm with its own fields (a pump, a trip) and let the helper drop the inactive ones.

Blocks receive the dashboard navigation (useDashboardNavigation()) and live states as props, so they render without a router, for example in tests:

  • FleetOverview lists sites as compact tiles (name and type, the status line, then the summary and readings inline; the zone only when not grouped by zone) or a table, with status totals, search, zone, status, and the dashboard’s own filters, sorting, zone grouping, empty and no-result states, and paging by 100. Filters live in the URL (q, zone, status, sort, and the key of each of the dashboard’s filters), and site links carry the query back. children receives the filtered sites, for an alarm list or a map. Tables scroll horizontally inside their own area; on phones the table representation shows tiles. title is optional; the host already shows the dashboard’s name. Optional props add the dashboard’s own content:

    • siteSummary ({ label, render(site) }): a block on each card under the status line, and a table column after Status.
    • columns (FleetColumn: id, label, cell(site), optional sortValue(site)): named values such as energy over a period, after the reading columns in the table and next to the summary readings on cards.
    • sorts (FleetSort: value, label, column, direction): sort options after configured order, name, and urgency, ordering by a column’s sort values; unknown values (null) come last. A value that repeats a built-in or an earlier option is ignored.
    • selection (FleetSelection: selected, onChange, actions(sites)): the dashboard holds the selected site IDs. The overview adds Select, a checkbox on each card and table row, and a sticky bar with the selected count, Select all matching (every filtered site, not only the shown page), Clear, and the dashboard’s actions. Changing a filter (search, zone, status, or a filter of the dashboard’s own, but not the sort) or leaving selection clears it. Nothing is stored in the URL. actions receives only selected sites the filters still show. Selection stays on while sites are selected, so a dashboard can put failed sites back into it.

    A card is one link to its site. Interactive content a dashboard renders on a card needs relative z-10 to sit above that link.

  • FleetMap places sites by fixed coordinates or by a geo parameter, colours markers by status, shows a legend of the states on the map, and opens a popup with status, summary readings, and a site link. A stale geo position keeps its last place with a faded marker; a missing one leaves the site off the map. sitePositionParameterIds(position) lists the position parameters it reads. details(site) adds the dashboard’s own content to the popup after the readings, such as period totals.

  • FleetAlarmList lists known active alarms with links, critical first, in a panel of fixed height that scrolls beyond three alarms and reads No active alarms only while every alarm state is known (otherwise Some alarm states are unavailable), so the page never shifts as alarms come and go; unknown replaces the all-clear while some alarm states cannot be evaluated, and pending keeps it empty until values arrive.

  • FleetOverview also publishes a fleet page summary for the assistant: status totals over every site, ignoring the overview filters, then sites most urgent first. alarmsOf and maintenanceOf add each site’s active alarms and overdue maintenance.

  • SiteReadings shows labeled readings, as a strip across the page or a compact grid; SiteReadingMetric is one of them (name, optional icon, value), such as on a panel card, and SiteReadingValue is a value alone, or ”—” when the reading is not bound. A stale value stays visible, muted, and marked with a clock (StaleMarker). SiteMaintenanceBadge counts overdue maintenance dates; dueDate="verification" labels them as verification dates (meters) instead.

  • EquipmentOperations groups setpoints, mode commands, start and stop, and other commands, and shows the maintenance due date and a history action. history takes parameterIds, pending, and open(parameterIds); the callback receives unique IDs in their original order. Null history or empty IDs hide the action. Pending requests disable it, and more than 20 unique parameters disable it with a translated explanation of the count and limit. Without parameterIds, as in older bundles, the callback works as before and the limit is not checked. dueDate="verification" labels the due date “Verification due” instead of “Maintenance due”.

  • overdueEquipment(equipment, states, now) names the equipment whose due date (its maintenanceParameterId) has passed, with unknown while a bound date is not current; pass a site and its equipment, or a meter list for verification dates. dueDateParameterIds(equipment) lists those parameters for the live demand.

  • SiteActiveAlarmBadges shows the active alarms of one piece of equipment as compact badges, such as in a panel header.

  • SiteAlarmList lists one piece of equipment’s active alarms, and those whose state is unknown, with their severity; with canEdit it lists every alarm with Edit and Remove. SiteEquipmentSetup is the setup row under a piece of equipment: its controls with Edit and Remove, reordering of each list with two or more items, and adding a reading, alarm or control, through onEdit(kind, item | null), onRemove(kind, item) and onReorder(kind) (SiteEquipmentActions). SiteItemActions is the Edit and Remove pair they use, for a dashboard’s own setup lists, such as SiteReadings actions.

  • SiteEquipmentView composes them for a site or one piece of equipment: SiteReadings, then SiteAlarmList, EquipmentOperations and, while canEdit, Edit and Remove on every reading and SiteEquipmentSetup. variant="site" spreads the readings across the page; equipment is the compact block of a panel. It takes the dashboard’s reading icon, the operations’ history and dueDate, and the SiteEquipmentActions. aside, such as a pump list, sits beside the alarms and operations on wide screens; commands goes between the alarms and the operations, and operationControls limits the controls the operations offer, for a dashboard that operates some commands through SiteCommandButtons.

  • SiteFigure is a labelled value for a dl strip (size large, medium or small; wide spans two columns). SiteIndicatorLights shows states as a fixed row of lights (SiteLight: icon, label, on, off or unknown, a tone, optional detail and stale), dim while off or unknown, so the row never shifts as states change. useParameterNames maps parameter IDs to their names in the UI language, such as alarm flags named after their parameter.

  • SitePageHeader and SiteNotFound give a site page its back link (keeping overview filters), title, status line, and the setup toggle. SiteSection is a plain page section with a small heading and optional actions.

  • FleetRoutes is a fleet dashboard’s root: overview at /, site(site) at <sitesPath>/<id>, and subpage(site, path) for pages below a site’s own, such as /sites/<id>/points/<id>, returning null for one that does not exist. A missing site or page shows SiteNotFound with notFound and backLabel. matchSitePath(path, sitesPath) splits a path the same way.

  • Setup mode of a site page: newSiteLocation(path, overviewSearch) is a new site’s page with setup=1 and the overview’s query. useSiteSetup({ search, canEdit, pageParams }) starts in setup mode from that flag and returns editing and setEditing for SitePageHeader, and overviewSearch for links back to the overview, without the flag and the pageParams only the page uses. useSiteEditor(site) holds the open setup dialog (editor, open, close) and baseline, the site it opened with, as its conflict baseline.

  • PhaseValues shows phase readings on one line, L1 first (“231.2 · 234.0 · 231.3 V”): one unit when the bound phases share it, otherwise a unit per value, and ”—” for an unbound phase. SiteReadingCell is an overview column built on it, for columns: the phases, then the site’s readings of that column (named when there are several), or ”—”.

  • Control mode: siteControlMode({ automatic, local }, states) reads who controls a site from its conditions: local while the local selector is on, otherwise automatic or manual, unknown while that feedback is unavailable, and null when the site reports neither. The mode never changes the status. siteLocalControl(local, states) returns on, off, unknown, or unbound. SiteModeBadge shows the mode beside the status, and useSiteControlModeLabel labels it, such as for a mode filter. siteActivity(state) reads active, inactive, or unknown from the activity feedback alone, and null while there is none.

  • SiteCommandButtons shows a site’s or its equipment’s commands as buttons grouped by purpose (switching, mode, other); siteCommandEntry(control, pressed) turns a configured command into one. Each opens the write sequence, so commands of several steps work. The buttons are disabled while the local selector is on and explain why; in automatic or unknown mode a switching command warns that the controller may switch it back; a dialog whose command changed in setup while it was open cannot run. canWrite carries the user’s write access; without it nothing shows.

  • Group commands write one command to many selected sites, for selection actions:

    • buildSiteGroupCommand({ sites, action, command, localControl, stateOf, modeOf, switching }) lists the writes. It includes each site whose command is one write and leaves out, with a reason, sites without a command, with several steps, under local control, or sharing a command parameter with another selected site at a different value. Sites writing the same parameter and value (a shared group register) share one write. switching counts sites in automatic or unknown mode for the warnings. More than siteGroupCommandLimit (100) sites block the batch.
    • sameSiteGroupCommand(reviewed, current) tells whether a list rebuilt from live feedback still matches the reviewed one; changed data notes alone do not count.
    • SiteGroupCommandConfirmation reviews the list with the sites’ state now (currentText) and the target, the warnings and the left-out sites; while outdated it offers Update list and cannot be confirmed. Confirming sends one batch.
    • siteGroupCommandResults(write, results) gives each site its parameterWriteOutcome, and SiteGroupCommandResults shows them with the error and the site’s live state (liveText). siteGroupRetrySelection(rows) selects the failed and unanswered sites again. Nothing is retried or reversed automatically.
  • SiteBindingEditor, FleetDeleteDialog, BoardEditor, FleetOrderEditor, and SiteDuplicateDialog are setup forms. Each validates its input and calls onSave with the result; the dashboard applies it inside useDashboardConfigurationMutation and throws FleetConfigurationError for conflicts, which fleetConfigurationFormError turns into form errors. For a change made without a form, such as a drag, fleetConfigurationErrorText gives the same failure as one sentence; a conflict asks to make the change again on the latest configuration, so the dashboard shows that one after the error. useParameterInfo, writeBindingError, geoBindingError, and counterBindingError check bound parameters.

  • FleetDeleteDialog with details explains what else the deletion changes, such as items that move elsewhere.

  • SiteBindingEditor with command ({ category, name? }) edits a named command slot: a command with a fixed purpose and no setpoint choice; name fills the name of a new command, and title replaces the dialog title.

  • SiteDuplicateDialog matches every binding to the same relative path under a target branch and lists the bindings that will be left out before saving. Without zones it has no zone field. With onSave the user picks one branch and names the copy. With onSaveAll the user picks one or more branches from a searchable list, gets one copy per branch named after it (the branch’s last segment, or its path when two end alike; there is no name field), sees the left-out bindings per branch, and the dialog passes every copy (SiteDuplicateCopy: name, zoneId, branch, mapping) to onSaveAll at once, for one configuration update.

  • Field groups render shared rows in a fleet dashboard’s own forms:

    • SiteSlotFields renders the reading and condition slots of a defineSiteRoles declaration in declaration order: a parameter per slot, a unit for readings (filled from the parameter unless the user typed one), and a comparison and value for conditions. It takes fields pointing at the rows built by the declaration’s formValue, roles, and labels. siteSlotErrors(invalid, path) turns the invalid rows of fromForm into field errors. The form schema types are siteSlotRowsType for those rows (siteSlotRows(keys, row) builds them), optionalParameterIdType for a single optional binding, and siteNameType for a name.
    • SitePlacementFields edits a site’s zone and map position: fixed coordinates, typed or picked on a map, or a geo parameter for a mobile unit. sitePlacementFormValue(site) is its starting value (schema sitePlacementFormType). It takes the board’s zones and, as context, sitePlacementContext(sites, siteId). sitePlacementFromForm(value, path, parameterInfo) returns { placement, errors }, where placement holds the site’s zoneId and position. parseSiteCoordinate accepts decimal degrees with a dot or comma.
    • SiteStarterFields offers optional pickers for the typical readings, alarms and setpoints of a new site or piece of equipment, each with its label. siteStarterFormValue(starters) is its starting value (schema siteStarterFormType), and siteStarterBindings(starters, value, path, parameterInfo) returns { bindings, errors } (a setpoint needs a writable parameter), the bindings that starterEquipment takes.
    • CommandStepsField edits a command’s ordered write steps (a parameter and a value each, with Earlier and Later). commandStepsFormValue(steps) is its value, commandStepsErrors(value, path, parameterInfo) checks it (at least one step, writable verified parameters, values), and commandStepsFromForm reads the steps. SiteBindingEditor uses it for commands.
    • DashboardPickerField (a field component of useAppForm) picks one of the workspace’s dashboards; exclude leaves some out, such as the dashboard being configured.

useParameterInfo() returns an async lookup (parameterId) => Promise<ParameterInfo | "unavailable" | null> that reads the parameter from the server. ParameterInfo has unit, writable, datetime, geo, the value-type name valueType and backgroundReadingPeriodMs (null when background readings are off). Null means nothing is selected (an empty ID). "unavailable" means the lookup failed, and the binding checks block saving then. The binding checks take its awaited result; sitePlacementFromForm, siteStarterBindings and commandStepsErrors take the lookup itself and are async too.

const parameterInfo = useParameterInfo();
async function setpointError(parameterId: string) {
return writeBindingError(await parameterInfo(parameterId));
}

useNow(intervalMs) re-renders with the current time in milliseconds every minute by default, for due dates, ages and countdowns; useNow(null) stops the ticks, such as while no age is on screen. useIsMobile() is true below Tailwind’s md width, for layouts that switch between a table and cards; useMediaQuery(query) follows any CSS media query.

Generic strings (search, filters, statuses, editors) come from this package’s catalog in every supported language; dashboards pass their domain labels.

Dashboards for meters (energy, heat, water, gas) show the consumption of a counter per period with a comparison. Periods, comparison windows and classification come from @overvis/hmi-shared (counter periods); every value is the server’s readings.aggregate delta. The dashboard never sums or integrates anything.

const { period } = useCounterPeriod({ defaultPeriod: "today" });
const counters = useCounterTotals({ workspaceId, counters: [energyId], periods: [period] });
<CounterPeriodSelect defaultPeriod="today" />
<CounterTotal total={counters.find(energyId, period)} unit={counters.unit(energyId)} />
  • useCounterTotals({ workspaceId, counters, periods, timeZone? }) returns totals (one { parameterId, period, current, previous, change } per counter and period), find, unit, isPending, isError, and retry.
    • Calendar periods use timeZone, or the browser’s zone when it is omitted.
    • Each window asks readings.aggregate for delta, first, latest and min, at most 100 pairs per request. readings.timeExtent runs once per counter and is cached for the page. At most four requests of each kind run at once.
    • When a window ends after the cached extent, a latest over the last background period plus a minute shows whether readings reach the window end. The extent is requested again only when they don’t, or when a counter cached without readings has started reporting.
    • A counter with readings recorded on its device is partial in a window that ends after its deviceLog.completeThrough, unless background readings reach the end. Its metadata is loaded again every minute, as is that of a Modbus counter without background readings, which may start receiving them; when the watermark moves, the counter’s extent and window values are loaded again, so totals complete after the next pull.
    • Results refresh just after each UTC minute closes. Completed periods are not requested again.
    • A counter that is not num gets no-data totals. When a counter’s parameter cannot be read, isError is true and retry reads it again.
  • CounterTotal shows the value with unit and a change badge, captioned for the period (for example “vs same time yesterday”). Without readings it shows “No data”, never 0. A counter reset shows “Counter reset” without a value. Partial coverage shows “Since …” or “Until …” and no change. While total is undefined it shows a skeleton. compact fits a table cell or list row: a smaller value without the change badge. change={false} drops the badge at full size and valueClassName restyles the value, such as a larger headline. Values are grouped and lose decimals as they grow; formatCounterValue(value, unit, locale) formats the same way elsewhere.
  • CounterChange shows only the change badge of a total, with the comparison as its tooltip or, with comparison, as a caption; nothing unless both windows have values. It complements a compact total, which has no badge. rising="good" tints a rise green and a fall amber, rising="bad" the reverse, for example for export and for consumption or cost.
  • CounterBars charts a counter’s increase over a period: per hour up to two days, per calendar day in timeZone beyond (counterBucketCadence(period) gives the same choice for a chart of your own). Each bar is that bucket clipped to the period and runs from the latest reading before it, the first from the reading the period total starts from, so the bars add up to the total however short an edge bucket is; a reset inside or between buckets draws no bar. It reads closed days (readings.days) or quarter hours (readings.cadence) once per aggregation and only the period’s edges with readings.aggregate, so a month takes about six requests; where a zone’s days or hours do not start on such a boundary (days in UTC+05:30), it reads each bucket. A rolling period’s first bucket and the bucket still in progress cover only part of their hour or day: they are lighter and left out of the average, which a dashed line marks. Up to 16 bars carry their values, the axis shows dates (YYYY-MM-DD) or hours (HH:mm), and the tooltip gives the bucket start and value. Nothing renders when the period has no increase.
  • useReadingPeak(workspaceId, parameterId, window) is the reading with the largest magnitude in a window [from, to), with its sign and time, such as the peak power of the period (negative when export peaked), or null.
  • useClosedMinute() is the current time, sampled on mount and again just after each minute closes; it is not itself a minute boundary. resolveCounterPeriod(id, useClosedMinute(), timeZone) floors a period to the last closed minute and returns the windows useCounterTotals uses, for example for a chart or readings.minMax over the same period.
  • CounterPeriodSelect is a select bound to a URL search key (searchKey, default period). useCounterPeriod reads the same key with the same periods and defaultPeriod. An unknown value in the URL selects the default, and choosing the default removes the key.
  • useResolvedCounterPeriod({ timeZone, defaultPeriod, periods, searchKey }) resolves the period in the URL that way and refreshes it at each closed minute. useDashboardTimeZone(setting) turns a dashboard’s time zone setting (dashboardTimeZoneType from @overvis/hmi-shared) into a zone: the viewer’s own for browser, the workspace’s when the setting is absent.
  • CounterTotalsNotice shows why totals stay empty after their requests failed, with Retry, as a notice for the totals of a page.
  • counterTotalValue(total) is a total’s value for sorting and bars, null for no data, a reset or while loading. counterSlotUnit(totals, slot) is a bound counter’s unit from its parameter, or the unit saved with the binding.
  • counterShare(part, whole, partUnit, wholeUnit) is a part’s share of its whole over the period, only for whole-period values in the same unit; formatCounterShare shows it as a percentage. commonCounterUnit(units) picks the unit most counters use, and rankByCounterTotal(items, counterOf, totals, periodId, count) ranks items by their counter in that unit, largest first, such as the largest sites of a portfolio.
  • counterBindingError(info) accepts num parameters only. For an integer it explains that a calculated parameter must scale the pulses to the metered quantity. Pulse counters are bound through such a calc parameter. numericBindingError(info) does the same for a reading charted or compared over time, such as power for a peak.
  • CounterBindingWarnings({ counters, share }) warns in a setup form, without blocking the save, about a counter that is not recorded (its history is recorded only while someone views it) and, with share, about a counter whose unit differs from its parent’s.
  • PeriodSettingsFields edits a board’s default period and the time zone its calendar periods follow: the workspace’s, each viewer’s own, or a named zone. Its fields point at a periodSettingsFormValue({ defaultPeriod, timeZone }), and periodSettingsFromForm(value, path) reads it back and returns a field error for an unknown zone. BoardSettingsFields (with boardSettingsFormValue and boardSettingsFromForm) is the display and zone part of BoardEditor, for a board dialog with more settings.

A counter reset is flagged, never corrected. Unit conversion is not done; units come from the parameters.

The assistant reads what an open dashboard page shows through the host’s page state. A page publishes a summary with usePublishDashboardSummary(getSummary). getSummary runs only when the assistant asks, with the values of the latest render. It may be async, and returns one of two kinds:

  • fleet (buildFleetSummary): site count, status totals with the labels the page shows, active alarms critical first, and sites most urgent first with overdue maintenance. FleetOverview publishes it.
  • site (buildSiteSummary): one site’s status and label, its active alarms, overdue maintenance, and optionally fleet, a buildFleetSummary result for the other sites. A site page shows only its own values, so it reads the others in getSummary with useReadParameterValuesOnce(). The reader adds a temporary demand to the shared live values and resolves when every value has arrived or failed, or after 8 seconds with the states known then.

A fleet site page publishes both with usePublishSiteSummary, which takes the site, all sites, the page’s readOnce, the parameterIds one site’s summary reads (such as siteAttentionParameterIds), evaluate(site, states, now) returning its state, active alarms and maintenance (SiteSummaryEvaluation), and the page’s statusLabels. Every site is read once, so the site and the fleet cannot contradict each other.

Each alarm (SiteActiveAlarm) names the equipment that raised it, or null for a site alarm, its name, and its severity. Maintenance (SiteMaintenanceSummary) lists overdue equipment and whether some due date is unavailable. Each site reports alarmUnknown and maintenanceUnknown, so unavailable feedback never reads as an empty, all-clear list; a fleet also counts such sites in alarmUnknownSites and maintenanceUnknownSites, including sites past the list limit. Lists stop at dashboardSummaryListLimit (20) entries, and the summary counts the rest. The summary stays small for any fleet size. Only one page of a dashboard publishes at a time. Outside a mounted dashboard the hooks do nothing.

Both builders take each site’s state as { status, alarmUnknown } (SiteSummaryState), so a page with its own status logic, such as map pins, maps it onto the site statuses with the labels it shows. A device dashboard can publish itself as one site, with its fault flags as SiteAlarmBindings (a condition with an equipment name, a name, and a severity) for evaluateSiteStatus and activeSiteAlarms; see Publish a page summary.

Generated drawings: derivedEntityIds gives each configuration entity stable drawing item IDs, hashedEntityId(text) names an item by what it connects, such as a pipe keyed by its line and end ports, validateDrawingLimits rejects drawings beyond the graphics limits, and reconcileGeneratedDrawing applies a configuration change to a drawing an editor may have customized, keeping its layout and extra items and placing new items below the artwork. Each end of a generated connection that the editor left alone follows the configuration, so reordered or moved equipment is reconnected; an end the editor moved stays where they put it, and a connection whose end moved loses its custom bends. Symbol properties and generated text the editor left alone also follow the configuration, such as a meter’s units after a rebinding or a renamed board’s label. A free end of an existing connection is a point in the layout the editor’s artwork replaced, so it stays where it is, and an end left on equipment that loses its port keeps its spot on that equipment. A free end of a new connection, such as a feeder starting on a busbar, stays on the item it lands on: it moves with new items below the artwork and with an existing one to where the editor keeps it. A generated connection attached to a port follows its equipment. graphicsSymbolElement, graphicsShapeElement and graphicsConnector from @overvis/hmi-shared/graphics build a placed symbol, a plain element and a line for such a drawing (see shared).

Stamp via dashboard.create({ template }). Template IDs are in dashboard procedures. The compiler is hmi build (@overvis/hmi-cli/dashboard-build); see the CLI reference. For a live dashboard’s configuration, empty state, live values and writes, see live data.

The build writes unminified ESM plus utilities-only index.css (scan that dashboard’s *.ts / *.tsx / *.js / *.jsx, tokens from this package’s styles-reference.css as installed in the project, no preflight) and the hmi-dashboard.json manifest. Vite, its plugins, Tailwind CSS, and TypeScript load from the first of the project, the working directory, and the CLI’s own installation that has all of them. The build writes the CSS; dashboards import none. The host declares two cascade layers for the generated utilities: plain classes use dashboard, below the host utilities, and variant classes use dashboard-variants, above them, so grid-cols-2 @min-[30rem]:grid-cols-3 switches even when the host also defines grid-cols-2. The build also scans this package’s code next to styles-reference.css; a variant class found there stays in dashboard, so shared UI components keep their interaction states inside the scoped dashboard stylesheet. Important variants (sm:p-4!) also stay in dashboard, because !important reverses the layer order. A data-theme="light" subtree stays light in the dark theme. See dashboard styling. config.ts must export dashboardConfigType; the build writes config.schema.json.

The package root exports the form scaffold that every UI in Setup, cloud and local hosts, and the shared components uses to submit fields to the API: useAppForm, FormRoot, SubmitButton, mutationFormValidators, focusFirstInvalidField, field helpers, and mapTrpcToFormErrors. Its shape and behavior are in Dashboard UI and forms, and the steps in Build a form that saves through the API.

SymbolEntryRole
trpc.createTRPCReact<PublicAppRouter>()
createQueryClient./hostDefaults: staleTime 30s, retry: false
createTrpcClient./hostConfigures requests and subscriptions for the host

Subscriptions use httpSubscriptionLink (+ EventSourcePolyfill when custom headers are set — needed for API-key local hosts).

Local hosts pass url, credentials: "omit", and getHeaders for x-api-key. Hosts signed in with a browser session can use the defaults. Hosts may supply fetch to observe or customize query and mutation HTTP requests, including isolated streams. Subscription requests continue to use EventSource.

isolatedPaths lists procedures whose calls each get their own HTTP request, so aborting one call (for example parameter.readValuesStream) cancels exactly that stream. client: { name, version } sends x-hmi-client: <name>/<version>; the API answers client_outdated when that version is older than it supports. localSetup: true makes dashboard.get and graphics.get return the dashboard configuration and drawings that the scaffold’s /__hmi/setup/dashboards/<id> endpoint serves from unpushed setup.db rows. A local drawing reports the revision it will have after a push, so saving over unpushed local changes is refused as a conflict. Without the endpoint the queries return saved data; when it fails they fail. LocalWorkspaceApp sets it.

trpcContext (from ./host) is the React context trpc uses. A host with its own createTRPCReact over a larger router of the same API passes it as context, so its hooks share trpc.Provider’s client and query cache.

  • Design system: Tailwind 4 + shadcn components, exported from the ./ui entry.
  • The catalog is the Overvis component set, not a synchronized mirror of shadcn. Components may be added, adapted, or removed as the product evolves.
  • Hosts import @overvis/hmi-workspace-ui/styles.css once (font: Inter variable).
  • ThemeProvider persists light/dark/system; exposes resolvedTheme to dashboard mounts.
  • cn utility for class merging.

The package’s translation catalogs support en, pl, de, uk, and ru. The browser checks localStorage, then cookies, browser preferences, and finally English.

Hosts await initializeLocale() before mounting translated components. Additional host catalogs register their load function with browserLocale.register before initialization. The package loads catalogs by language and caches them. useTranslation() returns a typed translator and subscribes to language changes; useLocale() subscribes when a component only needs the locale. t() reads the active language outside rendering, such as in event handlers or non-React helpers.

await setLocale(locale) loads all registered catalogs before updating components, the document language, and the saved preference. It does not reload the page. Failed loads retain the current language. LanguageSwitcher reports failure and lets the operator retry.

Literal keys and placeholder arguments of t() are checked by TypeScript; catalogs need no generation step.

The pages createWorkspaceRoutes builds behave as the workspace pages reference describes; the library adds no host-specific behavior to them. Their lists, editors, and drawings follow setup changes, renames, suspension, and access changes in the open workspace without a page reload.

Dates in the pages use YYYY-MM-DD HH:mm:ss; report fields use the report time zone. CSV exports prefix headers and text cells whose first non-whitespace character is =, +, -, or @ with an apostrophe before RFC 4180 quoting, so spreadsheets do not run them as formulas. Numeric cells, including negative values, stay numeric.

WorkspacePullInstructions mints workspace.mintPullToken with a cookie session and editor, admin or owner access, and shows:

pnpm dlx overvis-hmi pull <token>
# or npm / yarn variants

Mounted on workspace settings (cloud) and the empty workspace index — not under Setup.

  • Authentication, account switching, or the assistant chat panel
  • Serving built dashboard assets (API /static)

useToast() returns a helper with success, info, warning, and error methods that take a plain-text message. An optional second argument accepts workspaceId, workspaceName, and an app-relative href. The helper displays a toast and records the same text in browser history.

MessageHistoryProvider identity={userId} keeps the history of one identity; without an identity, history stays in memory only. MessageHistoryBell shows it. MessageScopeProvider scope={{ workspaceId, workspaceName }} attaches workspace metadata to descendant messages. A helper keeps the workspace of the component that returned it, so a completion message from an asynchronous action records its original workspace. Changing the history identity detaches pending callbacks and removes visible toasts from the previous identity. The steps for a custom host are in Embed the workspace UI in a React app.

The bell keeps the newest 100 messages for seven days, with browser-local timestamps, seen markers, Mark all seen, and Clear. Expired records are also removed for inactive accounts and local workspaces when history refreshes. Storage failure falls back to memory. History and seen state synchronize across tabs without displaying duplicate toasts. Stored messages retain their original display language. Only visible rows in the active Messages panel become seen while the browser tab is visible and focused.

A host may provide notifications (tab content) and notificationUnreadCount to add the server Notifications tab and badge to the same bell. Tab controls appear only when the host supplies notification content; the bell opens on that tab while the count is above zero. These do not change local message retention. showNotificationToast(kind, title, { description, action }) shows a toast for something the host lists elsewhere, such as a server notification, without recording it in Messages.

The legacy toast.success/info/warning/error export records history using the identity and workspace active when called. These four methods preserve Sonner message arguments, options (including durations and actions), and returned toast IDs. Rich React messages are recorded from their rendered text. Unlike useToast, it does not keep the context of the action that called it.