diff --git a/Dockerfile.web-rust b/Dockerfile.web-rust index 288162491..e25d33654 100644 --- a/Dockerfile.web-rust +++ b/Dockerfile.web-rust @@ -79,7 +79,8 @@ RUN cargo build -p op-host-web-server --release # from Cargo.lock so the CLI matches the linked runtime. RUN if [ "$WEB_BUNDLE_SOURCE" = "build" ]; then \ rustup target add wasm32-unknown-unknown && \ - version="$(awk '/^name = \"wasm-bindgen\"$/{found=1; next} found && /^version = /{gsub(/[\" ]/,\"\",$3); print $3; exit}' Cargo.lock)" && \ + version="$(awk '/^name = "wasm-bindgen"$/{found=1; next} found && /^version = /{gsub(/[" ]/,"",$3); print $3; exit}' Cargo.lock)" && \ + if [ -z "$version" ]; then echo "could not resolve wasm-bindgen version from Cargo.lock" >&2; exit 2; fi && \ cargo install wasm-bindgen-cli --version "$version" --locked && \ bash tools/check-wasm-bundle.sh ; \ else \ diff --git a/packages/bun.lock b/packages/bun.lock index f66e8887f..f36648fb6 100644 --- a/packages/bun.lock +++ b/packages/bun.lock @@ -30,6 +30,8 @@ }, "devDependencies": { "@testing-library/react": "^16.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", "jsdom": "^25.0.0", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -299,6 +301,10 @@ "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + "@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="], + + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + "@vitejs/plugin-vue": ["@vitejs/plugin-vue@5.2.4", "", { "peerDependencies": { "vite": "^5.0.0 || ^6.0.0", "vue": "^3.2.25" } }, "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA=="], "@vitest/expect": ["@vitest/expect@3.2.6", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.6", "@vitest/utils": "3.2.6", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-1+7q9BtaKzEmO+fmNT3kYvoNn5Y71XWAx2Q5HRim4tTVRQVRv4uJFAQ5FbK0OPUeNP/WmVCpxYxoJdvuHVjzBQ=="], diff --git a/packages/op-web-sdk-react/package.json b/packages/op-web-sdk-react/package.json index 33f4a38c2..3748e4c79 100644 --- a/packages/op-web-sdk-react/package.json +++ b/packages/op-web-sdk-react/package.json @@ -12,5 +12,5 @@ "scripts": { "build": "tsup", "typecheck": "tsc --noEmit", "test": "vitest run" }, "dependencies": { "@zseven-w/op-web-sdk": "workspace:*" }, "peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0" }, - "devDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0", "@testing-library/react": "^16.0.0", "tsup": "^8.0.0", "typescript": "^5.7.2", "vitest": "^3.0.0", "jsdom": "^25.0.0" } + "devDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "@testing-library/react": "^16.0.0", "tsup": "^8.0.0", "typescript": "^5.7.2", "vitest": "^3.0.0", "jsdom": "^25.0.0" } } diff --git a/packages/op-web-sdk-react/src/design-view.tsx b/packages/op-web-sdk-react/src/design-view.tsx index d74a5b03c..04ad358f5 100644 --- a/packages/op-web-sdk-react/src/design-view.tsx +++ b/packages/op-web-sdk-react/src/design-view.tsx @@ -57,6 +57,6 @@ export function DesignView(props: DesignViewProps) { { className: props.className }, createElement('canvas', { ref: canvasRef, style: { width: '100%', height: '100%', display: 'block' } }), // Only render children once the viewer is ready. - viewer ? createElement(DesignProvider, { viewer, children: props.children }) : null, + viewer ? createElement(DesignProvider, { viewer }, props.children) : null, ); } diff --git a/packages/op-web-sdk-react/src/use-viewer.ts b/packages/op-web-sdk-react/src/use-viewer.ts index 094751e2a..0b089f764 100644 --- a/packages/op-web-sdk-react/src/use-viewer.ts +++ b/packages/op-web-sdk-react/src/use-viewer.ts @@ -3,7 +3,7 @@ import type { OpViewer } from '@zseven-w/op-web-sdk'; import { ViewerContext } from './context.js'; // Provider component that injects an OpViewer into React context. -export function DesignProvider(props: { viewer: OpViewer; children: ReactNode }) { +export function DesignProvider(props: { viewer: OpViewer; children?: ReactNode }) { return createElement(ViewerContext.Provider, { value: props.viewer }, props.children); } diff --git a/packages/op-web-sdk/src/ops-types.ts b/packages/op-web-sdk/src/ops-types.ts index 26dbb60c9..974b2a1e8 100644 --- a/packages/op-web-sdk/src/ops-types.ts +++ b/packages/op-web-sdk/src/ops-types.ts @@ -1,4 +1,4 @@ -// GENERATED by jian-ops-schema ts-rs (crates/op-web-sdk/bindings/ops.ts). Do not edit by hand; re-sync via scripts/sync-wasm.sh. +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. // JsonValue inlined from the ts-rs serde_json helper (the original imports from // ./serde_json/JsonValue which is not part of this vendored package). @@ -63,6 +63,31 @@ export type Capability = "storage" | "network" | "camera" | "microphone" | "loca */ export type CheckboxNode = { width: SizingBehavior | null, height: SizingBehavior | null, checked: BoolOrExpression | null, label: string | null, fill: Array | null, stroke: PenStroke | null, effects: Array | null, cornerRadius: CornerRadius | null, states: WidgetStates | null, state: { [key in string]?: StateEntry } | null, bindings: { [key in string]?: Expression } | null, events: EventHandlers | null, lifecycle: NodeLifecycleHooks | null, semantics: SemanticsMeta | null, gestures: GestureOverrides | null, route: NavigationRoute | null, id: string, name: string | null, role: string | null, explain: string | null, x: number | null, y: number | null, rotation: number | null, opacity: NumberOrExpression | null, enabled: BoolOrExpression | null, visible: boolean | null, locked: boolean | null, flipX: boolean | null, flipY: boolean | null, theme: { [key in string]?: string } | null, }; +export type ConversionEntry = { kind: ConversionKind, +/** + * Caller-stable key, e.g. "src/Button.tsx#Button" or "route:/settings". + */ +key: string, sourcePath: string | null, +/** + * Content fingerprint of the source unit; used for incremental conversion. + */ +sourceHash: string | null, +/** + * Master frame id (component) or screen frame id. None for token entries. + */ +nodeId: string | null, +/** + * Source-node id to document-node id mapping for idempotent reruns. + */ +nodeIds: { [key in string]?: string } | null, }; + +export type ConversionKind = "token" | "component" | "screen"; + +/** + * Ledger of code-to-design conversion units. + */ +export type ConversionSpec = { entries: Array, }; + export type CornerRadius = number | [number, number, number, number]; /** @@ -191,6 +216,21 @@ export type LogicAbi = string; export type LogicModuleRef = { id: string, source: string, integrity: string | null, abi: LogicAbi, capabilities: Array | null, }; +/** + * Uniform-grid mesh gradient (v1). A `rows`×`cols` lattice of + * `MeshVertexStop`s is Gouraud-interpolated across the node's + * round-rect. Mirrors the sibling gradient bodies for the shared + * `explain`/`opacity`/`blend_mode` tail. + */ +export type MeshGradientBody = { rows: number, cols: number, stops: Array, explain: string | null, opacity: number | null, blendMode: BlendMode | null, }; + +/** + * A single vertex of a `MeshGradient` grid. `row`/`col` are 0-based + * indices into a `rows`×`cols` lattice; `color` is the Gouraud-shaded + * colour anchored at that vertex. + */ +export type MeshVertexStop = { row: number, col: number, color: string, }; + /** * Declarative per-node navigation: clicking the node pushes/replaces/pops a route. * Equivalent to `events.on_tap = [{"push": "..."}]` but more editor-discoverable. @@ -259,11 +299,15 @@ id: string | null, app: AppConfig | null, routes: RoutesConfig | null, state: { * Per-document design-system brief (the "design.md"). Optional — * absent on documents that never authored one. */ -designMd: DesignMdSpec | null, }; +designMd: DesignMdSpec | null, +/** + * Code-to-design conversion ledger. + */ +conversion: ConversionSpec | null, }; export type PenEffect = { "type": "blur" } & BlurBody | { "type": "background_blur" } & BlurBody | { "type": "shadow" } & ShadowBody; -export type PenFill = { "type": "solid" } & SolidFillBody | { "type": "linear_gradient" } & LinearGradientBody | { "type": "radial_gradient" } & RadialGradientBody | { "type": "image" } & ImageFillBody; +export type PenFill = { "type": "solid" } & SolidFillBody | { "type": "linear_gradient" } & LinearGradientBody | { "type": "radial_gradient" } & RadialGradientBody | { "type": "mesh_gradient" } & MeshGradientBody | { "type": "shader" } & ShaderFillBody | { "type": "image" } & ImageFillBody; /** * Union of all concrete node types. @@ -376,6 +420,38 @@ aiHidden: boolean | null, */ aiAliases: Array | null, }; +/** + * Native SkSL shader fill (v1). The `sksl` source is stored RAW and is + * treated as untrusted: the renderer entrypoint is the SkSL signature + * `half4 main(float2 fragCoord)`. On compile failure the backend + * degrades to a visible solid fill (the first `color` uniform, else + * mid-gray) and never panics. Mirrors the sibling gradient bodies for + * the shared `opacity`/`blend_mode` tail. + * + * Pencil-flavoured WebGL-GLSL import is an explicit follow-up, NOT v1; + * v1 expects SkSL (Skia's GLSL dialect) verbatim. + */ +export type ShaderFillBody = { +/** + * RAW SkSL source. Entrypoint: `half4 main(float2 fragCoord)`. + */ +sksl: string, +/** + * Optional named-uniform map (`float` / `vec*` / `color`). A + * shader may declare none; absent or empty both mean "no uniforms". + */ +uniforms: { [key in string]?: ShaderUniformValue } | null, explain: string | null, opacity: number | null, blendMode: BlendMode | null, }; + +/** + * One named uniform value bound into an SkSL shader fill via + * `RuntimeShaderBuilder` at paint time. `untagged` so the wire form is + * the bare JSON scalar/array the author wrote — a number is a `float`, + * a number array is a `vec2`/`vec3`/`vec4`, and a hex string is a + * `color` mapped to a `vec4` (premultiplied RGBA). The named-uniforms + * map on `ShaderFillBody` is optional; a shader may take none. + */ +export type ShaderUniformValue = number | Array | string; + export type ShadowBody = { inner: boolean | null, offsetX: number, offsetY: number, blur: number, spread: number, color: string, }; export type SidedThickness = { top: number | null, right: number | null, bottom: number | null, left: number | null, };