Skip to content

Commit 44f23d8

Browse files
committed
Initial commit
0 parents  commit 44f23d8

28 files changed

Lines changed: 1742 additions & 0 deletions

.editorconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
indent_style = space
7+
indent_size = 2
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true
10+
11+
[*.md]
12+
trim_trailing_whitespace = false

.github/workflows/ci.yml

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.ref }}
11+
cancel-in-progress: true
12+
13+
jobs:
14+
main:
15+
name: build & test (solid-js@1.9)
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/checkout@v5
19+
20+
- uses: oven-sh/setup-bun@v2
21+
with:
22+
bun-version: latest
23+
24+
- name: Install
25+
run: bun install --frozen-lockfile
26+
27+
- name: Format check
28+
run: bun run fmt:check
29+
30+
- name: Build
31+
run: bun run build
32+
33+
- name: Check
34+
run: bun run check
35+
36+
- name: Check package exports
37+
run: bun run check:exports
38+
39+
- name: Lint
40+
run: bun run lint
41+
42+
- name: Test
43+
run: bun run test:coverage
44+
45+
- name: Conformance
46+
run: bun run conformance
47+
48+
- name: SSR smoke test
49+
run: bun run test:ssr
50+
51+
solid-next:
52+
name: build & test (solid-js@2.0.0-beta, non-gating)
53+
runs-on: ubuntu-latest
54+
continue-on-error: true
55+
steps:
56+
- uses: actions/checkout@v5
57+
58+
- uses: oven-sh/setup-bun@v2
59+
with:
60+
bun-version: latest
61+
62+
- name: Install
63+
run: bun install --frozen-lockfile
64+
65+
- name: Bump solid-js to next
66+
run: bun add -D solid-js@2.0.0-beta
67+
68+
- name: Build
69+
run: bun run build
70+
71+
- name: Check
72+
run: bun run check
73+
74+
- name: Test
75+
run: bun run test
76+
77+
- name: Conformance
78+
run: bun run conformance
79+
80+
- name: SSR smoke test
81+
run: bun run test:ssr

.gitignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Dependencies
2+
node_modules/
3+
4+
# Build output
5+
dist/
6+
build/
7+
coverage/
8+
9+
# Conformance fixtures (downloaded by scripts/fetch-fixtures.ts)
10+
packages/solid-querybuilder/test/fixtures/
11+
*.tar.gz
12+
*.tar.gz.sha256
13+
14+
# Misc
15+
.DS_Store
16+
*.log
17+
.env
18+
.env.*
19+
!.env.example
20+
.idea
21+
.vscode/*
22+
!.vscode/extensions.json
23+
.tmp

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
provenance=true

.oxfmtrc.json

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"$schema": "./node_modules/oxfmt/configuration_schema.json",
3+
"printWidth": 100,
4+
"tabWidth": 2,
5+
"useTabs": false,
6+
"semi": true,
7+
"singleQuote": true,
8+
"trailingComma": "es5",
9+
"bracketSpacing": true,
10+
"bracketSameLine": true,
11+
"arrowParens": "avoid",
12+
"embeddedLanguageFormatting": "auto",
13+
"sortImports": {
14+
"newlinesBetween": false,
15+
"sortSideEffects": false,
16+
"customGroups": [
17+
{ "groupName": "bun", "elementNamePattern": ["bun"] },
18+
{ "groupName": "bun:", "elementNamePattern": ["bun:*"] },
19+
{ "groupName": "node:", "elementNamePattern": ["node:*", "node:*/*"] }
20+
],
21+
"groups": [
22+
["bun", "bun:", "node:"],
23+
["type-builtin", "value-builtin"],
24+
["type-external", "value-external"],
25+
["type-parent", "value-parent", "type-sibling", "value-sibling", "type-index", "value-index"],
26+
"type-import",
27+
"unknown"
28+
]
29+
},
30+
"sortPackageJson": false,
31+
"ignorePatterns": [
32+
"examples/*/**/*.*",
33+
"packages/*/dist/**/*.*",
34+
// Downloaded conformance fixtures
35+
"packages/*/test/fixtures/**/*.*",
36+
// Binaries
37+
"*.jpeg",
38+
"*.jpg",
39+
"*.png",
40+
"*.svg",
41+
// Unsupported
42+
"*.toml",
43+
"*.sh",
44+
"CNAME"
45+
],
46+
"overrides": [
47+
{
48+
"files": ["packages/*/**/*.test.ts", "packages/*/test/**/*.ts"],
49+
"options": {
50+
"objectWrap": "collapse"
51+
}
52+
}
53+
]
54+
}

.oxlintrc.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
3+
"categories": {
4+
"correctness": "error",
5+
"suspicious": "warn"
6+
},
7+
"ignorePatterns": ["dist", "node_modules", "coverage"]
8+
}

AGENTS.md

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# solid-querybuilder Development Guide
2+
3+
**COMMUNICATION STYLE**: Be aggressively concise. Prioritize brevity over grammar.
4+
5+
## Project overview
6+
7+
The package `solid-querybuilder` is a Solid 1.x/2.x port of
8+
[React Query Builder](https://react-querybuilder.js.org), built on the published
9+
`@react-querybuilder/core`. The port's defining constraint is **full DOM parity**: tag name,
10+
document order, `data-testid`, `data-path`, and byte-identical `class` attributes must match
11+
React Query Builder's output for all conformance cases.
12+
13+
Blueprints: `svelte-querybuilder@0.1.1` (Phase 1) and `@react-querybuilder/vue@0.2.0` (Phase 2).
14+
Deviate only where Solid idiom demands.
15+
16+
```
17+
solid-querybuilder/
18+
├── packages/solid-querybuilder/ # the library
19+
│ ├── src/ # components, reactive layer, types, styles
20+
│ ├── test/conformance/ # DOM-parity harness (fixtures gitignored)
21+
│ └── scripts/ # build/check/ssr-smoke helpers
22+
└── examples/ # demo (Vite) and a SolidStart SSR gate
23+
```
24+
25+
Wiring strategy is **hybrid**: `QueryManager` owns every write (history, guards, `reconfigure`);
26+
an internal `createStore` mirror (`reconcile`d by `id`) is the read path. See
27+
`~/git/SOLID_QB_PLAN.md` for the full rationale and step-by-step plan; this file only records the
28+
standing rules that apply to every step.
29+
30+
## Commands
31+
32+
- `bun install`
33+
- `bun run build` — vite lib build (dom), then `tsc --jsx preserve` (source), then types, then css
34+
- `bun run test` / `bun run test:coverage` — Vitest. **Never `bun test`**; that is Bun's builtin
35+
runner and bypasses Vitest entirely.
36+
- `bun run conformance` — fetch fixtures, then run the DOM-parity suites
37+
- `bun run test:ssr` — resolves the `solid` export condition and renders through
38+
`renderToStringAsync`; a real gate from step 1, superseded (but not replaced) by the SolidStart
39+
gate at step 8
40+
- `bun run check``tsc --noEmit`
41+
- `bun run lint`, `bun run fmt`, `bun run fmt:check`
42+
- `bun run check:all` — everything CI runs
43+
44+
## Authoring constraints
45+
46+
### The Solid export condition
47+
48+
`exports['.']` is the Solid triple: `solid` (raw JSX, resolved first) → `types``import`
49+
(dom-compiled fallback). **`"solid"` must come first** in the conditions object — Node picks the
50+
_first_ matching key, so a `solid` entry that merely exists but sits after `import` is silently
51+
dead. A consumer's `vite-plugin-solid` (or SolidStart) resolves it and compiles the raw JSX for
52+
its own target (`dom` in the browser, `ssr` on the server). Getting the order wrong ships a
53+
package that renders fine in the browser and silently breaks SSR/hydration.
54+
55+
`scripts/ssr-smoke.ts` is the gate, and it checks order two ways: a literal
56+
`Object.keys(exports['.'])[0] === 'solid'` assertion, and Node's real resolver invoked twice
57+
(`--conditions=solid` must give `dist/source/index.jsx`, no conditions must give `dist/index.js`).
58+
A key _lookup_ (`exports['.'].solid`) is order-blind and does not gate anything — do not
59+
regress it back to that.
60+
61+
Keep the script even after step 8 supersedes it with the SolidStart gate; it is the only thing
62+
that checks the export condition in isolation.
63+
64+
### The SSR smoke test runs one Solid instance
65+
66+
`scripts/ssr-smoke-entry.jsx` imports **both** `solid-js/web` and the library, and is loaded
67+
through `vite.ssrLoadModule`. That is load-bearing: `ssr.noExternal` gives Vite's module graph its
68+
own copy of `solid-js`, so importing `renderToStringAsync` in the host process instead would
69+
render with a _different instance_ than the component was compiled against. Solid keeps
70+
owner/`sharedConfig` state at module scope, so the copies do not share it — a trivial component
71+
survives this, but anything using `createContext`/`createStore`/`createEffect` (i.e.
72+
`QueryBuilder`, from step 4) does not.
73+
74+
Vite's `ssr.resolve.conditions` must be `['solid', 'node', 'development']`: `solid` so the library
75+
resolves to its raw-JSX entry, **`node` so `solid-js/web` resolves to its server build**. Listing
76+
`solid` alone clobbers Vite's defaults and hands back the browser build, whose
77+
`renderToStringAsync` is a stub that throws. Never add `browser`.
78+
79+
The entry is `.jsx`, not `.tsx`, deliberately: it stays out of the typecheck project so
80+
`bun run check` does not depend on `dist/` existing.
81+
82+
### Relative import specifiers
83+
84+
Must end `.js`, not `.ts`, in `src/`. `rewriteRelativeImportExtensions` is off, so `tsc` copies
85+
specifiers into the emitted `.d.ts` verbatim. `check-dist-specifiers.ts` additionally allows a
86+
`./foo.js` specifier in a `.d.ts` to resolve to a sibling `foo.d.ts` with no `foo.js` beside it (a
87+
type-only module erased by the bundler), and allows `./foo.jsx` under `dist/source`.
88+
89+
### Reactivity
90+
91+
- **Never destructure props.** `splitProps`/`mergeProps` only. A destructure at the top of a
92+
component silently severs reactivity and passes every type check. This is the single most likely
93+
Solid-specific defect class — check it at review of every component.
94+
- **`unwrap()` before handing anything to the manager.** The manager's Immer deep-freeze rejects a
95+
store proxy.
96+
- **`unwrap()` the manager itself** before reading its history (`UndoRedoActions`).
97+
`QueryManager` keeps history in private class fields, which a `Proxy` cannot read through.
98+
- Effects that write back into state use `createEffect(on([...explicit deps], ...))`, never a bare
99+
auto-tracking effect — the tracked set changing across branches is exactly the loop failure mode.
100+
Writes go through `untrack`, plus a re-entrancy flag.
101+
- Return getter objects (not objects of accessors, not memoized fresh objects) from composables
102+
whose result is read once by a Solid context or passed as a prop.
103+
104+
### DOM parity
105+
106+
- Build class strings with core's `clsx` exclusively. Never template interpolation.
107+
- Element order and conditional rendering are specified by React's `Rule.tsx` / `RuleGroup.tsx`.
108+
Read them as a spec, not as code to translate.
109+
- `Label` is a plain function component, not a fragment-returning helper with stray whitespace.
110+
111+
### Types
112+
113+
- `ReactNode``LabelNode` (`JSX.Element | string`); titles stay `string`.
114+
- `ComponentType<P>` → Solid's `Component<P>`.
115+
- Use `import type` for type-only imports (`verbatimModuleSyntax` is on).
116+
- **TypeScript is pinned to `^5.9`.** Neither `vite-plugin-solid`'s babel preset nor the
117+
declaration pipeline is validated against TypeScript 7.
118+
119+
## Gates
120+
121+
**Standing rule: every gate must be proven to fail.** When a step adds a gate, deliberately break
122+
it, record that it went red, then revert. A gate that cannot fail is worse than none.
123+
124+
Current gates (step 1): `fmt:check`, `build`, `check`, `check:exports`, `lint`, `test:coverage`
125+
(global 80% lines — vacuous until step 3 adds real executable code in `src/reactive/`), `test:ssr`.
126+
(`conformance` is a stub that exits 0 until step 6; it is not a gate yet.)
127+
128+
All five were proven red at step 1 and reverted: coverage (threshold to 99 + an injected
129+
uncovered function), export-condition **order** (`import` moved first), export-condition
130+
**target** (`solid` repointed at `dist/index.js`), the SSR **markup** assertion (component's label
131+
dropped), and `check-dist-specifiers` (a directory import appended to `dist/index.d.ts`).
132+
133+
⚠️ Two assertion shapes that look like gates but are not, both found and removed in review — do
134+
not reintroduce them:
135+
136+
- Checking `exports['.'].solid` by key lookup instead of by position. Order is the bug; presence
137+
is not.
138+
- Scanning `dist/index.js` for ssr-only specifiers to prove the dom and ssr builds differ.
139+
`dist/index.js` is a pure re-export barrel with no runtime code, so the check can never fire.
140+
Build distinctness is now covered properly by the two Node resolutions in `test:ssr`.
141+
142+
⚠️ Coverage-gate proof caveat: with `src/index.ts` a pure `export *`, v8 reports `0/0` and the
143+
threshold passes vacuously. The step-1 proof must also inject an uncovered multi-line function
144+
body to demonstrate the gate is live; non-vacuity is re-confirmed for real at step 3.
145+
146+
## Coverage
147+
148+
Coverage is configured in the **root** `vitest.config.ts` only. A `coverage` block in the
149+
package's `vite.config.ts` is silently ignored when the suite runs through `test.projects`, which
150+
is how CI runs it.
151+
152+
## Generated / fetched files
153+
154+
- `packages/solid-querybuilder/test/fixtures/` — downloaded by `scripts/fetch-fixtures.ts` (added
155+
at step 6), gitignored. A fresh clone must pass `bun run test` without them.
156+
157+
## Repo status
158+
159+
**Local only.** No `git init`, no GitHub remote. `.gitignore` and `.github/workflows/ci.yml` exist
160+
so they are in place whenever the repo is initialized.

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
6+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
### Added
11+
12+
- Repo bootstrap: Bun workspaces, root tooling config (`oxfmt`, `oxlint`, `.editorconfig`,
13+
`.npmrc`), root `vitest.config.ts` with `v8` coverage (80% lines), and CI (`main` gating job on
14+
`solid-js@1.9`, `solid-next` non-gating leg on `solid-js@2.0.0-beta`).
15+
- `packages/solid-querybuilder` scaffold: the Solid triple `exports` map (`solid``types`
16+
`import`), build pipeline (`vite build` dom bundle, `tsc --jsx preserve` source bundle, types,
17+
css), `check:exports` specifier guard, and `scripts/ssr-smoke.ts` as a real gate from day one —
18+
it asserts the `solid` condition is **first** in the exports map and confirms that with Node's
19+
real resolver run with and without `--conditions=solid`, then renders through Vite's SSR
20+
pipeline inside a single Solid instance and asserts the exact markup.

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2026 Jake Boone and other contributors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

0 commit comments

Comments
 (0)