/// /** * Test Pattern: Cross-Charm Stream Client * * VERIFIED CLAIMS: * * 1. Cross-Charm Stream Invocation via wish() - WORKS (with corrections to blessed doc) * - Streams from wished charms appear as Cells wrapping { $stream: true } marker * - Call .send(eventData) on the Cell itself (NOT on an "unwrapped" stream) * - The blessed doc's "auto-unwrap via Stream signature" explanation is WRONG * - Event must be an object (runtime calls preventDefault), can have data props but NO functions * * 2. ct.render Forces Charm Execution - VERIFIED * - Just wishing for a charm doesn't make it run * - Use to force execution * * PREREQUISITES DISCOVERED: * - Wish tags must be in JSDoc on Output type (not file-level comments) * - wish({ query: "#tag" }) searches FAVORITES only - charm must be favorited first * * TESTING: * 1. Deploy server charm first, favorite it * 2. Deploy this client charm * 3. Toggle to Mode B (ct.render active) * 4. Click "Invoke Server Stream" - server counter should increment */ import { Cell, Default, derive, handler, NAME, pattern, Stream, UI, wish, } from "commontools"; interface Input { // Toggle between Mode A (wish only) and Mode B (wish + ct.render) useCtRender: Default; // Track last invocation result lastInvocationStatus: Default; // Track invocation count invocationCount: Default; } interface Output { useCtRender: boolean; lastInvocationStatus: string; invocationCount: number; } // Handler that invokes a stream from the wished charm // KEY FINDING: Despite blessed doc claims, Stream in signature does NOT auto-unwrap. // The stream comes through as a Cell wrapping { $stream: true }. Call .send({}) on the Cell. const invokeServerStream = handler< unknown, { stream: Stream; lastInvocationStatus: Cell; invocationCount: Cell; } >((_event, state) => { try { // Stream arrives as a Cell, not an unwrapped callable stream const streamCell = state.stream as any; const innerValue = streamCell.get ? streamCell.get() : streamCell; if (innerValue && innerValue.$stream) { // Cell contains { $stream: true } marker - call .send() on the Cell itself // Event must be object (runtime calls preventDefault), can have data props, NO functions streamCell.send({}); // Could also be { someData: "value" } const count = state.invocationCount.get() + 1; state.invocationCount.set(count); state.lastInvocationStatus.set( `Success! Server counter should increment (invoked ${count} times)`, ); } else { state.lastInvocationStatus.set( `Stream not found or invalid: ${JSON.stringify(innerValue)}`, ); } } catch (error) { state.lastInvocationStatus.set(`Failed: ${error}`); } }); // Handler for toggling the render mode const toggleMode = handler }>( (_event, { useCtRender }) => { useCtRender.set(!useCtRender.get()); }, ); export default pattern( ({ useCtRender, lastInvocationStatus, invocationCount }) => { // Wish for the server charm by tag const wishResult = wish<{ counter: number; invocationLog: string[]; incrementCounter: Stream; }>({ query: "#cross-charm-test-server", }); // Access the result from the wish (WishState has a result property) const serverCharm = wishResult.result; // Extract the stream using derive const serverStream = derive( serverCharm, (charm) => charm?.incrementCounter, ); return { [NAME]: "Cross-Charm Test Client", [UI]: (

Cross-Charm Test Client

{/* Mode Toggle Section */}

Claim 2 Test: ct.render Forces Execution

Current Mode: {useCtRender ? "Mode B: Wish + ct.render" : "Mode A: Wish Only (no ct.render)"}
Toggle Mode

In Mode A, server charm should NOT execute. In Mode B, it should execute.

{/* Server Charm Rendering Section */}

Server Charm Status

{useCtRender ? (

Mode B Active: Rendering server charm with ct.render

{/* Use ct.render to force execution - even hidden, this makes the charm active */}
) : (

Mode A Active: Server charm wished for but NOT rendered (should not execute)

)}
{/* Stream Invocation Section */}

Claim 1 Test: Stream Invocation

Last Invocation Status: {lastInvocationStatus}
Invoke Server Stream

Click to invoke the incrementCounter stream from the server charm. Check the server charm to see if the counter incremented.

{/* Debug Info */}
Debug Info
              {JSON.stringify(
                {
                  useCtRender,
                  invocationCount,
                  lastInvocationStatus,
                  serverCharmExists: serverCharm !== undefined && serverCharm !== null,
                  serverStreamExists: serverStream !== undefined && serverStream !== null,
                },
                null,
                2
              )}
              
), useCtRender, lastInvocationStatus, invocationCount, }; }, );