@lariocpt/flawless-react 0.0.0-stage → 0.3.1

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.
Files changed (143) hide show
  1. package/.agents/skills/flawless-a11y/SKILL.md +113 -0
  2. package/.agents/skills/flawless-api/SKILL.md +159 -0
  3. package/.agents/skills/flawless-api-fixtures/SKILL.md +144 -0
  4. package/.agents/skills/flawless-e2e/SKILL.md +155 -0
  5. package/.agents/skills/flawless-expo/SKILL.md +283 -0
  6. package/.agents/skills/flawless-findings/SKILL.md +116 -0
  7. package/.agents/skills/flawless-load/SKILL.md +112 -0
  8. package/.agents/skills/flawless-nextjs/SKILL.md +198 -0
  9. package/.agents/skills/flawless-perf-harness/SKILL.md +158 -0
  10. package/.agents/skills/flawless-perf-static/SKILL.md +131 -0
  11. package/.agents/skills/flawless-react/SKILL.md +403 -0
  12. package/.agents/skills/flawless-react-rules/SKILL.md +96 -0
  13. package/.agents/skills/flawless-regen/SKILL.md +135 -0
  14. package/.agents/skills/flawless-unit-browser/SKILL.md +178 -0
  15. package/LICENSE +21 -0
  16. package/README.md +611 -2
  17. package/bin/flawless-react +4 -0
  18. package/package.json +67 -4
  19. package/scripts/postinstall.d.mts +46 -0
  20. package/scripts/postinstall.mjs +127 -0
  21. package/src/analyse/authShims.ts +220 -0
  22. package/src/analyse/bootability.ts +257 -0
  23. package/src/analyse/childBridge.ts +302 -0
  24. package/src/analyse/childrenIntoSlot.ts +66 -0
  25. package/src/analyse/compoundFamily.ts +100 -0
  26. package/src/analyse/dataExports.ts +200 -0
  27. package/src/analyse/extractChildAttrs.ts +112 -0
  28. package/src/analyse/extractDataDeps.ts +857 -0
  29. package/src/analyse/extractDisclosures.ts +186 -0
  30. package/src/analyse/extractFetchCalls.ts +81 -0
  31. package/src/analyse/extractFlows.ts +239 -0
  32. package/src/analyse/extractGuards.ts +270 -0
  33. package/src/analyse/extractHooks.ts +237 -0
  34. package/src/analyse/extractInteractions.ts +1421 -0
  35. package/src/analyse/extractLogging.ts +45 -0
  36. package/src/analyse/extractNativeA11y.ts +138 -0
  37. package/src/analyse/extractPredicates.ts +784 -0
  38. package/src/analyse/extractPropUsage.ts +162 -0
  39. package/src/analyse/extractProps.ts +1212 -0
  40. package/src/analyse/extractRenderMode.ts +165 -0
  41. package/src/analyse/extractRootTag.ts +117 -0
  42. package/src/analyse/extractState.ts +361 -0
  43. package/src/analyse/extractStaticText.ts +212 -0
  44. package/src/analyse/extractVariants.ts +186 -0
  45. package/src/analyse/harvestApiEndpoints.ts +284 -0
  46. package/src/analyse/hostProps.ts +185 -0
  47. package/src/analyse/hostTag.ts +72 -0
  48. package/src/analyse/htmlContentModel.ts +94 -0
  49. package/src/analyse/index.ts +744 -0
  50. package/src/analyse/mountPathCalls.ts +442 -0
  51. package/src/analyse/nativeContentModel.ts +129 -0
  52. package/src/analyse/nativeDialect.ts +83 -0
  53. package/src/analyse/nativeProps.ts +339 -0
  54. package/src/analyse/ownReturns.ts +31 -0
  55. package/src/analyse/parseFile.ts +919 -0
  56. package/src/analyse/pos.ts +8 -0
  57. package/src/analyse/providerWraps.ts +1977 -0
  58. package/src/analyse/pureModule.ts +88 -0
  59. package/src/analyse/reexportTable.ts +148 -0
  60. package/src/analyse/refs.ts +195 -0
  61. package/src/analyse/renderPath.ts +441 -0
  62. package/src/analyse/resolveDepReturns.ts +370 -0
  63. package/src/analyse/resolveIndexKeys.ts +98 -0
  64. package/src/analyse/resolveModule.ts +431 -0
  65. package/src/analyse/resolveTagRootTags.ts +103 -0
  66. package/src/analyse/serverActionReturns.ts +213 -0
  67. package/src/analyse/sourceCache.ts +263 -0
  68. package/src/analyse/spreadSiblings.ts +98 -0
  69. package/src/analyse/walkChildren.ts +98 -0
  70. package/src/analyse/zodSchema.ts +424 -0
  71. package/src/bin.ts +23 -0
  72. package/src/changed.ts +221 -0
  73. package/src/cli.ts +560 -0
  74. package/src/cliFlags.ts +281 -0
  75. package/src/core.ts +1229 -0
  76. package/src/docLine.ts +122 -0
  77. package/src/emitPaths.ts +51 -0
  78. package/src/expo/detect.ts +184 -0
  79. package/src/expo/refusal.ts +62 -0
  80. package/src/expo/routes.ts +145 -0
  81. package/src/index.ts +95 -0
  82. package/src/mcp/analysisTools.ts +720 -0
  83. package/src/mcp/infoTools.ts +135 -0
  84. package/src/mcp/protocol.ts +130 -0
  85. package/src/mcp/run.ts +269 -0
  86. package/src/mcp/runTools.ts +362 -0
  87. package/src/mcp/server.ts +279 -0
  88. package/src/mcp/spec.ts +86 -0
  89. package/src/mcp/support.ts +268 -0
  90. package/src/mcp/tools.ts +75 -0
  91. package/src/nextjs/apiRoutes.ts +116 -0
  92. package/src/nextjs/detect.ts +397 -0
  93. package/src/nextjs/emit.ts +106 -0
  94. package/src/nextjs/liveSpec.ts +79 -0
  95. package/src/nextjs/mswHandlers.ts +55 -0
  96. package/src/nextjs/providerWrapper.ts +603 -0
  97. package/src/nextjs/routes.ts +86 -0
  98. package/src/nextjs/swagger.ts +32 -0
  99. package/src/packageJson.ts +414 -0
  100. package/src/platform.ts +18 -0
  101. package/src/prompt.ts +132 -0
  102. package/src/reactCompiler.ts +165 -0
  103. package/src/reporters/findings.ts +156 -0
  104. package/src/reporters/report.ts +463 -0
  105. package/src/runner/runTests.ts +402 -0
  106. package/src/scaffold/adoptStyle.ts +181 -0
  107. package/src/scaffold/index.ts +253 -0
  108. package/src/scaffold/openapi.ts +183 -0
  109. package/src/scaffold/templates.ts +2379 -0
  110. package/src/scaffold-tests/index.ts +463 -0
  111. package/src/scaffold-tests/mockValues.ts +208 -0
  112. package/src/scaffold-tests/mocks.ts +794 -0
  113. package/src/scaffold-tests/nativeIndex.ts +52 -0
  114. package/src/scaffold-tests/nativeTemplates.ts +1528 -0
  115. package/src/scaffold-tests/postDispatch.ts +152 -0
  116. package/src/scaffold-tests/registry.ts +443 -0
  117. package/src/scaffold-tests/selectors.ts +493 -0
  118. package/src/scaffold-tests/selectorsNative.ts +273 -0
  119. package/src/scaffold-tests/stamp.ts +79 -0
  120. package/src/scaffold-tests/templates.ts +3578 -0
  121. package/src/skills/cli.ts +248 -0
  122. package/src/skills/compat.ts +166 -0
  123. package/src/skills/data.ts +192 -0
  124. package/src/skills/md.d.ts +7 -0
  125. package/src/skills/menu.ts +55 -0
  126. package/src/skills/spec.ts +227 -0
  127. package/src/suiteScripts.ts +110 -0
  128. package/src/tasks/a11yNative.ts +281 -0
  129. package/src/tasks/a11yScan.ts +67 -0
  130. package/src/tasks/biomeRunner.ts +301 -0
  131. package/src/tasks/biomeSweep.ts +166 -0
  132. package/src/tasks/e2eExpo.ts +436 -0
  133. package/src/tasks/e2eRoutes.ts +756 -0
  134. package/src/tasks/eslintReactRules.ts +67 -0
  135. package/src/tasks/index.ts +24 -0
  136. package/src/tasks/loadExpo.ts +273 -0
  137. package/src/tasks/loadPipes.ts +750 -0
  138. package/src/tasks/perfHarness.ts +145 -0
  139. package/src/tasks/staticPerfAnalysis.ts +510 -0
  140. package/src/tasks/types.ts +1 -0
  141. package/src/tasks/unitAndBrowser.ts +462 -0
  142. package/src/types.ts +1419 -0
  143. package/src/version.ts +12 -0
@@ -0,0 +1,178 @@
1
+ ---
2
+ name: flawless-unit-browser
3
+ description: Work with flawless-react's generated unit and browser specs — what they assert, the provider wrapper and its write-once fixtures, the mock allowlist, answering test.skip and TODO(flawless) markers, hand-hardening colocated specs, and registering new components in the browser host. Use when a generated unit/browser spec is red, when filling TODOs after a run, or when a component's browser spec reports "unknown component".
4
+ ---
5
+
6
+ # flawless unit + browser specs
7
+
8
+ > **Expo projects use a different plane entirely.** Under `--expo` this task emits
9
+ > jest-expo + @testing-library/react-native specs into `<outDir>/unit/`, with
10
+ > `renderRouter` for route screens, no browser half at all, and a selector ladder that
11
+ > has no last-resort rung. Everything below describes the **DOM** plane — see the
12
+ > `flawless-expo` skill for the native one.
13
+
14
+ The `unit-browser` task writes Vitest + Testing Library specs from static
15
+ analysis; when `perf-harness` is also selected it adds Playwright browser specs
16
+ that mount through the profiling host. Everything asserted was derived — the
17
+ specs describe the render at the props they pass, never the last `return` in
18
+ the body.
19
+
20
+ ## The provider wrapper and its fixtures
21
+
22
+ Unit specs mount inside `flawless/provider.tsx`, derived from the layout's JSX
23
+ chain around `{children}`. That file is regenerated; the values you supply live
24
+ in `flawless/provider.fixtures.ts`, which is written once and never again —
25
+ not even with `--force`.
26
+
27
+ Plain (non-Next) mode has no `provider.tsx`: the wraps are derived per render
28
+ instead. Each spec imports its nest under `__Prov`-prefixed bindings — the
29
+ app's own providers found by hook call, namespace compound, transitive
30
+ requirement and dynamic render (a `<Component />` whose tag a project hook
31
+ handed back: `const { Component } = useTabs()` — the component owes what the
32
+ hook's values can hand out, the hook body's own JSX plus one import hop into
33
+ the registry it selects from), convention matches for bare-module hooks (`useTheme` → the
34
+ exported `ThemeProvider` that renders that module's binding), and generated
35
+ shims for library contexts (`FlawlessQueryClientProvider`,
36
+ `FlawlessRouterProvider`/`FlawlessDataRouterProvider`, `FlawlessFormProvider`,
37
+ `FlawlessIntlProvider`). A mount failing on "useX must be used within a
38
+ XProvider" in plain mode means the derivation could not see the provider:
39
+ check that it is exported under the convention name and renders a binding from
40
+ the hook's own module; if the app side cannot be fixed, mount it with
41
+ `--provider`, which renders outside the derived nest.
42
+
43
+ The single highest-value hardening move: fill `initialState` (or whatever the
44
+ layout passed) with a REAL seeded shape instead of the emitted empty cast.
45
+ Mirror the app's seed data so every unit spec mounts the way the app actually
46
+ renders — authenticated, themed, populated:
47
+
48
+ ```ts
49
+ initialState: {
50
+ user: { id: "…", email: "trustee@demo.local", name: "…", /* seed persona */ },
51
+ status: "authenticated",
52
+ } satisfies ComponentProps<typeof StoreProvider>["initialState"],
53
+ ```
54
+
55
+ Pin any timestamp in the fixture — `Date.now()` in a fixture makes specs
56
+ nondeterministic.
57
+
58
+ ## The mock allowlist, and the cn caveat
59
+
60
+ Client specs mock only `next/navigation`, `next/headers`, `server-only`,
61
+ `next/link`, `next/font/*`, and `"use server"` modules. Do not widen it by
62
+ hand: mocking a UI component replaces the element the interaction tests click.
63
+
64
+ Historical wart, fixed in FR-027 but still present in suites generated before
65
+ it: specs whose file imports `cn` from a utils module may carry
66
+ `vi.mock("@/lib/utils", () => ({ cn: vi.fn(async () => ({})) }))` — an async
67
+ stub for a sync function. A regeneration emits the sync form; to fix an old
68
+ spec in place, replace it with a sync join so classes stay legible:
69
+
70
+ ```ts
71
+ vi.mock("@/lib/utils", () => ({
72
+ cn: vi.fn((...args: unknown[]) => args.filter(Boolean).join(" ")),
73
+ }));
74
+ ```
75
+
76
+ ## Answering skips and TODOs
77
+
78
+ `test.skip` with a named reason is intended output — never "fix" it by deleting
79
+ the assertion or hand-writing one. Give the component what the reason asks for
80
+ (a `data-testid`, a fixture) or extend the generator. `TODO(flawless)` markers
81
+ cluster into three kinds, each with a known answer:
82
+
83
+ | TODO kind | The answer |
84
+ | --- | --- |
85
+ | "some return shapes could not be inferred" | The tool already tried the dep's own declaration (a `Promise<T>` annotation, or a single-fetch body joined to the OpenAPI example — those fixtures carry an `/* inferred from … */` comment and no TODO), so this marker means BOTH usage and declaration came up empty: a computed return, a generic instantiation other than `Record<K,V>` or `Partial<T>` (both of which resolve to `{}`, a complete inhabitant), an `interface X extends Y` whose BASE does not resolve (the derived one merges its bases and bails whole if any fails), a dotted callee like `db.query.findMany`, an unannotated client factory (`getDb`), a `cache()`-wrapped export the function mapper cannot unwrap, or an annotation whose type — even one resolved through a workspace package's barrel, which works since B-8 — has a schema construct zod inference deliberately bails on (`.transform`, a union, a record, a date — `z.infer` enums and objects DO resolve since B-9, so a surviving marker means the schema left the supported subset). Give the mocked data-dep a realistic resolved value. If every consumer of the handle is itself mocked (a `getDb` whose queries are all mocked above), an opaque `{}` IS the right fixture — replace the TODO with a comment saying so. |
86
+ | "wrap renderTarget() in the matching Root" | The tool now does this itself when the part's OWN file exports the family (a Root-rendering sibling + a Content sibling) and the family is open-state (dialog and friends), so this TODO only appears for rootless files, cross-file families, or value-driven families (accordion/tabs). Mount the part inside its Root the way call sites do: `render(<AlertDialog><AlertDialogTrigger>Delete</AlertDialogTrigger></AlertDialog>)`, then assert its content — dialog-family parts need the content wrapper too (`<Dialog open><DialogContent>…`), and portal content is read off `baseElement`. |
87
+ | "server action stubbed — give it a realistic return value" | The tool now does this itself when the action returns a single literal (redirect/notFound exits discounted), so this TODO only appears when inference bailed: computed returns, two distinct shapes, or an unresolved sibling export. Read the action, pick the shape the component actually consumes, and write it into the stub. |
88
+
89
+ When you fill a fixture, also tighten the assertion: a mocked building named
90
+ `"flawless-name"` must actually reach the DOM
91
+ (`expect(screen.getByText("flawless-name")).toBeInTheDocument()`), or the spec
92
+ passes while rendering an empty state — the vacuous pass this tool exists to
93
+ prevent.
94
+
95
+ ## Gated steps: the spec opens the disclosure first
96
+
97
+ A control that only exists once a disclosure is open (a button inside a dialog,
98
+ menu or popover) cannot be clicked by a step that never opened it. When the
99
+ analysis says a trigger's label is **static text arriving as a child**, the step
100
+ is emitted *gated*: it opens the disclosure first, clicking the spec's own child
101
+ span, resets the spies so the isolation asserts stay meaningful, and then queries
102
+ its real target with the awaiting `findBy*` form —
103
+
104
+ ```tsx
105
+ await userEvent.click(screen.getAllByText(CHILD_TEXT)[0] as HTMLElement);
106
+ for (const v of Object.values(handlers)) v.mockClear();
107
+ await userEvent.click((await screen.findAllByText("Confirm"))[0] as HTMLElement);
108
+ expect(handlers.onConfirm).toHaveBeenCalled();
109
+ ```
110
+
111
+ `CHILD_TEXT` is the `"flawless child"` string the spec renders into a component
112
+ that accepts children, so a children-kind gate is only emitted where the spec
113
+ declares it. The other gate labels come from the same ladder as any target:
114
+ `data-testid`, `aria-label`, literal text. Three things to know:
115
+
116
+ - It exists in the unit and browser specs. Route e2e specs do not open dialogs;
117
+ they assert what the route renders on its own.
118
+ - A disclosure the component holds open itself — a bare `open`, `defaultOpen` or
119
+ `open={true}` at the call site — is not gated. Nothing is closed, so there is
120
+ nothing to open first.
121
+ - **A gated step with no prelude is a bug report, not a skip.** If the label
122
+ could not be resolved the step is emitted unprotected and goes red on the
123
+ click. Do not delete the assertion and do not mark it skipped: either give the
124
+ trigger an accessible name the analyser can see (a `data-testid`, an
125
+ `aria-label`, a literal child) or teach the generator that label shape. Same
126
+ rule as an unlabelled canary — the red is the tool telling you where its own
127
+ reading stopped.
128
+
129
+ ## Placement and survival
130
+
131
+ Colocated specs (`--colocate`, `<file>.flawless.test.tsx` beside the source)
132
+ are kept on re-runs, and their first line is a provenance stamp —
133
+ `// @flawless-generated sha256:<hex16> flawless-react@<version>`, hashing the
134
+ rest of the file. `--force` rewrites a spec only while that stamp still
135
+ matches; the moment you hand-harden one, the mismatch protects it (kept and
136
+ listed in the summary as hand-edited). Leave the stamp line alone when
137
+ editing: deleting it does not make the file regenerable, it just reclassifies
138
+ it as pre-stamp — the body edit already marks the file as yours, and the
139
+ delete-the-whole-file escape hatch is how you hand a spec back to the tool.
140
+ Specs inside the out dir are rewritten on any re-run; never hand-harden those.
141
+
142
+ Run them with `bun run flawless:unit` (the emitted vitest config selects both
143
+ `src/**/*.flawless.test.*` and `flawless/unit/**`). A project script that is a
144
+ bare `bun test` will collect colocated specs under the wrong runner — scope it
145
+ to `bun test tests`.
146
+
147
+ ## Browser specs and the host registry
148
+
149
+ Browser specs mount through `flawless/host/` and exist only when both
150
+ `unit-browser` and `perf-harness` ran. The mount tables (`REGISTRY`,
151
+ `FN_PROPS`, `WRAP_IN`) live in `host/registry.ts`, which **regenerates on
152
+ every run** — a newly analysed component arrives via a plain no-force re-run,
153
+ nothing to hand-edit. The write-once `host/main.tsx` is only the wrapper
154
+ (polyfills, provider, MSW, the profiling machinery); components you register
155
+ by hand go in its spreads — `ALL` for the component (`const ALL = {
156
+ ...REGISTRY, MyComponent: MyComponent as unknown as Component }` — the cast
157
+ is required: strict tsconfig rejects a props-typed component in a
158
+ Record<string, Component>), plus `ALL_FN_PROPS`/`ALL_WRAP_IN` entries when it
159
+ needs function-prop stubs or a wrapper chain —
160
+ which every lookup goes through and which survives regeneration.
161
+
162
+ `unknown component: <Name>` on a current suite means the re-run has not
163
+ happened yet — re-run the tool. On a pre-split suite (a `main.tsx` with no
164
+ `from "./registry"` import) it needs the one-time migration the run's warning
165
+ names: delete `host/main.tsx` and re-run (provider wiring regenerates from
166
+ `--provider`), or port the hand wiring into a wrapper that imports
167
+ `./registry`. No flag rewrites `main.tsx` — write-once is absolute, `--force`
168
+ included. (This was findings ledger FR-016, fixed by the registry split.)
169
+
170
+ ## Never
171
+
172
+ - Never delete a reasoned `test.skip` or replace it with a hollow assertion.
173
+ - Never mock `cn`/`clsx` away entirely (classes vanish) or mock UI components.
174
+ - Never hand-harden a spec inside the out dir — colocate it first.
175
+ - Never delete a colocated spec's stamp line casually — that demotes the file
176
+ to pre-stamp; edit the body and let the stamp mismatch protect it.
177
+ - Never trust a green run whose fixtures are empty casts: fill, then assert
178
+ the fixture content reaches the DOM.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lario Borges
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.