@consciousclouds/canvas-runtime 0.2.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,33 @@
1
+ # Changelog — @consciousclouds/canvas-runtime
2
+
3
+ Versioned on the independent frontend-SDK lifecycle (NOT `contract_version`).
4
+
5
+ ## [0.2.0] — 2026-06-09
6
+
7
+ ### Added
8
+
9
+ - Renderer-level permission filtering. New optional `permissions?: string[]`
10
+ prop on `CanvasRenderer`: when provided (even `[]`), a node with a
11
+ `requiredPermission` is dropped unless that permission — or `*` — is present.
12
+ Permission-filtered nodes are removed entirely (no placeholder), distinct
13
+ from the unknown/removed fail-closed placeholder. Enforcement now travels
14
+ with the package, so every consumer (ENO, Omni, CloudChief, BWC, TT) gets it
15
+ by default.
16
+ - Exported `isNodePermitted(node, permissions)` predicate.
17
+
18
+ ### Backward compatibility
19
+
20
+ - Omitting `permissions` leaves filtering **not engaged** — all nodes render,
21
+ identical to 0.1.0. No change for existing consumers.
22
+
23
+ ## [0.1.0] — 2026-06-09
24
+
25
+ ### Added
26
+
27
+ - `CanvasRenderer` — renders a `CanvasDefinition` by mapping each node onto a
28
+ consumer-supplied component (`componentMap`). Product-neutral: the consumer
29
+ (ENO, CloudChief) supplies its own component implementations.
30
+ - Fail-closed behavior: an unknown / `removed` / unmapped component renders an
31
+ explicit `UnsupportedComponent` placeholder — never arbitrary markup.
32
+ - Deprecation signalling for `deprecated` registry entries (`onDeprecated`).
33
+ - Action dispatch via an `onAction` callback (never navigates directly).
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # @consciousclouds/canvas-runtime
2
+
3
+ React renderer for the Intelligence Runtime **Canvas** layer. It takes a
4
+ `CanvasDefinition` and a **consumer-supplied `componentMap`** and renders each
5
+ node with the consumer's own components.
6
+
7
+ It is **product-neutral**: the renderer never imports a design system. Each
8
+ consuming app maps canvas component-ids to its own components. This keeps the
9
+ renderer publishable and keeps "AI selects, cannot invent UI" structural — any
10
+ component id not in the approved registry (or not in the map) renders an
11
+ explicit placeholder, never arbitrary markup.
12
+
13
+ ```tsx
14
+ import { CanvasRenderer } from '@consciousclouds/canvas-runtime';
15
+ // Supply your own components for each approved component id:
16
+ import { Stat, Table } from './your-design-system';
17
+
18
+ <CanvasRenderer
19
+ definition={canvas}
20
+ componentMap={{
21
+ 'stat-card': ({ node }) => <Stat {...node.props} />,
22
+ 'data-table': ({ node }) => <Table {...node.props} />,
23
+ }}
24
+ onAction={(action, node) => dispatch(action)}
25
+ />
26
+ ```
27
+
28
+ Data bindings are resolved **server-side** under the authenticated context;
29
+ the renderer receives already-authorized, already-bound values in `node.props`
30
+ and never re-fetches.
@@ -0,0 +1,35 @@
1
+ import * as React from 'react';
2
+ import type { CanvasDefinition, CanvasNode, ComponentRegistry } from '@consciousclouds/canvas-sdk';
3
+ import type { ComponentMap, ActionHandler } from './types.js';
4
+ export interface CanvasRendererProps {
5
+ definition: CanvasDefinition;
6
+ /** Consumer-supplied component implementations, keyed by approved component id. */
7
+ componentMap: ComponentMap;
8
+ /** Receives dispatched actions (the renderer never navigates directly). */
9
+ onAction?: ActionHandler;
10
+ /** Override the approved-component registry (defaults to the SDK allowlist). */
11
+ registry?: ComponentRegistry;
12
+ /** Notified when a rendered node uses a deprecated component. */
13
+ onDeprecated?: (type: string) => void;
14
+ /**
15
+ * Caller's permissions. When provided (even an empty array), any node with a
16
+ * `requiredPermission` is dropped unless that permission — or the `*`
17
+ * wildcard — is present. When OMITTED (undefined), permission filtering is
18
+ * not engaged and all nodes render (backward compatible). Permission-filtered
19
+ * nodes are removed entirely (no placeholder), distinct from the
20
+ * unknown/removed fail-closed placeholder.
21
+ */
22
+ permissions?: string[];
23
+ }
24
+ /**
25
+ * Whether a node passes permission filtering. Travels with the renderer so
26
+ * every consumer (ENO, Omni, CloudChief, BWC, TT) gets identical enforcement.
27
+ *
28
+ * - node without `requiredPermission` → permitted
29
+ * - `permissions` undefined → not engaged → permitted (backward compatible)
30
+ * - `*` wildcard or exact match → permitted
31
+ * - otherwise → not permitted (the node is dropped)
32
+ */
33
+ export declare function isNodePermitted(node: Pick<CanvasNode, 'requiredPermission'>, permissions: string[] | undefined): boolean;
34
+ export declare function CanvasRenderer(props: CanvasRendererProps): React.ReactElement;
35
+ //# sourceMappingURL=CanvasRenderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CanvasRenderer.d.ts","sourceRoot":"","sources":["../src/CanvasRenderer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAOnG,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE9D,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,gBAAgB,CAAC;IAC7B,mFAAmF;IACnF,YAAY,EAAE,YAAY,CAAC;IAC3B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,iEAAiE;IACjE,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,oBAAoB,CAAC,EAC5C,WAAW,EAAE,MAAM,EAAE,GAAG,SAAS,GAChC,OAAO,CAKT;AAaD,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,KAAK,CAAC,YAAY,CAqB7E"}
@@ -0,0 +1,55 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { COMPONENT_REGISTRY, isApprovedComponent, isDeprecatedComponent, } from '@consciousclouds/canvas-sdk';
3
+ import { UnsupportedComponent } from './UnsupportedComponent.js';
4
+ /**
5
+ * Whether a node passes permission filtering. Travels with the renderer so
6
+ * every consumer (ENO, Omni, CloudChief, BWC, TT) gets identical enforcement.
7
+ *
8
+ * - node without `requiredPermission` → permitted
9
+ * - `permissions` undefined → not engaged → permitted (backward compatible)
10
+ * - `*` wildcard or exact match → permitted
11
+ * - otherwise → not permitted (the node is dropped)
12
+ */
13
+ export function isNodePermitted(node, permissions) {
14
+ if (!node.requiredPermission)
15
+ return true;
16
+ if (permissions === undefined)
17
+ return true;
18
+ if (permissions.includes('*'))
19
+ return true;
20
+ return permissions.includes(node.requiredPermission);
21
+ }
22
+ function layoutStyle(layout) {
23
+ if (layout.type === 'grid') {
24
+ return {
25
+ display: 'grid',
26
+ gridTemplateColumns: `repeat(${layout.columns ?? 1}, minmax(0, 1fr))`,
27
+ gap: 12,
28
+ };
29
+ }
30
+ return { display: 'flex', flexDirection: 'column', gap: 12 };
31
+ }
32
+ export function CanvasRenderer(props) {
33
+ const { definition, componentMap, onAction, registry = COMPONENT_REGISTRY, onDeprecated, permissions, } = props;
34
+ return (_jsx("div", { "data-canvas-version": definition.version, "data-correlation-id": definition.correlationId, style: layoutStyle(definition.layout), children: definition.nodes
35
+ .filter((node) => isNodePermitted(node, permissions))
36
+ .map((node) => renderNode(node, componentMap, registry, onAction, onDeprecated)) }));
37
+ }
38
+ function renderNode(node, componentMap, registry, onAction, onDeprecated) {
39
+ // Fail closed: unknown or `removed` component id → placeholder, never markup.
40
+ if (!isApprovedComponent(node.type, registry)) {
41
+ return _jsx(UnsupportedComponent, { type: node.type, reason: "not in approved registry" }, node.id);
42
+ }
43
+ const Component = componentMap[node.type];
44
+ if (!Component) {
45
+ return _jsx(UnsupportedComponent, { type: node.type, reason: "no component supplied" }, node.id);
46
+ }
47
+ if (isDeprecatedComponent(node.type, registry)) {
48
+ onDeprecated?.(node.type);
49
+ if (typeof console !== 'undefined') {
50
+ console.warn(`[canvas] component '${node.type}' is deprecated`);
51
+ }
52
+ }
53
+ return _jsx(Component, { node: node, onAction: onAction }, node.id);
54
+ }
55
+ //# sourceMappingURL=CanvasRenderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CanvasRenderer.js","sourceRoot":"","sources":["../src/CanvasRenderer.tsx"],"names":[],"mappings":";AAEA,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAwBjE;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAC7B,IAA4C,EAC5C,WAAiC;IAEjC,IAAI,CAAC,IAAI,CAAC,kBAAkB;QAAE,OAAO,IAAI,CAAC;IAC1C,IAAI,WAAW,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAC3C,IAAI,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3C,OAAO,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;AACvD,CAAC;AAED,SAAS,WAAW,CAAC,MAAkC;IACrD,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC3B,OAAO;YACL,OAAO,EAAE,MAAM;YACf,mBAAmB,EAAE,UAAU,MAAM,CAAC,OAAO,IAAI,CAAC,mBAAmB;YACrE,GAAG,EAAE,EAAE;SACR,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAA0B;IACvD,MAAM,EACJ,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,QAAQ,GAAG,kBAAkB,EAC7B,YAAY,EACZ,WAAW,GACZ,GAAG,KAAK,CAAC;IAEV,OAAO,CACL,qCACuB,UAAU,CAAC,OAAO,yBAClB,UAAU,CAAC,aAAa,EAC7C,KAAK,EAAE,WAAW,CAAC,UAAU,CAAC,MAAM,CAAC,YAEpC,UAAU,CAAC,KAAK;aACd,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;aACpD,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC,GAC9E,CACP,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CACjB,IAAgB,EAChB,YAA0B,EAC1B,QAA2B,EAC3B,QAAmC,EACnC,YAAkD;IAElD,8EAA8E;IAC9E,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC9C,OAAO,KAAC,oBAAoB,IAAe,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAC,0BAA0B,IAA3D,IAAI,CAAC,EAAE,CAAuD,CAAC;IACnG,CAAC;IAED,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,KAAC,oBAAoB,IAAe,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAC,uBAAuB,IAAxD,IAAI,CAAC,EAAE,CAAoD,CAAC;IAChG,CAAC;IAED,IAAI,qBAAqB,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC/C,YAAY,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1B,IAAI,OAAO,OAAO,KAAK,WAAW,EAAE,CAAC;YACnC,OAAO,CAAC,IAAI,CAAC,uBAAuB,IAAI,CAAC,IAAI,iBAAiB,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;IAED,OAAO,KAAC,SAAS,IAAe,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,IAAvC,IAAI,CAAC,EAAE,CAAoC,CAAC;AACrE,CAAC"}
@@ -0,0 +1,13 @@
1
+ import * as React from 'react';
2
+ export interface UnsupportedComponentProps {
3
+ type: string;
4
+ reason?: string;
5
+ }
6
+ /**
7
+ * The fail-closed placeholder. Rendered for any component id that is not in
8
+ * the approved registry, is `removed`, or has no implementation in the
9
+ * component map. It is deliberately inert — never arbitrary markup, never the
10
+ * unknown component.
11
+ */
12
+ export declare function UnsupportedComponent({ type, reason }: UnsupportedComponentProps): React.ReactElement;
13
+ //# sourceMappingURL=UnsupportedComponent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UnsupportedComponent.d.ts","sourceRoot":"","sources":["../src/UnsupportedComponent.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,yBAAyB,GAAG,KAAK,CAAC,YAAY,CAiBpG"}
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * The fail-closed placeholder. Rendered for any component id that is not in
4
+ * the approved registry, is `removed`, or has no implementation in the
5
+ * component map. It is deliberately inert — never arbitrary markup, never the
6
+ * unknown component.
7
+ */
8
+ export function UnsupportedComponent({ type, reason }) {
9
+ return (_jsxs("div", { role: "note", "data-canvas-unsupported": type, style: {
10
+ padding: 8,
11
+ border: '1px dashed #c0392b',
12
+ color: '#c0392b',
13
+ fontSize: 12,
14
+ borderRadius: 6,
15
+ }, children: ["Unsupported component: ", _jsx("code", { children: type }), reason ? ` — ${reason}` : ''] }));
16
+ }
17
+ //# sourceMappingURL=UnsupportedComponent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UnsupportedComponent.js","sourceRoot":"","sources":["../src/UnsupportedComponent.tsx"],"names":[],"mappings":";AAOA;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,EAAE,IAAI,EAAE,MAAM,EAA6B;IAC9E,OAAO,CACL,eACE,IAAI,EAAC,MAAM,6BACc,IAAI,EAC7B,KAAK,EAAE;YACL,OAAO,EAAE,CAAC;YACV,MAAM,EAAE,oBAAoB;YAC5B,KAAK,EAAE,SAAS;YAChB,QAAQ,EAAE,EAAE;YACZ,YAAY,EAAE,CAAC;SAChB,wCAEsB,yBAAO,IAAI,GAAQ,EACzC,MAAM,CAAC,CAAC,CAAC,MAAM,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,IACzB,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { CanvasRenderer, isNodePermitted } from './CanvasRenderer.js';
2
+ export type { CanvasRendererProps } from './CanvasRenderer.js';
3
+ export { UnsupportedComponent } from './UnsupportedComponent.js';
4
+ export type { UnsupportedComponentProps } from './UnsupportedComponent.js';
5
+ export type { ComponentMap, CanvasComponentProps, ActionHandler } from './types.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtE,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,YAAY,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAC3E,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { CanvasRenderer, isNodePermitted } from './CanvasRenderer.js';
2
+ export { UnsupportedComponent } from './UnsupportedComponent.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { ComponentType } from 'react';
2
+ import type { CanvasNode, CanvasNodeType, ActionDef } from '@consciousclouds/canvas-sdk';
3
+ /** Called when a node dispatches an action. Never navigates directly. */
4
+ export type ActionHandler = (action: ActionDef, node: CanvasNode) => void;
5
+ /** Props passed to every consumer-supplied canvas component. */
6
+ export interface CanvasComponentProps {
7
+ node: CanvasNode;
8
+ onAction?: ActionHandler;
9
+ }
10
+ /**
11
+ * Consumer-supplied implementations for approved component ids. The renderer
12
+ * fails closed for any id missing from this map (or from the registry).
13
+ */
14
+ export type ComponentMap = Partial<Record<CanvasNodeType, ComponentType<CanvasComponentProps>>>;
15
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAEzF,yEAAyE;AACzE,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;AAE1E,gEAAgE;AAChE,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,aAAa,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@consciousclouds/canvas-runtime",
3
+ "version": "0.2.0",
4
+ "description": "Conscious Clouds Canvas renderer — maps a CanvasDefinition onto consumer-supplied React components from the approved Component Registry. Fails closed on unknown/removed/unmapped components. Product-neutral (the component map is injected).",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": ["dist", "README.md", "CHANGELOG.md", "LICENSE"],
15
+ "scripts": {
16
+ "build": "tsc -p tsconfig.build.json",
17
+ "clean": "rm -rf dist",
18
+ "type-check": "tsc --noEmit",
19
+ "test": "bun test",
20
+ "prepublishOnly": "bun run clean && bun run build && bun run type-check"
21
+ },
22
+ "dependencies": {
23
+ "@consciousclouds/canvas-sdk": "0.2.0"
24
+ },
25
+ "peerDependencies": {
26
+ "react": ">=18.0.0"
27
+ },
28
+ "devDependencies": {
29
+ "@types/react": "^19.2.7",
30
+ "@types/react-dom": "^19.2.1",
31
+ "react": "19.2.4",
32
+ "react-dom": "19.2.4",
33
+ "typescript": "^5.7.2"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "engines": {
39
+ "bun": ">=1.0.0",
40
+ "node": ">=18.0.0"
41
+ }
42
+ }