@rainbow-robotics/plugin-ui 1.3.6-dev.8 → 1.3.6

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.
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AACA;;;;;GAKG;AAEH,sDAAsD;AACtD,eAAO,MAAM,mBAAmB,4DAKtB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAErE,4DAA4D;AAC5D,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAE7D,MAAM,WAAW,kBAAkB;IACjC,sCAAsC;IACtC,KAAK,EAAE,iBAAiB,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IACzC,gFAAgF;IAChF,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,sEAAsE;IACtE,IAAI,CAAC,EACD,MAAM,GACN;QACE,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACN,qBAAqB;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,yDAAyD;IACzD,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,WAAW,CAAC,EAAE;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;KAC5B,CAAC;CACH;AAED,MAAM,WAAW,YAAY;IAC3B,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAC;IACX,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,oDAAoD;AACpD,eAAO,MAAM,cAAc,mDAAoD,CAAC;AAEhF,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjE,mEAAmE;AACnE,MAAM,WAAW,cAAc;IAC7B,gGAAgG;IAChG,IAAI,EAAE,OAAO,MAAM,EAAE,CAAC;IACtB,IAAI,EAAE,kBAAkB,CAAC;IACzB,yCAAyC;IACzC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,GAAG,EAAE,CAAC;IACxC,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/C;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACjC;IACE,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC/B,CAAC;AAEN,MAAM,WAAW,cAAc;IAC7B,8DAA8D;IAC9D,EAAE,EAAE,MAAM,CAAC;IACX,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,yCAAyC;IACzC,KAAK,EACD,MAAM,GACN;QACE,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACN,iCAAiC;IACjC,WAAW,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACnC,0CAA0C;IAC1C,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,cAAc,EAAE,CAAC;IAC7B,oCAAoC;IACpC,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAmBzD"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}
package/dist/manifest.js CHANGED
@@ -13,6 +13,8 @@ function validateManifest(value) {
13
13
  const m = value;
14
14
  if (typeof m.id !== "string" || m.id.length === 0)
15
15
  errors.push("manifest.id is required (string)");
16
+ else if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)*(\.[a-z][a-z0-9]*(-[a-z0-9]+)*)*$/.test(m.id))
17
+ errors.push('manifest.id must be "."-separated parts of lowercase letters/digits and single "-", each starting with a letter (e.g. com.example.my-plugin)');
16
18
  if (typeof m.name !== "string" || m.name.length === 0)
17
19
  errors.push("manifest.name is required (string)");
18
20
  if (typeof m.version !== "string" || m.version.length === 0)
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/manifest.ts"],
4
- "sourcesContent": ["/* eslint-disable @typescript-eslint/no-explicit-any */\n/**\n * Plugin manifest schema \u2014 the single source of truth shared by the front end\n * (nexus reads it) and the back end (rby2 mirrors it for validation).\n *\n * Evolve additively only.\n */\n\n/** Known contribution points the host understands. */\nexport const CONTRIBUTION_POINTS = [\n \"toolbar\",\n \"lnb\",\n \"move.panel\",\n \"program.panel\",\n] as const;\n\nexport type ContributionPoint = (typeof CONTRIBUTION_POINTS)[number];\n\n/** How a header.toolbar contribution opens when clicked. */\nexport type ContributionOpens = \"popover\" | \"panel\" | \"page\";\n\nexport interface PluginContribution {\n /** Where this contribution mounts. */\n point: ContributionPoint | (string & {});\n /** UI entry name relative to ui/ (without extension). Defaults to \"content\". */\n ui?: string;\n /** Icon path relative to the plugin root (e.g. \"assets/icon.svg\"). */\n icon?:\n | string\n | {\n \"16\"?: string;\n \"48\"?: string;\n \"126\"?: string;\n };\n /** Display label. */\n label?: string | Record<string, string>;\n /** For header.toolbar: how the contribution surfaces. */\n opens?: ContributionOpens;\n commandInfo?: {\n name: string;\n icon: string;\n funcName: string;\n args?: Record<string, any>;\n };\n}\n\nexport interface PluginAction {\n /** Action id referenced by `rb.action(id, payload)`. */\n id: string;\n /** Transport topic (Zenoh / ROS2) the back end routes to. */\n topic: string;\n /** Optional payload schema hint (field \u2192 type name). */\n schema?: Record<string, string>;\n}\n\n/** Allowed variable value types exposed to pyfm. */\nexport const VARIABLE_TYPES = [\"number\", \"string\", \"boolean\", \"array\"] as const;\n\nexport type PluginVariableType = (typeof VARIABLE_TYPES)[number];\n\n/** A variable the plugin pre-declares for use in pyfm programs. */\nexport interface PluginVariable {\n /** Identifier, must start with \"RBP_\" (then letters, digits, underscore). Unique per plugin. */\n name: `RBP_${string}`;\n type: PluginVariableType;\n /** Initial value \u2014 must match `type`. */\n init: number | string | boolean | any[];\n /** Optional. Without it the UI cannot explain what the variable is for. */\n description?: string | Record<string, string>;\n}\n\nexport type PluginRuntime =\n | { type: \"none\" }\n | { type: \"python\"; entry: string }\n | {\n type: \"ros2\";\n package: string;\n launch: string;\n rosClient: \"rclpy\" | \"rclcpp\";\n };\n\nexport interface PluginManifest {\n /** Reverse-DNS unique id, e.g. \"com.rainbow.gripper-tool\". */\n id: string;\n /** Human-readable name. */\n name: string;\n /** Plugin version (semver). */\n version: string;\n /** Minimum host (Muscat) version this plugin supports. */\n minAppVersion?: string;\n /** Icon path relative to plugin root. */\n icons:\n | string\n | {\n \"16\"?: string;\n \"48\"?: string;\n \"128\"?: string;\n };\n /** UI / chrome contributions. */\n contributes?: PluginContribution[];\n /** Host actions the plugin can invoke. */\n actions?: PluginAction[];\n /** Variables pre-declared for pyfm; the variable picker UI lists these. */\n variables?: PluginVariable[];\n /** Back-end runtime declaration. */\n runtime?: PluginRuntime;\n}\n\n/**\n * Lightweight structural check. Returns the list of problems (empty = valid).\n * Kept dependency-free so both front and back ends can reuse it; richer\n * validation (zod) can layer on top later without breaking this contract.\n */\nexport function validateManifest(value: unknown): string[] {\n const errors: string[] = [];\n if (typeof value !== \"object\" || value === null) {\n return [\"manifest must be an object\"];\n }\n const m = value as Record<string, unknown>;\n if (typeof m.id !== \"string\" || m.id.length === 0)\n errors.push(\"manifest.id is required (string)\");\n if (typeof m.name !== \"string\" || m.name.length === 0)\n errors.push(\"manifest.name is required (string)\");\n if (typeof m.version !== \"string\" || m.version.length === 0)\n errors.push(\"manifest.version is required (string)\");\n if (m.contributes !== undefined && !Array.isArray(m.contributes))\n errors.push(\"manifest.contributes must be an array\");\n if (m.actions !== undefined && !Array.isArray(m.actions))\n errors.push(\"manifest.actions must be an array\");\n if (m.variables !== undefined && !Array.isArray(m.variables))\n errors.push(\"manifest.variables must be an array\");\n return errors;\n}\n"],
5
- "mappings": "AASO,MAAM,sBAAsB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AA0CO,MAAM,iBAAiB,CAAC,UAAU,UAAU,WAAW,OAAO;AAyD9D,SAAS,iBAAiB,OAA0B;AACzD,QAAM,SAAmB,CAAC;AAC1B,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,WAAO,CAAC,4BAA4B;AAAA,EACtC;AACA,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,OAAO,YAAY,EAAE,GAAG,WAAW;AAC9C,WAAO,KAAK,kCAAkC;AAChD,MAAI,OAAO,EAAE,SAAS,YAAY,EAAE,KAAK,WAAW;AAClD,WAAO,KAAK,oCAAoC;AAClD,MAAI,OAAO,EAAE,YAAY,YAAY,EAAE,QAAQ,WAAW;AACxD,WAAO,KAAK,uCAAuC;AACrD,MAAI,EAAE,gBAAgB,UAAa,CAAC,MAAM,QAAQ,EAAE,WAAW;AAC7D,WAAO,KAAK,uCAAuC;AACrD,MAAI,EAAE,YAAY,UAAa,CAAC,MAAM,QAAQ,EAAE,OAAO;AACrD,WAAO,KAAK,mCAAmC;AACjD,MAAI,EAAE,cAAc,UAAa,CAAC,MAAM,QAAQ,EAAE,SAAS;AACzD,WAAO,KAAK,qCAAqC;AACnD,SAAO;AACT;",
4
+ "sourcesContent": ["/* eslint-disable @typescript-eslint/no-explicit-any */\n/**\n * Plugin manifest schema \u2014 the single source of truth shared by the front end\n * (nexus reads it) and the back end (rby2 mirrors it for validation).\n *\n * Evolve additively only.\n */\n\n/** Known contribution points the host understands. */\nexport const CONTRIBUTION_POINTS = [\n \"toolbar\",\n \"lnb\",\n \"move.panel\",\n \"program.panel\",\n] as const;\n\nexport type ContributionPoint = (typeof CONTRIBUTION_POINTS)[number];\n\n/** How a header.toolbar contribution opens when clicked. */\nexport type ContributionOpens = \"popover\" | \"panel\" | \"page\";\n\nexport interface PluginContribution {\n /** Where this contribution mounts. */\n point: ContributionPoint | (string & {});\n /** UI entry name relative to ui/ (without extension). Defaults to \"content\". */\n ui?: string;\n /** Icon path relative to the plugin root (e.g. \"assets/icon.svg\"). */\n icon?:\n | string\n | {\n \"16\"?: string;\n \"48\"?: string;\n \"126\"?: string;\n };\n /** Display label. */\n label?: string | Record<string, string>;\n /** For header.toolbar: how the contribution surfaces. */\n opens?: ContributionOpens;\n commandInfo?: {\n name: string;\n icon: string;\n funcName: string;\n args?: Record<string, any>;\n };\n}\n\nexport interface PluginAction {\n /** Action id referenced by `rb.action(id, payload)`. */\n id: string;\n /** Transport topic (Zenoh / ROS2) the back end routes to. */\n topic: string;\n /** Optional payload schema hint (field \u2192 type name). */\n schema?: Record<string, string>;\n}\n\n/** Allowed variable value types exposed to pyfm. */\nexport const VARIABLE_TYPES = [\"number\", \"string\", \"boolean\", \"array\"] as const;\n\nexport type PluginVariableType = (typeof VARIABLE_TYPES)[number];\n\n/** A variable the plugin pre-declares for use in pyfm programs. */\nexport interface PluginVariable {\n /** Identifier, must start with \"RBP_\" (then letters, digits, underscore). Unique per plugin. */\n name: `RBP_${string}`;\n type: PluginVariableType;\n /** Initial value \u2014 must match `type`. */\n init: number | string | boolean | any[];\n /** Optional. Without it the UI cannot explain what the variable is for. */\n description?: string | Record<string, string>;\n}\n\nexport type PluginRuntime =\n | { type: \"none\" }\n | { type: \"python\"; entry: string }\n | {\n type: \"ros2\";\n package: string;\n launch: string;\n rosClient: \"rclpy\" | \"rclcpp\";\n };\n\nexport interface PluginManifest {\n /** Reverse-DNS unique id, e.g. \"com.rainbow.gripper-tool\". */\n id: string;\n /** Human-readable name. */\n name: string;\n /** Plugin version (semver). */\n version: string;\n /** Minimum host (Muscat) version this plugin supports. */\n minAppVersion?: string;\n /** Icon path relative to plugin root. */\n icons:\n | string\n | {\n \"16\"?: string;\n \"48\"?: string;\n \"128\"?: string;\n };\n /** UI / chrome contributions. */\n contributes?: PluginContribution[];\n /** Host actions the plugin can invoke. */\n actions?: PluginAction[];\n /** Variables pre-declared for pyfm; the variable picker UI lists these. */\n variables?: PluginVariable[];\n /** Back-end runtime declaration. */\n runtime?: PluginRuntime;\n}\n\n/**\n * Lightweight structural check. Returns the list of problems (empty = valid).\n * Kept dependency-free so both front and back ends can reuse it; richer\n * validation (zod) can layer on top later without breaking this contract.\n */\nexport function validateManifest(value: unknown): string[] {\n const errors: string[] = [];\n if (typeof value !== \"object\" || value === null) {\n return [\"manifest must be an object\"];\n }\n const m = value as Record<string, unknown>;\n if (typeof m.id !== \"string\" || m.id.length === 0)\n errors.push(\"manifest.id is required (string)\");\n else if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)*(\\.[a-z][a-z0-9]*(-[a-z0-9]+)*)*$/.test(m.id))\n errors.push('manifest.id must be \".\"-separated parts of lowercase letters/digits and single \"-\", each starting with a letter (e.g. com.example.my-plugin)');\n if (typeof m.name !== \"string\" || m.name.length === 0)\n errors.push(\"manifest.name is required (string)\");\n if (typeof m.version !== \"string\" || m.version.length === 0)\n errors.push(\"manifest.version is required (string)\");\n if (m.contributes !== undefined && !Array.isArray(m.contributes))\n errors.push(\"manifest.contributes must be an array\");\n if (m.actions !== undefined && !Array.isArray(m.actions))\n errors.push(\"manifest.actions must be an array\");\n if (m.variables !== undefined && !Array.isArray(m.variables))\n errors.push(\"manifest.variables must be an array\");\n return errors;\n}\n"],
5
+ "mappings": "AASO,MAAM,sBAAsB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AA0CO,MAAM,iBAAiB,CAAC,UAAU,UAAU,WAAW,OAAO;AAyD9D,SAAS,iBAAiB,OAA0B;AACzD,QAAM,SAAmB,CAAC;AAC1B,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,WAAO,CAAC,4BAA4B;AAAA,EACtC;AACA,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,OAAO,YAAY,EAAE,GAAG,WAAW;AAC9C,WAAO,KAAK,kCAAkC;AAAA,WACvC,CAAC,gEAAgE,KAAK,EAAE,EAAE;AACjF,WAAO,KAAK,8IAA8I;AAC5J,MAAI,OAAO,EAAE,SAAS,YAAY,EAAE,KAAK,WAAW;AAClD,WAAO,KAAK,oCAAoC;AAClD,MAAI,OAAO,EAAE,YAAY,YAAY,EAAE,QAAQ,WAAW;AACxD,WAAO,KAAK,uCAAuC;AACrD,MAAI,EAAE,gBAAgB,UAAa,CAAC,MAAM,QAAQ,EAAE,WAAW;AAC7D,WAAO,KAAK,uCAAuC;AACrD,MAAI,EAAE,YAAY,UAAa,CAAC,MAAM,QAAQ,EAAE,OAAO;AACrD,WAAO,KAAK,mCAAmC;AACjD,MAAI,EAAE,cAAc,UAAa,CAAC,MAAM,QAAQ,EAAE,SAAS;AACzD,WAAO,KAAK,qCAAqC;AACnD,SAAO;AACT;",
6
6
  "names": []
7
7
  }
package/dist/react.d.ts CHANGED
@@ -2,7 +2,7 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as class_variance_authority_types from 'class-variance-authority/types';
3
3
  import { VariantProps } from 'class-variance-authority';
4
4
  import * as React$1 from 'react';
5
- import React__default, { ReactNode, MouseEvent, InputHTMLAttributes, MouseEventHandler, ReactElement, ChangeEvent, CSSProperties, TextareaHTMLAttributes, ComponentType, ComponentProps, Dispatch, SetStateAction, HTMLInputAutoCompleteAttribute, RefObject, HTMLAttributes } from 'react';
5
+ import React__default, { ReactNode, MouseEvent, InputHTMLAttributes, MouseEventHandler, ReactElement, ChangeEvent, CSSProperties, TextareaHTMLAttributes, ComponentType, ComponentProps, Dispatch, SetStateAction, HTMLInputAutoCompleteAttribute, RefObject, SVGAttributes, HTMLAttributes } from 'react';
6
6
  import { Matcher, DayPicker } from 'react-day-picker';
7
7
  import * as ShadDialogPrimitive from '@radix-ui/react-dialog';
8
8
  import { Command } from 'cmdk';
@@ -541,6 +541,14 @@ interface ModalPropsType<T = AllowAny> {
541
541
  hideCloseButton?: boolean;
542
542
  }
543
543
 
544
+ type KeypadOption = {
545
+ initTab?: string;
546
+ };
547
+ type VariableModalProps = ModalPropsType<string> & {
548
+ keypadOption?: KeypadOption;
549
+ byKeypad?: boolean;
550
+ };
551
+
544
552
  interface Props$e extends TextareaHTMLAttributes<HTMLTextAreaElement> {
545
553
  className?: string;
546
554
  type?: "text" | "program";
@@ -550,7 +558,7 @@ interface Props$e extends TextareaHTMLAttributes<HTMLTextAreaElement> {
550
558
  readOnly?: boolean;
551
559
  maxLength?: number;
552
560
  error?: boolean;
553
- variableModal?: ComponentType<ModalPropsType<string>>;
561
+ variableModal?: ComponentType<VariableModalProps>;
554
562
  handleChange?: (value: string, name?: string) => void;
555
563
  }
556
564
  /**
@@ -574,7 +582,7 @@ type Props$d = {
574
582
  /**
575
583
  * 선택된 날짜 값
576
584
  */
577
- value?: number | [number | undefined, number | undefined];
585
+ value?: Date | [Date | undefined, Date | undefined];
578
586
  /**
579
587
  * 날짜 입력 필드의 이름
580
588
  */
@@ -607,13 +615,35 @@ type Props$d = {
607
615
  /**
608
616
  * 날짜 값 변경 함수
609
617
  */
610
- handleChange?: (startValue: number | undefined, endValue: number | undefined, name: string) => void;
618
+ handleChange?: (startValue: Date | undefined, endValue: Date | undefined, name: string) => void;
611
619
  };
612
620
  /**
613
621
  * 날짜 입력 컴포넌트 입니다.
614
622
  */
615
623
  declare const DateInput: ({ className, showType, value, name, disabled, readOnly, size, variant, error, showPresets, showFooter, placeholder, handleChange, }: Props$d) => react_jsx_runtime.JSX.Element;
616
624
 
625
+ declare const RANGE_DATE_PRESETS: readonly [{
626
+ readonly key: "last-24-hours";
627
+ readonly label: "Last 24 hours";
628
+ }, {
629
+ readonly key: "last-3-days";
630
+ readonly label: "Last 3 days";
631
+ }, {
632
+ readonly key: "last-week";
633
+ readonly label: "Last week";
634
+ }, {
635
+ readonly key: "last-month";
636
+ readonly label: "Last month";
637
+ }, {
638
+ readonly key: "last-3-month";
639
+ readonly label: "Last 3 month";
640
+ }];
641
+ type RangePresetKey = (typeof RANGE_DATE_PRESETS)[number]["key"];
642
+ declare const getRangePresetDates: (key: RangePresetKey, now?: Date) => {
643
+ from: Date;
644
+ to: Date;
645
+ };
646
+
617
647
  type Props$c = {
618
648
  className?: string;
619
649
  calendarPanelClassName?: string;
@@ -624,7 +654,7 @@ type Props$c = {
624
654
  /**
625
655
  * 선택된 날짜 값
626
656
  */
627
- value?: [number | undefined, number | undefined];
657
+ value?: [Date | undefined, Date | undefined];
628
658
  /**
629
659
  * 날짜 입력 필드의 이름
630
660
  */
@@ -632,11 +662,15 @@ type Props$c = {
632
662
  /**
633
663
  * 최소 날짜 값
634
664
  */
635
- min?: number;
665
+ min?: Date;
636
666
  /**
637
667
  * 최대 날짜 값
638
668
  */
639
- max?: number;
669
+ max?: Date;
670
+ /**
671
+ * 선택 가능한 최대 일수
672
+ */
673
+ maxDays?: number;
640
674
  /**
641
675
  * 날짜 입력 필드의 플레이스홀더
642
676
  */
@@ -672,17 +706,16 @@ type Props$c = {
672
706
  /**
673
707
  * 날짜 값 변경 함수
674
708
  */
675
- handleChange?: (startValue: number | undefined, endValue: number | undefined, name: string) => void;
709
+ handleChange?: (startValue: Date | undefined, endValue: Date | undefined, name: string) => void;
676
710
  /**
677
711
  * 표시할 프리셋 키 목록. 미지정 시 전체 표시.
678
712
  */
679
713
  presets?: RangePresetKey[];
680
714
  };
681
- type RangePresetKey = "last-24-hours" | "last-3-days" | "last-week" | "last-month" | "last-3-month";
682
715
  /**
683
716
  * 범위 날짜 입력 컴포넌트 입니다.
684
717
  */
685
- declare const RangeDateInput: ({ className, calendarPanelClassName, showType, value, name, min, max, placeholder, disabled, disabledDates, readOnly, size, variant, error, showPresets, showFooter, handleChange, presets, }: Props$c) => react_jsx_runtime.JSX.Element;
718
+ declare const RangeDateInput: ({ className, calendarPanelClassName, showType, value, name, min, max, maxDays, placeholder, disabled, disabledDates, readOnly, size, variant, error, showPresets, showFooter, handleChange, presets, }: Props$c) => react_jsx_runtime.JSX.Element;
686
719
 
687
720
  interface Props$b extends ModalPropsType<boolean> {
688
721
  className?: string;
@@ -720,7 +753,7 @@ declare const RBAlert: ({ className, containerId, open, onOpenChange, title, con
720
753
  interface Props$a extends ModalPropsType<boolean> {
721
754
  className?: string;
722
755
  /** confirm 타이틀 */
723
- title?: string;
756
+ title?: string | ReactElement;
724
757
  /** confirm 설명 */
725
758
  description?: string | ReactElement;
726
759
  /** confirm 내용 */
@@ -743,7 +776,7 @@ interface Props$a extends ModalPropsType<boolean> {
743
776
  /** 모달 dialog일때 컨테이너 id */
744
777
  containerId?: string;
745
778
  /** 확인 버튼 클릭 시 실행할 함수 */
746
- onConfirm?: () => void;
779
+ onConfirm?: () => Promise<void> | void;
747
780
  /** 취소 버튼 클릭 시 실행할 함수 */
748
781
  onCancel?: () => void;
749
782
  }
@@ -951,6 +984,10 @@ interface PropsType$p extends Pick<InputHTMLAttributes<HTMLInputElement>, "name"
951
984
  * SelectBox의 크기
952
985
  */
953
986
  size?: SelectSize;
987
+ /**
988
+ * SelectBox DropBox의 최대 높이
989
+ */
990
+ maxDropBoxHeight?: number;
954
991
  /**
955
992
  * option list를 찾는 search 기능 활성화 여부
956
993
  */
@@ -2018,6 +2055,8 @@ type Props$3 = {
2018
2055
  stickColor?: string;
2019
2056
  /** 조이스틱 영역의 색상 */
2020
2057
  stickAreaColor?: string;
2058
+ /** 방향 아크·아이콘 강조 색상 */
2059
+ arcColor?: string;
2021
2060
  /**
2022
2061
  * 스로틀 임계치
2023
2062
  * (이동 비율 차이가 throttleThreshold 이상일 때 onMove 호출)
@@ -2050,7 +2089,7 @@ type Props$3 = {
2050
2089
  /** 조이스틱 종료 시 호출 */
2051
2090
  onEnd?: (data: RbJoyStickEventData) => void;
2052
2091
  };
2053
- declare const RBJoyStick: ({ className, type, size, stickColor, stickAreaColor, throttleThreshold, throttleDuration, xOnly, yOnly, disableDiagonal, disabled, keepMoveEventInterval, paddingSize, style, onStart, onMove, onEnd, }: Props$3) => react_jsx_runtime.JSX.Element;
2092
+ declare const RBJoyStick: ({ className, type, size, stickColor, arcColor, stickAreaColor, throttleThreshold, throttleDuration, xOnly, yOnly, disableDiagonal, disabled, keepMoveEventInterval, paddingSize, style, onStart, onMove, onEnd, }: Props$3) => react_jsx_runtime.JSX.Element;
2054
2093
 
2055
2094
  type PlaceType = "top" | "right" | "bottom" | "left";
2056
2095
  interface PropsType$6 {
@@ -2173,6 +2212,8 @@ declare function RBCardFooter({ className, size, ...props }: RBCardFooterProps):
2173
2212
 
2174
2213
  declare function ShadDialog({ ...props }: React$1.ComponentProps<typeof ShadDialogPrimitive.Root>): react_jsx_runtime.JSX.Element;
2175
2214
 
2215
+ declare function Calendar$1({ className, classNames, showOutsideDays, ...props }: React$1.ComponentProps<typeof DayPicker>): react_jsx_runtime.JSX.Element;
2216
+
2176
2217
  declare function RBCommand({ className, ...props }: React.ComponentProps<typeof Command>): react_jsx_runtime.JSX.Element;
2177
2218
  declare function RBCommandDialog({ title, description, children, ...props }: React.ComponentProps<typeof ShadDialog> & {
2178
2219
  title?: string;
@@ -2285,7 +2326,7 @@ interface PropsType$5 {
2285
2326
  declare function RBSwitchField({ children, className, controlPlacement, description, descriptionClassName, invalid, label, labelClassName, size, type, }: PropsType$5): react_jsx_runtime.JSX.Element;
2286
2327
 
2287
2328
  declare const toggleRootVariants: (props?: {
2288
- size?: "sm" | "default" | "lg";
2329
+ size?: "sm" | "lg" | "default";
2289
2330
  variant?: "default" | "outline";
2290
2331
  } & class_variance_authority_types.ClassProp) => string;
2291
2332
  type ToggleVariantProps = VariantProps<typeof toggleRootVariants>;
@@ -2336,7 +2377,7 @@ declare function RBResizable({ className, direction, ...props }: RBResizableProp
2336
2377
  declare function RBResizablePanel({ className, unstyled, ...props }: RBResizablePanelProps): react_jsx_runtime.JSX.Element;
2337
2378
  declare function RBResizableHandle({ className, hideThumb, onPointerDownCapture, onPointerUpCapture, onPointerCancelCapture, ...props }: RBResizableHandleProps): react_jsx_runtime.JSX.Element;
2338
2379
 
2339
- declare function Calendar({ classNames, showOutsideDays, ...props }: React.ComponentProps<typeof DayPicker>): react_jsx_runtime.JSX.Element;
2380
+ declare function Calendar({ className, classNames, showOutsideDays, ...props }: React.ComponentProps<typeof Calendar$1>): react_jsx_runtime.JSX.Element;
2340
2381
 
2341
2382
  interface PropsType$4 {
2342
2383
  label: string;
@@ -2348,32 +2389,22 @@ interface DatePickerTimeValue {
2348
2389
  hour: string;
2349
2390
  minute: string;
2350
2391
  }
2392
+ interface DatePickerValue {
2393
+ from: Date | undefined;
2394
+ to: Date | undefined;
2395
+ timeEnabled: boolean;
2396
+ }
2351
2397
  interface PropsType$3 {
2352
2398
  pickerType?: DatePickerType;
2353
- setTimeEnabled?: boolean;
2354
- startTime?: DatePickerTimeValue;
2355
- endTime?: DatePickerTimeValue;
2356
- onEndTimeChange?: (nextValue: DatePickerTimeValue) => void;
2357
- onSetTimeChange?: (checked: boolean) => void;
2358
- onStartTimeChange?: (nextValue: DatePickerTimeValue) => void;
2359
- selectedDate?: {
2360
- from: Date | undefined;
2361
- to: Date | undefined;
2362
- };
2363
- setSelectedDate?: (nextValue: {
2364
- from: Date | undefined;
2365
- to: Date | undefined;
2366
- }) => void;
2367
- enforceTimeOrder?: boolean;
2368
- min?: number;
2369
- max?: number;
2399
+ selectedDate: DatePickerValue;
2400
+ onChange: (value: DatePickerValue) => void;
2370
2401
  labels?: {
2371
2402
  from?: string;
2372
2403
  to?: string;
2373
2404
  setTime?: string;
2374
2405
  };
2375
2406
  }
2376
- declare function RBDatePickerFooterTime({ pickerType, setTimeEnabled, startTime, endTime, onEndTimeChange, onSetTimeChange, onStartTimeChange, selectedDate, setSelectedDate, enforceTimeOrder, labels, }: PropsType$3): react_jsx_runtime.JSX.Element;
2407
+ declare function RBDatePickerFooterTime({ pickerType, selectedDate, onChange, labels, }: PropsType$3): react_jsx_runtime.JSX.Element;
2377
2408
 
2378
2409
  interface PropsType$2 {
2379
2410
  className?: string;
@@ -2398,6 +2429,10 @@ declare const checkIconButtonVariants: (props?: {
2398
2429
  disabled?: boolean;
2399
2430
  } & class_variance_authority_types.ClassProp) => string;
2400
2431
 
2432
+ declare const Svg25DBold: (props?: React__default.SVGAttributes<SVGSVGElement> & {
2433
+ title?: string;
2434
+ description?: string;
2435
+ }) => react_jsx_runtime.JSX.Element;
2401
2436
  declare const SvgAMR: (props?: React__default.SVGAttributes<SVGSVGElement> & {
2402
2437
  title?: string;
2403
2438
  description?: string;
@@ -2466,6 +2501,10 @@ declare const SvgBulletSolid: (props?: React__default.SVGAttributes<SVGSVGElemen
2466
2501
  title?: string;
2467
2502
  description?: string;
2468
2503
  }) => react_jsx_runtime.JSX.Element;
2504
+ declare const SvgCANBus: (props?: React__default.SVGAttributes<SVGSVGElement> & {
2505
+ title?: string;
2506
+ description?: string;
2507
+ }) => react_jsx_runtime.JSX.Element;
2469
2508
  declare const SvgCalendarBlankBold: (props?: React__default.SVGAttributes<SVGSVGElement> & {
2470
2509
  title?: string;
2471
2510
  description?: string;
@@ -2967,6 +3006,7 @@ declare const SvgWifiCog: (props?: React__default.SVGAttributes<SVGSVGElement> &
2967
3006
  description?: string;
2968
3007
  }) => react_jsx_runtime.JSX.Element;
2969
3008
 
3009
+ declare const index_Svg25DBold: typeof Svg25DBold;
2970
3010
  declare const index_SvgAMR: typeof SvgAMR;
2971
3011
  declare const index_SvgArrowArcLeftBold: typeof SvgArrowArcLeftBold;
2972
3012
  declare const index_SvgArrowArcRightBold: typeof SvgArrowArcRightBold;
@@ -2985,6 +3025,7 @@ declare const index_SvgBot: typeof SvgBot;
2985
3025
  declare const index_SvgBox3D: typeof SvgBox3D;
2986
3026
  declare const index_SvgBugBeetleSolid: typeof SvgBugBeetleSolid;
2987
3027
  declare const index_SvgBulletSolid: typeof SvgBulletSolid;
3028
+ declare const index_SvgCANBus: typeof SvgCANBus;
2988
3029
  declare const index_SvgCalendarBlankBold: typeof SvgCalendarBlankBold;
2989
3030
  declare const index_SvgCameraBold: typeof SvgCameraBold;
2990
3031
  declare const index_SvgCameraResetBold: typeof SvgCameraResetBold;
@@ -3111,6 +3152,7 @@ declare const index_SvgWifiNone: typeof SvgWifiNone;
3111
3152
  declare const index_SvgXBold: typeof SvgXBold;
3112
3153
  declare namespace index {
3113
3154
  export {
3155
+ index_Svg25DBold as Svg25DBold,
3114
3156
  index_SvgAMR as SvgAMR,
3115
3157
  index_SvgArrowArcLeftBold as SvgArrowArcLeftBold,
3116
3158
  index_SvgArrowArcRightBold as SvgArrowArcRightBold,
@@ -3129,6 +3171,7 @@ declare namespace index {
3129
3171
  index_SvgBox3D as SvgBox3D,
3130
3172
  index_SvgBugBeetleSolid as SvgBugBeetleSolid,
3131
3173
  index_SvgBulletSolid as SvgBulletSolid,
3174
+ index_SvgCANBus as SvgCANBus,
3132
3175
  index_SvgCalendarBlankBold as SvgCalendarBlankBold,
3133
3176
  index_SvgCameraBold as SvgCameraBold,
3134
3177
  index_SvgCameraResetBold as SvgCameraResetBold,
@@ -3256,6 +3299,16 @@ declare namespace index {
3256
3299
  };
3257
3300
  }
3258
3301
 
3302
+ type StatusIconProps = SVGAttributes<SVGSVGElement>;
3303
+ /**
3304
+ * 채워진 원 + 흰색 글리프로 상태를 나타내는 아이콘 한 쌍이다. 이 폴더의
3305
+ * 나머지 아이콘(`index.tsx`)은 auto-svg-component-generator가 `.svg`
3306
+ * 소스로부터 자동 생성하므로, 손으로 그린 이 두 아이콘은 별도 파일로 둔다
3307
+ * — index.tsx는 빌드/dev 스크립트가 돌 때마다 통째로 덮어써진다.
3308
+ */
3309
+ declare const GreenCheckCircleIcon: ({ className, ...props }: StatusIconProps) => react_jsx_runtime.JSX.Element;
3310
+ declare const AmberWarningCircleIcon: ({ className, ...props }: StatusIconProps) => react_jsx_runtime.JSX.Element;
3311
+
3259
3312
  declare const lightColors: readonly ["red", "gray", "blue", "green", "orange", "yellow"];
3260
3313
  type LightColor = (typeof lightColors)[number];
3261
3314
  declare const lightSizes: readonly ["sm", "md", "lg"];
@@ -3575,6 +3628,16 @@ interface RbSdk {
3575
3628
  * unregister fn. Prefer the `useRbSave()` hook.
3576
3629
  */
3577
3630
  onSave: (getPayload: () => unknown) => () => void;
3631
+ /**
3632
+ * PCs allowed to talk to this plugin's ROS 2 nodes. ROS discovery is robot-local
3633
+ * (ROS_AUTOMATIC_DISCOVERY_RANGE=LOCALHOST); Muscat keeps this list per plugin, starts
3634
+ * the plugin with ROS_STATIC_PEERS set to it, and restarts the plugin when it changes.
3635
+ */
3636
+ rosPeers: {
3637
+ get: () => Promise<string[]>;
3638
+ /** Saves the list and restarts the plugin backend. Rejects on an invalid IP. */
3639
+ set: (peers: string[]) => Promise<string[]>;
3640
+ };
3578
3641
  }
3579
3642
  /** Access the host SDK from within a plugin component. */
3580
3643
  declare function useRb(): RbSdk;
@@ -3595,5 +3658,5 @@ declare function useRbFormData<T = unknown>(): T | undefined;
3595
3658
  */
3596
3659
  declare function useRbSave<T = unknown>(getPayload: () => T): void;
3597
3660
 
3598
- export { HStack, RBSelectOption as Option, Accordion as RBAccordion, AccordionGroup as RBAccordionGroup, RBAlert, RBBadge, RBBattery, Calendar as RBCalendar, RBCard, RBCardAction, RBCardContent, RBCardDescription, RBCardFooter, RBCardHeader, RBCardHeaderText, RBCardPreviewSlot, RBCardTitle, CheckBox as RBCheckBox, RBCheckIconButton, RBCheckLabelButton, RBChipItem, RBChipTab, RBChipsBoxItem, RBChipsBoxTab, RBCommand, RBCommandDialog, RBCommandEmpty, RBCommandGroup, RBCommandInput, RBCommandItem, RBCommandList, RBCommandSelected, RBCommandSeparator, RBCommandShortcut, RBConfirm, DateInput as RBDateInput, RBDatePickerFooterButton, RBDatePickerFooterTime, RBDatePickerPreset, RBDialog, RBDialogClose, RBDialogContent, RBDialogDescription, RBDialogFooter, RBDialogHeader, RBDialogOverlay, RBDialogPortal, RBDialogTitle, RBDialogTrigger, RBDim, RBDivider, RBDrawer, RBDrawerBody, RBDrawerClose, RBDrawerContent, RBDrawerDescription, RBDrawerFooter, RBDrawerHeader, RBDrawerOverlay, RBDrawerPortal, RBDrawerTitle, RBDrawerTrigger, RBDropdownMenu, RBDropdownMenuCheckboxItem, RBDropdownMenuContent, RBDropdownMenuGroup, RBDropdownMenuItem, RBDropdownMenuLabel, RBDropdownMenuPortal, RBDropdownMenuRadioGroup, RBDropdownMenuRadioItem, RBDropdownMenuSeparator, RBDropdownMenuShortcut, RBDropdownMenuSub, RBDropdownMenuSubContent, RBDropdownMenuSubTrigger, RBDropdownMenuTrigger, RBEmptyView, RBIconButton, index as RBIcons, RBInlineLabelButton, RBJoyStick, RBLabelButton, RBLight, RBMenubar, RBMenubarCheckboxItem, RBMenubarContent, RBMenubarGroup, RBMenubarItem, RBMenubarLabel, RBMenubarMenu, RBMenubarPortal, RBMenubarRadioGroup, RBMenubarRadioItem, RBMenubarSeparator, RBMenubarShortcut, RBMenubarSub, RBMenubarSubContent, RBMenubarSubTrigger, RBMenubarTrigger, RBModal, MultiRangeInput as RBMultiRangeInput, RBNoData, RBPagination, RBPopover, RBPopoverAnchor, RBPopoverContent, RBPopoverPlacements, RBPopoverTrigger, RBPrompt, RBQrBarCodeGuideModal, RBRadioBox, RBRadioButton, RangeDateInput as RBRangeDateInput, RBResizable, RBResizableHandle, RBResizablePanel, RBResizable as RBResizablePanelGroup, RBSelectBox, RBSelectDropBox, RBSelectOption, RBSheet, RBSheetClose, RBSheetContent, RBSheetDescription, RBSheetFooter, RBSheetHeader, RBSheetOverlay, RBSheetPortal, RBSheetTitle, RBSheetTrigger, SideNumberInput as RBSideNumberInput, RBSimpleToast, RBSlider, RBSpinner, RBSwitch, RBSwitchField, RBTab, RBTabItem, RBTable, RBTableRow, RBTabs, RBTabsContent, RBTabsList, RBTabsTrigger, RBTdColumn, RBTdExpend, RBTextArea, RBTextField, RBTextHelperText, RBTextInput, RBTextLabel, RBToggle, RBToggleGroup, RBToggleGroupItem, RBTooltip, VStack, accordionVariants, badgeRootVariants, batteryFillVariants, cardRootVariants, checkBoxRootVariants, checkIconButtonVariants, checkLabelButtonVariants, chipRootVariants, dividerVariants, emptyViewRootVariants, iconButtonVariants, inlineLabelButtonVariants, labelButtonVariants, lightColors, lightSizes, radioRootVariants, selectTriggerVariants, sideNumberInputVariants, tabRootVariants, textAreaVariants, textInputVariants, useRb, useRbFormData, useRbSave };
3599
- export type { PropsType$t as AccordionProps, AccordionVariantProps, PropsType$a as BadgeProps, BadgeVariantProps, BatteryProps, BatteryVariantProps, CardVariantProps, Props$h as CheckBoxProps, CheckBoxVariantProps, Props$g as CheckIconButtonProps, PropsType$h as ChipItemProps, PropsType$i as ChipTabProps, ChipVariantProps, PropsType$f as ChipsBoxItemProps, PropsType$g as ChipsBoxTabProps, Props$d as DateInputProps, PropsType$2 as DatePickerFooterButtonProps, PropsType$4 as DatePickerPresetProps, DatePickerTimeValue, PropsType$q as DimProps, DividerProps, DividerVariantProps, DrawerSide, DropdownItemVariant, PropsType$9 as EmptyViewProps, EmptyViewVariantProps, PropsType$b as ExpendProps, PropsType$1 as HStackProps, HelperTone, IconButtonProps, IconButtonVariantProps, InlineLabelButtonProps, InlineLabelButtonVariantProps, Props$3 as JoyStickProps, LabelButtonProps, LabelButtonVariantProps, LightProps, LightVariantProps, MenubarItemVariant, Props$4 as MultiRangeInputProps, Props$7 as PaginationProps, Props$b as RBAlertProps, RBCardContentProps, RBCardFooterProps, RBCardHeaderProps, RBCardHeaderTextProps, RBCardPreviewSlotProps, RBCardProps, RBCardTitleProps, Props$a as RBConfirmProps, RBDialogContentProps, RBDrawerContentProps, RBDrawerProps, RBPopoverContentProps, RBPopoverPlacement, Props$9 as RBPromptProps, PropsType$l as RBRadioBoxProps, RBResizableHandleProps, RBResizablePanelProps, RBResizableProps, PropsType$p as RBSelectBoxProps, RBSelectBoxRef, RBSheetContentProps, Props$1 as RBSimpleToastProps, RBSliderProps, PropsType$5 as RBSwitchFieldProps, RBSwitchProps, RBTabsContentProps, RBTabsListProps, RBTabsProps, RBTabsTriggerProps, Props$e as RBTextAreaProps, RBToggleGroupItemProps, RBToggleGroupProps, RBToggleProps, Props$5 as RadioButtonProps, RadioVariantProps, Props$c as RangeDateInputProps, RbJoyStickEventData, RbSdk, PropsType$o as SelectOptionProps, SelectTriggerVariantProps, SheetSide, Props$6 as SideNumberInputProps, SideNumberInputVariantProps, SwitchControlPlacement, SwitchSize, SwitchTone, SwitchType, PropsType$j as TabItemProps, PropsType$k as TabProps, TabVariantProps, PropsType$e as TableProps, PropsType$d as TableRowProps, TabsSize, TabsVariant, PropsType$c as TdColumnProps, TextAreaVariantProps, PropsType$7 as TextFieldProps, PropsType$r as TextHelperTextProps, PropsType$s as TextInputProps, TextInputVariantProps, PropsType$8 as TextLabelProps, ToggleGroupOrientation, ToggleVariantProps, PropsType$6 as TooltipProps, PropsType as VStackProps };
3661
+ export { AmberWarningCircleIcon, GreenCheckCircleIcon, HStack, RBSelectOption as Option, RANGE_DATE_PRESETS, Accordion as RBAccordion, AccordionGroup as RBAccordionGroup, RBAlert, RBBadge, RBBattery, RBCard, RBCardAction, RBCardContent, RBCardDescription, RBCardFooter, RBCardHeader, RBCardHeaderText, RBCardPreviewSlot, RBCardTitle, CheckBox as RBCheckBox, RBCheckIconButton, RBCheckLabelButton, RBChipItem, RBChipTab, RBChipsBoxItem, RBChipsBoxTab, RBCommand, RBCommandDialog, RBCommandEmpty, RBCommandGroup, RBCommandInput, RBCommandItem, RBCommandList, RBCommandSelected, RBCommandSeparator, RBCommandShortcut, RBConfirm, DateInput as RBDateInput, RBDatePickerFooterButton, RBDatePickerFooterTime, RBDatePickerPreset, RBDialog, RBDialogClose, RBDialogContent, RBDialogDescription, RBDialogFooter, RBDialogHeader, RBDialogOverlay, RBDialogPortal, RBDialogTitle, RBDialogTrigger, RBDim, RBDivider, RBDrawer, RBDrawerBody, RBDrawerClose, RBDrawerContent, RBDrawerDescription, RBDrawerFooter, RBDrawerHeader, RBDrawerOverlay, RBDrawerPortal, RBDrawerTitle, RBDrawerTrigger, RBDropdownMenu, RBDropdownMenuCheckboxItem, RBDropdownMenuContent, RBDropdownMenuGroup, RBDropdownMenuItem, RBDropdownMenuLabel, RBDropdownMenuPortal, RBDropdownMenuRadioGroup, RBDropdownMenuRadioItem, RBDropdownMenuSeparator, RBDropdownMenuShortcut, RBDropdownMenuSub, RBDropdownMenuSubContent, RBDropdownMenuSubTrigger, RBDropdownMenuTrigger, RBEmptyView, RBIconButton, index as RBIcons, RBInlineLabelButton, RBJoyStick, RBLabelButton, RBLight, RBMenubar, RBMenubarCheckboxItem, RBMenubarContent, RBMenubarGroup, RBMenubarItem, RBMenubarLabel, RBMenubarMenu, RBMenubarPortal, RBMenubarRadioGroup, RBMenubarRadioItem, RBMenubarSeparator, RBMenubarShortcut, RBMenubarSub, RBMenubarSubContent, RBMenubarSubTrigger, RBMenubarTrigger, RBModal, MultiRangeInput as RBMultiRangeInput, RBNoData, RBPagination, RBPopover, RBPopoverAnchor, RBPopoverContent, RBPopoverPlacements, RBPopoverTrigger, RBPrompt, RBQrBarCodeGuideModal, RBRadioBox, RBRadioButton, RangeDateInput as RBRangeDateInput, RBResizable, RBResizableHandle, RBResizablePanel, RBResizable as RBResizablePanelGroup, RBSelectBox, RBSelectDropBox, RBSelectOption, Calendar as RBShadCalendar, RBSheet, RBSheetClose, RBSheetContent, RBSheetDescription, RBSheetFooter, RBSheetHeader, RBSheetOverlay, RBSheetPortal, RBSheetTitle, RBSheetTrigger, SideNumberInput as RBSideNumberInput, RBSimpleToast, RBSlider, RBSpinner, RBSwitch, RBSwitchField, RBTab, RBTabItem, RBTable, RBTableRow, RBTabs, RBTabsContent, RBTabsList, RBTabsTrigger, RBTdColumn, RBTdExpend, RBTextArea, RBTextField, RBTextHelperText, RBTextInput, RBTextLabel, RBToggle, RBToggleGroup, RBToggleGroupItem, RBTooltip, VStack, accordionVariants, badgeRootVariants, batteryFillVariants, cardRootVariants, checkBoxRootVariants, checkIconButtonVariants, checkLabelButtonVariants, chipRootVariants, dividerVariants, emptyViewRootVariants, getRangePresetDates, iconButtonVariants, inlineLabelButtonVariants, labelButtonVariants, lightColors, lightSizes, radioRootVariants, selectTriggerVariants, sideNumberInputVariants, tabRootVariants, textAreaVariants, textInputVariants, useRb, useRbFormData, useRbSave };
3662
+ export type { PropsType$t as AccordionProps, AccordionVariantProps, PropsType$a as BadgeProps, BadgeVariantProps, BatteryProps, BatteryVariantProps, CardVariantProps, Props$h as CheckBoxProps, CheckBoxVariantProps, Props$g as CheckIconButtonProps, PropsType$h as ChipItemProps, PropsType$i as ChipTabProps, ChipVariantProps, PropsType$f as ChipsBoxItemProps, PropsType$g as ChipsBoxTabProps, Props$d as DateInputProps, PropsType$2 as DatePickerFooterButtonProps, PropsType$4 as DatePickerPresetProps, DatePickerTimeValue, DatePickerValue, PropsType$q as DimProps, DividerProps, DividerVariantProps, DrawerSide, DropdownItemVariant, PropsType$9 as EmptyViewProps, EmptyViewVariantProps, PropsType$b as ExpendProps, PropsType$1 as HStackProps, HelperTone, IconButtonProps, IconButtonVariantProps, InlineLabelButtonProps, InlineLabelButtonVariantProps, Props$3 as JoyStickProps, LabelButtonProps, LabelButtonVariantProps, LightProps, LightVariantProps, MenubarItemVariant, Props$4 as MultiRangeInputProps, Props$7 as PaginationProps, Props$b as RBAlertProps, RBCardContentProps, RBCardFooterProps, RBCardHeaderProps, RBCardHeaderTextProps, RBCardPreviewSlotProps, RBCardProps, RBCardTitleProps, Props$a as RBConfirmProps, RBDialogContentProps, RBDrawerContentProps, RBDrawerProps, RBPopoverContentProps, RBPopoverPlacement, Props$9 as RBPromptProps, PropsType$l as RBRadioBoxProps, RBResizableHandleProps, RBResizablePanelProps, RBResizableProps, PropsType$p as RBSelectBoxProps, RBSelectBoxRef, RBSheetContentProps, Props$1 as RBSimpleToastProps, RBSliderProps, PropsType$5 as RBSwitchFieldProps, RBSwitchProps, RBTabsContentProps, RBTabsListProps, RBTabsProps, RBTabsTriggerProps, Props$e as RBTextAreaProps, RBToggleGroupItemProps, RBToggleGroupProps, RBToggleProps, Props$5 as RadioButtonProps, RadioVariantProps, Props$c as RangeDateInputProps, RangePresetKey, RbJoyStickEventData, RbSdk, PropsType$o as SelectOptionProps, SelectTriggerVariantProps, SheetSide, Props$6 as SideNumberInputProps, SideNumberInputVariantProps, SwitchControlPlacement, SwitchSize, SwitchTone, SwitchType, PropsType$j as TabItemProps, PropsType$k as TabProps, TabVariantProps, PropsType$e as TableProps, PropsType$d as TableRowProps, TabsSize, TabsVariant, PropsType$c as TdColumnProps, TextAreaVariantProps, PropsType$7 as TextFieldProps, PropsType$r as TextHelperTextProps, PropsType$s as TextInputProps, TextInputVariantProps, PropsType$8 as TextLabelProps, ToggleGroupOrientation, ToggleVariantProps, PropsType$6 as TooltipProps, PropsType as VStackProps };
package/dist/sdk.d.ts CHANGED
@@ -41,6 +41,16 @@ export interface RbSdk {
41
41
  * unregister fn. Prefer the `useRbSave()` hook.
42
42
  */
43
43
  onSave: (getPayload: () => unknown) => () => void;
44
+ /**
45
+ * PCs allowed to talk to this plugin's ROS 2 nodes. ROS discovery is robot-local
46
+ * (ROS_AUTOMATIC_DISCOVERY_RANGE=LOCALHOST); Muscat keeps this list per plugin, starts
47
+ * the plugin with ROS_STATIC_PEERS set to it, and restarts the plugin when it changes.
48
+ */
49
+ rosPeers: {
50
+ get: () => Promise<string[]>;
51
+ /** Saves the list and restarts the plugin backend. Rejects on an invalid IP. */
52
+ set: (peers: string[]) => Promise<string[]>;
53
+ };
44
54
  }
45
55
  export declare const RbContext: import("react").Context<RbSdk>;
46
56
  /** Access the host SDK from within a plugin component. */
package/dist/sdk.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../src/sdk.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,YAAY,IAAI,UAAU,EAC1B,WAAW,IAAI,SAAS,EACzB,MAAM,MAAM,CAAC;AAGd,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAElD,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAEpE,MAAM,WAAW,KAAK;IACpB;;;;OAIG;IACH,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3D,+DAA+D;IAC/D,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,EACX,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,IAAI,KAC1B,OAAO,CAAC,UAAU,CAAC,CAAC;IAEzB;;;OAGG;IACH,MAAM,EAAE,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAErE;;;OAGG;IACH,KAAK,EAAE,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EACtB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,KACrC,OAAO,CAAC,SAAS,CAAC,CAAC;IAExB,qEAAqE;IACrE,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAEjC,gCAAgC;IAChC,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACjC,uDAAuD;IACvD,OAAO,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;IACrC,wFAAwF;IACxF,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAEnC;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf;;;;;OAKG;IACH,MAAM,EAAE,CAAC,UAAU,EAAE,MAAM,OAAO,KAAK,MAAM,IAAI,CAAC;CACnD;AAuCD,eAAO,MAAM,SAAS,gCAAgC,CAAC;AAEvD,0DAA0D;AAC1D,wBAAgB,KAAK,IAAI,KAAK,CAE7B;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,CAAC,GAAG,OAAO,KAAK,CAAC,GAAG,SAAS,CAE1D;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,CAAC,GAAG,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC,GAAG,IAAI,CAKhE"}
1
+ {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../src/sdk.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,YAAY,IAAI,UAAU,EAC1B,WAAW,IAAI,SAAS,EACzB,MAAM,MAAM,CAAC;AAGd,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAElD,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAEpE,MAAM,WAAW,KAAK;IACpB;;;;OAIG;IACH,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3D,+DAA+D;IAC/D,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,EACX,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,IAAI,KAC1B,OAAO,CAAC,UAAU,CAAC,CAAC;IAEzB;;;OAGG;IACH,MAAM,EAAE,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAErE;;;OAGG;IACH,KAAK,EAAE,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EACtB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,KACrC,OAAO,CAAC,SAAS,CAAC,CAAC;IAExB,qEAAqE;IACrE,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAEjC,gCAAgC;IAChC,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACjC,uDAAuD;IACvD,OAAO,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;IACrC,wFAAwF;IACxF,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAEnC;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf;;;;;OAKG;IACH,MAAM,EAAE,CAAC,UAAU,EAAE,MAAM,OAAO,KAAK,MAAM,IAAI,CAAC;IAElD;;;;OAIG;IACH,QAAQ,EAAE;QACR,GAAG,EAAE,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAC7B,gFAAgF;QAChF,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;KAC7C,CAAC;CACH;AAiDD,eAAO,MAAM,SAAS,gCAAgC,CAAC;AAEvD,0DAA0D;AAC1D,wBAAgB,KAAK,IAAI,KAAK,CAE7B;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,CAAC,GAAG,OAAO,KAAK,CAAC,GAAG,SAAS,CAE1D;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,CAAC,GAAG,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC,GAAG,IAAI,CAKhE"}
package/dist/sdk.js CHANGED
@@ -33,6 +33,16 @@ const noopSdk = {
33
33
  },
34
34
  data: void 0,
35
35
  onSave: () => () => {
36
+ },
37
+ rosPeers: {
38
+ get: async () => {
39
+ console.warn("[plugin-ui] rb.rosPeers called outside of a host context");
40
+ return [];
41
+ },
42
+ set: async () => {
43
+ console.warn("[plugin-ui] rb.rosPeers called outside of a host context");
44
+ return [];
45
+ }
36
46
  }
37
47
  };
38
48
  const RbContext = createContext(noopSdk);
package/dist/sdk.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/sdk.ts"],
4
- "sourcesContent": ["/* eslint-disable @typescript-eslint/no-explicit-any */\n/**\n * Plugin runtime SDK (`useRb()`).\n *\n * pub/sub/action/serve talk to the Zenoh router over WebSocket. Topics are\n * namespaced to {pluginId}/... ; a leading \"/\" makes the path absolute.\n * alert/confirm/prompt/toast delegate to the host.\n */\nimport { createContext, useContext, useEffect, useRef } from \"react\";\nimport type {\n WsSubscriber as Subscriber,\n WsQueryable as Queryable,\n} from \"./ws\";\n// Type-only import \u2014 no runtime coupling to host-ui.ts (which itself has no\n// dependency back on sdk.ts, so this stays one-directional).\nimport type { PluginUIServices } from \"./host-ui\";\n\nexport type RbNotifyType = \"info\" | \"success\" | \"warning\" | \"error\";\n\nexport interface RbSdk {\n /**\n * Publish JSON payload to a topic.\n * \"sensor/data\" \u2192 {pluginId}/sensor/data\n * \"/broadcast/x\" \u2192 broadcast/x (absolute)\n */\n pub: <P = any>(topic: string, payload: P) => Promise<void>;\n\n /** Subscribe to a topic. Returns a handle with undeclare(). */\n sub: <P = any>(\n topic: string,\n handler: (payload: P) => void,\n ) => Promise<Subscriber>;\n\n /**\n * Send a request and await the first reply (Zenoh query).\n * Use this to call a backend handler registered with rb.serve() / ZenohRouter.\n */\n action: <R = any, P = any>(topic: string, payload?: P) => Promise<R>;\n\n /**\n * Register a queryable \u2014 the frontend responds to backend queries.\n * Mirror of rb.action() on the other side.\n */\n serve: <P = any, R = any>(\n topic: string,\n handler: (payload?: P) => R | Promise<R>,\n ) => Promise<Queryable>;\n\n /** Show a transient notification through the host's toast system. */\n toast: PluginUIServices[\"toast\"];\n\n /** Show a host alert dialog. */\n alert: PluginUIServices[\"alert\"];\n /** Show a host confirm dialog. Resolves true/false. */\n confirm: PluginUIServices[\"confirm\"];\n /** Show a host prompt dialog. Resolves the entered value, or undefined if cancelled. */\n prompt: PluginUIServices[\"prompt\"];\n\n /**\n * Host-provided initial data for this mount \u2014 e.g. the previously-saved\n * values when the host re-opens a form. `undefined` for a fresh mount.\n * Prefer the `useRbFormData()` hook over reading this directly.\n */\n data?: unknown;\n\n /**\n * Register a getter the host calls (synchronously) to collect this plugin's\n * save payload \u2014 e.g. when the host's Save button is clicked. The returned\n * value must be JSON-serialisable (the host persists it). Returns an\n * unregister fn. Prefer the `useRbSave()` hook.\n */\n onSave: (getPayload: () => unknown) => () => void;\n}\n\nconst noopSdk: RbSdk = {\n pub: async <P = any>(_topic?: string, _payload?: P) => {\n console.warn(\"[plugin-ui] rb.pub called outside of a host context\");\n },\n sub: async <P = any>(_topic?: string, _handler?: (payload: P) => void) => {\n console.warn(\"[plugin-ui] rb.sub called outside of a host context\");\n return { undeclare: async () => {} } as unknown as Subscriber;\n },\n action: async <R = any, P = any>(_topic?: string, _payload?: P) => {\n console.warn(\"[plugin-ui] rb.action called outside of a host context\");\n return undefined as R;\n },\n serve: async <P = any, R = any>(\n _topic?: string,\n _handler?: (payload?: P) => R,\n ) => {\n console.warn(\"[plugin-ui] rb.serve called outside of a host context\");\n return { undeclare: async () => {} } as unknown as Queryable;\n },\n toast: (params) => {\n console.warn(\"[plugin-ui] rb.toast (no host):\", params);\n },\n alert: async (_) => {\n console.warn(\"[plugin-ui] rb.alert called outside of a host context\");\n },\n confirm: async (_) => {\n console.warn(\"[plugin-ui] rb.confirm called outside of a host context\");\n return false;\n },\n prompt: async (_) => {\n console.warn(\"[plugin-ui] rb.prompt called outside of a host context\");\n return undefined;\n },\n data: undefined,\n onSave: () => () => {},\n};\n\nexport const RbContext = createContext<RbSdk>(noopSdk);\n\n/** Access the host SDK from within a plugin component. */\nexport function useRb(): RbSdk {\n return useContext(RbContext);\n}\n\n/**\n * Read the host-provided initial data for this mount (e.g. previously-saved\n * form values). `undefined` for a fresh mount.\n *\n * const saved = useRbFormData<MyArgs>();\n * const methods = useForm({ defaultValues: saved ?? DEFAULTS });\n */\nexport function useRbFormData<T = unknown>(): T | undefined {\n return useRb().data as T | undefined;\n}\n\n/**\n * Register a getter the host calls to collect this plugin's save payload when\n * the host's Save is clicked. The latest `getPayload` is always used, so it can\n * close over current state. The returned value must be JSON-serialisable.\n *\n * useRbSave(() => ({ args: getValues(), summary: buildSummary() }));\n */\nexport function useRbSave<T = unknown>(getPayload: () => T): void {\n const rb = useRb();\n const ref = useRef(getPayload);\n ref.current = getPayload;\n useEffect(() => rb.onSave(() => ref.current()), [rb]);\n}\n"],
5
- "mappings": "AAQA,SAAS,eAAe,YAAY,WAAW,cAAc;AAkE7D,MAAM,UAAiB;AAAA,EACrB,KAAK,OAAgB,QAAiB,aAAiB;AACrD,YAAQ,KAAK,qDAAqD;AAAA,EACpE;AAAA,EACA,KAAK,OAAgB,QAAiB,aAAoC;AACxE,YAAQ,KAAK,qDAAqD;AAClE,WAAO,EAAE,WAAW,YAAY;AAAA,IAAC,EAAE;AAAA,EACrC;AAAA,EACA,QAAQ,OAAyB,QAAiB,aAAiB;AACjE,YAAQ,KAAK,wDAAwD;AACrE,WAAO;AAAA,EACT;AAAA,EACA,OAAO,OACL,QACA,aACG;AACH,YAAQ,KAAK,uDAAuD;AACpE,WAAO,EAAE,WAAW,YAAY;AAAA,IAAC,EAAE;AAAA,EACrC;AAAA,EACA,OAAO,CAAC,WAAW;AACjB,YAAQ,KAAK,mCAAmC,MAAM;AAAA,EACxD;AAAA,EACA,OAAO,OAAO,MAAM;AAClB,YAAQ,KAAK,uDAAuD;AAAA,EACtE;AAAA,EACA,SAAS,OAAO,MAAM;AACpB,YAAQ,KAAK,yDAAyD;AACtE,WAAO;AAAA,EACT;AAAA,EACA,QAAQ,OAAO,MAAM;AACnB,YAAQ,KAAK,wDAAwD;AACrE,WAAO;AAAA,EACT;AAAA,EACA,MAAM;AAAA,EACN,QAAQ,MAAM,MAAM;AAAA,EAAC;AACvB;AAEO,MAAM,YAAY,cAAqB,OAAO;AAG9C,SAAS,QAAe;AAC7B,SAAO,WAAW,SAAS;AAC7B;AASO,SAAS,gBAA4C;AAC1D,SAAO,MAAM,EAAE;AACjB;AASO,SAAS,UAAuB,YAA2B;AAChE,QAAM,KAAK,MAAM;AACjB,QAAM,MAAM,OAAO,UAAU;AAC7B,MAAI,UAAU;AACd,YAAU,MAAM,GAAG,OAAO,MAAM,IAAI,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;AACtD;",
4
+ "sourcesContent": ["/* eslint-disable @typescript-eslint/no-explicit-any */\n/**\n * Plugin runtime SDK (`useRb()`).\n *\n * pub/sub/action/serve talk to the Zenoh router over WebSocket. Topics are\n * namespaced to {pluginId}/... ; a leading \"/\" makes the path absolute.\n * alert/confirm/prompt/toast delegate to the host.\n */\nimport { createContext, useContext, useEffect, useRef } from \"react\";\nimport type {\n WsSubscriber as Subscriber,\n WsQueryable as Queryable,\n} from \"./ws\";\n// Type-only import \u2014 no runtime coupling to host-ui.ts (which itself has no\n// dependency back on sdk.ts, so this stays one-directional).\nimport type { PluginUIServices } from \"./host-ui\";\n\nexport type RbNotifyType = \"info\" | \"success\" | \"warning\" | \"error\";\n\nexport interface RbSdk {\n /**\n * Publish JSON payload to a topic.\n * \"sensor/data\" \u2192 {pluginId}/sensor/data\n * \"/broadcast/x\" \u2192 broadcast/x (absolute)\n */\n pub: <P = any>(topic: string, payload: P) => Promise<void>;\n\n /** Subscribe to a topic. Returns a handle with undeclare(). */\n sub: <P = any>(\n topic: string,\n handler: (payload: P) => void,\n ) => Promise<Subscriber>;\n\n /**\n * Send a request and await the first reply (Zenoh query).\n * Use this to call a backend handler registered with rb.serve() / ZenohRouter.\n */\n action: <R = any, P = any>(topic: string, payload?: P) => Promise<R>;\n\n /**\n * Register a queryable \u2014 the frontend responds to backend queries.\n * Mirror of rb.action() on the other side.\n */\n serve: <P = any, R = any>(\n topic: string,\n handler: (payload?: P) => R | Promise<R>,\n ) => Promise<Queryable>;\n\n /** Show a transient notification through the host's toast system. */\n toast: PluginUIServices[\"toast\"];\n\n /** Show a host alert dialog. */\n alert: PluginUIServices[\"alert\"];\n /** Show a host confirm dialog. Resolves true/false. */\n confirm: PluginUIServices[\"confirm\"];\n /** Show a host prompt dialog. Resolves the entered value, or undefined if cancelled. */\n prompt: PluginUIServices[\"prompt\"];\n\n /**\n * Host-provided initial data for this mount \u2014 e.g. the previously-saved\n * values when the host re-opens a form. `undefined` for a fresh mount.\n * Prefer the `useRbFormData()` hook over reading this directly.\n */\n data?: unknown;\n\n /**\n * Register a getter the host calls (synchronously) to collect this plugin's\n * save payload \u2014 e.g. when the host's Save button is clicked. The returned\n * value must be JSON-serialisable (the host persists it). Returns an\n * unregister fn. Prefer the `useRbSave()` hook.\n */\n onSave: (getPayload: () => unknown) => () => void;\n\n /**\n * PCs allowed to talk to this plugin's ROS 2 nodes. ROS discovery is robot-local\n * (ROS_AUTOMATIC_DISCOVERY_RANGE=LOCALHOST); Muscat keeps this list per plugin, starts\n * the plugin with ROS_STATIC_PEERS set to it, and restarts the plugin when it changes.\n */\n rosPeers: {\n get: () => Promise<string[]>;\n /** Saves the list and restarts the plugin backend. Rejects on an invalid IP. */\n set: (peers: string[]) => Promise<string[]>;\n };\n}\n\nconst noopSdk: RbSdk = {\n pub: async <P = any>(_topic?: string, _payload?: P) => {\n console.warn(\"[plugin-ui] rb.pub called outside of a host context\");\n },\n sub: async <P = any>(_topic?: string, _handler?: (payload: P) => void) => {\n console.warn(\"[plugin-ui] rb.sub called outside of a host context\");\n return { undeclare: async () => {} } as unknown as Subscriber;\n },\n action: async <R = any, P = any>(_topic?: string, _payload?: P) => {\n console.warn(\"[plugin-ui] rb.action called outside of a host context\");\n return undefined as R;\n },\n serve: async <P = any, R = any>(\n _topic?: string,\n _handler?: (payload?: P) => R,\n ) => {\n console.warn(\"[plugin-ui] rb.serve called outside of a host context\");\n return { undeclare: async () => {} } as unknown as Queryable;\n },\n toast: (params) => {\n console.warn(\"[plugin-ui] rb.toast (no host):\", params);\n },\n alert: async (_) => {\n console.warn(\"[plugin-ui] rb.alert called outside of a host context\");\n },\n confirm: async (_) => {\n console.warn(\"[plugin-ui] rb.confirm called outside of a host context\");\n return false;\n },\n prompt: async (_) => {\n console.warn(\"[plugin-ui] rb.prompt called outside of a host context\");\n return undefined;\n },\n data: undefined,\n onSave: () => () => {},\n rosPeers: {\n get: async () => {\n console.warn(\"[plugin-ui] rb.rosPeers called outside of a host context\");\n return [];\n },\n set: async () => {\n console.warn(\"[plugin-ui] rb.rosPeers called outside of a host context\");\n return [];\n },\n },\n};\n\nexport const RbContext = createContext<RbSdk>(noopSdk);\n\n/** Access the host SDK from within a plugin component. */\nexport function useRb(): RbSdk {\n return useContext(RbContext);\n}\n\n/**\n * Read the host-provided initial data for this mount (e.g. previously-saved\n * form values). `undefined` for a fresh mount.\n *\n * const saved = useRbFormData<MyArgs>();\n * const methods = useForm({ defaultValues: saved ?? DEFAULTS });\n */\nexport function useRbFormData<T = unknown>(): T | undefined {\n return useRb().data as T | undefined;\n}\n\n/**\n * Register a getter the host calls to collect this plugin's save payload when\n * the host's Save is clicked. The latest `getPayload` is always used, so it can\n * close over current state. The returned value must be JSON-serialisable.\n *\n * useRbSave(() => ({ args: getValues(), summary: buildSummary() }));\n */\nexport function useRbSave<T = unknown>(getPayload: () => T): void {\n const rb = useRb();\n const ref = useRef(getPayload);\n ref.current = getPayload;\n useEffect(() => rb.onSave(() => ref.current()), [rb]);\n}\n"],
5
+ "mappings": "AAQA,SAAS,eAAe,YAAY,WAAW,cAAc;AA6E7D,MAAM,UAAiB;AAAA,EACrB,KAAK,OAAgB,QAAiB,aAAiB;AACrD,YAAQ,KAAK,qDAAqD;AAAA,EACpE;AAAA,EACA,KAAK,OAAgB,QAAiB,aAAoC;AACxE,YAAQ,KAAK,qDAAqD;AAClE,WAAO,EAAE,WAAW,YAAY;AAAA,IAAC,EAAE;AAAA,EACrC;AAAA,EACA,QAAQ,OAAyB,QAAiB,aAAiB;AACjE,YAAQ,KAAK,wDAAwD;AACrE,WAAO;AAAA,EACT;AAAA,EACA,OAAO,OACL,QACA,aACG;AACH,YAAQ,KAAK,uDAAuD;AACpE,WAAO,EAAE,WAAW,YAAY;AAAA,IAAC,EAAE;AAAA,EACrC;AAAA,EACA,OAAO,CAAC,WAAW;AACjB,YAAQ,KAAK,mCAAmC,MAAM;AAAA,EACxD;AAAA,EACA,OAAO,OAAO,MAAM;AAClB,YAAQ,KAAK,uDAAuD;AAAA,EACtE;AAAA,EACA,SAAS,OAAO,MAAM;AACpB,YAAQ,KAAK,yDAAyD;AACtE,WAAO;AAAA,EACT;AAAA,EACA,QAAQ,OAAO,MAAM;AACnB,YAAQ,KAAK,wDAAwD;AACrE,WAAO;AAAA,EACT;AAAA,EACA,MAAM;AAAA,EACN,QAAQ,MAAM,MAAM;AAAA,EAAC;AAAA,EACrB,UAAU;AAAA,IACR,KAAK,YAAY;AACf,cAAQ,KAAK,0DAA0D;AACvE,aAAO,CAAC;AAAA,IACV;AAAA,IACA,KAAK,YAAY;AACf,cAAQ,KAAK,0DAA0D;AACvE,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AACF;AAEO,MAAM,YAAY,cAAqB,OAAO;AAG9C,SAAS,QAAe;AAC7B,SAAO,WAAW,SAAS;AAC7B;AASO,SAAS,gBAA4C;AAC1D,SAAO,MAAM,EAAE;AACjB;AASO,SAAS,UAAuB,YAA2B;AAChE,QAAM,KAAK,MAAM;AACjB,QAAM,MAAM,OAAO,UAAU;AAC7B,MAAI,UAAU;AACd,YAAU,MAAM,GAAG,OAAO,MAAM,IAAI,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;AACtD;",
6
6
  "names": []
7
7
  }
package/dist/ws.d.ts CHANGED
@@ -10,6 +10,7 @@ export declare class RbWs {
10
10
  private readonly _subHandlers;
11
11
  private readonly _serveHandlers;
12
12
  private constructor();
13
+ get pluginId(): string;
13
14
  static connect(url: string, pluginId: string): Promise<RbWs>;
14
15
  private _key;
15
16
  pub<P = any>(topic: string, payload: P): Promise<void>;
@@ -69,5 +70,9 @@ export declare function createLazyRbSdk(wsPromise: Promise<RbWs>, services: Plug
69
70
  }) => Promise<string | undefined>;
70
71
  data: unknown;
71
72
  onSave: (getPayload: () => unknown) => () => void;
73
+ rosPeers: {
74
+ get: () => Promise<string[]>;
75
+ set: (peers: string[]) => Promise<string[]>;
76
+ };
72
77
  };
73
78
  //# sourceMappingURL=ws.d.ts.map
package/dist/ws.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../src/ws.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAElD,MAAM,WAAW,QAAQ;IACvB,SAAS,IAAI,IAAI,CAAC;CACnB;AAED,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC;AACpC,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC;AAMnC,qBAAa,IAAI;IACf,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAGzB;IACJ,OAAO,CAAC,QAAQ,CAAC,cAAc,CAG3B;IAEJ,OAAO;IA+BP,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB5D,OAAO,CAAC,IAAI;IAKN,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtD,GAAG,CAAC,CAAC,GAAG,GAAG,EACf,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,IAAI,GAC5B,OAAO,CAAC,YAAY,CAAC;IAsBlB,MAAM,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAuChE,KAAK,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EAC1B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GACvC,OAAO,CAAC,WAAW,CAAC;IAYvB,KAAK,IAAI,IAAI;CAGd;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,EACxB,QAAQ,EAAE,gBAAgB,EAI1B,KAAK,CAAC,EAAE;IACN,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,OAAO,KAAK,MAAM,IAAI,CAAC;CACpD;iBAIoB,MAAM,WAAW,GAAG;iBAG9B,MAAM,WAEJ,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI;oBAGX,MAAM,YAAY,GAAG;mBAGlC,MAAM,WAEJ,CAAC,OAAO,CAAC,EAAE,GAAG,KAAK,GAAG;;aA1MtB,CAAC;;YAIE,CAAC;mBAGF,CAAC;yBAA+B,CAAC;0BAC7B,CAAC;gBAAuB,CAAC;eAAqB,CAAC;;;aApB1B,CAAA;;kBAC9B,CAAC;;;aACgB,CAAC;;kBAExB,CAAC;;;;;;aAEF,CAAD;;aAED,CAAC;mBAAyB,CAAC;kBAC3B,CAAA;;;;;;yBA2LuB,MAAM,OAAO,KAAK,MAAM,IAAI;EA2BrD"}
1
+ {"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../src/ws.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAElD,MAAM,WAAW,QAAQ;IACvB,SAAS,IAAI,IAAI,CAAC;CACnB;AAED,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC;AACpC,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC;AAMnC,qBAAa,IAAI;IACf,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAGzB;IACJ,OAAO,CAAC,QAAQ,CAAC,cAAc,CAG3B;IAEJ,OAAO;IAsCP,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB5D,OAAO,CAAC,IAAI;IAKN,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtD,GAAG,CAAC,CAAC,GAAG,GAAG,EACf,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,IAAI,GAC5B,OAAO,CAAC,YAAY,CAAC;IAsBlB,MAAM,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAuChE,KAAK,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EAC1B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GACvC,OAAO,CAAC,WAAW,CAAC;IAYvB,KAAK,IAAI,IAAI;CAGd;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,EACxB,QAAQ,EAAE,gBAAgB,EAI1B,KAAK,CAAC,EAAE;IACN,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,OAAO,KAAK,MAAM,IAAI,CAAC;CACpD;iBAIoB,MAAM,WAAW,GAAG;iBAG9B,MAAM,WAEJ,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI;oBAGX,MAAM,YAAY,GAAG;mBAGlC,MAAM,WAEJ,CAAC,OAAO,CAAC,EAAE,GAAG,KAAK,GAAG;;aArNtB,CAAC;;YAIE,CAAC;mBAGF,CAAC;yBAA+B,CAAC;0BAC7B,CAAC;gBAAuB,CAAC;eAAqB,CAAC;;;aApB1B,CAAA;;kBAC9B,CAAC;;;aACgB,CAAC;;kBAExB,CAAC;;;;;;aAEF,CAAD;;aAED,CAAC;mBAAyB,CAAC;kBAC3B,CAAA;;;;;;yBAsMuB,MAAM,OAAO,KAAK,MAAM,IAAI;;mBAqCnC,OAAO,CAAC,MAAM,EAAE,CAAC;qBACb,MAAM,EAAE,KAAG,OAAO,CAAC,MAAM,EAAE,CAAC;;EAPlD"}
package/dist/ws.js CHANGED
@@ -10,6 +10,10 @@ class RbWs {
10
10
  constructor(socket, pluginId) {
11
11
  this._socket = socket;
12
12
  this._pluginId = pluginId;
13
+ socket.io.on("reconnect", () => {
14
+ for (const topic of this._subHandlers.keys()) socket.emit("sub", { topic });
15
+ for (const topic of this._serveHandlers.keys()) socket.emit("serve_register", { topic });
16
+ });
13
17
  socket.on(
14
18
  "msg",
15
19
  ({ topic, payload }) => {
@@ -29,6 +33,9 @@ class RbWs {
29
33
  }
30
34
  );
31
35
  }
36
+ get pluginId() {
37
+ return this._pluginId;
38
+ }
32
39
  static connect(url, pluginId) {
33
40
  return new Promise((resolve, reject) => {
34
41
  const socket = io(url, { transports: ["websocket"] });
@@ -131,9 +138,23 @@ function createLazyRbSdk(wsPromise, services, mount) {
131
138
  prompt: services.prompt,
132
139
  data: mount?.data,
133
140
  onSave: mount?.onSave ?? (() => () => {
141
+ }),
142
+ rosPeers: rosPeersApi(async (op, payload) => {
143
+ const ws = await wsPromise;
144
+ return ws.action(`/muscat/plugin/${ws.pluginId}/ros_peers/${op}`, payload);
134
145
  })
135
146
  };
136
147
  }
148
+ function rosPeersApi(call) {
149
+ return {
150
+ get: async () => (await call("get"))?.peers ?? [],
151
+ set: async (peers) => {
152
+ const r = await call("set", { peers });
153
+ if (!r?.ok) throw new Error(r?.error ?? "rb.rosPeers.set failed");
154
+ return r.peers;
155
+ }
156
+ };
157
+ }
137
158
  export {
138
159
  RbWs,
139
160
  createLazyRbSdk
package/dist/ws.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/ws.ts"],
4
- "sourcesContent": ["/**\n * Socket.IO communication client for plugin frontends.\n *\n * Topics are automatically namespaced to prevent cross-plugin collisions:\n * \"sensor/data\" \u2192 \"{pluginId}/sensor/data\"\n * \"/broadcast/x\" \u2192 \"broadcast/x\" (leading slash = absolute)\n *\n * Browser \u2190\u2192 Python backend protocol:\n * browser emits : sub, unsub, pub, action (with ack), serve_reply\n * server emits : msg, serve_call\n */\nimport { io, type Socket } from \"socket.io-client\";\n// Type-only \u2014 no runtime coupling to host-ui.ts.\nimport type { PluginUIServices } from \"./host-ui\";\n\nexport interface WsHandle {\n undeclare(): void;\n}\n\nexport type WsSubscriber = WsHandle;\nexport type WsQueryable = WsHandle;\n\nfunction resolveTopic(pluginId: string, topic: string): string {\n return topic.startsWith(\"/\") ? topic.slice(1) : `${pluginId}/${topic}`;\n}\n\nexport class RbWs {\n private readonly _socket: Socket;\n private readonly _pluginId: string;\n private readonly _subHandlers = new Map<\n string,\n Set<(payload: unknown) => void>\n >();\n private readonly _serveHandlers = new Map<\n string,\n (payload?: unknown) => unknown | Promise<unknown>\n >();\n\n private constructor(socket: Socket, pluginId: string) {\n this._socket = socket;\n this._pluginId = pluginId;\n\n // Deliver incoming pub/sub messages to registered handlers\n socket.on(\n \"msg\",\n ({ topic, payload }: { topic: string; payload: unknown }) => {\n this._subHandlers.get(topic)?.forEach((h) => h(payload));\n },\n );\n\n // Python calls a browser-side serve handler\n socket.on(\n \"serve_call\",\n async ({\n id,\n topic,\n payload,\n }: {\n id: string;\n topic: string;\n payload: unknown;\n }) => {\n const handler = this._serveHandlers.get(topic);\n const result = handler ? await handler(payload) : undefined;\n socket.emit(\"serve_reply\", { id, payload: result });\n },\n );\n }\n\n static connect(url: string, pluginId: string): Promise<RbWs> {\n return new Promise((resolve, reject) => {\n const socket = io(url, { transports: [\"websocket\"] });\n const onConnect = () => {\n cleanup();\n resolve(new RbWs(socket, pluginId));\n };\n const onError = (err: Error) => {\n cleanup();\n reject(err);\n };\n const cleanup = () => {\n socket.off(\"connect\", onConnect);\n socket.off(\"connect_error\", onError);\n };\n socket.once(\"connect\", onConnect);\n socket.once(\"connect_error\", onError);\n });\n }\n\n private _key(topic: string): string {\n return resolveTopic(this._pluginId, topic);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async pub<P = any>(topic: string, payload: P): Promise<void> {\n this._socket.emit(\"pub\", { topic: this._key(topic), payload });\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async sub<P = any>(\n topic: string,\n handler: (payload: P) => void,\n ): Promise<WsSubscriber> {\n const key = this._key(topic);\n if (!this._subHandlers.has(key)) {\n this._subHandlers.set(key, new Set());\n this._socket.emit(\"sub\", { topic: key });\n }\n const h = handler as (payload: unknown) => void;\n this._subHandlers.get(key)!.add(h);\n return {\n undeclare: () => {\n const set = this._subHandlers.get(key);\n if (!set) return;\n set.delete(h);\n if (set.size === 0) {\n this._subHandlers.delete(key);\n this._socket.emit(\"unsub\", { topic: key });\n }\n },\n };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async action<R = any, P = any>(topic: string, payload?: P): Promise<R> {\n const key = this._key(topic);\n return new Promise((resolve, reject) => {\n this._socket\n .timeout(10_000)\n .emit(\n \"action\",\n { topic: key, payload },\n (err: Error | null, result: R) => {\n if (err) {\n // socket.io ack timeout \u2014 the bridge itself never answered.\n reject(new Error(`action \"${key}\": no ack from bridge (${err.message})`));\n } else if (\n result &&\n typeof result === \"object\" &&\n \"error\" in result &&\n typeof result.error === \"string\"\n ) {\n // Backend handler returned an error payload \u2014 surface it verbatim.\n reject(new Error(result.error));\n } else if (result === undefined || result === null) {\n // The bridge acked but with no payload: the backend handler is\n // registered yet didn't reply in time (hung inside the handler,\n // e.g. a blocked SDK call) or returned nothing. Distinct from a\n // \"No handler registered\" error, which comes back as result.error.\n reject(\n new Error(\n `action \"${key}\": no reply from plugin backend \u2014 the handler was reached but didn't return in time (check the backend's stdout on the robot).`,\n ),\n );\n } else {\n resolve(result);\n }\n },\n );\n });\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async serve<P = any, R = any>(\n topic: string,\n handler: (payload?: P) => R | Promise<R>,\n ): Promise<WsQueryable> {\n const key = this._key(topic);\n this._serveHandlers.set(key, handler as (payload?: unknown) => unknown);\n this._socket.emit(\"serve_register\", { topic: key });\n return {\n undeclare: () => {\n this._serveHandlers.delete(key);\n this._socket.emit(\"serve_unregister\", { topic: key });\n },\n };\n }\n\n close(): void {\n this._socket.disconnect();\n }\n}\n\n/**\n * Builds an rb-sdk-shaped object (pub/sub/action/serve + the passed-through\n * toast/alert/confirm/prompt) that lazily awaits a not-yet-connected `RbWs` \u2014\n * so callers don't block their first render on the WebSocket handshake (there\n * may be no backend reachable yet). Shared by both host mount paths (see\n * ./host.tsx and muscat-plugin-kit's dev bootstrap), which otherwise\n * duplicate this wiring.\n */\nexport function createLazyRbSdk(\n wsPromise: Promise<RbWs>,\n services: PluginUIServices,\n // Per-mount, host-provided (unlike `services`, which is a global singleton):\n // initial `data` and the `onSave` registration the host reads back. Plain\n // values/closures, so they cross the dev React-instance boundary safely.\n mount?: {\n data?: unknown;\n onSave?: (getPayload: () => unknown) => () => void;\n },\n) {\n return {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n pub: async (topic: string, payload: any) =>\n (await wsPromise).pub(topic, payload),\n sub: async (\n topic: string,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handler: (payload: any) => void,\n ) => (await wsPromise).sub(topic, handler),\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n action: async (topic: string, payload?: any) =>\n (await wsPromise).action(topic, payload),\n serve: async (\n topic: string,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handler: (payload?: any) => any,\n ) => (await wsPromise).serve(topic, handler),\n toast: services.toast,\n alert: services.alert,\n confirm: services.confirm,\n prompt: services.prompt,\n data: mount?.data,\n onSave: mount?.onSave ?? (() => () => {}),\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Socket.IO communication client for plugin frontends.\n *\n * Topics are automatically namespaced to prevent cross-plugin collisions:\n * \"sensor/data\" \u2192 \"{pluginId}/sensor/data\"\n * \"/broadcast/x\" \u2192 \"broadcast/x\" (leading slash = absolute)\n *\n * Browser \u2190\u2192 Python backend protocol:\n * browser emits : sub, unsub, pub, action (with ack), serve_reply\n * server emits : msg, serve_call\n */\nimport { io, type Socket } from \"socket.io-client\";\n// Type-only \u2014 no runtime coupling to host-ui.ts.\nimport type { PluginUIServices } from \"./host-ui\";\n\nexport interface WsHandle {\n undeclare(): void;\n}\n\nexport type WsSubscriber = WsHandle;\nexport type WsQueryable = WsHandle;\n\nfunction resolveTopic(pluginId: string, topic: string): string {\n return topic.startsWith(\"/\") ? topic.slice(1) : `${pluginId}/${topic}`;\n}\n\nexport class RbWs {\n private readonly _socket: Socket;\n private readonly _pluginId: string;\n private readonly _subHandlers = new Map<\n string,\n Set<(payload: unknown) => void>\n >();\n private readonly _serveHandlers = new Map<\n string,\n (payload?: unknown) => unknown | Promise<unknown>\n >();\n\n private constructor(socket: Socket, pluginId: string) {\n this._socket = socket;\n this._pluginId = pluginId;\n\n // The gateway keeps subscriptions/serve registrations in memory \u2014 re-send them after\n // a reconnect (e.g. the gateway restarted), or they silently stop working.\n socket.io.on(\"reconnect\", () => {\n for (const topic of this._subHandlers.keys()) socket.emit(\"sub\", { topic });\n for (const topic of this._serveHandlers.keys()) socket.emit(\"serve_register\", { topic });\n });\n\n // Deliver incoming pub/sub messages to registered handlers\n socket.on(\n \"msg\",\n ({ topic, payload }: { topic: string; payload: unknown }) => {\n this._subHandlers.get(topic)?.forEach((h) => h(payload));\n },\n );\n\n // Python calls a browser-side serve handler\n socket.on(\n \"serve_call\",\n async ({\n id,\n topic,\n payload,\n }: {\n id: string;\n topic: string;\n payload: unknown;\n }) => {\n const handler = this._serveHandlers.get(topic);\n const result = handler ? await handler(payload) : undefined;\n socket.emit(\"serve_reply\", { id, payload: result });\n },\n );\n }\n\n get pluginId(): string {\n return this._pluginId;\n }\n\n static connect(url: string, pluginId: string): Promise<RbWs> {\n return new Promise((resolve, reject) => {\n const socket = io(url, { transports: [\"websocket\"] });\n const onConnect = () => {\n cleanup();\n resolve(new RbWs(socket, pluginId));\n };\n const onError = (err: Error) => {\n cleanup();\n reject(err);\n };\n const cleanup = () => {\n socket.off(\"connect\", onConnect);\n socket.off(\"connect_error\", onError);\n };\n socket.once(\"connect\", onConnect);\n socket.once(\"connect_error\", onError);\n });\n }\n\n private _key(topic: string): string {\n return resolveTopic(this._pluginId, topic);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async pub<P = any>(topic: string, payload: P): Promise<void> {\n this._socket.emit(\"pub\", { topic: this._key(topic), payload });\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async sub<P = any>(\n topic: string,\n handler: (payload: P) => void,\n ): Promise<WsSubscriber> {\n const key = this._key(topic);\n if (!this._subHandlers.has(key)) {\n this._subHandlers.set(key, new Set());\n this._socket.emit(\"sub\", { topic: key });\n }\n const h = handler as (payload: unknown) => void;\n this._subHandlers.get(key)!.add(h);\n return {\n undeclare: () => {\n const set = this._subHandlers.get(key);\n if (!set) return;\n set.delete(h);\n if (set.size === 0) {\n this._subHandlers.delete(key);\n this._socket.emit(\"unsub\", { topic: key });\n }\n },\n };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async action<R = any, P = any>(topic: string, payload?: P): Promise<R> {\n const key = this._key(topic);\n return new Promise((resolve, reject) => {\n this._socket\n .timeout(10_000)\n .emit(\n \"action\",\n { topic: key, payload },\n (err: Error | null, result: R) => {\n if (err) {\n // socket.io ack timeout \u2014 the bridge itself never answered.\n reject(new Error(`action \"${key}\": no ack from bridge (${err.message})`));\n } else if (\n result &&\n typeof result === \"object\" &&\n \"error\" in result &&\n typeof result.error === \"string\"\n ) {\n // Backend handler returned an error payload \u2014 surface it verbatim.\n reject(new Error(result.error));\n } else if (result === undefined || result === null) {\n // The bridge acked but with no payload: the backend handler is\n // registered yet didn't reply in time (hung inside the handler,\n // e.g. a blocked SDK call) or returned nothing. Distinct from a\n // \"No handler registered\" error, which comes back as result.error.\n reject(\n new Error(\n `action \"${key}\": no reply from plugin backend \u2014 the handler was reached but didn't return in time (check the backend's stdout on the robot).`,\n ),\n );\n } else {\n resolve(result);\n }\n },\n );\n });\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async serve<P = any, R = any>(\n topic: string,\n handler: (payload?: P) => R | Promise<R>,\n ): Promise<WsQueryable> {\n const key = this._key(topic);\n this._serveHandlers.set(key, handler as (payload?: unknown) => unknown);\n this._socket.emit(\"serve_register\", { topic: key });\n return {\n undeclare: () => {\n this._serveHandlers.delete(key);\n this._socket.emit(\"serve_unregister\", { topic: key });\n },\n };\n }\n\n close(): void {\n this._socket.disconnect();\n }\n}\n\n/**\n * Builds an rb-sdk-shaped object (pub/sub/action/serve + the passed-through\n * toast/alert/confirm/prompt) that lazily awaits a not-yet-connected `RbWs` \u2014\n * so callers don't block their first render on the WebSocket handshake (there\n * may be no backend reachable yet). Shared by both host mount paths (see\n * ./host.tsx and muscat-plugin-kit's dev bootstrap), which otherwise\n * duplicate this wiring.\n */\nexport function createLazyRbSdk(\n wsPromise: Promise<RbWs>,\n services: PluginUIServices,\n // Per-mount, host-provided (unlike `services`, which is a global singleton):\n // initial `data` and the `onSave` registration the host reads back. Plain\n // values/closures, so they cross the dev React-instance boundary safely.\n mount?: {\n data?: unknown;\n onSave?: (getPayload: () => unknown) => () => void;\n },\n) {\n return {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n pub: async (topic: string, payload: any) =>\n (await wsPromise).pub(topic, payload),\n sub: async (\n topic: string,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handler: (payload: any) => void,\n ) => (await wsPromise).sub(topic, handler),\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n action: async (topic: string, payload?: any) =>\n (await wsPromise).action(topic, payload),\n serve: async (\n topic: string,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handler: (payload?: any) => any,\n ) => (await wsPromise).serve(topic, handler),\n toast: services.toast,\n alert: services.alert,\n confirm: services.confirm,\n prompt: services.prompt,\n data: mount?.data,\n onSave: mount?.onSave ?? (() => () => {}),\n rosPeers: rosPeersApi(async (op, payload) => {\n const ws = await wsPromise;\n return ws.action(`/muscat/plugin/${ws.pluginId}/ros_peers/${op}`, payload);\n }),\n };\n}\n\n// Answered by Muscat (common) on the robot and by `muscat-pk dev` locally.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction rosPeersApi(call: (op: \"get\" | \"set\", payload?: any) => Promise<any>) {\n return {\n get: async (): Promise<string[]> => (await call(\"get\"))?.peers ?? [],\n set: async (peers: string[]): Promise<string[]> => {\n const r = await call(\"set\", { peers });\n if (!r?.ok) throw new Error(r?.error ?? \"rb.rosPeers.set failed\");\n return r.peers;\n },\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rainbow-robotics/plugin-ui",
3
- "version": "1.3.6-dev.8",
3
+ "version": "1.3.6",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=20"
@@ -47,7 +47,7 @@
47
47
  "build": "node build-dist.mjs",
48
48
  "build-plugin-ui": "node build-dist.mjs",
49
49
  "lint": "node ../../node_modules/eslint/bin/eslint.js .",
50
- "check-types": "tsc --noEmit"
50
+ "check-types": "tsc --noEmit -p tsconfig.check.json"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@repo/eslint-config": "*",
package/src/manifest.ts CHANGED
@@ -119,6 +119,8 @@ export function validateManifest(value: unknown): string[] {
119
119
  const m = value as Record<string, unknown>;
120
120
  if (typeof m.id !== "string" || m.id.length === 0)
121
121
  errors.push("manifest.id is required (string)");
122
+ else if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)*(\.[a-z][a-z0-9]*(-[a-z0-9]+)*)*$/.test(m.id))
123
+ errors.push('manifest.id must be "."-separated parts of lowercase letters/digits and single "-", each starting with a letter (e.g. com.example.my-plugin)');
122
124
  if (typeof m.name !== "string" || m.name.length === 0)
123
125
  errors.push("manifest.name is required (string)");
124
126
  if (typeof m.version !== "string" || m.version.length === 0)
package/src/sdk.ts CHANGED
@@ -70,6 +70,17 @@ export interface RbSdk {
70
70
  * unregister fn. Prefer the `useRbSave()` hook.
71
71
  */
72
72
  onSave: (getPayload: () => unknown) => () => void;
73
+
74
+ /**
75
+ * PCs allowed to talk to this plugin's ROS 2 nodes. ROS discovery is robot-local
76
+ * (ROS_AUTOMATIC_DISCOVERY_RANGE=LOCALHOST); Muscat keeps this list per plugin, starts
77
+ * the plugin with ROS_STATIC_PEERS set to it, and restarts the plugin when it changes.
78
+ */
79
+ rosPeers: {
80
+ get: () => Promise<string[]>;
81
+ /** Saves the list and restarts the plugin backend. Rejects on an invalid IP. */
82
+ set: (peers: string[]) => Promise<string[]>;
83
+ };
73
84
  }
74
85
 
75
86
  const noopSdk: RbSdk = {
@@ -107,6 +118,16 @@ const noopSdk: RbSdk = {
107
118
  },
108
119
  data: undefined,
109
120
  onSave: () => () => {},
121
+ rosPeers: {
122
+ get: async () => {
123
+ console.warn("[plugin-ui] rb.rosPeers called outside of a host context");
124
+ return [];
125
+ },
126
+ set: async () => {
127
+ console.warn("[plugin-ui] rb.rosPeers called outside of a host context");
128
+ return [];
129
+ },
130
+ },
110
131
  };
111
132
 
112
133
  export const RbContext = createContext<RbSdk>(noopSdk);
package/src/ws.ts CHANGED
@@ -40,6 +40,13 @@ export class RbWs {
40
40
  this._socket = socket;
41
41
  this._pluginId = pluginId;
42
42
 
43
+ // The gateway keeps subscriptions/serve registrations in memory — re-send them after
44
+ // a reconnect (e.g. the gateway restarted), or they silently stop working.
45
+ socket.io.on("reconnect", () => {
46
+ for (const topic of this._subHandlers.keys()) socket.emit("sub", { topic });
47
+ for (const topic of this._serveHandlers.keys()) socket.emit("serve_register", { topic });
48
+ });
49
+
43
50
  // Deliver incoming pub/sub messages to registered handlers
44
51
  socket.on(
45
52
  "msg",
@@ -67,6 +74,10 @@ export class RbWs {
67
74
  );
68
75
  }
69
76
 
77
+ get pluginId(): string {
78
+ return this._pluginId;
79
+ }
80
+
70
81
  static connect(url: string, pluginId: string): Promise<RbWs> {
71
82
  return new Promise((resolve, reject) => {
72
83
  const socket = io(url, { transports: ["websocket"] });
@@ -223,5 +234,22 @@ export function createLazyRbSdk(
223
234
  prompt: services.prompt,
224
235
  data: mount?.data,
225
236
  onSave: mount?.onSave ?? (() => () => {}),
237
+ rosPeers: rosPeersApi(async (op, payload) => {
238
+ const ws = await wsPromise;
239
+ return ws.action(`/muscat/plugin/${ws.pluginId}/ros_peers/${op}`, payload);
240
+ }),
241
+ };
242
+ }
243
+
244
+ // Answered by Muscat (common) on the robot and by `muscat-pk dev` locally.
245
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
246
+ function rosPeersApi(call: (op: "get" | "set", payload?: any) => Promise<any>) {
247
+ return {
248
+ get: async (): Promise<string[]> => (await call("get"))?.peers ?? [],
249
+ set: async (peers: string[]): Promise<string[]> => {
250
+ const r = await call("set", { peers });
251
+ if (!r?.ok) throw new Error(r?.error ?? "rb.rosPeers.set failed");
252
+ return r.peers;
253
+ },
226
254
  };
227
255
  }