From 8ba76f2983c66f7910da5d3981ac0854aacd516f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:22:15 +0000 Subject: [PATCH 1/4] Initial plan From aa6b754e5bdd1d89ddbf9c51d9c64ade9b0fc80b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:32:17 +0000 Subject: [PATCH 2/4] fix parser short-circuit for chained literal OR fallbacks Co-authored-by: aarne <82001+aarne@users.noreply.github.com> --- packages/bridge-parser/src/parser/parser.ts | 5 +++++ packages/bridge/test/coalesce-cost.test.ts | 22 +++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/packages/bridge-parser/src/parser/parser.ts b/packages/bridge-parser/src/parser/parser.ts index 837999dd..134eeba3 100644 --- a/packages/bridge-parser/src/parser/parser.ts +++ b/packages/bridge-parser/src/parser/parser.ts @@ -5237,13 +5237,18 @@ function buildBridgeBody( let falsyFallback: string | undefined; let falsyControl: ControlFlowInstruction | undefined; + let hasTruthyLiteralFallback = false; for (const alt of subs(wireNode, "nullAlt")) { + if (hasTruthyLiteralFallback) continue; const altResult = extractCoalesceAlt(alt, lineNum); if ("literal" in altResult) { falsyFallback = altResult.literal; + hasTruthyLiteralFallback = Boolean(JSON.parse(altResult.literal)); } else if ("control" in altResult) { + falsyFallback = undefined; falsyControl = altResult.control; } else { + falsyFallback = undefined; sourceParts.push({ ref: altResult.sourceRef, isPipeFork: false }); } } diff --git a/packages/bridge/test/coalesce-cost.test.ts b/packages/bridge/test/coalesce-cost.test.ts index c1fa86cc..ce0f6243 100644 --- a/packages/bridge/test/coalesce-cost.test.ts +++ b/packages/bridge/test/coalesce-cost.test.ts @@ -649,6 +649,28 @@ bridge Query.lookup { assert.equal(data.label, "fallback"); }); + test("?. with chained || literals short-circuits at first truthy literal", async () => { + const doc = parseBridge(`version 1.5 +const lorem = { + "ipsum":"dolor sit amet", + "consetetur":8.9 +} + +bridge Query.lookup { + with const + with output as o + + o.label <- const.lorem.ipsums?.kala || "A" || "B" +}`); + const gateway = createGateway(typeDefs, doc, { tools: {} }); + const executor = buildHTTPExecutor({ fetch: gateway.fetch as any }); + + const result: any = await executor({ + document: parse(`{ lookup(q: "x") { label } }`), + }); + assert.equal(result.data.lookup.label, "A"); + }); + test("?. passes through value when tool succeeds", async () => { const { data } = await run( `version 1.5 From e7fade1be01c732ab9eff062fb62bd6a90d796aa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:35:06 +0000 Subject: [PATCH 3/4] test and parser update for chained literal OR fallback order Co-authored-by: aarne <82001+aarne@users.noreply.github.com> --- packages/bridge-parser/src/parser/parser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bridge-parser/src/parser/parser.ts b/packages/bridge-parser/src/parser/parser.ts index 134eeba3..e6b2d59b 100644 --- a/packages/bridge-parser/src/parser/parser.ts +++ b/packages/bridge-parser/src/parser/parser.ts @@ -5239,7 +5239,7 @@ function buildBridgeBody( let falsyControl: ControlFlowInstruction | undefined; let hasTruthyLiteralFallback = false; for (const alt of subs(wireNode, "nullAlt")) { - if (hasTruthyLiteralFallback) continue; + if (hasTruthyLiteralFallback) break; const altResult = extractCoalesceAlt(alt, lineNum); if ("literal" in altResult) { falsyFallback = altResult.literal; From 9d0f23d7372e870eb460d8eff0739d7fd22e9edb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:45:39 +0000 Subject: [PATCH 4/4] test mixed || ?? chain semantics and add release changeset Co-authored-by: aarne <82001+aarne@users.noreply.github.com> --- .changeset/tough-cups-smoke.md | 6 ++++++ packages/bridge/test/coalesce-cost.test.ts | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 .changeset/tough-cups-smoke.md diff --git a/.changeset/tough-cups-smoke.md b/.changeset/tough-cups-smoke.md new file mode 100644 index 00000000..3349eb9f --- /dev/null +++ b/.changeset/tough-cups-smoke.md @@ -0,0 +1,6 @@ +--- +"@stackables/bridge": patch +"@stackables/bridge-parser": patch +--- + +Fix chained `||` literal fallback parsing so authored left-to-right short-circuiting is preserved after safe pulls (`?.`), and add regression coverage for mixed `||` + `??` chains. diff --git a/packages/bridge/test/coalesce-cost.test.ts b/packages/bridge/test/coalesce-cost.test.ts index ce0f6243..6dfb8741 100644 --- a/packages/bridge/test/coalesce-cost.test.ts +++ b/packages/bridge/test/coalesce-cost.test.ts @@ -671,6 +671,28 @@ bridge Query.lookup { assert.equal(result.data.lookup.label, "A"); }); + test("mixed || and ?? remains left-to-right with first truthy || winner", async () => { + const doc = parseBridge(`version 1.5 +const lorem = { + "ipsum": "dolor sit amet", + "consetetur": 8.9 +} + +bridge Query.lookup { + with const + with output as o + + o.label <- const.lorem.kala || const.lorem.ipsums?.mees || "B" ?? "C" +}`); + const gateway = createGateway(typeDefs, doc, { tools: {} }); + const executor = buildHTTPExecutor({ fetch: gateway.fetch as any }); + + const result: any = await executor({ + document: parse(`{ lookup(q: "x") { label } }`), + }); + assert.equal(result.data.lookup.label, "B"); + }); + test("?. passes through value when tool succeeds", async () => { const { data } = await run( `version 1.5