App Studio is in release 3.0 as Beta: a paid add-on that no edition includes by default. A licence or a trial licence that includes the app_studio feature switches it on. The module is on by default, so the licence decides. This reference may change while App Studio is in Beta; the SDK changelog records each change.
Generated from the SDK sources by scripts/appstudio-sdk-docs.cjs for runtime-v1. Do not edit; run npm run appstudio:docs:sdk.
@maestrohub/sdk
Beta: look up the App Studio SDK calls an app uses to read live topics and the plant, write through declared actions and keep its own storage.
- Runtime
- runtime-v1
- Exports
- 244 (103 values, 141 types)
Runtime, data, live-topic, action and storage APIs available to App Studio apps (Beta in 3.0).
Runtime: runtime-v1. 244 exports (103 values, 141 types). Import with:
import { ... } from '@maestrohub/sdk';
Hooks
useActionPermission
function useActionPermission(action: string): { granted: boolean; reason?: string; }
useActionPermission answers one of the manifest's declared actions for
the viewer: the scope its door asks AND the viewer's own right on the
bound resource (an OPC UA write the viewer may not run, a topic they may
not publish to). Render the control disabled with reason from it;
never hide it. Fail-closed: an action the manifest does not declare, or
a shell that did not answer, is not granted. The server still checks
every write; this is what the viewer is told before they press.
useAggregate
function useAggregate(topic: string, options: AggregateOptions): ReadState<AggregateResult>
useAggregate reads a topic's values folded into time buckets (data.aggregate); needs uns:read.
useAlerts
function useAlerts(limit?: number): AlertsState
useAlerts renders the workspace's alarm transitions as they happen
(fires and clears), newest first, at most limit (default 200). Pair
it with alerts.recent() for the alarms that were already active when
the app opened. Needs the uns:read scope.
Example
const a = useAlerts();
return a.error ? <ErrorNotice error={a.error} /> : <Timeline items={a.events.map(e => ({ at: e.firedAt, title: e.topicPath, tone: e.cleared ? 'ok' : 'critical' }))} />;
useAppContext
function useAppContext(): AppContextState
useAppContext returns user/workspace/theme (throws outside AppProvider).
useAttachmentUrl
function useAttachmentUrl(collection: Collection, key: string, id: string | null): string | null
useAttachmentUrl fetches one attached photo and returns a URL an <img> can show (null while loading or for a missing file). The URL is released when the component unmounts or the file changes.
useAttachments
function useAttachments(collection: Collection, key: string | null): AttachmentsState
useAttachments lists one stored record's files (photos, PDFs) and adds
or removes them. Pass key: null while the record is not saved yet:
files can only hang off a record that exists. Failures (wrong type, a
file over the limit, the app's total reached) reject from add with an
ApiError whose message says why; show it.
useClock
function useClock(): AppClock
useClock is the clock the app shows: the workspace's time zone (or the
viewer's own when the workspace sets none, timeZoneSource: "viewer")
and the viewer's locale. Times are stored in UTC; this is how they are
read. formatDateTime, dayWindow and sinceClock use the same clock.
Example
const clock = useClock();
<Text>Plant time ({clock.timeZone})</Text>
useConnection
function useConnection(): ConnectionState
useConnection is the live connection as a whole: "live", or
"reconnecting" since the moment it dropped. A kiosk shows it, so a
value on the wall is never mistaken for a live one (see
ConnectionNotice in @maestrohub/app-ui, and stale on useTopic).
useContract
function useContract(topic: string): ReadState<TopicContract | null>
useContract reads a topic's contract by path (data.contract): its type, unit and bounds, answered from the topic's meaning; data is null for an untyped topic. Needs uns:read.
useEventCount
function useEventCount(topic: string, options: EventCountOptions): ReadState<EventCountResult>
useEventCount reads how many times a condition held or began, per bucket (data.eventCount).
useExecutionStream
function useExecutionStream(pipelineId: string, limit?: number): ExecutionStreamState
useExecutionStream renders one pipeline's live run events as they
happen: execution start and finish plus per-node progress, newest
first, at most limit (default 200). Needs pipelines:read
(pipelines:execute carries it). Pair it with the executions read API
for history: only manual and API-submitted runs stream live;
scheduled and message-triggered runs don't emit these events. An
empty pipelineId renders an idle stream ('closed', no error) so a
not-yet-bound template stays honest.
Example
const run = useExecutionStream(pipelineId);
return run.error ? <ErrorNotice error={run.error} /> : <Timeline items={run.events.map((e) => ({ at: e.endedAt ?? e.startedAt ?? '', title: e.type, tone: e.type === 'execution.failed' ? 'critical' : 'ok' }))} />;
useFunction
function useFunction(action: string, target: { connectionId: string; functionId: string; }): FunctionState
useFunction runs one declared connector-function action from a
component: action is its manifest "actions" name (what may this viewer
do, and why not), target the bound function. A failed call is an error
like any refusal: render it with <ErrorNotice error={fn.error} />.
useHistory
function useHistory(topic: string, options: HistoryOptions): ReadState<RangeResult>
useHistory reads a topic's stored values over a window (data.range); needs uns:read. For live values use useTopic.
usePendingWrites
function usePendingWrites(): PendingWrites
usePendingWrites lists the records waiting to be sent while the connection is down, oldest first. Show the count: a record that waits has not reached MaestroHub yet.
Example
const pending = usePendingWrites();
{pending.count > 0 && <Badge tone="warn">{pending.count} waiting to send</Badge>}
usePeople
function usePeople(options?: PeopleSearch): ReadState<PeoplePage>
usePeople lists who can open this app (people.search), re-read when the search changes. Debounce a typed search before passing it in.
usePermission
function usePermission(scope: string): { granted: boolean; reason?: string; }
usePermission reports whether the current VIEWER holds a scope the app's manifest requested, the FR-29/42 data every bound actionable control renders its state from. Unknown scope → not granted, with a reason (fail closed, and the UI still explains itself).
usePersonPicture
function usePersonPicture(person: Pick<Person, "kind" | "id" | "has_picture"> | null | undefined): string | null
usePersonPicture returns a URL an <img> can show for a person's picture, or null (no picture, a group, or still loading); show initialsOf(person) then. The URL is released on unmount or when the person changes.
usePipelineRun
function usePipelineRun(action: string, target: { pipelineId: string; nodeId: string; cancelAction?: string; }): PipelineRunState
usePipelineRun runs one declared pipelines.submit action from a
component and waits for the run: action is its manifest "actions"
name, target the bound pipeline's manual-trigger node. The sibling of
useFunction: render the outcome with <ErrorNotice error={run.error} />.
useRatio
function useRatio(options: RatioOptions): ReadState<RatioResult>
useRatio reads one topic folded over another per bucket (data.ratio): yield, availability, any good/total KPI.
useStateDuration
function useStateDuration(topic: string, options: StateDurationOptions): ReadState<StateDurationResult>
useStateDuration reads how long a state topic spent in each state (data.stateDuration).
useTopic
function useTopic(topic: string): TopicState
useTopic renders the latest value on a topic; status/error explain the gaps.
Example
const t = useTopic('plant/line1/oee');
return t.error ? <ErrorNotice error={t.error} /> : <MetricCard label="OEE" value={t.value ?? '—'} />;
useTopics
function useTopics(paths: string[]): TopicsState
useTopics is useTopic for many topics at once, on the same single socket: a scene of six stations with four signals each binds 24 topics, and hooks cannot be called in a loop whose length changes. Returns a state per path (missing paths read as connecting). A new array with the same paths, in any order, changes nothing; a changed set subscribes and unsubscribes only the difference. A burst of messages renders once per frame (about 16 ms), not once per message.
Example
const t = useTopics(stations.map((s) => s.stateTopic));
const state = t[stations[0].stateTopic]?.value;
Components
AppProvider
function AppProvider({ children }: { children: ReactNode; }): JSX.Element
AppProvider connects the React tree to the bridge state. When the shell disables the app mid-session (kill switch, FR-21) it also covers the whole frame: a read-only wall dashboard must not keep showing stale numbers behind a token that no longer exists. Plain inline styles: the SDK does not depend on @maestrohub/app-ui.
Functions
ago
function ago(iso?: string | null): string
ago renders how long ago an ISO-8601 instant was ("12s ago",
"3m ago", "2h ago") and "—" when there is no instant.
apiFetch
function apiFetch<T = unknown>(path: string, init?: RequestInit): Promise<T>
apiFetch calls a platform API path (e.g. "/api/v1/uns/topics") with the current app token. Throws ApiError on refusals, including the case the UI should treat as "render disabled-with-reason", a 403 INSUFFICIENT_SCOPE.
apiFetchBlob
function apiFetchBlob(path: string, init?: RequestInit): Promise<Blob>
apiFetchBlob is apiFetch for a binary answer (an attachment download): the same token, headers and refusals, and the body comes back as a Blob.
clockFor
function clockFor(workspaceZone?: string): AppClock
clockFor is the one rule for an app's clock, used by the shell that hands it over and by the SDK when no shell did: the workspace's zone when it sets one, else the viewer's browser zone; the viewer's browser language for numbers and dates.
dayWindow
function dayWindow(daysAgo?: number, timeZone?: string): ClockWindow
dayWindow is a calendar day in the app's clock as UTC instants: "today"
in Istanbul starts at 21:00Z the day before. daysAgo 0 is today, 1
yesterday. Today's window ends now.
Example
const { start, end } = dayWindow(); // today, plant time
data.range({ topic, start, end });
fmt
function fmt(n: unknown, digits?: number): string
fmt renders a number for a tile or a cell: integers as they are,
anything else with digits decimals (default 1), and "—" for
anything that is not a finite number.
formatDateTime
function formatDateTime(at: When | null | undefined, options?: FormatDateTimeOptions): string
formatDateTime writes an instant in the app's clock and locale:
"15.10.2026 08:00" for a Turkish viewer of an Istanbul workspace, the
same instant a UTC kiosk would otherwise show as 05:00. "—" when
there is no instant.
Example
formatDateTime(entry.event.at) // "15.10.2026 08:00"
formatDateTime(row.ts, { show: 'time' }) // "08:00"
formatNumber
function formatNumber(n: unknown, digits?: number, options?: FormatNumberOptions): string
formatNumber writes a number in the viewer's locale ("1.234,5" for a
Turkish or German viewer) with digits decimals at most (default 1),
exactly digits with pad, as a percentage of a ratio with
percent; "—" for anything that is not a finite number.
formatNumberish
function formatNumberish(value: Numberish, options?: FormatNumberishOptions): string
Locale-format a number: toLocaleString(locale, options) for both kinds. The same rules apply to a 19-digit ExactNumber as to any number (the locale's grouping and decimal mark, the fraction rounded to maximumFractionDigits), but its digits never pass through a double: 1729600000000000001 reads 1,729,600,000,000,000,001 (see ExactNumber.toLocaleString).
fractionDigits
function fractionDigits(digits: unknown, fallback?: number): number
fractionDigits is the decimals count every kit number formatter uses:
digits as a whole number in Intl's and toFixed's common range
(0 to 20). A numeric string from JSON config ("2") counts as its number;
anything else unreadable is fallback (default 1). Clamped, never
thrown: a bad digits must not take a Table down during render.
initialsOf
function initialsOf(p: Pick<Person, "kind" | "name">): string
Up to two initials for an avatar without a picture ("?" when there is no name).
isConfirmationRequired
function isConfirmationRequired(err: unknown): err is ApiError
isConfirmationRequired: the platform wants the viewer's confirmation first.
isExactNumber
function isExactNumber(value: unknown): value is ExactNumber
True for an ExactNumber (a number a double cannot hold, kept with every
digit), from any copy of this module. typeof cannot tell: it says
'object'.
isNumberish
function isNumberish(value: unknown): value is Numberish
True for a number or an ExactNumber: every value JSON wrote as a number.
isRateLimited
function isRateLimited(err: unknown): err is ApiError & { status: 429; }
isRateLimited: the platform refused because this app (all of its viewers
together) exceeded its request budget. Back off for retryAfterSeconds;
prefer useTopic/useTopics for live values instead of polling REST.
labelOf
function labelOf(p: Pick<Person, "kind" | "name">): string
What a picker shows for an entry: its name, or a label when it has none.
mountApp
function mountApp(App: React.ComponentType): void
mountApp is the boot contract: the served document loads boot.mjs, which imports the app's default component and calls this. The bridge handshake completes BEFORE first render, so hooks never see a half-connected state; uncaught errors report to the shell (NR-3/FR-8), and a render error shows AppCrashBoundary's panel, not a blank frame.
num
function num(value: unknown, field?: string): number | null
num reads the numeric reading out of a topic value (a number, a
numeric string, or an object carrying one under field (default
"value"), the shapes a UNS record's value takes depending on how
the connector published it. null when there is no number.
parseJson
function parseJson<T = unknown>(text: string): T
JSON.parse that keeps every digit. Throws JSON.parse's SyntaxError on invalid input.
parsePayload
function parsePayload(raw: string): Pick<TopicMessage, "value" | "timestamp" | "quality" | "source">
parsePayload mirrors the shell's data explorer: enriched envelope → value; JSON → parsed; else raw.
print
function print(options?: PrintOptions): Promise<void>
print opens the browser's print dialog for this app (paper or Save as PDF). It resolves when the dialog closes. Call it from a click.
sinceClock
function sinceClock(hhmm: string, timeZone?: string): ClockWindow
sinceClock is the last time the app's clock read hh:mm (today if it
has passed, else yesterday), until now: the window of a shift that
started at 06:00 plant time.
Example
const shift = sinceClock('06:00');
stringifyJson
function stringifyJson(value: unknown, replacer?: Replacer | null, space?: string | number): string
JSON.stringify that writes an ExactNumber as a JSON number with every digit. Everything else (strings and their escaping, toJSON, undefined and functions dropped from objects and nulled in arrays, Dates, the replacer and indentation) is JSON.stringify's own: an ExactNumber is swapped for a placeholder string, which is then replaced by its digits.
subjectOf
function subjectOf(p: Pick<Person, "kind" | "id">): string
The address notifications.notify({ to }) takes: "user:<id>" or "group:<id>".
subscribeTopic
function subscribeTopic(topic: string, onMessage: (m: TopicMessage) => void, onError?: (e: LiveError) => void): () => void
subscribeTopic delivers the topic's last stored value (seeded), then
every value published on topic, until the returned function is called. Refusals (missing scope, topic outside
the viewer's access, app disabled) arrive on onError with a reason
and a remediation, never as silence.
viewerClock
function viewerClock(): AppClock
The viewer's browser zone and language: what is used when the shell says nothing.
Classes
ApiError
class ApiError extends Error
new ApiError(status: number, code: string, message: string, details?: unknown, retryAfterSeconds?: number, remediation?: string): ApiError
ApiError carries the platform's flat {code, message, details?} refusal
shape. details is what a refusal hands back for the caller to act on
(a storage 409 carries the winning document there).
Members
readonly code:stringreadonly details?:unknownreadonly remediation?:string | undefined: The platform's own "what to do instead": every refusal body carries one (rule 36).readonly retryAfterSeconds?:number | undefined: Seconds the platform asked us to wait (429 APP_RATE_LIMITED, Retry-After).readonly status:number
ConflictError
class ConflictError<T = unknown> extends Error
new ConflictError<T = unknown>(current: StoredDocument<T> | null): ConflictError<T>
Thrown when a CAS write cannot converge; carries the winner's document.
Members
readonly current:StoredDocument<T> | null
DownloadRefusedError
class DownloadRefusedError extends Error
new DownloadRefusedError(reason: string): DownloadRefusedError
DownloadRefusedError is what bridge.download rejects with when the
file could not be handed to the viewer, locally (over
MAX_DOWNLOAD_CHARS) or by the shell (download_refused). .reason is
the human-readable explanation the app should show (rule 36).
Members
readonly reason:string
ExactNumber
class ExactNumber
new ExactNumber(text: string): ExactNumber
A JSON number a double cannot hold, with every digit: String(x) and
x.text are the digits as received, Number(x) the nearest double,
x.toFixed(n) / x.toLocaleString() format the digits, and arithmetic
and comparisons use the nearest double. It is an object, so
typeof x === 'number' and Number.isFinite(x) are false: detect one
with isExactNumber(x) (or accept both kinds with isNumberish(x)),
and take Number(x) where a plain number is needed.
Members
isInteger:() => boolean: Is the number whole (no non-zero fraction digit)? Number.isInteger for an ExactNumber.static of:(text: string) => number | ExactNumber: Wrap a JSON number token. Throws on anything that is not one. Returns the token's double instead when the double holds it exactly, so callers never hold an ExactNumber for a number a plainnumberrepresents.readonly text:string: The number as written in JSON: a valid JSON number token.toFixed:(digits?: number) => string: Number.prototype.toFixed: rounded todigitsfraction digits (half away from zero, as decimal arithmetic rounds), every integer digit kept, no grouping.toJSON:() => string: Native JSON.stringify writes the digits as a string (see the header).toLocaleString:(locales?: string | string[], options?: Intl.NumberFormatOptions) => string: Number.prototype.toLocaleString for the decimal style: minimumFractionDigits, maximumFractionDigits (default 3, as Intl's) and useGrouping are honoured exactly: the integer digits grouped by Intl.NumberFormat over a BigInt, so the locale's own grouping applies (12,34,567 in en-IN) and no digit passes through a double. Any other option (currency, percent, compact or significant digits) has no exact form here and formats the nearest double.toNumber:() => number: The nearest double: whatNumber(x)returns.toString:() => string: The digits as received.valueOf:() => number
FunctionFailedError
class FunctionFailedError extends ApiError
new FunctionFailedError(input: ExecuteFunctionInput, result: ExecuteFunctionResult): FunctionFailedError
FunctionFailedError is what connectors.run and useFunction throw or
hold when the platform let the call through and the device or service
answered with a failure (success: false: a PLC refusing a write, a
query error). An ApiError, so <ErrorNotice> and existing catch blocks
render it: .message is the device's error, .result the whole answer.
Members
readonly connectionId:stringreadonly functionId:stringreadonly result:ExecuteFunctionResult
Label
class Label
new Label(opts: LabelOptions): Label
A label being built; every method returns the label, toString() the ZPL.
Members
barcode:(xMm: number, yMm: number, value: string, options?: BarcodeOptions) => Label: A Code 128 barcode.box:(xMm: number, yMm: number, widthMm: number, heightMm: number, options?: BoxOptions) => Label: A rectangle outline (or a line when one side is 0).copies:(n: number) => Label: Print this many copies (1 to 999, default 1).qr:(xMm: number, yMm: number, value: string, options?: QrOptions) => Label: A QR code (error correction M).text:(xMm: number, yMm: number, value: string, options?: TextOptions) => Label: A line (or wrapped block) of text in the printer's scalable font.toString:() => string: The label as ZPL, from ^XA to ^XZ, UTF-8 (^CI28).
PipelineRunError
class PipelineRunError extends ApiError
new PipelineRunError(code: string, executionId: string, message: string, remediation: string): PipelineRunError
PipelineRunError is what pipelines.run rejects with when the run it
started failed, was cancelled, or outlasted the wait. An ApiError, so
<ErrorNotice> renders it; .executionId names the run.
Members
readonly executionId:string
Constants
ALERTS_FEED
const ALERTS_FEED: "alerts"
The pseudo-topic the alarm feed reports under in LiveError.topic.
BASE_CSS
const BASE_CSS: string
The base stylesheet (installed once, by mountApp).
CODE_ACTION_NOT_DECLARED
const CODE_ACTION_NOT_DECLARED: "ACTION_NOT_DECLARED"
ApiError.code when an app-token request hits one of the three write
doors (publish, connector execute, pipeline submit) without a matching
declared action in the app's manifest. The fix is a manifest change
and a republish, not a retry.
CODE_APPROVAL_REQUIRED
const CODE_APPROVAL_REQUIRED: "APPROVAL_REQUIRED"
ApiError.code when the matched manifest action is on the "approval"
rung: the write guard refuses direct calls to it, so such an action only
runs through a pipeline with an approval gate.
CODE_CONFIRMATION_REQUIRED
const CODE_CONFIRMATION_REQUIRED: "CONFIRMATION_REQUIRED"
ApiError.code when the platform's write guard requires the viewer's
confirmation for a rung-"confirm" action and the request carried no
confirmed-at assertion. Retry the same call with { confirmed: true }
from the confirmed click; isConfirmationRequired checks for it.
CODE_FUNCTION_FAILED
const CODE_FUNCTION_FAILED: "FUNCTION_FAILED"
The code a FunctionFailedError carries.
CODE_OFFLINE
const CODE_OFFLINE: "OFFLINE"
ApiError.code when a write was not sent because the connection to
MaestroHub is down. Nothing was done; the same action may be tried
again once it is back.
CODE_PIPELINE_RUN_CANCELLED
const CODE_PIPELINE_RUN_CANCELLED: "PIPELINE_RUN_CANCELLED"
... when the run was cancelled.
CODE_PIPELINE_RUN_FAILED
const CODE_PIPELINE_RUN_FAILED: "PIPELINE_RUN_FAILED"
The code a PipelineRunError carries when the run failed.
CODE_PIPELINE_RUN_TIMEOUT
const CODE_PIPELINE_RUN_TIMEOUT: "PIPELINE_RUN_TIMEOUT"
... when the run outlasted timeoutMs (it goes on running).
CODE_RATE_LIMITED
const CODE_RATE_LIMITED: "APP_RATE_LIMITED"
CODE_RATE_LIMITED: the app's per-workspace fair-use budget is spent (429).
FALLBACK_COLOURS
const FALLBACK_COLOURS: Record<"light" | "dark", FallbackColours>
The fallback colours per mode. The one copy: the UI kit's palette builds on these, so a plain element and a kit component agree when the shell sent no tokens.
Members
dark:FallbackColourslight:FallbackColours
MAX_DOWNLOAD_CHARS
const MAX_DOWNLOAD_CHARS: 20000000
The size cap (in characters) on a download message's content.
bridge.download / bridge.requestDownload refuse locally above it,
and the shell (AppFrameHost) answers download_refused with the same
limit for anything that slips through.
MAX_OPEN_FILE_BYTES
const MAX_OPEN_FILE_BYTES: number
The size cap (in bytes) on an open_file message's data.
MAX_SCREEN_CHARS
const MAX_SCREEN_CHARS: 8000
The cap (in characters) on a screen message's text; the server keeps the same (application.MaxScreenChars).
PRINT_CSS
const PRINT_CSS: "\n.mh-print-only { display: none !important; }\n@media print {\n @page { margin: 12mm; }\n html, body { background: #fff !important; }\n .mh-no-print { display: none !important; }\n .mh-print-only { display: block !important; }\n .mh-page-break { break-before: page; }\n .mh-keep-together { break-inside: avoid; }\n}\n"
The print stylesheet (installed once, by mountApp and by print()).
PROTOCOL_VERSION
const PROTOCOL_VERSION: 1
The bridge schema version stamped as v on every message in both
directions. The app-side bridge ignores any shell message whose v
differs, and the shell does the same for app messages.
SCOPE_ALERTS_ACK
const SCOPE_ALERTS_ACK: "alerts:ack"
The scope alerts.ack needs; guard its button with usePermission(SCOPE_ALERTS_ACK).
SCOPE_APPROVALS_DECIDE
const SCOPE_APPROVALS_DECIDE: "approvals:decide"
The scope approvals.approve / approvals.reject need; guard their buttons with it.
SCOPE_APPROVALS_READ
const SCOPE_APPROVALS_READ: "approvals:read"
The scope the approvals inbox reads need (approvals:decide carries it too).
SCOPE_CONNECTORS_READ
const SCOPE_CONNECTORS_READ: "connectors:read"
The scope connectors.catalog needs: the functions the viewer may read (never connection configs).
SCOPE_FUNCTIONS_DISCOVER
const SCOPE_FUNCTIONS_DISCOVER: "functions:discover"
The scope connectors.execute needs for a discover-effect function (browse namespaces, enumerate operations).
SCOPE_FUNCTIONS_READ
const SCOPE_FUNCTIONS_READ: "functions:read"
Per-effect function scopes: guard a button with the one matching the function's effect.
SCOPE_FUNCTIONS_STREAM
const SCOPE_FUNCTIONS_STREAM: "functions:stream"
The scope connectors.execute needs for a stream-effect function (subscribe, tail, watch).
SCOPE_FUNCTIONS_WRITE
const SCOPE_FUNCTIONS_WRITE: "functions:write"
The scope connectors.execute needs for a write-effect function (commands, tag writes, publishes).
SCOPE_PIPELINES_EXECUTE
const SCOPE_PIPELINES_EXECUTE: "pipelines:execute"
The scope pipelines.submit needs; guard its button with usePermission(SCOPE_PIPELINES_EXECUTE).
SCOPE_PIPELINES_READ
const SCOPE_PIPELINES_READ: "pipelines:read"
The scope the execution stream needs: pipelines:execute carries
read, so an app that submits runs can also watch them land.
SCOPE_PLANT_DECIDE
const SCOPE_PLANT_DECIDE: "proposals:decide"
The scope that lets facts land at once: with it, plant.update applies
for a viewer who may decide proposals (unless the part is held to
another person's approval); without it every update waits under Review.
SCOPE_PLANT_DECLARE
const SCOPE_PLANT_DECLARE: "entities:declare"
The scope plant.declare needs.
SCOPE_PLANT_PROPOSE
const SCOPE_PLANT_PROPOSE: "proposals:write"
The scope plant.update needs: facts are proposed as the viewer.
SCOPE_PLANT_READ
const SCOPE_PLANT_READ: "entities:read"
The scope every plant.* read needs; guard plant panels with usePermission(SCOPE_PLANT_READ). The scope string stays entities:read, since manifests already declare it.
SCOPE_PLANT_RECORD
const SCOPE_PLANT_RECORD: "entities:record"
The scope plant.record needs: events land in the plant's log at once.
SCOPE_UNS_PUBLISH
const SCOPE_UNS_PUBLISH: "uns:publish"
Scope names the actions need, exported so apps guard with the same strings.
SCOPE_UNS_READ
const SCOPE_UNS_READ: "uns:read"
The scope live subscriptions need. live.subscribe checks the viewer's
grant for it before opening a socket and reports scope_missing on
onError when it is absent; guard live panels with usePermission(SCOPE_UNS_READ).
alerts
const alerts: { recent(options?: RecentAlertsOptions): Promise<AlertItem[]>; history(options?: AlertHistoryOptions): Promise<AlertHistoryItem[]>; subscribe: (sub: Parameters<typeof live.subscribeAlerts>[0]) => () => void; ack(input: AckAlertInput, options?: ActionOptions): Promise<AckAlertResult>; }
The alarm surface: recent and history read the platform's alert
feed (needs uns:read, since alert visibility inherits topic read), subscribe
streams transitions live (see useAlerts), and ack acknowledges one
alarm through the write ladder (a declared alerts.ack action bound
to an alerts binding, with its rung) and needs alerts:ack. The
viewer's own alert:ack authority per topic still bounds it: an app
never silences an alarm its viewer could not.
Members
ack:(input: AckAlertInput, options?: ActionOptions) => Promise<AckAlertResult>: Acknowledge one alarm with the viewer's authority. The answer says what it did:not_foundwhen no active alarm carries that key.history:(options?: AlertHistoryOptions) => Promise<AlertHistoryItem[]>: One topic's (or every topic's) transitions, newest first.recent:(options?: RecentAlertsOptions) => Promise<AlertItem[]>: Alarms currently known to the platform (active, cleared-recently, shelved), newest first.subscribe:(sub: Parameters<typeof live.subscribeAlerts>[0]) => () => void: Live transitions over the app's scoped socket (needs uns:read). See useAlerts.
approvals
const approvals: { list(options?: ListApprovalsOptions): Promise<ListApprovalsResponse>; get(id: string): Promise<ApprovalDecision>; approve(input: DecideApprovalInput, options?: ActionOptions): Promise<ApprovalDecision>; reject(input: DecideApprovalInput, options?: ActionOptions): Promise<ApprovalDecision>; }
The approvals surface: the human decision inbox for pipeline gates.
list / get read it (approvals:read; approvals:decide carries read);
approve / reject decide one; the pipeline then continues with the
held payload, or stops, through the write ladder (a declared
approvals.decide action bound to an approvals binding, with its
rung) and with the viewer's own approval:decide authority. There is no
live stream for approvals; poll list gently (the inbox changes at
human speed) or lean on the deadline the row carries.
Members
approve:(input: DecideApprovalInput, options?: ActionOptions) => Promise<ApprovalDecision>: Approve: the pipeline continues with the held payload, as the viewer.get:(id: string) => Promise<ApprovalDecision>: One decision, with its payload.list:(options?: ListApprovalsOptions) => Promise<ListApprovalsResponse>: The inbox, newest first; state defaults to "pending" server-side.reject:(input: DecideApprovalInput, options?: ActionOptions) => Promise<ApprovalDecision>: Reject: the pipeline continues with decision=rejected, as the viewer.
bridge
const bridge: Bridge
The module-singleton bridge: one iframe, one bridge.
Members
currentState:() => AppContextState | nullcurrentToken:() => string | null: currentToken returns the live bearer value or null (disabled/expired-out).download:(name: string, mime: string, content: string) => Promise<void>: download is the promise form of requestDownload: it settles once the shell has had its say. Rejects with a DownloadRefusedError (carrying.reason) when the content is over MAX_DOWNLOAD_CHARS or the shell answersdownload_refused; resolves after DOWNLOAD_REFUSAL_WINDOW_MS without a refusal. Refusals are only received after start() has run (the listener is the handshake's); mountApp always does that.openFile:(name: string, data: ArrayBuffer) => Promise<void>: openFile asks the shell to show the viewer a file (a stored photo or PDF): a new tab, or a download when the browser blocks the tab. Rejects with a DownloadRefusedError when the file is over MAX_OPEN_FILE_BYTES or the shell refuses it (it accepts JPEG, PNG, WebP and PDF, read from the bytes); resolves after the refusal window.post:(msg: AppToShellMessage) => voidreportError:(message: string, stack?: string) => void: reportError sends an uncaught error to the shell (NR-3 / FR-8).reportLiveError:(e: { code: string; reason: string; remediation?: string; topic?: string; }) => void: reportLiveError tells the shell a live subscription failed. The builder shows it next to the preview with the remediation; the Run and Kiosk hosts ignore it (the app's own UI already told the viewer).reportTelemetry:(method: string, path: string, status: number, durationMs: number) => void: reportTelemetry sends one API call's timing to the shell (G7).requestDownload:(name: string, mime: string, content: string) => boolean: requestDownload asks the shell to hand the viewer a file (the sandbox cannot start downloads itself). Text content only; refused (returns false) above MAX_DOWNLOAD_CHARS. Synchronous, fire-and-forget: a refusal the SHELL decides on later is logged, not returned; callers that need to react to it usedownload.start:() => Promise<AppContextState>: start runs the handshake once; subsequent calls return the same promise.subscribe:(fn: Listener) => () => void
connectors
const connectors: { execute(input: ExecuteFunctionInput, options?: ActionOptions): Promise<ExecuteFunctionResult>; catalog(options?: CatalogOptions): Promise<CatalogFunction[]>; run(input: ExecuteFunctionInput, options?: ActionOptions): Promise<ExecuteFunctionResult>; }
The connector-function surface: execute runs a saved function on a
connection with the viewer's authority (see the member docs). Rides the
write ladder and needs the functions:<effect> scope of that function.
Members
catalog:(options?: CatalogOptions) => Promise<CatalogFunction[]>: The connector functions THIS viewer may read, for a picker (FR-41 dynamic bindings): searched in plant language, each with its effect, scope and params. Needs theconnectors:readscope. Listing is not running: executing a picked function still needs a declared action.execute:(input: ExecuteFunctionInput, options?: ActionOptions) => Promise<ExecuteFunctionResult>: Execute a saved connector function (an OPC UA write, a DB query, a command) with the viewer's authority. The platform resolves the function's effect and demands the matching functions:<effect> scope. Resolves withsuccess: falsewhen the device or service refused the call; check it.runthrows instead.run:(input: ExecuteFunctionInput, options?: ActionOptions) => Promise<ExecuteFunctionResult>
data
const data: { subscribe: (topic: string, onMessage: (m: TopicMessage) => void, onError?: (e: LiveError) => void) => () => void; publish(input: PublishInput, options?: ActionOptions): Promise<PublishResult>; range(input: RangeInput): Promise<RangeResult>; aggregate(input: AggregateInput): Promise<AggregateResult>; ratio(input: RatioInput): Promise<RatioResult>; stateDuration(input: StateDurationInput): Promise<StateDurationResult>; contract(topic: string): Promise<TopicContract | null>; eventCount(input: EventCountInput): Promise<EventCountResult>; }
The UNS data surface: subscribe for live values (see subscribeTopic)
and publish for one write to an existing topic (see the member docs).
publish goes through the write ladder (declared action, idempotency
key, in-flight lock) and needs uns:publish.
Members
aggregate:(input: AggregateInput) => Promise<AggregateResult>: A topic's values folded into time buckets (POST /uns/data/aggregated): one value perbucketWidth("1m", "15m", "1h") byagg. Needs uns:read. Non-numeric samples are left out and counted (nonNumericInRange, with aremediation).contract:(topic: string) => Promise<TopicContract | null>: A topic's contract, by its path (GET /uns/topics/contract): what it carries (type, unit, bounds, alert thresholds, enum values) and the shape its stored series has. Resolves null when the topic has no contract: it is untyped, or there is no such topic. Needs uns:read. Label axes and tiles withschema.unit, and draw bounds from it. Since 3.0 the contract is answered from the topic's meaning.eventCount:(input: EventCountInput) => Promise<EventCountResult>publish:(input: PublishInput, options?: ActionOptions) => Promise<PublishResult>: Publish one value to an existing UNS topic.range:(input: RangeInput) => Promise<RangeResult>: A topic's stored values over a window, oldest first (POST /uns/data/range). Needs uns:read. Givesince("8h", "30m", "7d") orstart(andend, default now). Ask for a bounded window and alimit: the platform caps the answer and says so (totalvstotalInRange).ratio:(input: RatioInput) => Promise<RatioResult>: One topic's fold over another's, per bucket (POST /uns/data/ratio): yield = good parts / all parts, availability = run time / planned time. Each side has its own agg. A bucket whose denominator is 0 says so (divZero). Needs uns:read.stateDuration:(input: StateDurationInput) => Promise<StateDurationResult>: How long a state topic spent in each state (POST /uns/data/state-duration): mode "exact" per distinct value (run / idle / fault), "range" per labelled numeric band (ranges), "breakdown" per bucket. Needs uns:read.subscribe:(topic: string, onMessage: (m: TopicMessage) => void, onError?: (e: LiveError) => void) => () => void: Live values on a topic over the app's scoped socket (needs uns:read). See subscribeTopic.
live
const live: LiveConnection
The app's single live-data connection (one socket per runtime), shared
by useTopic, subscribeTopic and data.subscribe. Call
live.subscribe(topic, subscriber) directly only when you need the
onStatus callback; see the LiveConnection member docs.
Members
alertSubscriberCount:() => number: Test seam: number of alarm-feed subscribers.connection:() => ConnectionState: The connection as a whole now.onConnection:(listener: (s: ConnectionState) => void) => () => void: Hear every change of the connection's state; returns the unsubscribe.pipelineCount:() => number: Test seam: number of pipelines with live watchers.subscribe:(topic: string, sub: TopicSubscriber) => () => voidsubscribeAlerts:(sub: AlertSubscriber) => () => void: subscribeAlerts delivers every alarm transition in the workspace the viewer may see (alert visibility inherits topic read) until the returned function is called. Needs uns:read like a topic; refusals arrive ononErrorwith a reason and a remediation.subscribePipeline:(pipelineId: string, sub: PipelineSubscriber) => () => void: subscribePipeline delivers ONE pipeline's live execution events (execution.started/completed/failed/cancelled plus per-node progress) until the returned function is called. Gated exactly like the HTTP pipeline reads: the pipelines:read scope (pipelines:execute carries it) plus the viewer's own access to that pipeline; refusals arrive ononErrorwith a reason and a remediation. Only manual and API-submitted runs emit events (mode 'live' | 'sandbox'); scheduled and message-triggered runs appear in the executions API.topicCount:() => number: Test seam: number of topics with live subscribers.
notifications
const notifications: { notify(input: NotifyInput, options?: ActionOptions): Promise<NotifyResult>; }
The notification surface: notify posts one notice to the person
feed of THE APP'S OWN PEOPLE: its owner plus every viewer who has
opened it, or, with to, the people and groups named (each one
someone who can open the app; see people). The platform stamps the rest: the producer, the app-name
prefix on the title, and the audience, so an app can neither
impersonate the platform nor reach beyond its users. A declared
manifest action (kind notifications.notify, a notifications
binding, a rung), with no scope needed. Workspace channels (Slack, Teams)
hear app notices only when an operator explicitly subscribes a
channel to them.
Members
notify:(input: NotifyInput, options?: ActionOptions) => Promise<NotifyResult>: Post one notice to the app's people.
pendingWrites
const pendingWrites: Outbox
The app's outbox; pendingWrites.flush() tries the queue now.
Members
current:() => PendingWritesflush:() => void: Try the queue now: the connection is back, or the app asked.send:<T>(label: string, run: () => Promise<T>) => Promise<T>: send runs a record write now; if it does not get through, keeps it and resolves once a later attempt does. Any other answer (a refusal, a conflict) settles the call as it is.subscribe:(l: () => void) => (() => void)
people
const people: { search(options?: PeopleSearch): Promise<PeoplePage>; picture(p: Pick<Person, "kind" | "id" | "has_picture">): Promise<Blob | null>; }
The app's people directory: search lists who can open this app
(people first, then groups, by name; never an email), picture reads a
person's avatar. Prefer usePeople / usePersonPicture in components.
Members
picture:(p: Pick<Person, "kind" | "id" | "has_picture">) => Promise<Blob | null>: Fetches a person's picture (null when they have none). The avatar route is public, like every <img> of a person on the platform; the app token is not sent.search:(options?: PeopleSearch) => Promise<PeoplePage>: Lists who can open this app, people first, then groups, by name.
pipelines
const pipelines: { submit(input: SubmitPipelineInput, options?: ActionOptions): Promise<SubmitPipelineResult>; run(input: SubmitPipelineInput, options?: RunPipelineOptions): Promise<PipelineRunResult>; cancel(executionId: string, options?: ActionOptions): Promise<CancelPipelineRunResult>; }
The pipeline surface: submit fires a pipeline's manual-trigger node
with the viewer's authority (see the member docs). Rides the write
ladder and needs pipelines:execute.
Members
cancel:(executionId: string, options?: ActionOptions) => Promise<CancelPipelineRunResult>: Ask the platform to stop a run THIS app started (by the execution id submit or run answered). Needspipelines:executeand a declaredpipelines.cancelaction on the pipeline's binding. Cancelling is cooperative: the run stops at its next checkpoint, andrunrejects with PIPELINE_RUN_CANCELLED when it has. A run another app, a person or a schedule started is refused (APP_RUN_NOT_STARTED_BY_APP).run:(input: SubmitPipelineInput, options?: RunPipelineOptions) => Promise<PipelineRunResult>: Fire a pipeline's manual trigger and wait for the run it started: resolves when it completes, with each node's output; rejects with a PipelineRunError when it fails, is cancelled, or outlaststimeoutMs(the run itself goes on).onProgressgets the run's live events as they happen. Needspipelines:execute, and the same declared action assubmit.submit:(input: SubmitPipelineInput, options?: ActionOptions) => Promise<SubmitPipelineResult>: Fire a pipeline's manual trigger with the viewer's authority.
plant
const plant: { whatCanIAsk(): Promise<Envelope<Vocabulary>>; find(options: FindOptions): Promise<Envelope<Candidate[]>>; describe(thing: string, options?: DescribeOptions): Promise<Envelope<Described>>; walk(options: WalkOptions): Promise<Envelope<WalkedThing[]>>; numbers<T = unknown>(options: NumbersOptions): Promise<Envelope<T>>; timeline(options: TimelineOptions): Promise<Envelope<TimelineEntry[]>>; record(input: RecordEventsInput, options?: ActionOptions): Promise<Envelope<Recorded>>; update(input: UpdateFactsInput, options?: ActionOptions): Promise<Envelope<Recorded>>; declare(thing: string, mode: DeclaredMode, options?: ActionOptions & { note?: string; }): Promise<Envelope<Declared>>; }
The plant. The reads need the entities:read scope (SCOPE_PLANT_READ);
the viewer's own access bounds every row, as always. Each read is one of
the knowledge module's tools, answered as an Envelope: read result,
and say what coverage and hidden say. Key per-machine app state by the
thing's id, because a topic path renames.
Three writes, each a declared manifest action on a plant binding, with
the viewer's own authority. record (events in the log, under
entities:record) is an observation: it lands at once for a viewer who
may record. update (facts on things, under proposals:write) changes
what the model says a thing is: a change set proposed as the viewer and
applied in the same call only where the app carries proposals:decide
and the viewer may decide proposals; otherwise it waits under Review and
the answer says why. declare (under entities:declare) says what a
thing is doing (maintenance keeps its alarms quiet) and applies at once.
A part held to another person's approval holds every write for them.
Members
declare:(thing: string, mode: DeclaredMode, options?: ActionOptions & { note?: string; }) => Promise<Envelope<Declared>>: Declare what a thing is doing (a declaredplant.declareaction; needsentities:declare); everything under it follows.describe:(thing: string, options?: DescribeOptions) => Promise<Envelope<Described>>: One thing in full: its facts, places, identities, bindings (the topics), and with "values" what it reads now.find:(options: FindOptions) => Promise<Envelope<Candidate[]>>: Things by name, by a number another system issued, or by the topic(s) they are readings of.numbers:<T = unknown>(options: NumbersOptions) => Promise<Envelope<T>>: A figure for one thing or hundreds at once (the latest value, an average, time in a state, OEE), ordered whenorderis set: the "N machines, worst first" question. The result's shape follows the measure.record:(input: RecordEventsInput, options?: ActionOptions) => Promise<Envelope<Recorded>>: Record events in the plant's log (a declaredplant.recordaction; needsentities:record). In the log at once, or proposed where a part is held: readresult.state.timeline:(options: TimelineOptions) => Promise<Envelope<TimelineEntry[]>>: What happened to a thing, or what is happening now under a place: alarms, trips, work orders, shifts, calibrations.update:(input: UpdateFactsInput, options?: ActionOptions) => Promise<Envelope<Recorded>>: Set facts on things (a declaredplant.updateaction; needsproposals:write). Applied at once or proposed: readresult.state.walk:(options: WalkOptions) => Promise<Envelope<WalkedThing[]>>: Everything reached from a thing, down a tree (the machines under a line) or along links (what a pump feeds), with their readings whenvaluesis set.whatCanIAsk:() => Promise<Envelope<Vocabulary>>: The plant's vocabulary: its sites (the roots of every tree), the trees it keeps, its types, what it measures. Ask it first.
storage
const storage: { shared: { collection: <T>(name: string) => Collection<T>; }; user: { collection: <T>(name: string) => Collection<T>; }; }
The app's own document store: shared (one copy per workspace, CAS by
default) and user (one copy per viewer), and both expose
collection(name) returning a Collection. Not for plant data; see
the Collection member docs for get/put/delete/list/usage.
Example
const prefs = storage.user.collection<{ unit: string }>('prefs');
await prefs.put('display', { unit: 'bar' }, (current, mine) => ({ ...current, ...mine }));
Members
shared:{ collection: <T>(name: string) => Collection<T>; }: App-wide documents (the manifest'sscope: "shared"collections).user:{ collection: <T>(name: string) => Collection<T>; }: Per-viewer documents (the manifest'sscope: "user"collections).
zpl
const zpl: { label(options: LabelOptions): Label; escapeField: (data: string) => string; }
The ZPL builder: zpl.label({ widthMm, heightMm, dpi? }) starts a
Zebra label; send toString() to the printer through a TCP connector
function (port 9100, utf8 encoding).
Members
escapeField:(data: string) => stringlabel:(options: LabelOptions) => Label: Starts a label of this size.
Types
AckAlertInput
interface AckAlertInput
Input to alerts.ack: the alarm's identity as the feed returns it
(topicId + level, plus propertyId / direction when the feed
carries them). The property is named by id: the platform keys an
incident by id so a rename never splits it, and a name is not a key.
Mirrors uns AcknowledgeAlertRequest verbatim.
Members
direction?:string | undefinedlevel:stringpropertyId?:string | undefinedtopicId:string
ActionOptions
interface ActionOptions
Options every write action accepts.
Members
confirmed?:boolean | undefined: The viewer confirmed this specific invocation (rung "confirm").idempotencyKey?:string | undefined: Reuse a key to retry the SAME request; omit for a fresh invocation.
ActionPermission
interface ActionPermission
One action's answer for the viewer.
Members
granted:booleanreason?:string | undefined
AggregateBucket
interface AggregateBucket
One bucket (AggregatedBucketResponse).
Members
bucketStart:string: RFC 3339 start of the bucket.count:number: Samples folded into it.quality?:number | undefinedvalue:number
AggregateFn
type AggregateFn = 'avg' | 'min' | 'max' | 'sum' | 'count' | 'avg_tw';
The fold data.aggregate applies per bucket; avg_tw is the time-weighted average.
AggregateInput
interface AggregateInput extends ReadWindow
Input to data.aggregate.
Members
agg:AggregateFnattribute?:string | undefinedbucketWidth:string: A duration: "1m", "15m", "1h".end?:string | Date | undefinedsince?:string | undefined: How far back from now: a number and a unit, s m h or d ("90s", "30m", "8h", "7d").start?:string | Date | undefinedtopic:string
AggregateOptions
type AggregateOptions = Omit<AggregateInput, 'topic'> & {
refreshMs?: number;
};
Options for useAggregate: data.aggregate's input without the topic, plus an optional re-read period (at least 5 s).
AggregateResult
interface AggregateResult
What data.aggregate answers (AggregatedResponse).
Members
agg:stringbucketWidth:stringbuckets:AggregateBucket[]nonNumericInRange:number: Samples left out because they are not numbers.remediation?:string | undefined: How to have those samples aggregated, when some were left out.topic:stringtotal:numbertotalInRange:number
AlarmState
interface AlarmState
A live alarm's state, as plant.timeline reports it with state set.
Members
acknowledged:booleanacknowledged_at?:string | undefinedacknowledged_by_name?:string | undefinedactive:booleanactive_hours:numbershelved:booleanshelved_until?:string | undefinedstanding:booleanto_act_on:boolean
AlertEvent
interface AlertEvent
One alarm transition from the platform's rule engine; mirrors the
uns contract SchemaAlertFiredEvent (shared/contracts/uns) the socket
relays as an alert.fired frame. cleared false = the alarm fired,
true = it cleared.
An attribute holds one alarm at a time. A fire is the attribute's alarm
from then on: when it replaces another level (critical easing to
warning, warning escalating to critical, or a crossing to the other
side of normal), previousLevel / previousDirection name the alarm it
replaced, which has ended. A clear ends the attribute's alarm whatever
its level. For the alarms open right now, read alerts.recent().
Members
attributePath?:string | undefined: The property's name at the transition: a label, never a key.cleared:booleandirection:string: "low" | "high".firedAt:string: RFC3339.level:string: "warning" | "critical": the threshold that tripped.message?:string | undefinedpreviousDirection?:string | undefined: On a fire: the side of normal of the alarm it replaced.previousLevel?:string | undefined: On a fire: the level of the alarm it replaced (now ended). Absent on a first fire and on a clear.previousSeverity?:string | undefined: On a fire: the replaced alarm's operator-facing severity.propertyId?:string | undefined: The alarm key's property, by id. Empty for value schemas.receivedAt:numberrecordTimestamp:stringschemaId:stringschemaName:stringseverity:string: Operator-facing "warning" | "critical": the level's own severity, raised to "critical" when the schema marks warnings urgent. Routing and escalation read this;levelsays which threshold was crossed.stateRevision?:number | undefined: The order of this attribute's transitions: higher is later. A topic can take data from several sources, so events can arrive out of order: sort or discard by this, not by arrival. Absent on events from older platform versions.threshold:numbertopicId:stringtopicPath:stringunit?:string | undefinedvalue:number
AlertHistoryOptions
interface AlertHistoryOptions
Options for alerts.history: one topic's transitions, newest first, before an instant.
Members
before?:string | undefined: RFC3339: only events before this instant.limit?:number | undefinedtopicId?:string | undefined
AlertItem
interface AlertItem
One alarm as the platform's feed returns it; mirrors uns CurrentAlertDTO verbatim.
Members
acknowledgedAt?:string | undefinedattributePath?:string | undefined: The property's name at the last fire: a label, never a key.clearedAt?:string | undefineddirection?:string | undefined: "low" | "high".fireCount:numberfirstFiredAt:string: RFC3339.lastClearedAt?:string | undefinedlastFiredAt:stringlastRecordTs?:string | undefinedlevel:string: "warning" | "critical": the threshold that tripped.message?:string | undefinedpropertyId?:string | undefined: The alarm key's property, by id (empty for a value schema).schemaId:stringschemaMajor:numberschemaMinor:numberschemaName:stringseverity:string: Operator-facing "info" | "warning" | "critical".shelvedAt?:string | undefinedshelvedBy?:string | undefinedshelvedUntil?:string | undefinedthreshold:numbertopicId:stringtopicPath:stringunit?:string | undefinedvalue:numberworkspaceId:string
AlertSubscriber
interface AlertSubscriber
Callbacks handed to live.subscribeAlerts; useAlerts builds one for you.
Members
onError?:((e: LiveError) => void) | undefinedonEvent:(e: AlertEvent) => voidonStatus?:((s: LiveStatus) => void) | undefined
AlertsState
interface AlertsState
What useAlerts returns: the latest transitions (newest first) plus the feed's status.
Members
error:LiveError | nullevents:AlertEvent[]status:LiveStatus
AppClock
interface AppClock
The clock an app shows: the zone, where it came from, and the locale numbers and dates are written in.
Members
locale:string: A BCP 47 tag, such as "tr-TR" or "en-US": the viewer's browser language.timeZone:string: An IANA zone name, such as "Europe/Istanbul".timeZoneSource:"workspace" | "viewer": "workspace": the workspace's own clock. "viewer": none is set, so the viewer's browser zone is used.
AppContextState
interface AppContextState
What the shell told the app about its viewer, as read by useAppContext
and bridge.currentState. Built from the context message; theme
follows later theme pushes and disabled is set by a disable push.
Members
actions?:Record<string, ActionPermission> | undefined: Per declared action: may this viewer do it;useActionPermissionreads these. Undefined on a shell that predates it.canEditApp:boolean: The viewer may edit this app: gate curation UI on it (writers: "editors" collections).clock?:AppClock | undefined: The clock the shell handed over; absent on an older shell (read it throughuseClock, which falls back to the viewer's).disabled?:string | undefined: Set once the shell disabled the app; carries the notice to show. The token is dropped at the same time.permissions:Record<string, { granted: boolean; reason?: string; }>: Per-scope grant results;usePermissionreads these.theme:ThemeState: The shell's current theme mode.user:{ id: string; displayName: string; }: The viewer's id and display name.workspace:{ id: string; slug: string; }: The workspace the app runs in.
AppToShellMessage
type AppToShellMessage = HelloMessage | ReadyMessage | ErrorMessage | TelemetryMessage | DownloadMessage | OpenFileMessage | LiveErrorMessage | ScreenMessage;
Every message the app posts to the shell: hello (handshake start),
ready (handshake done), error (uncaught error), telemetry (one
API call's timing), download (hand the viewer a file), open_file
(show the viewer a stored attachment), live_error (a live-data
subscription failed, for the builder) and screen (what the app shows,
for the builder).
Discriminated on type.
ApprovalDecision
interface ApprovalDecision
One pending or decided approval; mirrors the engine's DecisionResponse verbatim.
Members
createdAt:stringdeadline:string: RFC3339: the gate expires by itself after this.decidedAt?:string | undefineddecidedBy?:string | undefineddecisionNote?:string | undefinedfreshUntil?:string | undefinedid:stringkind:string: Apps see "pipeline_gate" only; the agent-writes surface is not exposed.nodeId:stringpayload?:unknown: The held upstream data: exactly what continues on approve.pipelineId:stringpipelineName?:string | undefinedreason?:string | undefinedrequestedBy?:string | undefinedsignature:stringsourceExecutionId?:string | undefinedstate:string: "pending" | "approved" | "rejected" | "expired" | "executed".title:stringverdictOutcome?:string | undefined: "queued" once the scheduler has the run; "run_failed" when that run never started, "undeliverable" when it never can be (verdictWaiting says why).verdictPending?:boolean | undefined: The verdict is recorded but not yet confirmed queued to the pipeline.verdictWaiting?:string | undefined: Why a pending verdict is not moving, when it is not (e.g. no scheduler serving).
Attachment
interface Attachment
One file attached to a stored record, as the attachments API returns it.
Members
content_type:string: Read from the bytes: image/jpeg, image/png, image/webp, application/pdf, or model/gltf-binary for a .glb 3D model.created_at:string: RFC3339 instant it was attached.created_by:string: The user who attached it.id:string: The file's id; pass it toblob,openandremove.name:string: The file name the app sent, without any folder part.size_bytes:number: Size in bytes.
AttachmentUsageInfo
interface AttachmentUsageInfo
What Collection.attachmentUsage returns: the app's attachment total across every record.
Members
cap_bytes:number: The app's total; an upload that would exceed it is refused with a 413 ATTACHMENT_QUOTA_EXCEEDED.file_count:number: Number of files the app stores.max_file_bytes:number: The largest single photo or PDF; a bigger one is refused with a 413 ATTACHMENT_TOO_LARGE.max_model_bytes:number: The largest single 3D model (.glb); a bigger one is refused with a 413 ATTACHMENT_TOO_LARGE.total_bytes:number: Bytes used by them.
Attachments
interface Attachments
The files of one stored record, from collection.attachments(key).
Save the record first: attaching to a key with no document is a 404
ATTACHMENT_RECORD_MISSING. Deleting the record deletes its files.
Accepted, read from the bytes: JPEG, PNG, WebP, PDF, and binary glTF 2.0
models (.glb, uncompressed); anything else is a 415
ATTACHMENT_TYPE_NOT_ALLOWED. At most 50 per record.
Members
add:(file: Blob, name?: string) => Promise<Attachment>: Attaches a file (a File from an input, or any Blob);namedefaults to the File's name.blob:(id: string) => Promise<Blob>: The file's bytes;URL.createObjectURLon it renders a photo in an <img>.list:() => Promise<Attachment[]>: The record's files, oldest first (metadata only).open:(id: string, name?: string) => Promise<void>: Shows the viewer the file in a new tab (a download when the browser blocks the tab).remove:(id: string) => Promise<void>: Removes one file; resolves even if it was already gone.
AttachmentsState
interface AttachmentsState extends ReadState<Attachment[]>
What useAttachments holds: the record's files, plus add/remove/open that re-read the list.
Members
add:(file: Blob, name?: string) => Promise<Attachment>: Attaches a file (e.g. from <input type="file" accept="image/*,application/pdf" capture>), then re-reads.busy:boolean: True while an add or remove is in flight.data?:Attachment[] | undefinederror?:unknownloading:booleanopen:(id: string, name?: string) => Promise<void>: Shows the viewer one file in a new tab.refresh:() => void: Read again now (the window is recomputed: asincewindow moves to now).remove:(id: string) => Promise<void>: Removes one file, then re-reads.
BarcodeOptions
interface BarcodeOptions
Options for Label.barcode (Code 128).
Members
heightMm?:number | undefined: Bar height (default 12 mm).humanReadable?:boolean | undefined: Print the value under the bars (default true).moduleDots?:number | undefined: Narrow bar width in dots, 1 to 10 (default 2).
Binding
interface Binding
A binding: which series carries one property of a thing.
Members
id:stringproperty_id:stringseries:{ kind: string; ref: string; workspace_id?: string; }:kind"uns" andrefthe topic path, THE way from a thing to its live data.thing_id:stringunit?:string | undefined
BoxOptions
interface BoxOptions
Options for Label.box.
Members
thicknessMm?:number | undefined: Line thickness (default 0.5 mm).
CancelPipelineRunResult
interface CancelPipelineRunResult
What the engine answers a cancel with (cancelExecutionResponse): the stop is requested, not yet done.
Members
executionId:stringmessage:stringstatus:string: 'cancel_requested': the run stops at its next checkpoint.
Candidate
interface Candidate
plant.find: one thing that answers.
Members
name:stringphysical_place?:string | undefined: Where it sits in the physical tree, in words.property?:string | undefinedreading?:Reading | undefinedsite:stringthing:stringtopic?:string | undefined: Withtopic/topics: the topic the row answers for, and the property it is bound to.type?:string | undefined
CatalogFunction
interface CatalogFunction
One connector function the viewer may read (GET /appstudio/catalog/functions, application.FunctionMatch; field names as the server sends them).
Members
connection_id:stringconnection_name:stringconnection_type:stringdescription?:string | undefinedeffect:string: read | write | stream | discover; '' = unclassified, which no app may execute.function_type:stringid:stringname:stringparams_schema?:Record<string, unknown> | undefined: JSON schema of the paramsconnectors.executetakes for it.scope:string: The manifest scope executing it needs (functions:<effect>); '' when none can.
CatalogOptions
interface CatalogOptions
Input to connectors.catalog.
Members
limit?:number | undefined: Default 20, at most 50.query?:string | undefined: Plant-language words ("furnace line 3"); empty lists all.
ClockWindow
interface ClockWindow
A period as two UTC instants, ready for a read (data.range, plant.timeline).
Members
end:string: RFC 3339, UTC.start:string: RFC 3339, UTC.
Collection
interface Collection<T = unknown>
A handle on one declared storage collection, from
storage.shared.collection(name) or storage.user.collection(name).
Every method is one or more apiFetch calls to
/api/v1/appstudio/storage/<collection>; an undeclared collection is a 404.
Members
attachmentUsage:() => Promise<AttachmentUsageInfo>: The app's attachment total, its cap, and the largest file allowed.attachments:(key: string) => Attachments: The files (photos, PDFs) of one stored record; seeAttachments.delete:(key: string) => Promise<void>: Removes a document; resolves even if it was already gone.get:(key: string) => Promise<StoredDocument<T> | null>: Returns null when absent.list:(opts?: ListOptions) => Promise<ListPage<T>>: One page of documents; follownext_cursorfor more.readonly name:string: The collection name the handle was made for.put:(key: string, value: T, merge?: (current: T | null, mine: T) => T) => Promise<StoredDocument<T>>: Writes a value. On a CAS collection: merges onto the current document viamerge(default: replace) and retries until it lands or MAX_CAS_ATTEMPTS is hit. On a last-write-wins collection: one write.usage:() => Promise<UsageInfo>: Document count, bytes used and the cap for this collection's scope.
ConnectionState
interface ConnectionState
The app's live connection as a whole (D7), for a kiosk that must say
when what it shows stopped being live. since is when it entered the
state (ms since epoch): for "reconnecting", when the connection dropped,
so "values as of 08:03" is honest.
Members
since:numberstate:"connecting" | "live" | "idle" | "reconnecting" | "disabled": idle: nothing subscribed. connecting: the first connect. live: values arrive. reconnecting: dropped, being retried. disabled: the shell disabled the app.
ContextMessage
interface ContextMessage
Shell → app: identity, per-scope grant results, theme, and the token.
Members
actions?:Record<string, ActionPermission> | undefined: Per declared action (manifest "actions"): may this viewer do it, and if not why: the scope AND the viewer's own right on the bound resource (FR-42 b). What useActionPermission renders from. Every declared action is answered; absent on a shell that predates it.canEditApp?:boolean | undefined: The viewer holds app:update on this app (a "keeper": App Data Authority R1): editor-only affordances render enabled from it. The platform re-checks server-side on every write.clock?:AppClock | undefined: The clock the app shows (D6): the workspace's time zone, or the viewer's own when the workspace sets none, and the viewer's locale. Absent on a shell that predates it; the SDK then uses the viewer's.permissions:Record<string, { granted: boolean; reason?: string; }>: Per-scope grant results from the token exchange (granted + denied_scopes), which is what usePermission renders disabled-with-reason from (FR-29/42).reportScreen?:boolean | undefined: The builder asks forscreenreports: the preview's visible text, which an AI client building the app reads through run_report. Set by the builder's preview only; a published app never sends them.theme:ThemeStatetoken:TokenPayloadtype:"context"user:{ id: string; displayName: string; }v:1workspace:{ id: string; slug: string; }
DataRecord
interface DataRecord
One stored value (DataRecordResponse).
Members
id:stringquality?:number | undefined: OPC-style quality; absent when good.qualityReason?:string | undefinedretain:booleantimestamp:string: RFC 3339.value:unknown
DecideApprovalInput
interface DecideApprovalInput
Input to approvals.approve / approvals.reject.
Members
id:string: The decision's id, from the inbox.note?:string | undefined: Recorded beside the verdict; the decider is always the viewer.
Declared
interface Declared
What plant.declare did: the declaration, and how far it reaches.
Members
by:stringcleared?:{ id: string; thing: string; mode: string; since: string; until?: string; by?: string; note?: string; } | undefined: The declaration this one replaced on the thing.covers:number: How many things it reaches, itself included.mode:DeclaredModename:stringnote?:string | undefinedoverridden?:number | undefinedsince:stringthing:stringtopics:number: How many uns topics bound to them fall quiet (or wake, for running).
DeclaredMode
type DeclaredMode = 'running' | 'maintenance' | 'changeover' | 'stopped';
The modes plant.declare names: running clears a declaration.
DescribeOptions
interface DescribeOptions
What plant.describe may bring beyond the thing itself.
Members
as_of?:string | undefined: The thing as it was at an instant, RFC3339.include?:("running" | "values" | "links" | "ended" | "missing" | "brief")[] | undefined: "values" for what it reads now, "links" for its links, "missing" for recorded absences.property?:string | undefined: Narrow the readings to one property.
Described
interface Described
plant.describe: one thing in full.
Members
bindings?:Binding[] | undefined: The series bound to it:series.refis the topic path for live values.declared?:InForce | undefineddocuments?:string[] | undefinedfacts?:Record<string, FactValue> | undefinedidentities?:Identity[] | undefinedlinks?:DescribedLink[] | undefinedparts?:string[] | undefinedplaces?:Record<string, string> | undefined: Where it sits, in every tree that holds it, keyed by the tree kind (physical, location, floc, …).readings?:Reading[] | undefined: With include "values": what it reads now.thing:Thingtype_chain?:string[] | undefined: Its type and every type it extends, nearest first.
DescribedLink
interface DescribedLink
A link on a thing, named from the thing's side.
Members
link:stringname:stringother:stringother_name:stringother_site?:string | undefinedsince:stringuntil?:string | undefined
DisableMessage
interface DisableMessage
Shell → app: kill switch / unshare: drop the token, show the notice.
Members
message:stringtype:"disable"v:1
DownloadMessage
interface DownloadMessage
App → shell: hand the viewer a file (G1 export). The sandbox has no allow-downloads, so a download started inside the iframe is inert; the shell materialises it. Text content only (CSV/JSON/text), size-capped.
Members
content:stringid?:string | undefined: Correlates adownload_refusedreply with the request that caused it.mime:stringname:stringtype:"download"v:1
DownloadRefusedMessage
interface DownloadRefusedMessage
Shell → app: the shell refused a download (over MAX_DOWNLOAD_CHARS,
non-text content). id echoes the request's id so the SDK can settle
the right caller's promise; a refusal is never silent (rule 36).
Members
id?:string | undefinedreason:stringtype:"download_refused"v:1
Envelope
interface Envelope<T>
Every plant answer: the result, and what the planner says about it:
what it could not read (coverage), how many rows the viewer may not see
(hidden), the sentence it computed, and the calls that would say more.
Members
ambiguous?:boolean | undefined: More than one thing answered to the name asked.computed?:string | undefined: The answer's own sentence, in words.coverage?:{ missing?: string[]; without_reading?: string[]; } | undefined: What the answer needed and the plant does not record, or did not read.cut_short?:{ limit: string; } | undefined: The answer was cut short at this limit.hidden?:number | undefined: Rows the viewer's access hides, counted, never shown.next?:{ tool: string; why: string; }[] | undefined: What to ask next, and why.readings_as_of?:string | undefined: The instant the readings in the answer are as of, RFC3339.result:T
ErrorMessage
interface ErrorMessage
App → shell: an uncaught error (NR-3 + FR-8's send-to-agent hook).
Members
message:stringstack?:string | undefinedtype:"error"v:1
EventCountInput
interface EventCountInput extends ReadWindow
Input to data.eventCount: the condition is value <op> value, with a plain number, boolean or string.
Members
bucketWidth:stringend?:string | Date | undefinedmode:"sample" | "transition"op:"eq" | "neq" | "gt" | "gte" | "lt" | "lte"since?:string | undefined: How far back from now: a number and a unit, s m h or d ("90s", "30m", "8h", "7d").start?:string | Date | undefinedtopic:stringvalue:string | number | boolean
EventCountOptions
type EventCountOptions = Omit<EventCountInput, 'topic'> & {
refreshMs?: number;
};
Options for useEventCount: data.eventCount's input without the topic, plus refreshMs.
EventCountResult
interface EventCountResult
What data.eventCount answers (EventCountResponse).
Members
bucketWidth:stringbuckets:{ bucketStart: string; count: number; }[]mode:stringtopic:stringtotal:number
ExecuteFunctionInput
interface ExecuteFunctionInput
Input to connectors.execute.
Members
connectionId:string: The connection the saved function belongs to.functionId:string: A SAVED function on that connection (apps cannot run ad-hoc function bodies).params?:Record<string, unknown> | undefined: Function parameters; sent as{}when omitted.
ExecuteFunctionResult
interface ExecuteFunctionResult
Mirrors the platform's FunctionResultResponse.
Members
data?:Record<string, unknown> | undefineddurationMs:numbererror?:string | undefinedmetadata?:Record<string, unknown> | undefinedsuccess:booleantimestamp:string
ExecutionEvent
interface ExecutionEvent
One live event from a pipeline's execution stream: the platform's
unified live-exec family (shared/contracts/pipeline live_events.go),
delivered as WS frames typed execution.* / node.*. Field names
mirror the contract's json tags verbatim (camelCase); a full-boot Go
test pins them. Only manual and API-submitted runs emit these events
(mode separates real runs from sandbox test runs); scheduled and
message-triggered runs do not; read those from the executions API.
Members
data:Record<string, unknown>: The frame's full payload, verbatim, for fields not lifted above.durationMs?:number | undefinedendedAt?:string | undefinederror?:string | undefined: A node event's error, or execution.failed's errorSummary.executionId:stringmode:string: 'live' for a real run, 'sandbox' for a test run.nodeId?:string | undefined: Node events only.nodeName?:string | undefinednodeType?:string | undefinedpipelineId:stringreceivedAt:numberstartedAt?:string | undefinedtype:string: 'execution.started' | 'execution.completed' | 'execution.failed' | 'execution.cancelled' | 'node.started' | 'node.progress' | 'node.completed' | 'node.failed' | 'node.skipped' | 'node.recovered' | 'node.prompt'.node.progressis a loop body node's coalesced progress: cumulativecompleted/failedcounts and the latest iteration'soutput.
ExecutionStreamState
interface ExecutionStreamState
What useExecutionStream returns: the run events (newest first) plus the stream's status.
Members
error:LiveError | nullevents:ExecutionEvent[]status:LiveStatus
FactInput
interface FactInput
A fact's value, with its unit when the property has one; value: null removes the fact.
Members
unit?:string | undefinedvalue:string | number | boolean | null
FactValue
interface FactValue
A fact a thing carries, with its unit when it has one.
Members
unit?:string | undefinedvalue:unknown
FallbackColours
interface FallbackColours
One mode's fallback colours: what an app shows when the shell sent no tokens.
Members
background:string: Page background.border:string: Borders and rules.danger:string: Destructive fill.ink:string: Primary text.muted:string: Secondary text.primary:string: Accent: primary buttons, links, focus ring.primaryInk:string: Text on the accent.subtle:string: Subtle fill: table header, neutral button, track.surface:string: Card / control background.
FindOptions
interface FindOptions
What plant.find takes: a name, a number another system issued, a topic, or many topics at once.
Members
as_of?:string | undefined: The plant as it was at an instant, RFC3339.identifier?:string | undefined: A number another system issued, withsystemsaying which.name?:string | undefined: A name or part of one, in any language the plant records.system?:string | undefinedtopic?:string | undefined: A uns topic path: answered with the thing it is a reading of and the property.topics?:string[] | undefined: Many topic paths in one call: a screen's whole list; nevertopicin a loop.
FormatDateTimeOptions
interface FormatDateTimeOptions
Options for formatDateTime.
Members
seconds?:boolean | undefined: Show seconds with the time.show?:"datetime" | "date" | "time" | undefined: What to show: "datetime" (default), "date" or "time".timeZone?:string | undefined: Overrides the app's zone, for a site that keeps another clock.
FormatNumberOptions
interface FormatNumberOptions
Options for formatNumber.
Members
pad?:boolean | undefined: Always writedigitsdecimals (2.00, not2): a column of figures that must line up.percent?:boolean | undefined:nis a ratio, written as a percentage in the locale's own form (0.088→8.8%,8,8 %,%8,8).
FunctionState
interface FunctionState
What useFunction holds and returns.
Members
error?:unknown: The last call's failure, cleared when the next one starts.granted:boolean: useActionPermission(action): render the control disabled withreasonwhen false.loading:boolean: A call is in flight.reason?:string | undefinedresult?:ExecuteFunctionResult | undefined: The last successful result.run:(params?: Record<string, unknown>, options?: ActionOptions) => Promise<ExecuteFunctionResult | undefined>: Runs the function once. Never rejects: a refusal or a failed call lands inerror(a FunctionFailedError for success:false) andrunresolves undefined; a success lands inresultand is returned.
HelloMessage
interface HelloMessage
App → shell: announce readiness to receive context.
Members
app:{ slug: string; }type:"hello"v:1
HistoryOptions
type HistoryOptions = Omit<RangeInput, 'topic'> & {
refreshMs?: number;
};
Options for useHistory: data.range's input without the topic, plus an optional re-read period (at least 5 s).
HostPalette
interface HostPalette
The host's design tokens, resolved by the shell to plain CSS colours
(hsl(220 14% 10%), #fff) so an app renders in the shell's exact
theme: every variant, not a hard-coded light/dark pair. Optional in
every message: a shell that cannot resolve them (no stylesheet yet)
omits the field and the UI kit falls back to its own palette.
Members
background:string: Page background (--background).border:string: Borders and rules (--border).danger:string: Destructive fill: a danger button's background (--destructive). Not a text colour.dangerInk?:string | undefined: Text on the destructive fill (--destructive-foreground). Optional: a shell that sends none gets the readable one of white / near-black computed againstdanger.font:string: The shell's font stack (--font-sans).ink:string: Primary text (--foreground).muted:string: Secondary text (--muted-foreground).primary:string: Accent: primary buttons, active tab, default chart series (--primary).primaryInk:string: Text on the accent (--primary-foreground).radius:string: The shell's corner radius (--radius), a CSS length.subtle:string: Subtle fill: table header, neutral button (--muted).surface:string: Card / control surface (--card).
Identity
interface Identity
A number another system knows the thing by: the SAP equipment, the functional location, the OPC UA node.
Members
id:stringidentifier:stringkind?:string | undefinedsystem:string
InForce
interface InForce
A declaration in force on a thing, its own or the nearest above it: maintenance, decommissioned, …
Members
by?:string | undefinedmode:stringnote?:string | undefinedon:string: The thing the declaration was made on.on_name?:string | undefinedsince:string
LabelOptions
interface LabelOptions
Size and resolution of the label.
Members
dpi?:203 | 300 | 600 | undefined: Printer resolution: 203 (default), 300 or 600 dpi.heightMm:numberwidthMm:number
ListApprovalsOptions
interface ListApprovalsOptions
Filters for approvals.list; mirrors the engine's query names verbatim.
Members
limit?:number | undefinedoffset?:number | undefinedpipelineId?:string | undefinedstate?:string | undefined: "pending" (the default) | "approved" | "rejected" | "expired" | "executed" | "all".
ListApprovalsResponse
interface ListApprovalsResponse
The inbox page shape.
Members
items:ApprovalDecision[]total:number
ListOptions
interface ListOptions
Query options for Collection.list; each one becomes a query parameter only when set.
Members
cursor?:string | undefined: Thenext_cursorof the previous page.limit?:number | undefined: Maximum documents per page.since?:string | undefined: RFC3339: only documents updated at or after this instant.
ListPage
interface ListPage<T = unknown>
One page of Collection.list.
Members
items:StoredDocument<T>[]: The documents on this page.next_cursor?:string | undefined: Pass asListOptions.cursorto fetch the next page; absent on the last page.
LiveError
interface LiveError
A refusal or failure on the live path: always carries a next step.
Members
code:stringreason:stringremediation:stringtopic?:string | undefined
LiveErrorMessage
interface LiveErrorMessage
App → shell: a live-data subscription was refused or failed (a missing
scope, a topic the platform would not serve, an auth failure). The
SDK already tells the app's own code through onError; this copy is
for the BUILDER, which otherwise sees only "Status: error" inside the
preview and cannot say why. Carries the same code / reason /
remediation the app got, so the builder can teach the fix.
Members
code:stringreason:stringremediation?:string | undefinedtopic?:string | undefinedtype:"live_error"v:1
LiveStatus
type LiveStatus = 'connecting' | 'live' | 'error' | 'closed';
Connection state a subscriber sees: connecting until the server acks
the subscription (also while reconnecting), live once it has, error
after a refusal or failure. closed is part of the type but the
current LiveConnection never emits it.
Numberish
type Numberish = number | ExactNumber;
A number as JSON carries it: a plain number, or an ExactNumber past what a double holds.
NumbersOptions
interface NumbersOptions
What plant.numbers takes: a set of things, a property, a measure, a window.
Members
as_of?:string | undefined: The plant and its readings as they were at an instant, RFC3339.measure?:string | undefined: Which number: "latest", "avg", "sum", "min", "max", "delta", "count", "time_in_state", "oee", …order?:"asc" | "desc" | undefined: Order the rows: "asc" or "desc".property?:string | undefined: The property to read or rank, e.g. "mh:vibration".set:SetOptionstop?:number | undefined: How many rows to keep; the rest are counted, not dropped.window?:string | undefined: The period: "now", "current_shift", "yesterday", "last_7d", "this_month", a date, a month.
OpenFileMessage
interface OpenFileMessage
App → shell: show the viewer a stored attachment (D3), a photo or PDF
the app fetched with its token. The sandbox can neither open a tab nor
download, so the shell does it: it reads the type from the bytes
itself (JPEG, PNG, WebP or PDF only), opens the file in a new tab, and
falls back to a download when the browser blocks the tab. A refusal
comes back as download_refused with the same id.
Members
data:ArrayBuffer: The file's bytes.id?:string | undefined: Correlates adownload_refusedreply with the request that caused it.name:stringtype:"open_file"v:1
PendingWrite
interface PendingWrite
One record waiting to be sent.
Members
attempts:number: Send attempts since it was queued.id:numberlabel:string: What it is, in words: "save notes/shift-2", "record 2 events", "notice".queuedAt:number: When it was made, ms since epoch.
PendingWrites
interface PendingWrites
What usePendingWrites returns.
Members
count:numberitems:PendingWrite[]
PeoplePage
interface PeoplePage
A page of the directory.
Members
items:Person[]truncated:boolean: More matched thanlimit: narrow the search.
PeopleSearch
interface PeopleSearch
Filters for people.search.
Members
kind?:"user" | "group" | undefined: Only people ('user') or only groups ('group').limit?:number | undefined: At most this many (default 50, at most 200).search?:string | undefined: Part of a name, any case.
Person
interface Person
One entry of the directory: a person or a group.
Members
has_picture:boolean: People only: a stored picture exists (people.picture fetches it).id:stringkind:"user" | "group"name:string: Display name; '' for a person who never set one (show "Unnamed person").
PipelineRunResult
interface PipelineRunResult
What pipelines.run resolves with: a run that finished.
Members
durationMs?:number | undefinederror?:string | undefined: The run's error summary, when it completed with errors.executionId:stringoutputs:Record<string, Record<string, unknown>>: Each node's output, by node id: from the stored run when it has them, else from the run's live events.status:string: 'completed', or 'completed_with_errors' when some nodes failed but the pipeline was set to carry on.
PipelineRunState
interface PipelineRunState
What usePipelineRun holds and returns.
Members
canCancel?:{ granted: boolean; reason?: string; } | undefined: useActionPermission(cancelAction), when one was given.cancel?:(() => Promise<void>) | undefined: Ask the platform to stop the run in flight, through the manifest'scancelAction(a declared pipelines.cancel action). Never rejects: a refusal lands inerror; once the run has stopped,errorholds its PIPELINE_RUN_CANCELLED. Undefined when no cancelAction was given.error?:unknown: The last run's failure, cleared when the next one starts.granted:boolean: useActionPermission(action): render the control disabled withreasonwhen false.loading:boolean: A run is in flight.progress?:ExecutionEvent | undefined: The latest live event of the run in flight (a node started or finished): render progress from it.reason?:string | undefinedresult?:PipelineRunResult | undefined: The last finished run.run:(data?: Record<string, unknown>, options?: RunPipelineOptions) => Promise<PipelineRunResult | undefined>: Fires the pipeline and waits for the run. Never rejects: a refusal, a failed or cancelled run, or a timeout lands inerror(a PipelineRunError for the run's own outcome) andrunresolves undefined; a finished run lands inresultand is returned.
PipelineSubscriber
interface PipelineSubscriber
Callbacks handed to live.subscribePipeline; useExecutionStream builds one for you.
Members
onError?:((e: LiveError) => void) | undefinedonEvent:(e: ExecutionEvent) => voidonStatus?:((s: LiveStatus) => void) | undefined
PlantEvent
interface PlantEvent
An event the plant recorded or worked out: alarm, trip, work_order, mode_change, …
Members
at:stringcleared_at?:string | undefinedid:stringkind:stringname?:string | undefinedpayload?:Record<string, unknown> | undefinedseverity?:"info" | "warning" | "critical" | undefinedsite_id:stringsource?:string | undefinedthing_id:string
PrintOptions
interface PrintOptions
Options for print().
Members
keepTheme?:boolean | undefined: Keep the current theme (dark) instead of printing in light colours.title?:string | undefined: The title the print dialog shows and the PDF file name, e.g. "Shift report, Line 2, 2026-10-15".
PublishInput
interface PublishInput
Input to data.publish.
Members
source?:string | undefined: Optional source label recorded with the point.topic:string: Existing UNS topic path, e.g. "factory/line1/reset".value:unknown: JSON-serialisable value the topic's schema accepts.
PublishResult
interface PublishResult
What POST /api/v1/uns/data/publish returns on success.
Members
message:string: The platform's human-readable acknowledgement.
QrOptions
interface QrOptions
Options for Label.qr.
Members
magnification?:number | undefined: Cell size, 1 to 10 (default 4).
RangeInput
interface RangeInput extends ReadWindow
Input to data.range.
Members
attribute?:string | undefined: One attribute of an object topic, by its path.end?:string | Date | undefinedlimit?:number | undefined: At most this many records.since?:string | undefined: How far back from now: a number and a unit, s m h or d ("90s", "30m", "8h", "7d").start?:string | Date | undefinedtopic:string: The topic's full path (mHv1.0/...).
RangeResult
interface RangeResult
What data.range answers (DataRecordsResponse).
Members
records:DataRecord[]total:number: Records in this answer.totalInRange?:number | undefined: Records the window holds, when the answer was capped.
RatioBucket
interface RatioBucket
One ratio bucket (RatioBucketResponse).
Members
bucketStart:stringcount:numberdenValue:numberdivZero:booleannumValue:numberquality?:number | undefinedvalue:number: numValue / denValue; 0 with divZero true when the denominator is 0.
RatioInput
interface RatioInput extends ReadWindow
Input to data.ratio.
Members
bucketWidth:stringdenominator:RatioSideend?:string | Date | undefinednumerator:RatioSidesince?:string | undefined: How far back from now: a number and a unit, s m h or d ("90s", "30m", "8h", "7d").start?:string | Date | undefined
RatioOptions
type RatioOptions = RatioInput & {
refreshMs?: number;
};
Options for useRatio: data.ratio's input plus an optional re-read period (at least 5 s).
RatioResult
interface RatioResult
What data.ratio answers (RatioResponse).
Members
bucketWidth:stringbuckets:RatioBucket[]denominatorAgg:stringdenominatorNonNumeric:numberdenominatorTopic:stringnumeratorAgg:stringnumeratorNonNumeric:numbernumeratorTopic:stringremediation?:string | undefinedtotal:number
RatioSide
interface RatioSide
One side of a ratio: a topic and how its bucket is folded.
Members
agg:AggregateFntopic:string
ReadState
interface ReadState<T>
What a read hook holds: the last answer, whether a read is in flight, and its failure.
Members
data?:T | undefinederror?:unknownloading:booleanrefresh:() => void: Read again now (the window is recomputed: asincewindow moves to now).
ReadWindow
interface ReadWindow
A read window: since back from now, or start (and optional end, default now). Dates or ISO strings.
Members
end?:string | Date | undefinedsince?:string | undefined: How far back from now: a number and a unit, s m h or d ("90s", "30m", "8h", "7d").start?:string | Date | undefined
Reading
interface Reading
A reading of a thing now, as the planner words it: the value, when, how old, how far it can be trusted.
Members
age:string: How old it is, in words.at:string: When it was read, RFC3339.flat?:boolean | undefinedname?:string | undefinedproperty:string: The property id, e.g. "mh:vibration".quality:"good" | "uncertain" | "bad"ref:string: The series it comes from; for kind "uns", the topic path: whatuseTopictakes.source?:string | undefinedstale?:boolean | undefinedtext?:string | undefinedunit?:string | undefinedvalue?:number | undefined
ReadyMessage
interface ReadyMessage
App → shell: handshake complete.
Members
type:"ready"v:1
RecentAlertsOptions
interface RecentAlertsOptions
Options for alerts.recent: activeOnly keeps only alarms still firing; shelved only shelved ones.
Members
activeOnly?:boolean | undefinedlimit?:number | undefinedshelved?:boolean | undefined
RecordEvent
interface RecordEvent
One event to record on a thing.
Members
at?:string | undefined: When it happened, RFC3339; the plant's now when left out. Not ahead of the plant's clock.cleared_at?:string | undefined: When an event that lasts ended (a changeover), RFC3339; not beforeat.kind:RecordableKindname?:string | undefined: What it was: "short fill", "BRIX", a work order's title. At most 128 characters.payload?:Record<string, unknown> | undefined: What else to keep with it: a count, a result, a reason code. At most 16 KB as JSON.severity?:"info" | "warning" | "critical" | undefinedthing:string: The thing's id (key app state by id, never by a topic path).
RecordEventsInput
interface RecordEventsInput
Input to plant.record: events landing together or not at all, at most 100.
Members
events:RecordEvent[]note?:string | undefined: Why: required where a part asks a reason for every change.
RecordableKind
type RecordableKind = 'reject' | 'lab_result' | 'fill' | 'changeover' | 'work_order' | 'calibration' | 'proof_test' | 'shift';
The event kinds a person may record; what a machine signals or the readings show is not recorded by hand.
Recorded
interface Recorded
What became of a record. state is "applied" (in the plant now) or
"proposed" (waiting under Review; waiting says why: the viewer may
not decide, the app lacks proposals:decide, or the part is held to
another person's approval). Events land at once unless a part is held;
facts wait unless the viewer may decide. Show the difference: a
proposed record has not happened in the plant yet.
Members
applied?:string | undefinedchange_set:stringevents?:string[] | undefined: The recorded events' ids, in the order given.pieces:RecordedPiece[]state:"applied" | "proposed"waiting?:string | undefined
RecordedPiece
interface RecordedPiece
One piece of a record and what the plant made of it.
Members
describes:stringindex:numberreported?:string[] | undefinedwould_fail:boolean
RunPipelineOptions
interface RunPipelineOptions extends ActionOptions
Options for pipelines.run: the write options, plus how long to wait and who hears progress.
Members
confirmed?:boolean | undefined: The viewer confirmed this specific invocation (rung "confirm").idempotencyKey?:string | undefined: Reuse a key to retry the SAME request; omit for a fresh invocation.onProgress?:((event: ExecutionEvent) => void) | undefined: Each live event of THIS run (node started/completed, execution finished), as it happens.onStarted?:((executionId: string) => void) | undefined: Called once the platform has named the run: the idpipelines.canceltakes.timeoutMs?:number | undefined: How long to wait for the run to finish; default 60 000 ms. Past it the promise rejects; the run goes on.
ScreenMessage
interface ScreenMessage
App → shell: what the app shows, as text (its visible text in reading
order), sent while the shell asked for it (reportScreen), a moment
after the screen settles and only when it changed. The builder passes
it to the server for run_report.
Members
text:stringtype:"screen"v:1
SetFacts
interface SetFacts
Facts to set on one thing, by property id.
Members
facts:Record<string, FactInput>thing:string
SetOptions
interface SetOptions
Which things a plant.numbers or plant.timeline set covers, in match's words, or a plain list of ids.
Members
include_subtypes?:boolean | undefinedthings?:string[] | undefinedtype?:string | undefinedunder?:string | undefined: Everything below this thing in the physical tree.
ShellToAppMessage
type ShellToAppMessage = ContextMessage | TokenMessage | ThemeMessage | DisableMessage | DownloadRefusedMessage;
Every message the shell may post into the app iframe: the initial
context, token rotations, theme flips, disable (kill switch /
unshare) and download_refused. Discriminated on type.
SiteClock
interface SiteClock
A site's clock.
Members
name:stringnow:stringsite:stringzone:string
StateDurationEntry
interface StateDurationEntry
Time in one state (StateDurationEntryResponse); durationNanos / 1e9 is seconds.
Members
count:numberdurationNanos:numberstate:string
StateDurationInput
interface StateDurationInput extends ReadWindow
Input to data.stateDuration.
Members
bucketWidth?:string | undefinedend?:string | Date | undefinedmode:"exact" | "range" | "breakdown"ranges?:StateRange[] | undefinedsince?:string | undefined: How far back from now: a number and a unit, s m h or d ("90s", "30m", "8h", "7d").start?:string | Date | undefinedtopic:string
StateDurationOptions
type StateDurationOptions = Omit<StateDurationInput, 'topic'> & {
refreshMs?: number;
};
Options for useStateDuration: data.stateDuration's input without the topic, plus refreshMs.
StateDurationResult
interface StateDurationResult
What data.stateDuration answers (StateDurationResponse).
Members
buckets:{ bucketStart: string; entries: StateDurationEntry[]; }[]mode:stringtopic:stringtotal:number
StateRange
interface StateRange
A labelled numeric band for state-duration mode "range" (RangeSpecRequest).
Members
label:stringmax:numbermin:number
StoredDocument
interface StoredDocument<T = unknown>
One document as the storage API returns it.
Members
key:string: The document key within its collection.revision:number: Server-assigned revision;putsends it back for compare-and-set writes.updated_at:string: RFC3339 instant of the last write.updated_by:string: The user who last wrote the document.value:T: The stored JSON value.
SubmitPipelineInput
interface SubmitPipelineInput
Input to pipelines.submit.
Members
data?:Record<string, unknown> | undefined: Payload delivered to the run as its trigger data.nodeId:string: The manual-trigger node's id inside that pipeline.pipelineId:string: The pipeline that owns the manual-trigger node.
SubmitPipelineResult
interface SubmitPipelineResult
What the scheduler's trigger endpoint returns on success.
Members
execution_id?:string | undefined: The run the trigger started (scheduler TriggerResponse.execution_id): follow it withuseExecutionStream, read it at GET /engine/executions/:id.status:string: The scheduler's status word for the accepted trigger.
TelemetryMessage
interface TelemetryMessage
App → shell: one API call's timing (G7). The builder aggregates these into "slow data sources" so latency is a build-time signal, not a production surprise. Sent for every apiFetch; cheap, fire-and-forget.
Members
durationMs:numbermethod:stringpath:stringstatus:numbertype:"telemetry"v:1
TextOptions
interface TextOptions
Options for Label.text: size, and an optional wrapping block.
Members
align?:"left" | "center" | "right" | undefinedheightMm?:number | undefined: Character height (default 4 mm).maxLines?:number | undefinedwidthMm?:number | undefined: Wrap within this width, up tomaxLineslines.
ThemeMessage
interface ThemeMessage
Shell → app: a live theme switch (FR-17). The context message carries the initial mode; this one follows every flip the viewer makes in the shell, so an app never has to reload to match its host.
Members
theme:ThemeStatetype:"theme"v:1
ThemeState
interface ThemeState
The theme as the shell sends it: the mode, and the host tokens when the shell could resolve them.
Members
mode:"light" | "dark"palette?:HostPalette | undefined
Thing
interface Thing
One thing the plant knows: a machine, a line, an area, a site.
Members
facts?:Record<string, FactValue> | undefinedid:stringlabels?:Record<string, string> | undefinedname:stringsite_id:stringtype_id?:string | undefined: Its type, e.g. "company:centrifugal_pump".validity?:{ valid_from?: string; valid_to?: string; } | undefinedworkspace_id?:string | undefined
TimelineEntry
interface TimelineEntry
One entry of plant.timeline: the event and the thing it happened to.
Members
alarm?:AlarmState | undefinedevent:PlantEventsite_name?:string | undefinedthing_name:stringwhy?:string | undefined: Withrank: why it comes first.
TimelineOptions
interface TimelineOptions
What plant.timeline takes: whose events, of which kinds, over which period or state.
Members
as_of?:string | undefined: The model as it was at an instant, RFC3339.kinds?:string[] | undefined: Which kinds of event: "alarm", "trip", "work_order", "mode_change", "due", …; leave it out for all.limit?:number | undefinedrank?:boolean | undefined: Order by what to look at first (severity, then how much sits downstream) rather than by time.state?:"active" | "to_act_on" | "acknowledged" | "shelved" | "standing" | undefined: Live alarms ("active"), or only those to act on, acknowledged, shelved, standing.things?:string[] | undefined: The things to ask about, by id or by name.under?:string | undefined: Everything below this thing in the physical tree.window?:string | undefined: The period: "now", "current_shift", "yesterday", "last_7d", "last_24h", a date, a month.
TokenMessage
interface TokenMessage
Shell → app: a rotated token (silent re-issue, DS4).
Members
actions?:Record<string, ActionPermission> | undefined: The re-issue's per-action answers: a right granted or taken away mid-session shows here.token:TokenPayloadtype:"token"v:1
TokenPayload
interface TokenPayload
The scoped app token the shell hands over in context and re-issues in token pushes.
Members
expiresAt:string: RFC3339: the shell re-issues before this; the app never refreshes itself.value:string: The bearer valueapiFetchand the live socket authenticate with.
TopicContract
interface TopicContract
What data.contract answers (TopicContractResponse).
Members
binding:Record<string, unknown> | null: The topic's binding to its schema.schema:TopicSchema | nullseries?:{ data_type: string; unit?: string; mismatch?: Record<string, unknown>; } | undefined: The shape the stored series has; absent before the first publish.
TopicMessage
interface TopicMessage
One value delivered on a topic.
Members
quality?:number | undefinedraw:string: The payload exactly as the server sent it.receivedAt:numberretain:booleanseeded?:boolean | undefined: True for the topic's last stored value, delivered as the subscription starts so a topic that publishes rarely is not blank until its next publish. Every later message is a newly published one.source?:string | undefinedtimestamp?:string | undefined: Server-side fields of the enriched envelope (application.MQTTPayload) when present.topic:stringvalue:unknown: The parsed value: the enriched envelope'svalue, else parsed JSON, else the raw string.
TopicSchema
interface TopicSchema
What a topic carries, as its schema says (TopicContractResponse.schema; DataSchema on the wire).
Members
alert?:Record<string, unknown> | undefined: The alert thresholds the platform fires on.attributes?:Record<string, unknown>[] | undefined: An object's attributes, each with its own type and unit.display_name?:string | undefinedenum_values?:{ value: string; display?: string; semantic_id?: string; }[] | undefined: An enum's closed set.id:stringkind:number: 1 one value, 2 an object with attributes, 3 an enum.major_version:numbermax?:number | undefinedmin?:number | undefinedminor_version:numbername:stringunit?:string | undefinedvalue_type?:{ [key: string]: unknown; kind: string; primitive?: string; } | undefined: For a value: {kind: "primitive", primitive: "float64" | "int64" | "bool" | "string" | ...}.
TopicState
interface TopicState
What useTopic returns; status and error explain why value may be null.
Members
error:LiveError | null: The last refusal or failure; cleared by the next message.message:TopicMessage | null: The full last message (raw payload, retain flag, envelope fields;seededon the stored one), or null before the first.stale:boolean: The value shown is the last one received and is not live now (the connection dropped or the topic was refused after it arrived). Show it as such:message.receivedAtsays when it came.status:LiveStatus: Current connection state for this topic.value:unknown: The latest value (the enriched envelope'svalue): the topic's last stored value as soon as the subscription starts, then each newly published one. Null only for a topic that has never published.
TopicSubscriber
interface TopicSubscriber
Callbacks handed to live.subscribe; useTopic and subscribeTopic build one for you.
Members
onError?:((e: LiveError) => void) | undefined: Refusals and failures (missing scope, refused topic, auth error, reconnecting, app disabled).onMessage:(m: TopicMessage) => void: Every value delivered on the topic.onStatus?:((s: LiveStatus) => void) | undefined: Connection-state transitions for this subscription.
TopicsState
type TopicsState = Record<string, TopicState>;
What useTopics returns: one TopicState per path asked for.
TreeView
interface TreeView
A tree the plant keeps: a legal value of plant.walk's tree.
Members
default?:boolean | undefinedid:stringlink_types?:string[] | undefinedname:stringtree_kind:string
TypeCount
interface TypeCount
How many things are of a type.
Members
subtypes?:string[] | undefinedthings:numbertype:stringwith_subtypes?:number | undefined
UpdateFactsInput
interface UpdateFactsInput
Input to plant.update: facts on one or more things, landing together or not at all.
Members
facts:SetFacts[]note?:string | undefined: Why: required where a part asks a reason for every change.
UsageInfo
interface UsageInfo
What Collection.usage returns: how full the collection's scope is.
Members
cap_bytes:number: The quota; a write that would exceed it is refused with a 413.doc_count:number: Number of documents stored.total_bytes:number: Bytes used.
Vocabulary
interface Vocabulary
plant.whatCanIAsk: the plant's vocabulary: its sites, trees, types, and what it measures.
Members
event_kinds?:string[] | undefinedlink_types?:string[] | undefinedmeasured_properties:string[]now:stringsites:SiteClock[]trees:TreeView[]types:TypeCount[]
WalkOptions
interface WalkOptions
What plant.walk takes: a start, and either a tree or the link types to follow.
Members
as_of?:string | undefined: The plant as it was at an instant, RFC3339.depth?:number | undefined: How many hops; leave it out to go as far as it goes.direction?:"downstream" | "upstream" | "both" | "up" | "down" | undefined: Along links: "upstream" / "downstream" / "both"; in a tree: "up" / "down".from:string: The thing to start at, by id or by name.tree?:string | undefined: Follow a whole tree: "physical", "location", "floc", "electrical", "cost_centre";whatCanIAsklists this plant's.type?:string[] | undefined: Return only these types, having walked through the rest.values?:string[] | undefined: Properties to read for everything reached, in one read: the fleet's values, ranked by you.via?:string[] | undefined: The link types to follow instead of a tree, e.g. ["feeds"].
WalkedThing
interface WalkedThing
One thing a plant.walk reached.
Members
depth:numbername?:string | undefinedreadings?:Reading[] | undefined: Withvalues: the properties asked, read now.site:stringstub?:boolean | undefinedthing:stringtype?:string | undefinedvia?:string[] | undefined: The link types followed to reach it.