Skip to content

Grok repo idiomaticity review (narrowed) #3

Description

@jakeboone02

Scope

Review of the vue-querybuilder codebase against the goal of an idiomatic Vue 3 library that reuses @react-querybuilder/core for all query logic and preserves DOM parity with React Query Builder.

Narrowed 2026-09-21. Five of the eight original sections were already satisfied; two are refuted. See the audit comment for the full disposition table and the reasoning behind each closure. Only the open items remain below.

Architecture (context)

  • Query state is owned by QueryManager from core.
  • Reactive layer: shallowRef of immutable queries, useQueryBuilder / useRule / useRuleGroup / useQueryActions / useValueEditorReset, provide/inject for config inheritance, scoped slots for every control key.
  • Four driving modes: defaultQuery, query + onQueryChange, v-model:query, external manager.
  • Conformance suite asserts byte-identical DOM output against upstream fixtures.
  • DnD, UI-framework packages, expr/datetime UI, and async option lists are explicitly out of scope for v1.

No change to the overall architecture is required; the items below are refinements toward a clearer, more idiomatic Vue API surface.

Open

1. Typing helpers for authors of replacement controls

ControlComponent stays unparameterized — the reasoning at src/types/controls.ts:190-210 holds and is not reopened. The ergonomics gap it leaves is real, though, and ControlPropsMap already holds the data.

Add a public alias:

export type ControlProps<
  K extends ControlKey,
  F extends FullField = FullField,
  O extends string = string,
> = ControlPropsMap<F, O>[K];
  • K constrained to core's ControlKey, so the port cannot drift from core's canonical key list.
  • Exported publicly from src/types/controls.ts.
  • docs/customization.md gains defineProps<Pick<ControlProps<'valueEditor'>, 'value' | 'handleOnChange'>>().
  • Gate: a src/types/types.test-d.ts case asserting mutual assignability with ValueEditorProps. Proven red by removing a member from the target.

Type-only. No runtime code, no coverage impact.

2. Pinia / store recipe (docs only)

Extend docs/customization.md "Driving the query from outside" with a Pinia subsection holding the QueryManager in a store.

Worth demonstrating rather than leaving implied: this is exactly the case core 8.23.0 made work. QueryManager now keeps its state in a non-enumerable, symbol-keyed own property carrying __v_skip, so a manager held in a store is readable through the proxy and is not deep-proxied. On an older core the same code threw Cannot read private member #past.

Scope: a fenced example only — no Pinia dependency, no examples/demo integration. Cross-reference the proxy-safety note in docs/differences-from-react-querybuilder.md §3.

3. The as unknown as slot merge, timeboxed

src/components/QueryBuilder.vue:94 casts the merged props/slots object through unknown. The likely cause is generic variance between the component's own RG parameter and the RuleGroupTypeAny that useQueryBuilder is called with — not a slot-merge typing gap, in which case a typed merge helper removes nothing.

Timebox to one sitting:

  1. Try a typed merge helper and see whether the cast collapses to a single as, or disappears.
  2. If the variance survives (expected), keep the cast and replace it with a one-line comment naming RG vs RuleGroupTypeAny as the reason.

Either outcome beats an unexplained double cast. Do not spend a second sitting on it.

Accepted from a refuted item

src/composables/useQueryBuilder.ts will not be split (see the audit comment), but it gains banner comments delimiting the five phases the original review named: manager lifecycle, reconfigure watcher and equality gate, resolver derivation, schema assembly, controlled write-back. Zero behavior change.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions