From b95cd149c6b46adc1281c895ae9b5bb5b588ab25 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. --- nix/checked-candidate.nix | 1 + src/react-build/declarations.ts | 17 ++++++++++++++++- test/prepared-presentations.test.ts | 13 +++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/nix/checked-candidate.nix b/nix/checked-candidate.nix index 8fc6be4..9533963 100644 --- a/nix/checked-candidate.nix +++ b/nix/checked-candidate.nix @@ -165,6 +165,7 @@ let interface = "${compile dependency}/candidate.json"; generator = protocol; typescript = runtimePackages.camino-replica-core.dependencies; + reactSdk = runtimePackages.camino-react; dependencies = buildDependencies dependency; }) ] diff --git a/src/react-build/declarations.ts b/src/react-build/declarations.ts index 72635ae..2a7c042 100644 --- a/src/react-build/declarations.ts +++ b/src/react-build/declarations.ts @@ -101,13 +101,28 @@ export function presentationDeclarations( if (!child.name || names.has(child.name) || !child.slot || !child.root) throw Error(`${name}: child requires a unique name, slot and root`); names.add(child.name); + // A local module identifies a closed role, including its generic arguments. + // This avoids inventing an import alias for a generic application solely + // to select that role in the React preparation manifest. + let role: ReactChildDeclaration["role"]; + if (child.role) { + const authored = object(child.role, `${name}.${child.name} role`); + if (typeof authored.module === "string") { + const target = pkg.modules?.find((entry) => entry.displayName === authored.module); + if (!target || Object.keys(authored).length !== 1) + throw Error(`${name}.${child.name}: expected a local module role`); + role = { interface: target.interfaceRevisionId, member: target.memberId }; + } else { + role = { ...child.role, interface: imported(child.role.interface) }; + } + } if (child.each && child.with) throw Error(`${name}.${child.name}: cannot combine each and with`); if (child.each && (!Number.isSafeInteger(child.each.limit) || child.each.limit < 1)) throw Error(`${name}.${child.name}: finite positive collection limit required`); return { ...child, subject: imported(child.subject), - ...(child.role ? { role: { ...child.role, interface: imported(child.role.interface) } } : {}), + ...(role ? { role } : {}), }; }); artifact.modules.push({ module: module.id, composition, children: parsed }); diff --git a/test/prepared-presentations.test.ts b/test/prepared-presentations.test.ts index 5895fbb..d456f7a 100644 --- a/test/prepared-presentations.test.ts +++ b/test/prepared-presentations.test.ts @@ -121,6 +121,19 @@ async function fixture(replacement = "First", change?: (artifact: ReactPreparati return { workspace: workspace.workspace, pkg: workspace.workspace.packageImports[0]!, authored, interfaces }; } +test("a local module names its checked role without a synthetic import alias", async () => { + const { pkg, authored, interfaces } = await fixture(); + const source = structuredClone(authored) as any; + source.modules.LibraryCard.children[0].role = { module: "First" }; + const declared = presentationDeclarations(JSON.stringify(source), pkg, [...interfaces.values()]); + assert.deepEqual(declared.artifact.modules[0]!.children[0]!.role, { interface: "row-role@1", member: "row-render" }); + source.modules.LibraryCard.children[0].role = { module: "Missing" }; + assert.throws( + () => presentationDeclarations(JSON.stringify(source), pkg, [...interfaces.values()]), + /local module role/, + ); +}); + test("workspace expands distinct providers' private inputs without parent field declarations", async () => { const { pkg } = await fixture(); const parent = pkg.checkedQueries!.find((query) => query.declaration.id === "react:input:library-card:data")!;