ReviewOS

also looking at this

stacks/stx

fix(ssr): respect client signal ownership before evaluation

#2100
Merged chris wants to merge codex/client-signal-ssr-ownership into main
2 files +46 -29
packages/stx/test/expressions/signals-gate-stamp.test.tsmodified+24-1
Changes to packages/stx/test/expressions/signals-gate-stamp.test.ts
@@ -27,7 +27,7 @@
2727 * probed already preserved correctly. These tests therefore pin the stamp's own
2828 * contract, which is what the next steps will build on.
2929 */
30import { afterAll, beforeAll, describe, expect, it } from 'bun:test'
30import { afterAll, beforeAll, describe, expect, it, mock } from 'bun:test'
3131import fs from 'node:fs'
3232import os from 'node:os'
3333import path from 'node:path'
@@ -54,6 +54,10 @@ const label = derived(() => 'live')
5454 fs.writeFileSync(path.join(dir, 'partials', 'plain.stx'), '<span></span>')
5555 fs.mkdirSync(path.join(dir, 'components'), { recursive: true })
5656 fs.writeFileSync(path.join(dir, 'components', 'Alert.stx'), '<div class="alert"><slot /></div>')
57 fs.writeFileSync(path.join(dir, 'components', 'PostEditor.stx'), `<script client>
58const post = useReactiveProp('post', null)
59</script>
60<button>{{ post() ? 'Save changes' : 'Create post' }}</button>`)
5761})
5862
5963afterAll(() => {
@@ -136,6 +140,25 @@ const n = state(0)
136140 * "untested". These are those tests.
137141 */
138142describe('cases the per-caller overrides used to carry', () => {
143 it.each([
144 `<script client>const post = state(null)</script><button>Save changes</button>`,
145 '<PostEditor />',
146 ])('never invokes a server helper shadowed by an authored client signal', async (template) => {
147 // Stacks auto-imports its POST helper as `post`. Evaluating the client
148 // editor's signal before preserving it used to submit an undefined URL.
149 const post = mock(() => Promise.resolve({ server: true }))
150 const out = await processDirectives(template, { post }, path.join(dir, 'page.stx'), options(), new Set<string>())
151 expect(post).not.toHaveBeenCalled()
152 expect(out).toContain("Save changes")
153 })
154
155 it('still evaluates a server helper when no client signal owns its name', async () => {
156 const title = mock(() => 'Server title')
157 const out = await processDirectives('<h1></h1>', { title }, path.join(dir, 'page.stx'), options(), new Set<string>())
158 expect(out).toContain('<h1>Server title</h1>')
159 expect(title).toHaveBeenCalledTimes(1)
160 })
161
139162 it('preserves slot content authored by the CALLER', async () => {
140163 // options.__stx_force_signals existed for this: `<Alert></Alert>`
141164 // is written by the caller, so judging it by the COMPONENT's own script