From 8e55a11c4a08128b9407891ec785e4a824cd0af7 Mon Sep 17 00:00:00 2001 From: "Timothy J. Aveni" Date: Mon, 28 Sep 2026 21:05:55 -0700 Subject: [PATCH] Correct Reader producer completion and prepare only the active source Route durable completion to the initiating PresentationMount, guarded against stale navigation and unrelated fan-out. Publish Gutenberg passages directly, preserve unfinished manual drafts, and open completed documents in the main reader pane. Prepare the selected provider and explicitly opened inspector only. Consolidate Studio factory contracts so optional execution placement is determined by implementations. Support typed stateful presentation props and local-module role selection in the React build layer. Update integration, browser, portal, and authoring documentation. --- build-react-contracts.mjs | 20 ++++++++++++++++---- react-contracts.nix | 1 + 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/build-react-contracts.mjs b/build-react-contracts.mjs index ed07a9e..e4d6736 100644 --- a/build-react-contracts.mjs +++ b/build-react-contracts.mjs @@ -1,6 +1,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { execFileSync } from "node:child_process"; +import { installDependencies } from "./install-dependencies.mjs"; const config = JSON.parse(await fs.readFile(process.argv[2], "utf8")); const resource = JSON.parse(await fs.readFile(config.interface, "utf8")); if (resource.kind !== "interface") throw Error("React props belong to an interface resource"); @@ -29,14 +30,24 @@ const metadata = await fs.readFile(packageFile, "utf8").then(JSON.parse, (error) throw error; }); await fs.writeFile(packageFile, JSON.stringify({ ...metadata, type: "module" })); -if (config.dependencies) await fs.symlink(config.dependencies, path.join(work, "node_modules")); +const modules = path.join(output, "node_modules"); +await fs.mkdir(modules, { recursive: true }); +await installDependencies({ + destination: modules, + application: config.dependencies, + selected: config.reactSdk ? { "@quixos/camino-react": config.reactSdk } : {}, + reserved: ["@quixos/camino-react"], +}); +await fs.symlink(modules, path.join(work, "node_modules")); const members = Object.create(null); let index = 0; for (const [id, entry] of Object.entries(config.members)) { const member = (iface.template?.members ?? iface.members).find((member) => member.id === id); if ( member?.kind !== "module" || - !["org.quixos.react.component/1", "org.quixos.react.subject-only/1"].includes(member.contract) + !["org.quixos.react.component/1", "org.quixos.react.component/2", "org.quixos.react.subject-only/1"].includes( + member.contract, + ) ) throw Error("Props entry is not a React module member: " + id); if ( @@ -52,7 +63,7 @@ for (const [id, entry] of Object.entries(config.members)) { `import type {${entry.export} as Props} from ${typeImport}; type Assert = T; type ObjectProps = Assert; -type ReservedRendererProps = Assert extends never ? true : false>; +type ReservedRendererProps = Assert extends never ? true : false>; ${member.contract === "org.quixos.react.subject-only/1" ? "type SubjectOnly = Assert<{} extends Props ? true : false>;" : ""} export type {Props};\n`, ); @@ -69,6 +80,8 @@ execFileSync( "--strict", "--target", "es2024", + "--lib", + "es2024,dom,dom.iterable,esnext.disposable", "--module", "nodenext", "--moduleResolution", @@ -88,7 +101,6 @@ for (const file of declarations) { await fs.copyFile(path.join(work, file), path.join(output, "types", file)); } await fs.writeFile(path.join(output, "package.json"), JSON.stringify({ type: "module" })); -if (config.dependencies) await fs.symlink(config.dependencies, path.join(output, "node_modules")); await fs.writeFile( path.join(output, "react-contracts.json"), JSON.stringify({ schemaVersion: 1, interfaceRevisionId: iface.revisionId, members }, null, 2), diff --git a/react-contracts.nix b/react-contracts.nix index acd5b9b..d9c5bab 100644 --- a/react-contracts.nix +++ b/react-contracts.nix @@ -11,6 +11,7 @@ let source = src; interface = context.interface; inherit members dependencies; + reactSdk = context.reactSdk or null; node = "${pkgs.nodejs_24}/bin/node"; tsc = "${context.typescript}/node_modules/typescript/bin/tsc"; }