@devicechain/widgets 0.27.0-rc.1 → 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -175,6 +175,7 @@ function CommandButton({ widget, data, actions }) {
175
175
  }, [schemaKey]);
176
176
  const canWrite = actions?.can("command:write") ?? false;
177
177
  const hasSeam = typeof actions?.sendCommand === "function";
178
+ const denied = !!actions && !canWrite;
178
179
  const deviceToken = data.deviceToken;
179
180
  const formDisabled = !canWrite || !hasSeam || pending;
180
181
  const canSend = !formDisabled && !!deviceToken && !!commandName;
@@ -216,7 +217,15 @@ function CommandButton({ widget, data, actions }) {
216
217
  if (gen === sendGen.current) setPending(false);
217
218
  });
218
219
  };
219
- return /* @__PURE__ */ jsx(WidgetFrame, { title: optString(widget.options, "title"), children: /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", height: "100%", gap: 10, overflowY: "auto" }, children: !commandName ? /* @__PURE__ */ jsx("div", { style: { ...hint, margin: "auto" }, children: "No command configured." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
220
+ return /* @__PURE__ */ jsx(WidgetFrame, { title: optString(widget.options, "title"), children: /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", height: "100%", gap: 10, overflowY: "auto" }, children: !commandName ? /* @__PURE__ */ jsx("div", { style: { ...hint, margin: "auto" }, children: "No command configured." }) : denied ? (
221
+ // A viewer without command:write is not offered the control at all: a form and a
222
+ // Send button that can never work read as a broken board. The reason stands in its
223
+ // place, and the delivery history (read-only) stays.
224
+ /* @__PURE__ */ jsxs(Fragment, { children: [
225
+ /* @__PURE__ */ jsx("div", { role: "note", style: { ...hint, flexShrink: 0 }, children: NO_PERMISSION }),
226
+ /* @__PURE__ */ jsx(CommandHistory, { commands: data.commands, sentToken, loading: data.loading, error: data.error })
227
+ ] })
228
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
220
229
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8, flexShrink: 0 }, children: [
221
230
  params.map(
222
231
  (param) => isScalar(param) ? /* @__PURE__ */ jsx(
@@ -252,7 +261,7 @@ function CommandButton({ widget, data, actions }) {
252
261
  children: pending ? "Sending\u2026" : `Send ${commandLabel}`
253
262
  }
254
263
  ),
255
- !canWrite ? /* @__PURE__ */ jsx("div", { style: hint, children: "You don\u2019t have permission to issue commands." }) : !hasSeam ? /* @__PURE__ */ jsx("div", { style: hint, children: "Commands aren\u2019t available in this view." }) : !deviceToken ? /* @__PURE__ */ jsx("div", { style: hint, children: "No device is selected for this command." }) : null,
264
+ !canWrite ? /* @__PURE__ */ jsx("div", { style: hint, children: NO_PERMISSION }) : !hasSeam ? /* @__PURE__ */ jsx("div", { style: hint, children: "Commands aren\u2019t available in this view." }) : !deviceToken ? /* @__PURE__ */ jsx("div", { style: hint, children: "No device is selected for this command." }) : null,
256
265
  sendError ? /* @__PURE__ */ jsx("div", { role: "alert", style: { ...hint, color: commandStatusColor("FAILED") }, children: sendError }) : null
257
266
  ] }),
258
267
  /* @__PURE__ */ jsx(CommandHistory, { commands: data.commands, sentToken, loading: data.loading, error: data.error })
@@ -383,6 +392,7 @@ function StatusBadge({ status }) {
383
392
  );
384
393
  }
385
394
  var hint = { fontSize: fontVar("xs"), color: css("muted-foreground") };
395
+ var NO_PERMISSION = "You don\u2019t have permission to issue commands.";
386
396
  var SEND_FAILED = "Couldn\u2019t issue the command. Try again.";
387
397
  var inputStyle = {
388
398
  width: "100%",
@@ -416,4 +426,4 @@ export {
416
426
  buildPayload,
417
427
  CommandButton
418
428
  };
419
- //# sourceMappingURL=chunk-XSLFHCNI.js.map
429
+ //# sourceMappingURL=chunk-OV5KANSD.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/widgets/command-status.ts", "../../src/widgets/command-params.ts", "../../src/widgets/command-button.tsx"],
4
+ "sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Command delivery-status presentation for the command-button widget (command-delivery\n// lifecycle: QUEUED \u2192 SENT \u2192 SUCCESSFUL | FAILED, with HELD for a dispatch withheld from\n// a known-absent device, PARKED for one published to a device that turned out to be\n// unreachable, and TIMEOUT / EXPIRED / CANCELLED as the other terminals).\n// Status color is SEMANTIC (good/working/waiting/bad), deliberately fixed rather than\n// derived from the dashboard accent, so the same status reads the same on every theme \u2014\n// and it mirrors the console's DeviceCommandsPanel status variants (success /\n// destructive / outline / in-flight) so an operator reads the same signal on both\n// surfaces.\n\nimport { isTerminalCommandStatus } from '@devicechain/dashboards';\n\n// \uD83D\uDD34 EVERY STATUS NEEDS AN ENTRY, INCLUDING THE ONES THAT LOOK NEUTRAL. UNKNOWN_COLOR\n// below is the same slate as EXPIRED, so a status with no entry does not merely fall\n// back \u2014 it becomes INDISTINGUISHABLE from \"lapsed before it was sent\". HELD and PARKED\n// (the command is still coming) rendered in EXPIRED's slate tell an operator the opposite\n// of the truth, which is why they, and CANCELLED, are spelled out rather than left to the\n// fallback.\n//\n// The scheme is by MEANING, not by lifecycle position: sky = moving under its own steam,\n// amber = waiting on a device that isn't there. HELD and PARKED share the amber family\n// because they are the same answer to \"why hasn't it arrived?\" \u2014 the device is away \u2014 and\n// take different shades because they are different answers to \"what did the platform\n// already try?\": HELD never left the platform, PARKED was published and found nobody home.\nconst STATUS_COLORS: Record<string, string> = {\n SUCCESSFUL: '#16a34a', // green-600 \u2014 acknowledged by the device\n FAILED: '#dc2626', // red-600\n TIMEOUT: '#dc2626', // red-600 \u2014 never acknowledged in time\n EXPIRED: '#64748b', // slate-500 \u2014 lapsed before it was ever sent\n CANCELLED: '#7c3aed', // violet-600 \u2014 deliberately called off; a decision, not a failure\n QUEUED: '#0284c7', // sky-600 \u2014 in flight\n SENT: '#0284c7',\n HELD: '#d97706', // amber-600 \u2014 withheld: the device is absent, the command still stands\n PARKED: '#f59e0b', // amber-500 \u2014 published, nobody home; held for the device's return\n};\n\nconst UNKNOWN_COLOR = '#64748b';\n\n// commandStatusColor maps a status to its badge color, falling back to muted slate for\n// an unrecognized value (a future/hand-edited status).\nexport function commandStatusColor(status: string): string {\n return STATUS_COLORS[status] ?? UNKNOWN_COLOR;\n}\n\n// isTerminalStatus reports whether a command has reached a state it can't leave \u2014 the\n// widget stops treating it as in-flight.\n//\n// It is NOT the question \"can this still be cancelled?\": SENT is non-terminal and is not\n// cancellable, so a host gating a cancel control must ask @devicechain/dashboards'\n// isCancellableCommandStatus instead of negating this.\n//\n// It is an alias, not a second definition: the terminal set lives in @devicechain/dashboards\n// so the console, this package and the preview cannot disagree about it. The name is kept\n// because it is part of this package's published surface.\nexport function isTerminalStatus(status: string): boolean {\n return isTerminalCommandStatus(status);\n}\n\n// commandStatusLabel renders a status in Title Case ('SUCCESSFUL' \u2192 'Successful').\nexport function commandStatusLabel(status: string): string {\n if (!status) return '';\n return status.charAt(0).toUpperCase() + status.slice(1).toLowerCase();\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Pure form logic for the command-button widget's typed parameter form (ADR-043). The\n// console bakes a command definition's `parameterSchema` (a JSON string) into the\n// widget's options; these helpers parse it, seed defaults, validate operator input, and\n// serialize the typed values back to the JSON payload command-delivery sends the device.\n// Kept separate from the widget so the coercion/validation is unit-tested in isolation.\n//\n// SCOPE: the form types top-level SCALAR parameters (DOUBLE / INT / BOOLEAN / STRING,\n// with enum / bounds / required / default). OBJECT (nested) parameters are not\n// form-editable \u2014 the form blocks sending when one is REQUIRED (it can't be satisfied\n// here) and otherwise omits it. The payload is a flat JSON object keyed by parameter name.\n\nimport type { CommandParameter } from '@devicechain/dashboards';\n\nconst DATA_TYPES = new Set(['DOUBLE', 'INT', 'BOOLEAN', 'STRING']);\n\n// parseParameterSchema turns the baked JSON string into descriptors, defensively:\n// a null/empty/malformed schema (or a non-array root) yields an empty list so a command\n// with no arguments (or a hand-broken schema) renders as a bare Send button, never throws.\n// Each descriptor is NORMALIZED (enum\u2192string[], default\u2192string, bounds\u2192finite number,\n// kind/dataType\u2192known value) so a hand-edited dashboard definition with wrong-typed fields\n// (e.g. `enum: \"on\"` or `default: 5`) can't crash the form at render/send time.\nexport function parseParameterSchema(schema: string | null | undefined): CommandParameter[] {\n if (!schema) return [];\n let parsed: unknown;\n try {\n parsed = JSON.parse(schema);\n } catch {\n return [];\n }\n if (!Array.isArray(parsed)) return [];\n const out: CommandParameter[] = [];\n for (const raw of parsed) {\n const norm = normalizeParam(raw);\n if (norm) out.push(norm);\n }\n return out;\n}\n\n// normalizeParam validates one descriptor and coerces every field to its expected type,\n// dropping the descriptor entirely if it has no string `name`. Unknown/wrong-typed fields\n// are discarded rather than trusted, so downstream code never sees a non-array enum or a\n// numeric default.\nfunction normalizeParam(raw: unknown): CommandParameter | null {\n if (typeof raw !== 'object' || raw === null) return null;\n const r = raw as Record<string, unknown>;\n if (typeof r.name !== 'string') return null;\n\n const param: CommandParameter = { name: r.name };\n if (typeof r.description === 'string') param.description = r.description;\n if (r.kind === 'OBJECT') param.kind = 'OBJECT';\n else if (r.kind === 'SCALAR') param.kind = 'SCALAR';\n if (typeof r.dataType === 'string' && DATA_TYPES.has(r.dataType)) {\n param.dataType = r.dataType as CommandParameter['dataType'];\n }\n if (typeof r.unit === 'string') param.unit = r.unit;\n if (r.required === true) param.required = true;\n if (typeof r.default === 'string') param.default = r.default;\n if (typeof r.minValue === 'number' && Number.isFinite(r.minValue)) param.minValue = r.minValue;\n if (typeof r.maxValue === 'number' && Number.isFinite(r.maxValue)) param.maxValue = r.maxValue;\n if (Array.isArray(r.enum)) {\n const values = r.enum.filter((v): v is string => typeof v === 'string');\n if (values.length > 0) param.enum = values;\n }\n return param;\n}\n\n// isScalar reports whether a parameter is a typed single value the form renders an input\n// for. Kind absent means SCALAR (the Go default); only an explicit OBJECT is nested.\nexport function isScalar(param: CommandParameter): boolean {\n return param.kind !== 'OBJECT';\n}\n\n// parseBool interprets a schema/string boolean the way the server's strconv.ParseBool\n// does (accepting '1'/'t'/'true'/'yes'/'on', case-insensitively) so a definition default\n// of \"True\" doesn't silently render \u2014 and send \u2014 as false.\nexport function parseBool(raw: string): boolean {\n const s = raw.trim().toLowerCase();\n return s === 'true' || s === '1' || s === 't' || s === 'yes' || s === 'on';\n}\n\n// defaultValues seeds the form's value map from each scalar parameter. A BOOLEAN always\n// gets a concrete 'true'/'false' (default false) \u2014 a checkbox is never \"empty\", so the\n// value must reflect the box's state exactly. Other scalars seed from their declared\n// default (as a string \u2014 inputs are string-backed), else start empty.\nexport function defaultValues(params: CommandParameter[]): Record<string, string> {\n const values: Record<string, string> = {};\n for (const p of params) {\n if (!isScalar(p)) continue;\n if (p.dataType === 'BOOLEAN') values[p.name] = parseBool(p.default ?? '') ? 'true' : 'false';\n else if (p.default != null) values[p.name] = p.default;\n }\n return values;\n}\n\n// validateParams returns a per-parameter error map (empty = valid). It enforces required\n// (a missing value on a required scalar; a BOOLEAN always has a value so is never\n// \"required-missing\"), numeric validity/bounds for INT/DOUBLE, and blocks a REQUIRED\n// OBJECT parameter (it can't be supplied through the typed form).\nexport function validateParams(\n params: CommandParameter[],\n values: Record<string, string>,\n): Record<string, string> {\n const errors: Record<string, string> = {};\n for (const p of params) {\n if (!isScalar(p)) {\n // A required structured parameter can't be satisfied here \u2014 refuse to send an\n // incomplete command rather than silently omitting it.\n if (p.required) errors[p.name] = 'Structured parameter \u2014 not supported here';\n continue;\n }\n if (p.dataType === 'BOOLEAN') continue; // a checkbox always carries a valid value\n const raw = (values[p.name] ?? '').trim();\n if (raw === '') {\n if (p.required) errors[p.name] = 'Required';\n continue; // an empty optional value is fine \u2014 it's just omitted from the payload\n }\n if (p.dataType === 'INT' || p.dataType === 'DOUBLE') {\n const n = Number(raw);\n if (!Number.isFinite(n)) {\n errors[p.name] = 'Must be a number';\n } else if (p.dataType === 'INT' && !Number.isInteger(n)) {\n errors[p.name] = 'Must be a whole number';\n } else if (p.minValue != null && n < p.minValue) {\n errors[p.name] = `Must be \u2265 ${p.minValue}`;\n } else if (p.maxValue != null && n > p.maxValue) {\n errors[p.name] = `Must be \u2264 ${p.maxValue}`;\n }\n }\n }\n return errors;\n}\n\n// buildPayload serializes the typed values to the JSON payload string command-delivery\n// sends the device \u2014 coercing each scalar to its declared type (INT/DOUBLE \u2192 number,\n// BOOLEAN \u2192 boolean, STRING \u2192 string). A BOOLEAN is always included (its checkbox state\n// is meaningful); other empty optional values are omitted. OBJECT parameters are omitted\n// (validateParams blocks a required one first). Returns undefined when no parameter\n// contributes a value (a parameterless command sends no payload).\nexport function buildPayload(\n params: CommandParameter[],\n values: Record<string, string>,\n): string | undefined {\n const payload: Record<string, unknown> = {};\n for (const p of params) {\n if (!isScalar(p)) continue;\n const raw = (values[p.name] ?? '').trim();\n if (p.dataType === 'BOOLEAN') {\n payload[p.name] = parseBool(raw);\n continue;\n }\n if (raw === '') continue;\n payload[p.name] = coerce(p, raw);\n }\n return Object.keys(payload).length === 0 ? undefined : JSON.stringify(payload);\n}\n\nfunction coerce(param: CommandParameter, raw: string): unknown {\n switch (param.dataType) {\n case 'INT':\n case 'DOUBLE': {\n const n = Number(raw);\n return Number.isFinite(n) ? n : raw; // validation gates this; fall back to raw defensively\n }\n case 'BOOLEAN':\n return parseBool(raw);\n default:\n return raw; // STRING (and untyped)\n }\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// command-button \u2014 issue a device command from the dashboard and watch its delivery\n// lifecycle (command-delivery). The console bakes a command definition into the widget's\n// options at author time (its name + parameterSchema); this renders a typed form from\n// that schema, issues via the action seam (gated on command:write), and shows the recent\n// commands for the target device with live status (QUEUED \u2192 SENT \u2192\n// SUCCESSFUL / FAILED \u2026), so an operator can see whether a command was acted on rather\n// than only that it was sent. Bound through the hub's control channel, so it renders identically from live\n// data or the synthetic preview source.\n//\n// The baked schema is a SNAPSHOT taken when the author picked the command: a later edit\n// to the profile's command definition (a changed schema, or a deleted command) leaves\n// this widget's form stale until the author re-picks it. That's acceptable \u2014 dashboards\n// are opaque snapshots \u2014 and a since-removed command fails visibly at the delivery\n// boundary (the server validates device existence and JSON); it isn't silently wrong.\n\nimport type { CommandDispatch, CommandParameter } from '@devicechain/dashboards';\nimport { useEffect, useMemo, useRef, useState, type CSSProperties } from 'react';\n\nimport {\n type CommandStreamState,\n css,\n fontVar,\n formatDateTime,\n optString,\n useWidgetTimeZone,\n WidgetFrame,\n type WidgetProps,\n} from '@devicechain/widget-sdk';\n\nimport { commandStatusColor, commandStatusLabel } from './command-status';\nimport {\n buildPayload,\n defaultValues,\n isScalar,\n parseBool,\n parseParameterSchema,\n validateParams,\n} from './command-params';\n\nexport function CommandButton({ widget, data, actions }: WidgetProps<CommandStreamState>) {\n const commandName = optString(widget.options, 'commandName');\n const commandLabel = optString(widget.options, 'commandLabel') ?? commandName;\n const parameterSchema = optString(widget.options, 'parameterSchema') ?? null;\n\n const params = useMemo(() => parseParameterSchema(parameterSchema), [parameterSchema]);\n // A stable key over the configured command: reset the form when the author repoints\n // the widget at a different command (or edits its schema).\n const schemaKey = `${commandName ?? ''}::${parameterSchema ?? ''}`;\n\n const [values, setValues] = useState<Record<string, string>>(() => defaultValues(params));\n const [errors, setErrors] = useState<Record<string, string>>({});\n const [pending, setPending] = useState(false);\n const [sendError, setSendError] = useState<string | null>(null);\n const [sentToken, setSentToken] = useState<string | null>(null);\n // Bumped whenever the configured command changes; an in-flight send captures the\n // current value and drops its result if it settles after a repoint (so command A's\n // dispatch token/error can't land under a freshly-configured command B).\n const sendGen = useRef(0);\n\n // Reset the form to the schema's defaults whenever the configured command changes.\n useEffect(() => {\n sendGen.current += 1;\n setValues(defaultValues(params));\n setErrors({});\n setSendError(null);\n setSentToken(null);\n // params is derived from schemaKey; keying on schemaKey avoids re-running on an\n // equal-but-new params array reference.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [schemaKey]);\n\n const canWrite = actions?.can('command:write') ?? false;\n const hasSeam = typeof actions?.sendCommand === 'function';\n // A viewer the host KNOWS lacks command:write (a host supplied the seam and said no) is not\n // offered the control. A mount with no seam at all (a preview, the widget gallery) still shows\n // the form, disabled, so the author can see what the widget looks like.\n const denied = !!actions && !canWrite;\n const deviceToken = data.deviceToken;\n // The form is operable only when the viewer may write AND the runtime supplies the send\n // seam; a read-only mount shows the form disabled. Sending additionally needs a bound\n // target device and a configured command.\n const formDisabled = !canWrite || !hasSeam || pending;\n const canSend = !formDisabled && !!deviceToken && !!commandName;\n\n const setField = (name: string, value: string) => {\n setValues((prev) => ({ ...prev, [name]: value }));\n // Clear a field's error as the operator edits it.\n setErrors((prev) => (prev[name] ? { ...prev, [name]: '' } : prev));\n setSendError(null);\n };\n\n const send = () => {\n if (!deviceToken || !commandName || !actions?.sendCommand) return;\n const found = validateParams(params, values);\n const active = Object.fromEntries(Object.entries(found).filter(([, v]) => v));\n if (Object.keys(active).length > 0) {\n setErrors(active);\n return;\n }\n const payload = buildPayload(params, values);\n const gen = sendGen.current; // capture: drop the result if the command is repointed mid-flight\n setPending(true);\n setSendError(null);\n // Call synchronously so a dispatch is observable, but guard a synchronous throw so a\n // misbehaving seam can't leave the button stuck-disabled.\n let promise: Promise<CommandDispatch>;\n try {\n promise = actions.sendCommand(deviceToken, commandName, payload);\n } catch {\n setSendError(SEND_FAILED);\n setPending(false);\n return;\n }\n promise\n .then((dispatch) => {\n if (gen !== sendGen.current) return;\n // \uD83D\uDD34 A REFUSAL IS SHOWN, A FAILURE IS NOT. The server decided this command was\n // wrong and said why in client-safe terms (an unknown command for this device, a\n // parameter that violates the schema, the tenant's held-command ceiling) \u2014 that\n // reason is the whole point of the answer, and an operator can act on it. A\n // thrown error, below, is an availability failure: it says nothing about the\n // command, and its text is internal, so it gets one fixed message instead.\n if (dispatch.status === 'rejected') {\n setSendError(dispatch.reason);\n return;\n }\n setSentToken(dispatch.token);\n })\n .catch(() => {\n if (gen === sendGen.current) setSendError(SEND_FAILED);\n })\n .finally(() => {\n if (gen === sendGen.current) setPending(false);\n });\n };\n\n return (\n <WidgetFrame title={optString(widget.options, 'title')}>\n {/* A short widget (its minimum is two rows, and a cell on a phone is ~56 px) scrolls the\n form down rather than cutting off the send button or the line explaining why it is off. */}\n <div style={{ display: 'flex', flexDirection: 'column', height: '100%', gap: 10, overflowY: 'auto' }}>\n {!commandName ? (\n <div style={{ ...hint, margin: 'auto' }}>No command configured.</div>\n ) : denied ? (\n // A viewer without command:write is not offered the control at all: a form and a\n // Send button that can never work read as a broken board. The reason stands in its\n // place, and the delivery history (read-only) stays.\n <>\n <div role=\"note\" style={{ ...hint, flexShrink: 0 }}>\n {NO_PERMISSION}\n </div>\n <CommandHistory commands={data.commands} sentToken={sentToken} loading={data.loading} error={data.error} />\n </>\n ) : (\n <>\n {/* Typed parameter form. */}\n <div style={{ display: 'flex', flexDirection: 'column', gap: 8, flexShrink: 0 }}>\n {params.map((param) =>\n isScalar(param) ? (\n <ParamField\n key={param.name}\n widgetId={widget.id}\n param={param}\n value={values[param.name] ?? ''}\n error={errors[param.name]}\n disabled={formDisabled}\n onChange={(v) => setField(param.name, v)}\n />\n ) : (\n // A structured (OBJECT) parameter isn't form-editable. A REQUIRED one\n // blocks sending (validateParams flags it); flag that in the note.\n <div key={param.name} style={{ ...hint, color: param.required ? commandStatusColor('FAILED') : css('muted-foreground') }}>\n {param.name}: {param.required ? 'required ' : ''}structured parameter \u2014 not supported here.\n </div>\n ),\n )}\n\n <button\n type=\"button\"\n style={{ ...sendButton, opacity: canSend ? 1 : 0.55, cursor: canSend ? 'pointer' : 'not-allowed' }}\n disabled={!canSend}\n aria-busy={pending}\n onClick={send}\n >\n {pending ? 'Sending\u2026' : `Send ${commandLabel}`}\n </button>\n\n {!canWrite ? (\n <div style={hint}>{NO_PERMISSION}</div>\n ) : !hasSeam ? (\n <div style={hint}>Commands aren\u2019t available in this view.</div>\n ) : !deviceToken ? (\n <div style={hint}>No device is selected for this command.</div>\n ) : null}\n {sendError ? (\n <div role=\"alert\" style={{ ...hint, color: commandStatusColor('FAILED') }}>\n {sendError}\n </div>\n ) : null}\n </div>\n\n {/* Recent-command history with live delivery status. */}\n <CommandHistory commands={data.commands} sentToken={sentToken} loading={data.loading} error={data.error} />\n </>\n )}\n </div>\n </WidgetFrame>\n );\n}\n\n// \u2500\u2500 Parameter field \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction ParamField({\n widgetId,\n param,\n value,\n error,\n disabled,\n onChange,\n}: {\n widgetId: string;\n param: CommandParameter;\n value: string;\n error?: string;\n disabled: boolean;\n onChange: (value: string) => void;\n}) {\n // Scope the input id to the widget so two command-buttons sharing a parameter name\n // don't produce duplicate DOM ids (which would misdirect a label click to the first).\n const labelId = `cmd-${widgetId}-${param.name}`;\n const unit = param.unit ? ` (${param.unit})` : '';\n return (\n <label htmlFor={labelId} style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>\n <span style={{ fontSize: fontVar('sm'), color: css('muted-foreground') }}>\n {param.name}\n {unit}\n {param.required ? <span style={{ color: commandStatusColor('FAILED') }}> *</span> : null}\n </span>\n <ParamInput id={labelId} param={param} value={value} disabled={disabled} onChange={onChange} />\n {error ? (\n <span style={{ fontSize: fontVar('xs'), color: commandStatusColor('FAILED') }}>{error}</span>\n ) : null}\n </label>\n );\n}\n\nfunction ParamInput({\n id,\n param,\n value,\n disabled,\n onChange,\n}: {\n id: string;\n param: CommandParameter;\n value: string;\n disabled: boolean;\n onChange: (value: string) => void;\n}) {\n // An enum (of any data type) \u2192 a select; a boolean \u2192 a checkbox; a number \u2192 a numeric\n // input with the declared bounds; everything else \u2192 text.\n if (param.enum && param.enum.length > 0) {\n return (\n <select id={id} style={inputStyle} value={value} disabled={disabled} onChange={(e) => onChange(e.target.value)}>\n {!param.required ? <option value=\"\">\u2014</option> : null}\n {param.enum.map((opt) => (\n <option key={opt} value={opt}>\n {opt}\n </option>\n ))}\n </select>\n );\n }\n if (param.dataType === 'BOOLEAN') {\n return (\n <input\n id={id}\n type=\"checkbox\"\n style={{ width: 16, height: 16 }}\n checked={parseBool(value)}\n disabled={disabled}\n onChange={(e) => onChange(e.target.checked ? 'true' : 'false')}\n />\n );\n }\n const numeric = param.dataType === 'INT' || param.dataType === 'DOUBLE';\n return (\n <input\n id={id}\n type={numeric ? 'number' : 'text'}\n style={inputStyle}\n value={value}\n disabled={disabled}\n min={param.minValue ?? undefined}\n max={param.maxValue ?? undefined}\n step={param.dataType === 'INT' ? 1 : 'any'}\n placeholder={param.default ?? undefined}\n onChange={(e) => onChange(e.target.value)}\n />\n );\n}\n\n// \u2500\u2500 History \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction CommandHistory({\n commands,\n sentToken,\n loading,\n error,\n}: {\n commands: CommandStreamState['commands'];\n sentToken: string | null;\n loading: boolean;\n error: unknown;\n}) {\n // An error is shown inline here (not by replacing the whole widget) so a transient poll\n // blip never tears down the still-usable send form. Last-good rows stay on screen when\n // present; only an empty-and-errored history shows the notice.\n const empty = commands.length === 0;\n const timeZone = useWidgetTimeZone();\n return (\n <div\n style={{\n flex: 1,\n minHeight: 0,\n overflow: 'auto',\n borderTop: `1px solid ${css('border')}`,\n paddingTop: 6,\n }}\n >\n {empty ? (\n <div style={{ ...hint, textAlign: 'center', color: error ? commandStatusColor('FAILED') : css('muted-foreground') }}>\n {error ? 'Couldn\u2019t load command history.' : loading ? 'Loading\u2026' : 'No commands issued yet.'}\n </div>\n ) : (\n <ul style={{ listStyle: 'none', margin: 0, padding: 0, display: 'flex', flexDirection: 'column', gap: 4 }}>\n {commands.map((command) => (\n <li\n key={command.token}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n fontSize: fontVar('sm'),\n // Highlight the command just issued from this widget.\n fontWeight: command.token === sentToken ? 600 : 400,\n }}\n >\n <StatusBadge status={command.status} />\n <span style={{ color: css('foreground'), whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>\n {command.name}\n </span>\n <span style={{ marginLeft: 'auto', color: css('muted-foreground'), whiteSpace: 'nowrap' }}>\n {formatDateTime(command.queuedTime, timeZone)}\n </span>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n\nfunction StatusBadge({ status }: { status: string }) {\n const color = commandStatusColor(status);\n return (\n <span\n style={{\n display: 'inline-flex',\n alignItems: 'center',\n gap: 4,\n flexShrink: 0,\n fontSize: fontVar('xs'),\n fontWeight: 600,\n color,\n }}\n >\n <span aria-hidden=\"true\" style={{ width: 7, height: 7, borderRadius: '50%', background: color }} />\n {commandStatusLabel(status)}\n </span>\n );\n}\n\n// \u2500\u2500 styles / helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst hint: CSSProperties = { fontSize: fontVar('xs'), color: css('muted-foreground') };\n\n// What a viewer is told when the platform could not decide the enqueue. It is fixed\n// prose on purpose: the thrown error's text describes in-cluster machinery, and a\n// dashboard is the surface most likely to be embedded outside the tenant's own console.\nconst NO_PERMISSION = 'You don\u2019t have permission to issue commands.';\n\nconst SEND_FAILED = 'Couldn\u2019t issue the command. Try again.';\n\nconst inputStyle: CSSProperties = {\n width: '100%',\n boxSizing: 'border-box',\n padding: '4px 8px',\n fontSize: fontVar('md'),\n borderRadius: 4,\n border: `1px solid ${css('border')}`,\n background: css('background'),\n color: css('foreground'),\n};\n\nconst sendButton: CSSProperties = {\n padding: '6px 12px',\n fontSize: fontVar('md'),\n fontWeight: 600,\n borderRadius: 4,\n border: 'none',\n background: css('primary'),\n color: css('background'),\n marginTop: 2,\n};\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -6,7 +6,7 @@ import {
6
6
  isScalar,
7
7
  parseParameterSchema,
8
8
  validateParams
9
- } from "./chunk-XSLFHCNI.js";
9
+ } from "./chunk-OV5KANSD.js";
10
10
 
11
11
  // src/widgets/command-slider.tsx
12
12
  import { useEffect as useEffect2, useId, useMemo, useRef as useRef2, useState as useState2 } from "react";
@@ -963,4 +963,4 @@ export {
963
963
  CommandSlider,
964
964
  CommandToggle
965
965
  };
966
- //# sourceMappingURL=control-widgets-UCTJJJ2D.js.map
966
+ //# sourceMappingURL=control-widgets-UZ3IAJ6D.js.map
package/dist/index.js CHANGED
@@ -59,7 +59,7 @@ import {
59
59
  isTerminalStatus,
60
60
  parseParameterSchema,
61
61
  validateParams
62
- } from "./chunks/chunk-XSLFHCNI.js";
62
+ } from "./chunks/chunk-OV5KANSD.js";
63
63
 
64
64
  // src/dashboard-renderer.tsx
65
65
  import {
@@ -1320,7 +1320,7 @@ var alarmCountWidget = defineWidget2({
1320
1320
 
1321
1321
  // src/widgets/control-lazy.tsx
1322
1322
  import { defineWidget as defineWidget3 } from "@devicechain/widget-sdk";
1323
- var chunk3 = lazyChunk(() => import("./chunks/control-widgets-UCTJJJ2D.js"));
1323
+ var chunk3 = lazyChunk(() => import("./chunks/control-widgets-UZ3IAJ6D.js"));
1324
1324
  var loadControlWidgets = chunk3.load;
1325
1325
  var commandSliderWidget = defineWidget3({
1326
1326
  descriptor: builtinWidgetDescriptors["command-slider"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devicechain/widgets",
3
- "version": "0.27.0-rc.1",
3
+ "version": "1.0.0-rc.1",
4
4
  "description": "DeviceChain's built-in dashboard widgets across six channels — telemetry (time-series chart + gauge over Apache ECharts, latest card, table, and lazily loaded level, indicator and radial widgets: linear and level gauges, battery, status light, signal bars, compass, progress ring), static (label, image; no data), alarm (table, count, severity breakdown), control (command button), selection (entity selector), and location (map over MapLibre GL, lazily loaded, tile source supplied by the host app) — plus the renderer that lays them out. Each is a registration built on @devicechain/widget-sdk. Themed via CSS custom properties, no Tailwind leakage.",
5
5
  "keywords": [
6
6
  "devicechain",
@@ -57,18 +57,18 @@
57
57
  "test": "vitest run"
58
58
  },
59
59
  "peerDependencies": {
60
- "@devicechain/client": "0.27.0-rc.1",
61
- "@devicechain/dashboards": "0.27.0-rc.1",
62
- "@devicechain/widget-sdk": "0.27.0-rc.1",
60
+ "@devicechain/client": "1.0.0-rc.1",
61
+ "@devicechain/dashboards": "1.0.0-rc.1",
62
+ "@devicechain/widget-sdk": "1.0.0-rc.1",
63
63
  "echarts": "^6.0.0",
64
64
  "maplibre-gl": "^6.13.0",
65
65
  "react": "^19.3.0",
66
66
  "react-dom": "^19.3.0"
67
67
  },
68
68
  "devDependencies": {
69
- "@devicechain/client": "0.27.0-rc.1",
70
- "@devicechain/dashboards": "0.27.0-rc.1",
71
- "@devicechain/widget-sdk": "0.27.0-rc.1",
69
+ "@devicechain/client": "1.0.0-rc.1",
70
+ "@devicechain/dashboards": "1.0.0-rc.1",
71
+ "@devicechain/widget-sdk": "1.0.0-rc.1",
72
72
  "@testing-library/react": "^16.3.3",
73
73
  "@types/node": "^26.6.5",
74
74
  "@types/react": "^19.3.0",
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/widgets/command-status.ts", "../../src/widgets/command-params.ts", "../../src/widgets/command-button.tsx"],
4
- "sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Command delivery-status presentation for the command-button widget (command-delivery\n// lifecycle: QUEUED \u2192 SENT \u2192 SUCCESSFUL | FAILED, with HELD for a dispatch withheld from\n// a known-absent device, PARKED for one published to a device that turned out to be\n// unreachable, and TIMEOUT / EXPIRED / CANCELLED as the other terminals).\n// Status color is SEMANTIC (good/working/waiting/bad), deliberately fixed rather than\n// derived from the dashboard accent, so the same status reads the same on every theme \u2014\n// and it mirrors the console's DeviceCommandsPanel status variants (success /\n// destructive / outline / in-flight) so an operator reads the same signal on both\n// surfaces.\n\nimport { isTerminalCommandStatus } from '@devicechain/dashboards';\n\n// \uD83D\uDD34 EVERY STATUS NEEDS AN ENTRY, INCLUDING THE ONES THAT LOOK NEUTRAL. UNKNOWN_COLOR\n// below is the same slate as EXPIRED, so a status with no entry does not merely fall\n// back \u2014 it becomes INDISTINGUISHABLE from \"lapsed before it was sent\". HELD and PARKED\n// (the command is still coming) rendered in EXPIRED's slate tell an operator the opposite\n// of the truth, which is why they, and CANCELLED, are spelled out rather than left to the\n// fallback.\n//\n// The scheme is by MEANING, not by lifecycle position: sky = moving under its own steam,\n// amber = waiting on a device that isn't there. HELD and PARKED share the amber family\n// because they are the same answer to \"why hasn't it arrived?\" \u2014 the device is away \u2014 and\n// take different shades because they are different answers to \"what did the platform\n// already try?\": HELD never left the platform, PARKED was published and found nobody home.\nconst STATUS_COLORS: Record<string, string> = {\n SUCCESSFUL: '#16a34a', // green-600 \u2014 acknowledged by the device\n FAILED: '#dc2626', // red-600\n TIMEOUT: '#dc2626', // red-600 \u2014 never acknowledged in time\n EXPIRED: '#64748b', // slate-500 \u2014 lapsed before it was ever sent\n CANCELLED: '#7c3aed', // violet-600 \u2014 deliberately called off; a decision, not a failure\n QUEUED: '#0284c7', // sky-600 \u2014 in flight\n SENT: '#0284c7',\n HELD: '#d97706', // amber-600 \u2014 withheld: the device is absent, the command still stands\n PARKED: '#f59e0b', // amber-500 \u2014 published, nobody home; held for the device's return\n};\n\nconst UNKNOWN_COLOR = '#64748b';\n\n// commandStatusColor maps a status to its badge color, falling back to muted slate for\n// an unrecognized value (a future/hand-edited status).\nexport function commandStatusColor(status: string): string {\n return STATUS_COLORS[status] ?? UNKNOWN_COLOR;\n}\n\n// isTerminalStatus reports whether a command has reached a state it can't leave \u2014 the\n// widget stops treating it as in-flight.\n//\n// It is NOT the question \"can this still be cancelled?\": SENT is non-terminal and is not\n// cancellable, so a host gating a cancel control must ask @devicechain/dashboards'\n// isCancellableCommandStatus instead of negating this.\n//\n// It is an alias, not a second definition: the terminal set lives in @devicechain/dashboards\n// so the console, this package and the preview cannot disagree about it. The name is kept\n// because it is part of this package's published surface.\nexport function isTerminalStatus(status: string): boolean {\n return isTerminalCommandStatus(status);\n}\n\n// commandStatusLabel renders a status in Title Case ('SUCCESSFUL' \u2192 'Successful').\nexport function commandStatusLabel(status: string): string {\n if (!status) return '';\n return status.charAt(0).toUpperCase() + status.slice(1).toLowerCase();\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Pure form logic for the command-button widget's typed parameter form (ADR-043). The\n// console bakes a command definition's `parameterSchema` (a JSON string) into the\n// widget's options; these helpers parse it, seed defaults, validate operator input, and\n// serialize the typed values back to the JSON payload command-delivery sends the device.\n// Kept separate from the widget so the coercion/validation is unit-tested in isolation.\n//\n// SCOPE: the form types top-level SCALAR parameters (DOUBLE / INT / BOOLEAN / STRING,\n// with enum / bounds / required / default). OBJECT (nested) parameters are not\n// form-editable \u2014 the form blocks sending when one is REQUIRED (it can't be satisfied\n// here) and otherwise omits it. The payload is a flat JSON object keyed by parameter name.\n\nimport type { CommandParameter } from '@devicechain/dashboards';\n\nconst DATA_TYPES = new Set(['DOUBLE', 'INT', 'BOOLEAN', 'STRING']);\n\n// parseParameterSchema turns the baked JSON string into descriptors, defensively:\n// a null/empty/malformed schema (or a non-array root) yields an empty list so a command\n// with no arguments (or a hand-broken schema) renders as a bare Send button, never throws.\n// Each descriptor is NORMALIZED (enum\u2192string[], default\u2192string, bounds\u2192finite number,\n// kind/dataType\u2192known value) so a hand-edited dashboard definition with wrong-typed fields\n// (e.g. `enum: \"on\"` or `default: 5`) can't crash the form at render/send time.\nexport function parseParameterSchema(schema: string | null | undefined): CommandParameter[] {\n if (!schema) return [];\n let parsed: unknown;\n try {\n parsed = JSON.parse(schema);\n } catch {\n return [];\n }\n if (!Array.isArray(parsed)) return [];\n const out: CommandParameter[] = [];\n for (const raw of parsed) {\n const norm = normalizeParam(raw);\n if (norm) out.push(norm);\n }\n return out;\n}\n\n// normalizeParam validates one descriptor and coerces every field to its expected type,\n// dropping the descriptor entirely if it has no string `name`. Unknown/wrong-typed fields\n// are discarded rather than trusted, so downstream code never sees a non-array enum or a\n// numeric default.\nfunction normalizeParam(raw: unknown): CommandParameter | null {\n if (typeof raw !== 'object' || raw === null) return null;\n const r = raw as Record<string, unknown>;\n if (typeof r.name !== 'string') return null;\n\n const param: CommandParameter = { name: r.name };\n if (typeof r.description === 'string') param.description = r.description;\n if (r.kind === 'OBJECT') param.kind = 'OBJECT';\n else if (r.kind === 'SCALAR') param.kind = 'SCALAR';\n if (typeof r.dataType === 'string' && DATA_TYPES.has(r.dataType)) {\n param.dataType = r.dataType as CommandParameter['dataType'];\n }\n if (typeof r.unit === 'string') param.unit = r.unit;\n if (r.required === true) param.required = true;\n if (typeof r.default === 'string') param.default = r.default;\n if (typeof r.minValue === 'number' && Number.isFinite(r.minValue)) param.minValue = r.minValue;\n if (typeof r.maxValue === 'number' && Number.isFinite(r.maxValue)) param.maxValue = r.maxValue;\n if (Array.isArray(r.enum)) {\n const values = r.enum.filter((v): v is string => typeof v === 'string');\n if (values.length > 0) param.enum = values;\n }\n return param;\n}\n\n// isScalar reports whether a parameter is a typed single value the form renders an input\n// for. Kind absent means SCALAR (the Go default); only an explicit OBJECT is nested.\nexport function isScalar(param: CommandParameter): boolean {\n return param.kind !== 'OBJECT';\n}\n\n// parseBool interprets a schema/string boolean the way the server's strconv.ParseBool\n// does (accepting '1'/'t'/'true'/'yes'/'on', case-insensitively) so a definition default\n// of \"True\" doesn't silently render \u2014 and send \u2014 as false.\nexport function parseBool(raw: string): boolean {\n const s = raw.trim().toLowerCase();\n return s === 'true' || s === '1' || s === 't' || s === 'yes' || s === 'on';\n}\n\n// defaultValues seeds the form's value map from each scalar parameter. A BOOLEAN always\n// gets a concrete 'true'/'false' (default false) \u2014 a checkbox is never \"empty\", so the\n// value must reflect the box's state exactly. Other scalars seed from their declared\n// default (as a string \u2014 inputs are string-backed), else start empty.\nexport function defaultValues(params: CommandParameter[]): Record<string, string> {\n const values: Record<string, string> = {};\n for (const p of params) {\n if (!isScalar(p)) continue;\n if (p.dataType === 'BOOLEAN') values[p.name] = parseBool(p.default ?? '') ? 'true' : 'false';\n else if (p.default != null) values[p.name] = p.default;\n }\n return values;\n}\n\n// validateParams returns a per-parameter error map (empty = valid). It enforces required\n// (a missing value on a required scalar; a BOOLEAN always has a value so is never\n// \"required-missing\"), numeric validity/bounds for INT/DOUBLE, and blocks a REQUIRED\n// OBJECT parameter (it can't be supplied through the typed form).\nexport function validateParams(\n params: CommandParameter[],\n values: Record<string, string>,\n): Record<string, string> {\n const errors: Record<string, string> = {};\n for (const p of params) {\n if (!isScalar(p)) {\n // A required structured parameter can't be satisfied here \u2014 refuse to send an\n // incomplete command rather than silently omitting it.\n if (p.required) errors[p.name] = 'Structured parameter \u2014 not supported here';\n continue;\n }\n if (p.dataType === 'BOOLEAN') continue; // a checkbox always carries a valid value\n const raw = (values[p.name] ?? '').trim();\n if (raw === '') {\n if (p.required) errors[p.name] = 'Required';\n continue; // an empty optional value is fine \u2014 it's just omitted from the payload\n }\n if (p.dataType === 'INT' || p.dataType === 'DOUBLE') {\n const n = Number(raw);\n if (!Number.isFinite(n)) {\n errors[p.name] = 'Must be a number';\n } else if (p.dataType === 'INT' && !Number.isInteger(n)) {\n errors[p.name] = 'Must be a whole number';\n } else if (p.minValue != null && n < p.minValue) {\n errors[p.name] = `Must be \u2265 ${p.minValue}`;\n } else if (p.maxValue != null && n > p.maxValue) {\n errors[p.name] = `Must be \u2264 ${p.maxValue}`;\n }\n }\n }\n return errors;\n}\n\n// buildPayload serializes the typed values to the JSON payload string command-delivery\n// sends the device \u2014 coercing each scalar to its declared type (INT/DOUBLE \u2192 number,\n// BOOLEAN \u2192 boolean, STRING \u2192 string). A BOOLEAN is always included (its checkbox state\n// is meaningful); other empty optional values are omitted. OBJECT parameters are omitted\n// (validateParams blocks a required one first). Returns undefined when no parameter\n// contributes a value (a parameterless command sends no payload).\nexport function buildPayload(\n params: CommandParameter[],\n values: Record<string, string>,\n): string | undefined {\n const payload: Record<string, unknown> = {};\n for (const p of params) {\n if (!isScalar(p)) continue;\n const raw = (values[p.name] ?? '').trim();\n if (p.dataType === 'BOOLEAN') {\n payload[p.name] = parseBool(raw);\n continue;\n }\n if (raw === '') continue;\n payload[p.name] = coerce(p, raw);\n }\n return Object.keys(payload).length === 0 ? undefined : JSON.stringify(payload);\n}\n\nfunction coerce(param: CommandParameter, raw: string): unknown {\n switch (param.dataType) {\n case 'INT':\n case 'DOUBLE': {\n const n = Number(raw);\n return Number.isFinite(n) ? n : raw; // validation gates this; fall back to raw defensively\n }\n case 'BOOLEAN':\n return parseBool(raw);\n default:\n return raw; // STRING (and untyped)\n }\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// command-button \u2014 issue a device command from the dashboard and watch its delivery\n// lifecycle (command-delivery). The console bakes a command definition into the widget's\n// options at author time (its name + parameterSchema); this renders a typed form from\n// that schema, issues via the action seam (gated on command:write), and shows the recent\n// commands for the target device with live status (QUEUED \u2192 SENT \u2192\n// SUCCESSFUL / FAILED \u2026), so an operator can see whether a command was acted on rather\n// than only that it was sent. Bound through the hub's control channel, so it renders identically from live\n// data or the synthetic preview source.\n//\n// The baked schema is a SNAPSHOT taken when the author picked the command: a later edit\n// to the profile's command definition (a changed schema, or a deleted command) leaves\n// this widget's form stale until the author re-picks it. That's acceptable \u2014 dashboards\n// are opaque snapshots \u2014 and a since-removed command fails visibly at the delivery\n// boundary (the server validates device existence and JSON); it isn't silently wrong.\n\nimport type { CommandDispatch, CommandParameter } from '@devicechain/dashboards';\nimport { useEffect, useMemo, useRef, useState, type CSSProperties } from 'react';\n\nimport {\n type CommandStreamState,\n css,\n fontVar,\n formatDateTime,\n optString,\n useWidgetTimeZone,\n WidgetFrame,\n type WidgetProps,\n} from '@devicechain/widget-sdk';\n\nimport { commandStatusColor, commandStatusLabel } from './command-status';\nimport {\n buildPayload,\n defaultValues,\n isScalar,\n parseBool,\n parseParameterSchema,\n validateParams,\n} from './command-params';\n\nexport function CommandButton({ widget, data, actions }: WidgetProps<CommandStreamState>) {\n const commandName = optString(widget.options, 'commandName');\n const commandLabel = optString(widget.options, 'commandLabel') ?? commandName;\n const parameterSchema = optString(widget.options, 'parameterSchema') ?? null;\n\n const params = useMemo(() => parseParameterSchema(parameterSchema), [parameterSchema]);\n // A stable key over the configured command: reset the form when the author repoints\n // the widget at a different command (or edits its schema).\n const schemaKey = `${commandName ?? ''}::${parameterSchema ?? ''}`;\n\n const [values, setValues] = useState<Record<string, string>>(() => defaultValues(params));\n const [errors, setErrors] = useState<Record<string, string>>({});\n const [pending, setPending] = useState(false);\n const [sendError, setSendError] = useState<string | null>(null);\n const [sentToken, setSentToken] = useState<string | null>(null);\n // Bumped whenever the configured command changes; an in-flight send captures the\n // current value and drops its result if it settles after a repoint (so command A's\n // dispatch token/error can't land under a freshly-configured command B).\n const sendGen = useRef(0);\n\n // Reset the form to the schema's defaults whenever the configured command changes.\n useEffect(() => {\n sendGen.current += 1;\n setValues(defaultValues(params));\n setErrors({});\n setSendError(null);\n setSentToken(null);\n // params is derived from schemaKey; keying on schemaKey avoids re-running on an\n // equal-but-new params array reference.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [schemaKey]);\n\n const canWrite = actions?.can('command:write') ?? false;\n const hasSeam = typeof actions?.sendCommand === 'function';\n const deviceToken = data.deviceToken;\n // The form is operable only when the viewer may write AND the runtime supplies the send\n // seam; a read-only mount shows the form disabled. Sending additionally needs a bound\n // target device and a configured command.\n const formDisabled = !canWrite || !hasSeam || pending;\n const canSend = !formDisabled && !!deviceToken && !!commandName;\n\n const setField = (name: string, value: string) => {\n setValues((prev) => ({ ...prev, [name]: value }));\n // Clear a field's error as the operator edits it.\n setErrors((prev) => (prev[name] ? { ...prev, [name]: '' } : prev));\n setSendError(null);\n };\n\n const send = () => {\n if (!deviceToken || !commandName || !actions?.sendCommand) return;\n const found = validateParams(params, values);\n const active = Object.fromEntries(Object.entries(found).filter(([, v]) => v));\n if (Object.keys(active).length > 0) {\n setErrors(active);\n return;\n }\n const payload = buildPayload(params, values);\n const gen = sendGen.current; // capture: drop the result if the command is repointed mid-flight\n setPending(true);\n setSendError(null);\n // Call synchronously so a dispatch is observable, but guard a synchronous throw so a\n // misbehaving seam can't leave the button stuck-disabled.\n let promise: Promise<CommandDispatch>;\n try {\n promise = actions.sendCommand(deviceToken, commandName, payload);\n } catch {\n setSendError(SEND_FAILED);\n setPending(false);\n return;\n }\n promise\n .then((dispatch) => {\n if (gen !== sendGen.current) return;\n // \uD83D\uDD34 A REFUSAL IS SHOWN, A FAILURE IS NOT. The server decided this command was\n // wrong and said why in client-safe terms (an unknown command for this device, a\n // parameter that violates the schema, the tenant's held-command ceiling) \u2014 that\n // reason is the whole point of the answer, and an operator can act on it. A\n // thrown error, below, is an availability failure: it says nothing about the\n // command, and its text is internal, so it gets one fixed message instead.\n if (dispatch.status === 'rejected') {\n setSendError(dispatch.reason);\n return;\n }\n setSentToken(dispatch.token);\n })\n .catch(() => {\n if (gen === sendGen.current) setSendError(SEND_FAILED);\n })\n .finally(() => {\n if (gen === sendGen.current) setPending(false);\n });\n };\n\n return (\n <WidgetFrame title={optString(widget.options, 'title')}>\n {/* A short widget (its minimum is two rows, and a cell on a phone is ~56 px) scrolls the\n form down rather than cutting off the send button or the line explaining why it is off. */}\n <div style={{ display: 'flex', flexDirection: 'column', height: '100%', gap: 10, overflowY: 'auto' }}>\n {!commandName ? (\n <div style={{ ...hint, margin: 'auto' }}>No command configured.</div>\n ) : (\n <>\n {/* Typed parameter form. */}\n <div style={{ display: 'flex', flexDirection: 'column', gap: 8, flexShrink: 0 }}>\n {params.map((param) =>\n isScalar(param) ? (\n <ParamField\n key={param.name}\n widgetId={widget.id}\n param={param}\n value={values[param.name] ?? ''}\n error={errors[param.name]}\n disabled={formDisabled}\n onChange={(v) => setField(param.name, v)}\n />\n ) : (\n // A structured (OBJECT) parameter isn't form-editable. A REQUIRED one\n // blocks sending (validateParams flags it); flag that in the note.\n <div key={param.name} style={{ ...hint, color: param.required ? commandStatusColor('FAILED') : css('muted-foreground') }}>\n {param.name}: {param.required ? 'required ' : ''}structured parameter \u2014 not supported here.\n </div>\n ),\n )}\n\n <button\n type=\"button\"\n style={{ ...sendButton, opacity: canSend ? 1 : 0.55, cursor: canSend ? 'pointer' : 'not-allowed' }}\n disabled={!canSend}\n aria-busy={pending}\n onClick={send}\n >\n {pending ? 'Sending\u2026' : `Send ${commandLabel}`}\n </button>\n\n {!canWrite ? (\n <div style={hint}>You don\u2019t have permission to issue commands.</div>\n ) : !hasSeam ? (\n <div style={hint}>Commands aren\u2019t available in this view.</div>\n ) : !deviceToken ? (\n <div style={hint}>No device is selected for this command.</div>\n ) : null}\n {sendError ? (\n <div role=\"alert\" style={{ ...hint, color: commandStatusColor('FAILED') }}>\n {sendError}\n </div>\n ) : null}\n </div>\n\n {/* Recent-command history with live delivery status. */}\n <CommandHistory commands={data.commands} sentToken={sentToken} loading={data.loading} error={data.error} />\n </>\n )}\n </div>\n </WidgetFrame>\n );\n}\n\n// \u2500\u2500 Parameter field \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction ParamField({\n widgetId,\n param,\n value,\n error,\n disabled,\n onChange,\n}: {\n widgetId: string;\n param: CommandParameter;\n value: string;\n error?: string;\n disabled: boolean;\n onChange: (value: string) => void;\n}) {\n // Scope the input id to the widget so two command-buttons sharing a parameter name\n // don't produce duplicate DOM ids (which would misdirect a label click to the first).\n const labelId = `cmd-${widgetId}-${param.name}`;\n const unit = param.unit ? ` (${param.unit})` : '';\n return (\n <label htmlFor={labelId} style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>\n <span style={{ fontSize: fontVar('sm'), color: css('muted-foreground') }}>\n {param.name}\n {unit}\n {param.required ? <span style={{ color: commandStatusColor('FAILED') }}> *</span> : null}\n </span>\n <ParamInput id={labelId} param={param} value={value} disabled={disabled} onChange={onChange} />\n {error ? (\n <span style={{ fontSize: fontVar('xs'), color: commandStatusColor('FAILED') }}>{error}</span>\n ) : null}\n </label>\n );\n}\n\nfunction ParamInput({\n id,\n param,\n value,\n disabled,\n onChange,\n}: {\n id: string;\n param: CommandParameter;\n value: string;\n disabled: boolean;\n onChange: (value: string) => void;\n}) {\n // An enum (of any data type) \u2192 a select; a boolean \u2192 a checkbox; a number \u2192 a numeric\n // input with the declared bounds; everything else \u2192 text.\n if (param.enum && param.enum.length > 0) {\n return (\n <select id={id} style={inputStyle} value={value} disabled={disabled} onChange={(e) => onChange(e.target.value)}>\n {!param.required ? <option value=\"\">\u2014</option> : null}\n {param.enum.map((opt) => (\n <option key={opt} value={opt}>\n {opt}\n </option>\n ))}\n </select>\n );\n }\n if (param.dataType === 'BOOLEAN') {\n return (\n <input\n id={id}\n type=\"checkbox\"\n style={{ width: 16, height: 16 }}\n checked={parseBool(value)}\n disabled={disabled}\n onChange={(e) => onChange(e.target.checked ? 'true' : 'false')}\n />\n );\n }\n const numeric = param.dataType === 'INT' || param.dataType === 'DOUBLE';\n return (\n <input\n id={id}\n type={numeric ? 'number' : 'text'}\n style={inputStyle}\n value={value}\n disabled={disabled}\n min={param.minValue ?? undefined}\n max={param.maxValue ?? undefined}\n step={param.dataType === 'INT' ? 1 : 'any'}\n placeholder={param.default ?? undefined}\n onChange={(e) => onChange(e.target.value)}\n />\n );\n}\n\n// \u2500\u2500 History \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction CommandHistory({\n commands,\n sentToken,\n loading,\n error,\n}: {\n commands: CommandStreamState['commands'];\n sentToken: string | null;\n loading: boolean;\n error: unknown;\n}) {\n // An error is shown inline here (not by replacing the whole widget) so a transient poll\n // blip never tears down the still-usable send form. Last-good rows stay on screen when\n // present; only an empty-and-errored history shows the notice.\n const empty = commands.length === 0;\n const timeZone = useWidgetTimeZone();\n return (\n <div\n style={{\n flex: 1,\n minHeight: 0,\n overflow: 'auto',\n borderTop: `1px solid ${css('border')}`,\n paddingTop: 6,\n }}\n >\n {empty ? (\n <div style={{ ...hint, textAlign: 'center', color: error ? commandStatusColor('FAILED') : css('muted-foreground') }}>\n {error ? 'Couldn\u2019t load command history.' : loading ? 'Loading\u2026' : 'No commands issued yet.'}\n </div>\n ) : (\n <ul style={{ listStyle: 'none', margin: 0, padding: 0, display: 'flex', flexDirection: 'column', gap: 4 }}>\n {commands.map((command) => (\n <li\n key={command.token}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n fontSize: fontVar('sm'),\n // Highlight the command just issued from this widget.\n fontWeight: command.token === sentToken ? 600 : 400,\n }}\n >\n <StatusBadge status={command.status} />\n <span style={{ color: css('foreground'), whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>\n {command.name}\n </span>\n <span style={{ marginLeft: 'auto', color: css('muted-foreground'), whiteSpace: 'nowrap' }}>\n {formatDateTime(command.queuedTime, timeZone)}\n </span>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n\nfunction StatusBadge({ status }: { status: string }) {\n const color = commandStatusColor(status);\n return (\n <span\n style={{\n display: 'inline-flex',\n alignItems: 'center',\n gap: 4,\n flexShrink: 0,\n fontSize: fontVar('xs'),\n fontWeight: 600,\n color,\n }}\n >\n <span aria-hidden=\"true\" style={{ width: 7, height: 7, borderRadius: '50%', background: color }} />\n {commandStatusLabel(status)}\n </span>\n );\n}\n\n// \u2500\u2500 styles / helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst hint: CSSProperties = { fontSize: fontVar('xs'), color: css('muted-foreground') };\n\n// What a viewer is told when the platform could not decide the enqueue. It is fixed\n// prose on purpose: the thrown error's text describes in-cluster machinery, and a\n// dashboard is the surface most likely to be embedded outside the tenant's own console.\nconst SEND_FAILED = 'Couldn\u2019t issue the command. Try again.';\n\nconst inputStyle: CSSProperties = {\n width: '100%',\n boxSizing: 'border-box',\n padding: '4px 8px',\n fontSize: fontVar('md'),\n borderRadius: 4,\n border: `1px solid ${css('border')}`,\n background: css('background'),\n color: css('foreground'),\n};\n\nconst sendButton: CSSProperties = {\n padding: '6px 12px',\n fontSize: fontVar('md'),\n fontWeight: 600,\n borderRadius: 4,\n border: 'none',\n background: css('primary'),\n color: css('background'),\n marginTop: 2,\n};\n"],
5
- "mappings": "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",
6
- "names": []
7
- }