dsh-aris-panel 0.2.7 → 0.2.8

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.
package/dsh/client.js CHANGED
@@ -1009,7 +1009,7 @@ function ArisWorkbenchView(props) {
1009
1009
  setError(String(cause))
1010
1010
  setData(undefined)
1011
1011
  }).finally(function () { if (!silent) setBusy(false) })
1012
- }, [connection, sessionId])
1012
+ }, [sessionId])
1013
1013
 
1014
1014
  React.useEffect(function () { load() }, [load])
1015
1015
 
package/dsh/http-rpc.mjs CHANGED
@@ -19,11 +19,11 @@
19
19
  *
20
20
  * - `connection.fetch` — exact-path Fetch registry under `/api`
21
21
  * (documented as `HostConnectionFetch.register(ConnectionFetchRoute)`).
22
- * Registration touches no service beyond Connection, so it mounts whatever
23
- * the Connection context injects. Serving is dsh's own `/api` prefix route,
24
- * which applies the Host/Origin trust fence and the browser-session cookie
25
- * before dispatching to any exact fetch route — the same authority the RPC
26
- * channel used to carry.
22
+ * Registration resolves nothing beyond the Connection service itself, so it
23
+ * mounts under whatever Connection context the host provides. Serving is
24
+ * dsh's own `/api` prefix route, which applies the Host/Origin trust fence
25
+ * and the browser-session cookie before dispatching to any exact fetch
26
+ * route — the same authority the RPC channel used to carry.
27
27
  *
28
28
  * ARIS therefore mounts one JSON endpoint per view on the Fetch registry. The
29
29
  * wire contract is deliberately tiny and mirrored in `dsh/client.js`:
@@ -121,8 +121,9 @@ export function httpRpcRoute(path, handler) {
121
121
  *
122
122
  * The caller supplies the effect and the injected services, because the two
123
123
  * ARIS views need different ones (`sessions` for both, `agents` and
124
- * `workspaceRegistry` for the workbench only). Registering touches no
125
- * `webServer` lookup, so it works on every shipped dsh build.
124
+ * `workspaceRegistry` for the workbench only). Registration resolves no
125
+ * `webServer`, which is what keeps the mount independent of the Connection
126
+ * context's inject list.
126
127
  *
127
128
  * @param ctx - the ARIS plugin context, which must have `connection` injected.
128
129
  * @param path - exact `/api/<segment>` route path the browser POSTs to.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-aris-panel",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "ARIS research-workflow skills + interactive workbench panel for DeepSeek Harness (user fork of dsh-aris: 83 skills, bundled upstream dsh-aris 0.1.1 payload, ppt-master PPTX backend for the talk pipeline, cross-model Codex review, idea/workflow launcher, research status board).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -8,7 +8,7 @@
8
8
  "node": "^22.19.0 || >=24.0.0"
9
9
  },
10
10
  "scripts": {
11
- "test": "node test/transport-smoke.mjs"
11
+ "test": "node test/transport-smoke.mjs && node test/client-render-smoke.mjs"
12
12
  },
13
13
  "keywords": [
14
14
  "dsh-plugin",
@@ -0,0 +1,131 @@
1
+ /**
2
+ * ARIS browser-half render smoke test.
3
+ *
4
+ * node test/client-render-smoke.mjs
5
+ *
6
+ * Why this exists: `test/transport-smoke.mjs` proves the host half mounts, but
7
+ * dsh/client.js is only ever executed by a browser, so a client-side error
8
+ * (an undefined identifier left behind by a refactor, say) reaches the user as
9
+ * "the panel does not open" with no failing test. This file executes the real
10
+ * bundle in Node against a minimal React/element renderer and walks every
11
+ * returned component, so ReferenceErrors and typos in component bodies or hook
12
+ * dependency arrays fail here instead.
13
+ *
14
+ * It is deliberately a stub renderer, not React: components are called and
15
+ * their element trees walked, hooks run eagerly, and network calls are answered
16
+ * with empty payloads. That catches crashes, not behaviour.
17
+ */
18
+
19
+ import { pathToFileURL } from 'node:url'
20
+
21
+ let failures = 0
22
+ function check(label, condition, detail) {
23
+ if (condition) {
24
+ console.log(` PASS ${label}`)
25
+ return
26
+ }
27
+ failures += 1
28
+ console.log(` FAIL ${label}${detail === undefined ? '' : ` — ${detail}`}`)
29
+ }
30
+
31
+ /** Collect anything a hook or effect throws instead of letting it escape. */
32
+ const crashes = []
33
+ process.on('unhandledRejection', (reason) => { crashes.push(reason) })
34
+
35
+ console.log('\n1. the bundle loads the way dsh client-modules loads it')
36
+ let registration
37
+ let documentState
38
+ {
39
+ const styleNodes = []
40
+ documentState = { styleNodes }
41
+ globalThis.window = {
42
+ __ModuleLoader__: {
43
+ load(module) { registration = module },
44
+ },
45
+ }
46
+ globalThis.document = {
47
+ createElement() {
48
+ const node = { textContent: '', removed: false, remove() { node.removed = true } }
49
+ styleNodes.push(node)
50
+ return node
51
+ },
52
+ head: { append() {} },
53
+ }
54
+ // The views answer every call with an empty successful payload.
55
+ globalThis.fetch = async () => ({ status: 200, json: async () => ({ ok: true, value: {} }) })
56
+
57
+ await import(new URL('../dsh/client.js', import.meta.url).href)
58
+ check('the bundle registers one module', registration !== undefined && registration.id === 'dsh-aris-panel',
59
+ registration === undefined ? 'window.__ModuleLoader__.load was never called' : String(registration.id))
60
+ }
61
+
62
+ console.log('\n2. every component renders without crashing')
63
+ let rendered
64
+ {
65
+ /** Minimal React: enough for createElement + the hooks this bundle uses. */
66
+ const React = {
67
+ createElement(type, props, ...children) {
68
+ return { type, props: { ...(props ?? {}), children: children.flat() } }
69
+ },
70
+ useState(initial) { return [typeof initial === 'function' ? initial() : initial, () => {}] },
71
+ useEffect(run) { run() },
72
+ useCallback(fn) { return fn },
73
+ useRef(value) { return { current: value } },
74
+ }
75
+ const requireStub = (name) => {
76
+ if (name === 'react') return React
77
+ throw new Error(`client bundle required an unexpected module: ${name}`)
78
+ }
79
+
80
+ const plugin = registration.factory(requireStub)
81
+ check('the factory returns a plugin with an inject list', Array.isArray(plugin.inject), JSON.stringify(plugin.inject))
82
+ check('the plugin declares the slot service', plugin.inject.includes('slots'))
83
+
84
+ let render
85
+ const ctx = {
86
+ connection: {},
87
+ effect(run) { return run() },
88
+ slots: {
89
+ inject(name, registerCall) {
90
+ check('the plugin injects the conversation view slot', name === 'conversation.view', name)
91
+ registerCall()
92
+ },
93
+ register(spec, renderCall) {
94
+ check('the ARIS slot registers itself', spec.id === 'aris' && spec.name === 'conversation.view', JSON.stringify(spec))
95
+ render = renderCall
96
+ },
97
+ },
98
+ }
99
+ plugin.apply(ctx)
100
+ check('the slot exposes a render function', typeof render === 'function')
101
+
102
+ /** Call function components and walk host elements, like React would. */
103
+ let elements = 0
104
+ const walk = (node, depth = 0) => {
105
+ if (node === null || node === undefined || typeof node === 'boolean' || typeof node === 'string' || typeof node === 'number') return
106
+ if (Array.isArray(node)) { for (const child of node) walk(child, depth); return }
107
+ if (depth > 40) throw new Error('render tree did not settle')
108
+ elements += 1
109
+ if (typeof node.type === 'function') { walk(node.type(node.props ?? {}), depth + 1); return }
110
+ walk(node.props?.children, depth + 1)
111
+ }
112
+
113
+ try {
114
+ rendered = render({ sessionId: 'render-smoke', openView: undefined })
115
+ walk(rendered)
116
+ check('the ARIS tab renders a tree', elements > 5, `${elements} elements`)
117
+ } catch (error) {
118
+ check('the ARIS tab renders a tree', false, `${error.name}: ${error.message}`)
119
+ }
120
+ }
121
+
122
+ console.log('\n3. the stylesheet effect is wired')
123
+ check('one style node was created', documentState.styleNodes.length === 1, String(documentState.styleNodes.length))
124
+
125
+ const asyncCrashes = crashes.filter((reason) => reason !== undefined)
126
+ check('no asynchronous crash escaped the views', asyncCrashes.length === 0, asyncCrashes.map(String).join('; '))
127
+ check('the module left no uncaught error', rendered !== undefined)
128
+
129
+ console.log(failures === 0 ? '\nOK — the browser half renders' : `\n${failures} check(s) failed`)
130
+ await new Promise((resolve) => setTimeout(resolve, 20))
131
+ process.exit(failures === 0 && crashes.length === 0 ? 0 : 1)