Add checked React field bindings and Web Studio factories

Replace opaque props with checked generic presentation contracts and lazy typed
interface references. Generate readonly/writable field APIs and component checks.

Preserve CRDT editing through explicit resolved getter/setter contracts, binding-
fenced delta RPCs, native watches and replica-aware field adapters. Custom setters
retain semantic writes; storage snapshots never grant write authority. Cover
concurrent edits, lost acknowledgements, readonly contracts and authorization.

Add receiver-free static factory dispatch, state-field binding shorthand, and
conformance-based creation. Migrate TODO, editable scaffolds and authoring guides.
Verify language/codegen, SDK, RPC, browser lifecycle, local scaffolds, production
browser bundling and CRDT persistence with temporary PostgreSQL.
This commit is contained in:
Timothy J. Aveni
2026-09-16 11:25:20 -07:00
parent 0e183516c0
commit 5bb8ee876d
12 changed files with 474 additions and 112 deletions
+23 -17
View File
@@ -8,29 +8,35 @@ import {
jsToProtoValue,
liveValue,
protoValueToJs,
opaqueReactPropsBinding,
} from "../dist/index.js";
const scalar = (name) => ({ kind: "scalar", name });
const unit = { kind: "builtin", name: "unit" };
test("opaque React props retain managed references while ordinary messages remain closed", () => {
test("free function handlers never acquire a receiver or object session", async () => {
const handler = bindQxHandler(
{ receiver: "none", inputType: unit, outputType: unit, ports: {} },
async (context) => {
assert.equal("objectId" in context, false);
assert.equal("openSession" in context, false);
},
{},
);
await handler({
inputProto: {},
get objectId() {
throw new Error("no receiver exists");
},
openSession() {
assert.fail("no object session");
},
});
});
test("React props no longer have an opaque-message reference escape hatch", () => {
const descriptorId = "org.quixos.web-studio.ReactProps";
const reference = referenceFromWire("obj:task");
const type = { kind: "message", descriptorId };
const messages = { [descriptorId]: opaqueReactPropsBinding };
const decoded = decodeQxValue(type, encodeQxValue(type, { subject: reference, title: "Task" }, messages), messages);
assert.ok(decoded.subject.equals(reference));
assert.equal(decoded.title, "Task");
assert.throws(() => opaqueReactPropsBinding.encode(null), /object/);
assert.throws(() => opaqueReactPropsBinding.encode([]), /object/);
assert.throws(
() =>
encodeQxValue(
{ kind: "message", descriptorId: "Ordinary" },
{ subject: reference },
{ Ordinary: opaqueReactPropsBinding },
),
/Managed references/,
);
const messages = { [descriptorId]: { encode: jsToProtoValue, decode: protoValueToJs } };
assert.throws(() => encodeQxValue(type, { subject: reference }, messages), /Managed references/);
assert.throws(() => decodeQxValue(type, jsToProtoValue({ subject: reference }), messages), /Managed references/);
});
test("declared record inputs preserve opaque references without opening message codecs", async () => {
const target = referenceFromWire("obj:board");