@structuralists/scaffolding 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,14 @@
1
- import { useState } from 'react';
2
- import type { FormErrors, FormHelpers, FormValuesObject } from './types';
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { createFormDebugger } from './FormDebugger';
3
+ import type { FormDebuggerComponent } from './FormDebugger';
4
+ import { createSnapshotStore } from './snapshotStore';
5
+ import type { SnapshotStore } from './snapshotStore';
6
+ import type {
7
+ FormDebugSnapshot,
8
+ FormErrors,
9
+ FormHelpers,
10
+ FormValuesObject,
11
+ } from './types';
3
12
  import type { Refine, Validations } from '../validations/types';
4
13
 
5
14
  // `const V` freezes the inferred type of an inline `constraints` object —
@@ -38,6 +47,31 @@ export const useFormState = <
38
47
 
39
48
  const isValid = Object.keys(errors).length === 0;
40
49
 
50
+ // Debugger plumbing: one store + one component per hook instance, created
51
+ // lazily on first render. The component's identity must be stable across
52
+ // renders — recreated each render it would remount (and lose its
53
+ // open/closed state) on every keystroke.
54
+ const debugRef = useRef<{
55
+ store: SnapshotStore<FormDebugSnapshot<T>>;
56
+ Debugger: FormDebuggerComponent;
57
+ } | null>(null);
58
+ if (debugRef.current === null) {
59
+ const store = createSnapshotStore<FormDebugSnapshot<T>>({
60
+ values,
61
+ errors,
62
+ isValid,
63
+ submitAttempted,
64
+ });
65
+ debugRef.current = { store, Debugger: createFormDebugger(store) };
66
+ }
67
+ const { store, Debugger } = debugRef.current;
68
+
69
+ // Publish after every commit. With no debugger window subscribed this is a
70
+ // field write and an empty notify loop — effectively free.
71
+ useEffect(() => {
72
+ store.publish({ values, errors, isValid, submitAttempted });
73
+ });
74
+
41
75
  const submit = () => {
42
76
  setSubmitAttempted(true);
43
77
  if (!isValid) return;
@@ -46,5 +80,13 @@ export const useFormState = <
46
80
  onSubmit?.(values as Refine<T, V>);
47
81
  };
48
82
 
49
- return { values, onValueChanges, errors, isValid, submitAttempted, submit };
83
+ return {
84
+ values,
85
+ onValueChanges,
86
+ errors,
87
+ isValid,
88
+ submitAttempted,
89
+ submit,
90
+ Debugger,
91
+ };
50
92
  };
@@ -0,0 +1,35 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+
4
+ import { defineConfig } from 'vitest/config';
5
+
6
+ import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
7
+
8
+ import { playwright } from '@vitest/browser-playwright';
9
+
10
+ const dirname =
11
+ typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url));
12
+
13
+ // Runs every story as a Vitest test in headless Chromium: each story must
14
+ // mount without throwing, its play function (if any) must pass, and the
15
+ // a11y addon reports axe-core violations (see `a11y` in .storybook/preview.tsx).
16
+ // More info: https://storybook.js.org/docs/writing-tests/integrations/vitest-addon
17
+ export default defineConfig({
18
+ test: {
19
+ projects: [
20
+ {
21
+ extends: true,
22
+ plugins: [storybookTest({ configDir: path.join(dirname, '.storybook') })],
23
+ test: {
24
+ name: 'storybook',
25
+ browser: {
26
+ enabled: true,
27
+ headless: true,
28
+ provider: playwright({}),
29
+ instances: [{ browser: 'chromium' }],
30
+ },
31
+ },
32
+ },
33
+ ],
34
+ },
35
+ });
package/CLAUDE.md DELETED
@@ -1,55 +0,0 @@
1
- # @structuralists/scaffolding
2
-
3
- Generic React component library. Storybook for dev.
4
-
5
- Designed to be used to scaffold up an app
6
-
7
- ## Conventions
8
-
9
- ### Component prop destructuring
10
-
11
- React components must take a single argument named `props` and destructure
12
- on the first line of the function body — not in the parameter list.
13
-
14
- ```tsx
15
- // ✅ correct
16
- export const Foo = (props: FooProps) => {
17
- const { a, b, c } = props;
18
-
19
- return <div>{a}</div>;
20
- };
21
-
22
- // ❌ wrong — destructures in the parameter list
23
- export const Foo = ({ a, b, c }: FooProps) => {
24
- return <div>{a}</div>;
25
- };
26
- ```
27
-
28
- Why: the destructuring line at the top of the body acts as a quick legend
29
- of what the component reads from its props, scannable without parsing the
30
- function signature. Keeps the call shape uniform across the package.
31
-
32
- ### Custom hook arguments
33
-
34
- Project-defined hooks must take a single argument named `args` (an object)
35
- and destructure on the first line of the function body — same shape as the
36
- component-prop rule above.
37
-
38
- ```ts
39
- // ✅ correct
40
- export const useFoo = (args: UseFooArgs) => {
41
- const { a, b, c } = args;
42
- // ...
43
- };
44
-
45
- // ❌ wrong — positional args
46
- export const useFoo = (a: string, b: number, c?: boolean) => {
47
- // ...
48
- };
49
- ```
50
-
51
- Why: named args read clearly at the call site (`useFoo({ a, b })`), survive
52
- reordering, and let new optional fields be added without breaking callers.
53
- Built-in React hooks (`useState`, `useEffect`, etc.) keep their stock
54
- positional signatures — this rule applies only to hooks defined in this
55
- package.