Volume II / CHAPTER 07
For understanding and contributing
Building around the core
The same ownership questions return when we add a document, component or development tool.
Notation conventions
Function names, types, commands, options and filenames appear in monospace, for example describe, wf test or main.cpp. Italics introduce a term at its definition. Code blocks give the fragments to enter; the preceding text states their location and prerequisites.
The node is at the other end
With ui.document(), C++ can request a button and change its text. It would be misleading, however, to imagine that the node object contains the button itself: the button belongs to the browser document. The C++ node lets us identify it.
Operations are grouped into batches, sent to the page and applied by a JavaScript interpreter in islands the page has lent. Both ends retain the names and identities needed to address the same elements.
Removing a subtree or releasing an island makes those identities unusable. Reloading likewise creates a new document. A direct page mutation outside this mechanism is not automatically reported to C++.
Building a form, lending an island
A form adds a way of describing what we want to display. We supply fields, labels, bounds and accessors; form code then uses the document to build controls. That description alone does not create a new type transportable by the bridge.
Descriptions can be used in C++23. C++26 reflection is an optional way of obtaining them. It does not change the role of the layer building the form.
With React, another question appears: who may remove the elements? An Island component lends a subtree to C++. Before unmounting it, React and C++ must coordinate its release. Without that agreement, one could remove the panel while the other still used it.
Requesting the same operation through two paths
We already know how to run tests from the developer terminal. To run them from an agent, we could have it read terminal text. We would then depend on message presentation even though the program already knows the command and its result.
The automation launcher therefore reuses the terminal’s operation engine. A native contract describes arguments, results, errors and permissions. The JSON command-line interface and MCP adapter expose that contract.
A webfront.start call gives us an identifier; webfront.result lets us follow what actually happened. An authorised command can still fail. The controller must also decide what happens when the terminal and agent request incompatible operations, and limit retained output.
The test remains after the agent leaves
An agent can help us write a journey test. The useful result is the test we can run again tomorrow, including without that agent.
JasmineTest loads specifications into a test application’s page. The corresponding CMake function associates them with the program; the runner collects their report and turns it into a CTest result. The page, callbacks and rendering engine remain those of the application. MCP is not involved in this execution.
To understand the runner, we should also look at what happens if the report is missing or the page stops responding. A passing test tells us about the observed journey. A correctly reported failure lets us trust the recipe executing it.
Check your understanding