@theaiplatform/miniapp-sdk 0.0.0 → 0.0.2

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.
@@ -0,0 +1,190 @@
1
+ //! Capability: miniapp-platform
2
+ //!
3
+ //! Clean wasm-bindgen consumer of `@theaiplatform/miniapp-sdk/ui/wasm`.
4
+
5
+ use std::{
6
+ cell::{Cell, RefCell},
7
+ collections::HashSet,
8
+ rc::Rc,
9
+ };
10
+
11
+ use js_sys::Function;
12
+ use serde::Deserialize;
13
+ use serde_json::{Value, json};
14
+ use wasm_bindgen::{JsCast, prelude::*};
15
+ use web_sys::Element;
16
+
17
+ #[wasm_bindgen(module = "@theaiplatform/miniapp-sdk/ui/wasm")]
18
+ extern "C" {
19
+ type JsMiniAppUiRoot;
20
+
21
+ #[wasm_bindgen(js_name = createMiniAppUiRoot, catch)]
22
+ fn create_miniapp_ui_root(
23
+ container: &Element,
24
+ model: &JsValue,
25
+ dispatch: &Function,
26
+ ) -> Result<JsMiniAppUiRoot, JsValue>;
27
+
28
+ #[wasm_bindgen(method, catch)]
29
+ fn update(this: &JsMiniAppUiRoot, model: &JsValue) -> Result<(), JsValue>;
30
+
31
+ #[wasm_bindgen(method, catch)]
32
+ fn focus(this: &JsMiniAppUiRoot, control_id: &str) -> Result<(), JsValue>;
33
+
34
+ #[wasm_bindgen(method, catch)]
35
+ fn unmount(this: &JsMiniAppUiRoot) -> Result<(), JsValue>;
36
+ }
37
+
38
+ #[derive(Deserialize)]
39
+ #[serde(rename_all = "camelCase")]
40
+ struct UiAction {
41
+ event_id: String,
42
+ revision: u64,
43
+ control_id: String,
44
+ action: String,
45
+ }
46
+
47
+ fn js_error(message: impl AsRef<str>) -> JsValue {
48
+ js_sys::Error::new(message.as_ref()).into()
49
+ }
50
+
51
+ fn model(revision: u64, name: &str, busy: bool) -> Value {
52
+ json!({
53
+ "revision": revision,
54
+ "state": if busy { "loading" } else { "idle" },
55
+ "rootIds": ["layout"],
56
+ "nodes": [
57
+ {
58
+ "type": "stack",
59
+ "id": "layout",
60
+ "gap": "md",
61
+ "children": ["title", "name", "run", "status"]
62
+ },
63
+ {
64
+ "type": "heading",
65
+ "id": "title",
66
+ "level": 1,
67
+ "text": "Rust/WASM mini-app"
68
+ },
69
+ {
70
+ "type": "input",
71
+ "id": "name",
72
+ "label": "Name",
73
+ "value": name,
74
+ "action": "name.change",
75
+ "required": true,
76
+ "disabled": false,
77
+ "busy": false,
78
+ "inputType": "text"
79
+ },
80
+ {
81
+ "type": "button",
82
+ "id": "run",
83
+ "label": if busy { "Running…" } else { "Run" },
84
+ "action": "run",
85
+ "variant": "default",
86
+ "disabled": busy,
87
+ "busy": busy
88
+ },
89
+ {
90
+ "type": "status-bar",
91
+ "id": "status",
92
+ "tone": "neutral",
93
+ "text": "All UI state is controlled by Rust."
94
+ }
95
+ ]
96
+ })
97
+ }
98
+
99
+ /// Mounted SDK UI retained by Rust. The closure field keeps the JS dispatcher
100
+ /// alive for exactly the lifetime of the imperative root.
101
+ #[wasm_bindgen]
102
+ pub struct MiniApp {
103
+ root: Option<JsMiniAppUiRoot>,
104
+ _dispatch: Closure<dyn FnMut(JsValue) -> Result<(), JsValue>>,
105
+ revision: Rc<Cell<u64>>,
106
+ }
107
+
108
+ #[wasm_bindgen]
109
+ impl MiniApp {
110
+ /// Mounts the initial controlled model and installs replay/stale checks on
111
+ /// every action delivered back from JavaScript.
112
+ pub fn mount(container: Element) -> Result<MiniApp, JsValue> {
113
+ let revision = Rc::new(Cell::new(1_u64));
114
+ let dispatch_revision = Rc::clone(&revision);
115
+ let received_event_ids = Rc::new(RefCell::new(HashSet::<String>::new()));
116
+ let dispatch_event_ids = Rc::clone(&received_event_ids);
117
+ let dispatch =
118
+ Closure::<dyn FnMut(JsValue) -> Result<(), JsValue>>::new(move |value: JsValue| {
119
+ let action: UiAction = serde_wasm_bindgen::from_value(value)
120
+ .map_err(|error| js_error(format!("Malformed SDK UI action: {error}")))?;
121
+ if action.revision != dispatch_revision.get() {
122
+ return Err(js_error(format!(
123
+ "Rejected stale action revision {} for current revision {}",
124
+ action.revision,
125
+ dispatch_revision.get()
126
+ )));
127
+ }
128
+ if !dispatch_event_ids
129
+ .borrow_mut()
130
+ .insert(action.event_id.clone())
131
+ {
132
+ return Err(js_error(format!("Rejected replayed action {}", action.event_id)));
133
+ }
134
+
135
+ // A real mini-app routes the validated action into its Rust
136
+ // reducer. Reading these fields demonstrates that no React
137
+ // callback or host object crosses the ABI.
138
+ let _action_target = (&action.control_id, &action.action);
139
+ Ok(())
140
+ });
141
+ let initial_model = serde_wasm_bindgen::to_value(&model(1, "Ada", false))
142
+ .map_err(|error| js_error(format!("Could not serialize initial model: {error}")))?;
143
+ let root =
144
+ create_miniapp_ui_root(&container, &initial_model, dispatch.as_ref().unchecked_ref())?;
145
+
146
+ Ok(MiniApp {
147
+ root: Some(root),
148
+ _dispatch: dispatch,
149
+ revision,
150
+ })
151
+ }
152
+
153
+ /// Replaces controlled state with a newer model revision.
154
+ pub fn update(&self, revision: u64, name: &str, busy: bool) -> Result<(), JsValue> {
155
+ let root = self
156
+ .root
157
+ .as_ref()
158
+ .ok_or_else(|| js_error("The mini-app UI is unmounted"))?;
159
+ let next_model = serde_wasm_bindgen::to_value(&model(revision, name, busy))
160
+ .map_err(|error| js_error(format!("Could not serialize updated model: {error}")))?;
161
+ root.update(&next_model)?;
162
+ self.revision.set(revision);
163
+ Ok(())
164
+ }
165
+
166
+ /// Focuses a stable control identifier and maps JavaScript exceptions into
167
+ /// a normal Rust `Result`.
168
+ pub fn focus(&self, control_id: &str) -> Result<(), JsValue> {
169
+ self.root
170
+ .as_ref()
171
+ .ok_or_else(|| js_error("The mini-app UI is unmounted"))?
172
+ .focus(control_id)
173
+ }
174
+
175
+ /// Releases the UI root. Repeated calls are intentionally harmless.
176
+ pub fn unmount(&mut self) -> Result<(), JsValue> {
177
+ if let Some(root) = self.root.take() {
178
+ root.unmount()?;
179
+ }
180
+ Ok(())
181
+ }
182
+ }
183
+
184
+ impl Drop for MiniApp {
185
+ fn drop(&mut self) {
186
+ if let Some(root) = self.root.take() {
187
+ let _ = root.unmount();
188
+ }
189
+ }
190
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theaiplatform/miniapp-sdk",
3
- "version": "0.0.0",
3
+ "version": "0.0.2",
4
4
  "description": "Public SDK for building portable miniapps that run in The AI Platform.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -23,10 +23,25 @@
23
23
  "types": "./dist/sdk.d.ts",
24
24
  "import": "./dist/sdk.js"
25
25
  },
26
+ "./mcp": {
27
+ "types": "./dist/mcp.d.ts",
28
+ "import": "./dist/mcp.js"
29
+ },
26
30
  "./web": {
27
31
  "types": "./dist/web.d.ts",
28
32
  "import": "./dist/web.js"
29
33
  },
34
+ "./ui": {
35
+ "types": "./dist/ui.d.ts",
36
+ "import": "./dist/ui.js"
37
+ },
38
+ "./ui/wasm": {
39
+ "types": "./dist/ui/wasm.d.ts",
40
+ "import": "./dist/ui/wasm.js"
41
+ },
42
+ "./ui/styles.css": "./dist/ui/styles.css",
43
+ "./ui/tailwind.css": "./dist/ui/tailwind.css",
44
+ "./ui-components.json": "./ui-components.json",
30
45
  "./surface": {
31
46
  "types": "./dist/surface.d.ts",
32
47
  "import": "./dist/surface.js"
@@ -45,7 +60,9 @@
45
60
  "types": "./dist/index.d.ts",
46
61
  "files": [
47
62
  "dist",
63
+ "examples",
48
64
  "config-schema.json",
65
+ "ui-components.json",
49
66
  "README.md",
50
67
  "LICENSE.md",
51
68
  "THIRD_PARTY_NOTICES.md"
@@ -97,15 +114,47 @@
97
114
  "@rsbuild/core": "^2.1.4",
98
115
  "@rslib/core": "^0.23.2",
99
116
  "@rstest/core": "^0.9.10",
117
+ "@tailwindcss/postcss": "^4.3.0",
118
+ "@types/jsdom": "^28.0.3",
100
119
  "@types/node": "^25.7.0",
120
+ "@types/react": "19.2.14",
121
+ "@types/react-dom": "19.2.3",
122
+ "jsdom": "^29.1.1",
123
+ "postcss": "^8.5.6",
124
+ "tailwindcss": "^4.3.0",
125
+ "tw-animate-css": "^1.4.0",
101
126
  "typescript": "npm:@typescript/typescript6@6.0.2"
102
127
  },
103
128
  "dependencies": {
129
+ "@radix-ui/react-alert-dialog": "^1.1.15",
130
+ "@radix-ui/react-avatar": "1.1.11",
131
+ "@radix-ui/react-checkbox": "^1.3.3",
132
+ "@radix-ui/react-compose-refs": "^1.1.2",
133
+ "@radix-ui/react-dialog": "^1.1.15",
134
+ "@radix-ui/react-label": "^2.1.8",
135
+ "@radix-ui/react-progress": "^1.1.8",
136
+ "@radix-ui/react-radio-group": "^1.3.8",
137
+ "@radix-ui/react-scroll-area": "^1.2.10",
138
+ "@radix-ui/react-select": "^2.2.6",
139
+ "@radix-ui/react-separator": "^1.1.8",
140
+ "@radix-ui/react-slider": "^1.3.6",
141
+ "@radix-ui/react-slot": "^1.2.4",
142
+ "@radix-ui/react-tabs": "^1.1.13",
143
+ "@radix-ui/react-toggle": "^1.1.10",
144
+ "@radix-ui/react-toggle-group": "^1.1.11",
145
+ "@radix-ui/react-tooltip": "^1.2.8",
104
146
  "ajv": "^8.20.0",
105
- "saxes": "^6.0.0"
147
+ "class-variance-authority": "^0.7.1",
148
+ "clsx": "^2.1.1",
149
+ "lucide-react": "^1.14.0",
150
+ "prism-react-renderer": "^2.4.1",
151
+ "react-resizable-panels": "^4.11.1",
152
+ "saxes": "^6.0.0",
153
+ "tailwind-merge": "^3.6.0",
154
+ "zod": "^4.4.3"
106
155
  },
107
156
  "scripts": {
108
- "build": "rm -rf dist && rslib build && node scripts/copy-rspack-loaders.mjs",
157
+ "build": "rm -rf dist && rslib build && node scripts/copy-rspack-loaders.mjs && node scripts/build-ui-styles.mjs",
109
158
  "test": "rstest run",
110
159
  "typecheck": "tsc --noEmit"
111
160
  },
@@ -0,0 +1,64 @@
1
+ {
2
+ "version": 1,
3
+ "package": "@theaiplatform/miniapp-sdk",
4
+ "import": "@theaiplatform/miniapp-sdk/ui",
5
+ "styles": {
6
+ "precompiled": "@theaiplatform/miniapp-sdk/ui/styles.css",
7
+ "tailwindV4": "@theaiplatform/miniapp-sdk/ui/tailwind.css"
8
+ },
9
+ "groups": {
10
+ "foundations": [
11
+ "Avatar",
12
+ "Badge",
13
+ "Button",
14
+ "ButtonGroup",
15
+ "H1",
16
+ "H2",
17
+ "H3",
18
+ "H4",
19
+ "H5",
20
+ "H6",
21
+ "Icon",
22
+ "Progress",
23
+ "Separator",
24
+ "Skeleton",
25
+ "StatusIndicator"
26
+ ],
27
+ "layout": [
28
+ "Alert",
29
+ "Card",
30
+ "Empty",
31
+ "Item",
32
+ "ResizablePanelGroup",
33
+ "ScrollArea",
34
+ "Table",
35
+ "Tabs"
36
+ ],
37
+ "forms": [
38
+ "Checkbox",
39
+ "Field",
40
+ "Input",
41
+ "InputGroup",
42
+ "Label",
43
+ "NativeSelect",
44
+ "RadioCard",
45
+ "RadioGroup",
46
+ "Select",
47
+ "Slider",
48
+ "Textarea",
49
+ "Toggle",
50
+ "ToggleGroup"
51
+ ],
52
+ "overlays": ["AlertDialog", "Dialog", "Sheet", "Tooltip"],
53
+ "miniapp": [
54
+ "CodeBlock",
55
+ "MiniAppIconButton",
56
+ "MiniAppMetric",
57
+ "MiniAppPageHeader",
58
+ "MiniAppSectionHeader",
59
+ "MiniAppStatusBar",
60
+ "MiniAppToolbar"
61
+ ],
62
+ "utilities": ["cn"]
63
+ }
64
+ }