Skip to content

Commit edf9986

Browse files
committed
Remove references to port plan steps/phases
1 parent 8e2186c commit edf9986

13 files changed

Lines changed: 57 additions & 63 deletions

File tree

AGENTS.md

Lines changed: 35 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ The package `solid-querybuilder` is a **Solid 2.0** port of
1010
document order, `data-testid`, `data-path`, and byte-identical `class` attributes must match
1111
React Query Builder's output for all conformance cases.
1212

13-
Blueprints: `svelte-querybuilder@0.1.1` (Phase 1) and `@react-querybuilder/vue@0.2.0` (Phase 2).
13+
Blueprints: `svelte-querybuilder@0.1.1` and `@react-querybuilder/vue@0.2.0`.
1414
Deviate only where Solid idiom demands.
1515

1616
```
@@ -21,7 +21,7 @@ solid-querybuilder/
2121
│ └── scripts/ # build/check/ssr-smoke helpers
2222
└── examples/
2323
├── demo/ # Vite + Solid 2, aliased to the library's src
24-
└── ssr/ # hand-rolled Vite SSR consumer; the phase SSR gate
24+
└── ssr/ # hand-rolled Vite SSR consumer; the SSR gate
2525
```
2626

2727
**Target is Solid 2 only.** Peers are `solid-js@^2.0.0-beta.32` **and
@@ -32,8 +32,7 @@ shims or `solid-js@1` code paths here.
3232

3333
Wiring strategy is **hybrid**: `QueryManager` owns every write (history, guards, `reconfigure`);
3434
an internal store mirror (`reconcile`d by `id`) is the read path. See
35-
`~/git/SOLID_QB_PLAN.md` for the full rationale and step-by-step plan; this file only records the
36-
standing rules that apply to every step.
35+
`~/git/SOLID_QB_PLAN.md` for the full rationale; this file only records the standing rules.
3736

3837
## Commands
3938

@@ -45,11 +44,10 @@ standing rules that apply to every step.
4544
- `bun run check:versions` — asserts the resolved prerelease toolchain has not drifted; runs
4645
first in CI
4746
- `bun run test:ssr`**two halves, in sequence.** First `packages/solid-querybuilder`'s
48-
`scripts/ssr-smoke.ts` (the step-1 script: export-condition order in isolation, plus a markup
49-
assertion). Then `examples/ssr`'s `ssr-smoke-test.ts` (step 8: builds the example against the
50-
published `dist`, serves it, asserts status + markup, then hydrates in jsdom). Step 8
51-
**supersedes but does not replace** the first — keep both; only the first checks the export
52-
condition in isolation.
47+
`scripts/ssr-smoke.ts` (export-condition order in isolation, plus a markup assertion). Then
48+
`examples/ssr`'s `ssr-smoke-test.ts` (builds the example against the published `dist`, serves
49+
it, asserts status + markup, then hydrates in jsdom). The second **supersedes but does not
50+
replace** the first — keep both; only the first checks the export condition in isolation.
5351
- `bun run check``tsc --noEmit`, then `check:examples`, which fans the same out to
5452
`@solid-querybuilder/example-*` so an example type error breaks CI
5553
- `bun run lint`, `bun run fmt`, `bun run fmt:check`
@@ -72,10 +70,10 @@ package that renders fine in the browser and silently breaks SSR/hydration.
7270
A key _lookup_ (`exports['.'].solid`) is order-blind and does not gate anything — do not
7371
regress it back to that.
7472

75-
Keep the script even after step 8 supersedes it with the SolidStart gate; it is the only thing
76-
that checks the export condition in isolation. (There is no Solid 2 SolidStart yet — step 8's
77-
gate is a plain Vite SSR example — but the export condition is what that gate rests on either
78-
way.)
73+
Keep the script even though the SolidStart gate would supersede it; it is the only thing
74+
that checks the export condition in isolation. (There is no Solid 2 SolidStart yet — the
75+
example's gate is a plain Vite SSR example — but the export condition is what that gate rests on
76+
either way.)
7977

8078
### The SSR smoke test runs one Solid instance
8179

@@ -85,7 +83,7 @@ own copy of `solid-js`, so importing `renderToString` in the host process instea
8583
with a _different instance_ than the component was compiled against. Solid keeps
8684
owner/`sharedConfig` state at module scope, so the copies do not share it — a trivial component
8785
survives this, but anything using `createContext`/`createStore`/`createEffect` (i.e.
88-
`QueryBuilder`, from step 4) does not.
86+
`QueryBuilder`) does not.
8987

9088
**Conditions are the plugin's job now, not the config's.** `vite-plugin-solid@3` gives the ssr
9189
environment `['solid', 'development', 'module', 'node', 'development|production']` on its own, so
@@ -111,7 +109,7 @@ example is the only thing in the repo that exercises the publishable artifact en
111109
dom-compiled bundle and render nothing server-side.
112110
- **The SSR server is started programmatically on an ephemeral port, never by spawning a CLI.** A
113111
spawned preview leaves an orphan holding the port and serving a stale build, silently poisoning
114-
the next run — caught in Phase 1, hit again in Phase 2.
112+
the next run.
115113
- **The hydration half runs both scripts in-process, not in jsdom.** `runScripts: 'dangerously'` is
116114
a dead end: jsdom's vm global trips Bun with "Proxy is not allowed in the global prototype
117115
chain", and it cannot execute the `type="module"` client bundle anyway. The inline
@@ -126,8 +124,7 @@ example is the only thing in the repo that exercises the publishable artifact en
126124

127125
`src/index.tsx` does `export * from '@react-querybuilder/core'`, not just `export type *`. A
128126
consumer calls `formatQuery` from `solid-querybuilder` and never depends on core directly, exactly
129-
as React Query Builder's own barrel works. Step 3's plan called for this; `examples/ssr` is what
130-
proved it missing at step 8.
127+
as React Query Builder's own barrel works. `examples/ssr` is what proved a gap here in review.
131128

132129
### Relative import specifiers
133130

@@ -153,7 +150,7 @@ type-only module erased by the bundler), and allows `./foo.jsx` under `dist/sour
153150
- **Split effects, not `on()`.** `on()` is gone; `createEffect(compute, apply)` makes the compute
154151
phase the dependency declaration, so the old "always use `on`" rule is now enforced by the API
155152
shape. Deps in compute, writes in apply. `{ defer: true }` survives as an option.
156-
- **Apply-phase writes are legal — no `ownedWrite` needed** (proven at step 1.5). The owned-write
153+
- **Apply-phase writes are legal — no `ownedWrite` needed.** The owned-write
157154
rule rejects writes made while an owner is on the stack, and the apply phase is unowned. Note
158155
`ownedWrite` is a **signal** option, not an effect option; `createEffect` has no such option.
159156
- ⚠️ **The body of `createRoot(fn)` IS an owned scope.** A write there throws
@@ -187,8 +184,8 @@ setX(v)` returns the setter's return value and throws "invalid cleanup value". U
187184
shape and throws.
188185
- `createProjection(fn, seed, options?)` is a derived, **read-only** store with the same `'id'`
189186
default key. It can be driven from a non-reactive external source (the manager's subscribe
190-
callback) by bumping a version signal from that callback and reading the signal in `fn` — proven
191-
at step 1.5, and it is what `createQueryBuilderState` uses (step 3).
187+
callback) by bumping a version signal from that callback and reading the signal in `fn`; this is
188+
what `createQueryBuilderState` uses.
192189
- `createStore`'s setter takes a **draft callback** (`setStore(draft => { draft.x = … })`). There is
193190
no 1.x `setStore('key', value)` path-argument form; it throws `fn is not a function`.
194191

@@ -218,7 +215,7 @@ it passes whether or not the `snapshot()` is there:
218215
- A subquery renders **bare `<div>`s** for its group header/body, not a `rule-group` element
219216
(React's `RuleWithSubQueryGroupComponentsWrapper`), and it is not customizable.
220217

221-
### The conformance harness (step 6)
218+
### The conformance harness
222219

223220
`vitest.conformance.config.ts` runs **two projects**, because the two fixture layers demand
224221
opposite render modes. This is structural, not cosmetic — one plugin instance cannot serve both.
@@ -268,32 +265,32 @@ opposite render modes. This is structural, not cosmetic — one plugin instance
268265
`RuleTypeOf<RG>` helper, and no re-widening cast inside components (all of which Vue needed).
269266
- `src/types/types.test-d.ts` is compiled by `tsc` (`bun run check`), **not** run by Vitest. It is
270267
a **two-sided** gate: a failed assertion errors, and an `@ts-expect-error` that stops erroring
271-
(member quietly re-added) errors as `TS2578`. Both directions proven at step 2.
268+
(member quietly re-added) errors as `TS2578`. Both directions are proven.
272269
- **TypeScript is pinned to `^5.9`.** Neither `vite-plugin-solid`'s babel preset nor the
273270
declaration pipeline is validated against TypeScript 7.
274271

275272
## Gates
276273

277-
**Standing rule: every gate must be proven to fail.** When a step adds a gate, deliberately break
274+
**Standing rule: every gate must be proven to fail.** When a gate is added, deliberately break
278275
it, record that it went red, then revert. A gate that cannot fail is worse than none.
279276

280-
Current gates (steps 6 + 7 + 8): `check:versions`, `fmt:check`, `build`, `check`, `check:exports`,
281-
`lint`, `test:coverage` (global 80% lines, plus a per-directory 90% lines on `packages/*/src/**`
282-
widened at step 5 from the step-3 `packages/*/src/reactive/**`, which it subsumes; both
277+
Current gates: `check:versions`, `fmt:check`, `build`, `check`, `check:exports`,
278+
`lint`, `test:coverage` (global 80% lines, plus a per-directory 90% lines on `packages/*/src/**`,
279+
which subsumes the narrower `packages/*/src/reactive/**` key; both
283280
non-vacuous, both proved red with no injected dead code), **`conformance`** (237 assertions: 50
284281
static classnames, 50 accessible descriptions, 50 post-flush classnames, 58 action sequences, 19
285282
port-side action sequences, plus alignment/drift/format), `test:ssr` (**both halves**), and
286283
`check` including the examples.
287284

288-
The step-7 a11y gate was proved red and reverted: deleting the `title` binding from
285+
The a11y gate was proved red and reverted: deleting the `title` binding from
289286
`ValueSelector.tsx` turned **all nine** axe cases red on the **WCAG** assertion (`select-name`, a
290287
level-A violation, not merely a best-practice one) while all three keyboard tests stayed green.
291288
Note the best-practice assertion is an **equality** check against `['label-title-only']`, not a
292289
suppression — RQB labels selectors and text editors with `title` alone and DOM parity is locked, so
293290
that one rule is accepted (recorded under "Known limitations" in `CHANGELOG.md`) while any _other_
294291
best-practice regression still fails.
295292

296-
The four step-6 conformance gates were each proved red and reverted:
293+
The four conformance gates were each proved red and reverted:
297294

298295
1. **DOM parity**` conformance-gate-probe` appended to `ActionElement.tsx`'s class turned
299296
exactly 100 cases red (50 static + 50 post-flush), which is the split the two projects promise.
@@ -303,28 +300,28 @@ The four step-6 conformance gates were each proved red and reverted:
303300
three case-alignment tests) red while all 50 rendered cases stayed green.
304301
4. **Value-editor reset** — an early `return` in `createValueEditorReset`'s apply phase left
305302
conformance at 237/237 green (as upstream predicts: every case is `differsFromStatic: false`)
306-
while turning 5 of the 9 post-mount unit assertions red. That asymmetry is exactly why the
307-
plan forbids proving this one through the post-flush fixture.
303+
while turning 5 of the 9 post-mount unit assertions red. That asymmetry is exactly why this one
304+
cannot be proved through the post-flush fixture alone.
308305

309306
Separately confirmed: with `test/fixtures/` removed, `bun run test` still passes 284/284 and
310307
`conformance:test` fails with the actionable "run `bun run conformance:fetch`" message rather than
311308
an opaque parse error.
312309

313-
The step-8 example gate was proved red twice, independently, and reverted both times:
310+
The example gate was proved red twice, independently, and reverted both times:
314311
`document.title` injected into `QueryBuilder.tsx` turned the served response into a 500 and took
315312
19 assertions with it; a one-attribute divergence in `examples/ssr/src/entry-client.tsx` turned the
316313
hydration surface comparison red while every markup assertion stayed green. Those two failure
317314
modes share no code, which is the point of having both.
318315

319-
All five were proven red at step 1 and reverted: coverage (threshold to 99 + an injected
316+
All five were proven red once and reverted: coverage (threshold to 99 + an injected
320317
uncovered function), export-condition **order** (`import` moved first), export-condition
321318
**target** (`solid` repointed at `dist/index.js`), the SSR **markup** assertion (component's label
322319
dropped), and `check-dist-specifiers` (a directory import appended to `dist/index.d.ts`).
323320

324-
**Four of the five were re-proved red on the Solid 2 toolchain at step 1.5** — a gate proved red
321+
**Four of the five were re-proved red on the Solid 2 toolchain** — a gate proved red
325322
under Solid 1 is not evidence about Solid 2, since the plugin, the resolver behavior and the SSR
326323
renderer all changed. Coverage, condition **order** (both layers fired), condition **target**, and
327-
SSR **markup** under the new synchronous `renderToString`. `check-dist-specifiers` is unaffected by
324+
SSR **markup** under the synchronous `renderToString`. `check-dist-specifiers` is unaffected by
328325
the runtime swap; it was re-run against the rebuilt `dist` instead.
329326

330327
⚠️ Two assertion shapes that look like gates but are not, both found and removed in review — do
@@ -337,8 +334,8 @@ not reintroduce them:
337334
Build distinctness is now covered properly by the two Node resolutions in `test:ssr`.
338335

339336
⚠️ Coverage-gate proof caveat: with `src/index.ts` a pure `export *`, v8 reports `0/0` and the
340-
threshold passes vacuously. The step-1 proof must also inject an uncovered multi-line function
341-
body to demonstrate the gate is live; non-vacuity is re-confirmed for real at step 3.
337+
threshold passes vacuously. The proof must also inject an uncovered multi-line function
338+
body to demonstrate the gate is live; non-vacuity is re-confirmed against real reactive-layer code.
342339

343340
## Coverage
344341

@@ -348,8 +345,8 @@ is how CI runs it.
348345

349346
## Generated / fetched files
350347

351-
- `packages/solid-querybuilder/test/fixtures/` — downloaded by `scripts/fetch-fixtures.ts` (added
352-
at step 6), gitignored. A fresh clone must pass `bun run test` without them.
348+
- `packages/solid-querybuilder/test/fixtures/` — downloaded by `scripts/fetch-fixtures.ts`,
349+
gitignored. A fresh clone must pass `bun run test` without them.
353350

354351
## Repo status
355352

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7070
### Fixed
7171

7272
- `src/index.tsx` re-exports `@react-querybuilder/core` **at runtime**, not just at the type level.
73-
Step 3 called for this and it was never landed; `examples/ssr` found it by failing to build on
73+
`examples/ssr` found the gap by failing to build on
7474
`import { formatQuery } from 'solid-querybuilder'`. Consumers can now use core's formatters,
7575
defaults, and `QueryManager` without depending on core directly, as React Query Builder's own
7676
barrel allows.

examples/ssr/ssr-smoke-test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* The Phase 3 SSR gate.
2+
* The SSR gate.
33
*
44
* Replaces the SolidStart example the original plan called for: `@solidjs/start@2.0.0` is a
55
* **Solid 1** release (it depends on `solid-js@^1.9`), and there is no Solid-2 line on any
@@ -22,8 +22,7 @@
2222
*
2323
* The server is started **programmatically on an ephemeral port**, never by spawning a CLI: a
2424
* spawned `vite preview` leaves an orphan holding the port and serving a stale build, which
25-
* silently poisons the next run. That was caught in development in Phase 1 and hit again in
26-
* Phase 2.
25+
* silently poisons the next run.
2726
*/
2827
import { createServer } from 'node:http';
2928
import type { AddressInfo } from 'node:net';

packages/solid-querybuilder/scripts/ssr-smoke-entry.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
* call. The library is imported by BARE SPECIFIER on purpose: that exercises the `solid` export
77
* condition the same way a real SSR consumer does.
88
*
9-
* `QueryBuilder` (not a placeholder) since step 4: it uses `createContext`, `createStore`, and
10-
* `createEffect`, so it is also the thing that would break first if the two module graphs ever
11-
* stopped sharing one Solid instance.
9+
* `QueryBuilder` is the real component under test (not a placeholder): it uses `createContext`,
10+
* `createStore`, and `createEffect`, so it is also the thing that would break first if the two
11+
* module graphs ever stopped sharing one Solid instance.
1212
*
1313
* Plain `.jsx`, not `.tsx`, so it stays out of the typecheck project — `bun run check` must not
1414
* depend on `dist/` existing.

packages/solid-querybuilder/scripts/ssr-smoke.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/**
2-
* Real gate from day one — not a placeholder. Both prior ports (Svelte, Vue) carried `test:ssr`
3-
* as a documented no-op until their step 8; Solid cannot afford that: the `solid` export
2+
* Real gate from day one — not a placeholder. The `solid` export
43
* condition is the single most Solid-specific way to ship a broken package, and it is invisible
54
* until something renders server-side.
65
*
@@ -13,11 +12,10 @@
1312
* `renderToString` from `@solidjs/web` (synchronous in Solid 2), and assert the full
1413
* markup.
1514
*
16-
* The component under test is `QueryBuilder` (step 4 repointed this from the step-1
17-
* `Placeholder`) — it exercises `createContext`, `createStore`, and `createEffect`, which is
18-
* what makes the single-Solid-instance requirement below load-bearing rather than theoretical.
19-
* Step 8 adds a SolidStart SSR gate but keeps this script, because it is the only thing that
20-
* checks the export condition in isolation.
15+
* The component under test is `QueryBuilder` — it exercises `createContext`, `createStore`, and
16+
* `createEffect`, which is what makes the single-Solid-instance requirement below load-bearing
17+
* rather than theoretical. A SolidStart SSR gate would supersede this script but not replace it,
18+
* because it is the only thing that checks the export condition in isolation.
2119
*/
2220
import { existsSync } from 'node:fs';
2321
import { dirname, relative, resolve } from 'node:path';
@@ -147,7 +145,7 @@ const vite = await createServer({
147145
// so a `renderToString` imported out here in the host process would be a DIFFERENT instance
148146
// than the one the component was compiled against. Solid keeps owner/`sharedConfig` state at
149147
// module scope, so the two copies do not share it — a trivial component survives that, but
150-
// anything using `createContext`/`createStore`/`createEffect` (i.e. `QueryBuilder`, from step 4)
148+
// anything using `createContext`/`createStore`/`createEffect` (i.e. `QueryBuilder`)
151149
// does not. Keep the render inside the graph.
152150
const entry = resolve(packageRoot, 'scripts/ssr-smoke-entry.jsx');
153151
const mod = await vite.ssrLoadModule(entry);

packages/solid-querybuilder/src/components/Rule.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ const ruleTestIDs = (container: Element): string[] =>
2323

2424
describe('Rule', () => {
2525
/**
26-
* The element-order contract. React's `Rule.tsx` is the spec; step 6's fixtures pin the class
27-
* strings byte for byte, but *order* is asserted here, where a failure names the component.
26+
* The element-order contract. React's `Rule.tsx` is the spec; the conformance fixtures pin the
27+
* class strings byte for byte, but *order* is asserted here, where a failure names the component.
2828
*
2929
* A purely additive change — an extra class, an extra attribute — does not turn this red. Only
3030
* a reordering or a dropped control does.

packages/solid-querybuilder/src/components/features.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const testIDs = (el: Element): (string | null)[] =>
3131
[...el.children].map(c => c.getAttribute('data-testid'));
3232

3333
/**
34-
* The feature surface completed at Milestone B. Every one of these is cheap — core does the
34+
* The full feature surface. Every one of these is cheap — core does the
3535
* work — but each is a distinct prop path from `QueryBuilder` to a rendered control, and none of
3636
* them was reachable before the remaining controls existed.
3737
*/

packages/solid-querybuilder/src/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Core is re-exported at runtime, not just at the type level: a consumer calls `formatQuery`,
22
// `defaultOperators`, `transformQuery` and friends from this package and never depends on
3-
// `@react-querybuilder/core` directly, exactly as React Query Builder's own barrel does. Step 3's
4-
// plan called for this and step 8's `examples/ssr` is what proved it missing — the example imports
3+
// `@react-querybuilder/core` directly, exactly as React Query Builder's own barrel does.
4+
// `examples/ssr` is what proved this missing — the example imports
55
// `formatQuery` from the bare specifier and would not build without it.
66
//
77
// This is a star export, so it loses every name the port declares explicitly below; that is the

0 commit comments

Comments
 (0)