tw-migrate 0.1.0-alpha.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.
@@ -0,0 +1,338 @@
1
+ # RFC: Browser Ecosystem End-to-End Validation
2
+
3
+ ## Status
4
+
5
+ Implemented
6
+
7
+ ## Summary
8
+
9
+ `tw-migrate` currently verifies planner output, source edits, CLI behavior, and transactional writes without proving that a migrated application renders the same result in a browser. This RFC adds an opt-in Vitest Browser Mode suite that compares migration results in Chromium before and after migration.
10
+
11
+ The suite has two permanent coverage layers:
12
+
13
+ 1. Twelve repository-owned controlled fixtures cover the Cartesian product of React+Vite, Next.js, and Vite+HTML with CSS, SCSS, indented Sass, and Less.
14
+ 2. Two to five pinned public projects cover installation and runtime compatibility in existing Tailwind CSS v4 applications.
15
+
16
+ External projects supplement the controlled fixtures. They do not replace them. Public projects cannot reliably provide every runtime and stylesheet combination, stable source expectations, or long-lived interaction probes. Controlled fixtures provide that deterministic contract; external projects detect integration assumptions that the fixtures do not model.
17
+
18
+ External repositories are checked out only by the dedicated GitHub Actions workflow. The repository does not vendor them or require contributors to maintain local sibling clones.
19
+
20
+ ## Background
21
+
22
+ The existing test layers answer different questions:
23
+
24
+ - Rust tests verify parsing, selector analysis, and utility planning.
25
+ - Node tests verify the public API, source maps, and byte-exact rewrites.
26
+ - Packaged CLI snapshots verify installation, output, exit status, and filesystem behavior.
27
+
28
+ None of these layers starts a real application toolchain and asks Chromium whether the migrated utilities preserve the computed result. A candidate can compile and a source rewrite can look correct while Vite, Next.js, PostCSS, a preprocessor, or Tailwind produces different browser styles.
29
+
30
+ An external-only matrix was considered and rejected. Public Tailwind v4 projects do not provide a dependable twelve-cell set, particularly for `.module.sass` and Next.js Less. Their source, scripts, routes, and selectors also change independently of this repository. Filling the gaps by patching those projects would turn them into less deterministic controlled fixtures with a network dependency.
31
+
32
+ ## Goals
33
+
34
+ 1. Prove that migrated utilities preserve standard computed styles in Chromium.
35
+ 2. Cover React+Vite, Next.js, and Vite+HTML across CSS, SCSS, Sass, and Less.
36
+ 3. Exercise CSS Modules in React and Next.js and global stylesheets in static HTML.
37
+ 4. Verify exact migration reports and source bytes for controlled cases.
38
+ 5. Verify second-run idempotency for every case.
39
+ 6. Run against packed root and native packages rather than checkout entrypoints or addons.
40
+ 7. Exercise a small set of unmodified public Tailwind v4 projects at immutable commits.
41
+ 8. Produce bounded, allowlisted diagnostics for CI failures.
42
+ 9. Keep the default Rust, Node, and packaged snapshot suites independent of browsers and external clones.
43
+
44
+ ## Non-Goals
45
+
46
+ 1. Replacing planner, Node, or packaged CLI tests.
47
+ 2. Using screenshots as the pass/fail oracle.
48
+ 3. Testing Firefox or WebKit before Chromium coverage proves stable.
49
+ 4. Comparing Tailwind or application custom properties.
50
+ 5. Exhaustively testing animation timelines or pseudo-elements.
51
+ 6. Adding Tailwind, routes, selectors, or migration targets to external projects.
52
+ 7. Automatically updating external project pins.
53
+ 8. Running external repository code with secrets, write permissions, or persisted checkout credentials.
54
+ 9. Making the browser suite part of the default `pnpm test` command.
55
+
56
+ ## Coverage Model
57
+
58
+ ### Controlled fixtures
59
+
60
+ The controlled corpus owns combination completeness:
61
+
62
+ | Runtime | CSS | SCSS | Sass | Less |
63
+ | --- | --- | --- | --- | --- |
64
+ | React+Vite | required | required | required | required |
65
+ | Next.js | required | required | required | required |
66
+ | Vite+HTML | required | required | required | required |
67
+
68
+ React+Vite and Next.js use CSS Modules. Vite+HTML uses a linked global stylesheet. Each fixture is a small runnable application with exact dependency versions and migration expectations.
69
+
70
+ Every controlled case declares independent probes for:
71
+
72
+ - base state;
73
+ - hover;
74
+ - focus;
75
+ - focus-visible;
76
+ - below the target breakpoint;
77
+ - above the target breakpoint.
78
+
79
+ Each probe uses a stable role, accessible name, text, data attribute, or id selector. Class selectors are prohibited because migration changes classes. A stable `data-identity` sequence proves that baseline and post-migration captures inspect the same elements in the same order.
80
+
81
+ Controlled cases also assert:
82
+
83
+ - the exact first `MigrationReport`;
84
+ - expected candidate tokens and warning output;
85
+ - exact bytes for every migration-owned changed file;
86
+ - an empty second-run diff and `changedFiles` list;
87
+ - an unchanged source-scoped tree after the second run.
88
+
89
+ These fixtures remain after external coverage is added. Removing them would discard the only deterministic proof of the twelve runtime/style cells and the exact rewrite contract.
90
+
91
+ ### External ecosystem projects
92
+
93
+ The external corpus owns real-project compatibility rather than matrix completeness. It contains two to five public, non-archived, non-fork repositories pinned by full commit SHA.
94
+
95
+ A project qualifies only when it already has:
96
+
97
+ - Tailwind CSS v4;
98
+ - a committed npm, pnpm, or Yarn lockfile;
99
+ - a portable install and start path;
100
+ - a route that does not require authentication or an external API;
101
+ - a stable non-class selector;
102
+ - an existing stylesheet rule that `tw-migrate` converts;
103
+ - reproducible execution on Ubuntu, macOS, and Windows.
104
+
105
+ External cases keep their original source, package manager, lockfile, and scripts. The harness does not inject routes or probes. Each case verifies the declared migration target, browser equality, and source-scoped idempotency. It does not duplicate the controlled corpus's exhaustive report and byte expectations.
106
+
107
+ External repositories are checked out only in CI under the runner's temporary directory. The workflow uses a pinned repository and commit from typed manifest data, verifies `HEAD`, and discards the checkout with the ephemeral runner. Local controlled runs never depend on `../vite-plus`, another sibling checkout, or a previously cloned project.
108
+
109
+ Network, checkout, install, or upstream script failures fail the external case. The workflow does not silently skip them.
110
+
111
+ #### Admitted external projects
112
+
113
+ The manifest SHA is authoritative. The immutable links below record the evidence used to admit each CI-only project.
114
+
115
+ | Case | Repository status | Tailwind v4 and runtime | Migration witness | Auth/API-free route and selector | Reproducibility |
116
+ | --- | --- | --- | --- | --- | --- |
117
+ | `external-namechecker` (`285e10d3627f3eac5217d69e9eaccee956d7ac70`) | Public, non-archived, non-fork ([repository](https://github.com/toddcooke/namechecker/tree/285e10d3627f3eac5217d69e9eaccee956d7ac70)) | [Next.js and Tailwind v4 dependencies](https://github.com/toddcooke/namechecker/blob/285e10d3627f3eac5217d69e9eaccee956d7ac70/package.json) | [`page.module.css`](https://github.com/toddcooke/namechecker/blob/285e10d3627f3eac5217d69e9eaccee956d7ac70/app/page.module.css) is consumed by [`TailwindLayout.tsx`](https://github.com/toddcooke/namechecker/blob/285e10d3627f3eac5217d69e9eaccee956d7ac70/app/TailwindLayout.tsx); `.main` emits `p-4` | `/` renders without submitting its optional lookup API; unique `main` target. Next.js updates the reviewed `tsconfig.json` path on startup; the harness rejects any other tracked write and restores its exact bytes after every server run | macOS arm64 lifecycle passed 2026-07-24; Linux, macOS, and Windows are required by `.github/workflows/ecosystem.yml` before merge |
118
+ | `external-stylized-components` (`a26df5d21457095e466a41966822edb2ff016cff`) | Public, non-archived, non-fork ([repository](https://github.com/cortiz2894/stylized-components/tree/a26df5d21457095e466a41966822edb2ff016cff)) | [Next.js and Tailwind v4 dependencies](https://github.com/cortiz2894/stylized-components/blob/a26df5d21457095e466a41966822edb2ff016cff/package.json) | [`landing.module.css`](https://github.com/cortiz2894/stylized-components/blob/a26df5d21457095e466a41966822edb2ff016cff/src/app/landing.module.css) is consumed by [`page.tsx`](https://github.com/cortiz2894/stylized-components/blob/a26df5d21457095e466a41966822edb2ff016cff/src/app/page.tsx); `.page` emits `h-[calc(100vh_-_53px)]` | `/` renders its local showcase without credentials; `body > div:not([hidden])` target with `Import GLB` readiness | macOS arm64 lifecycle passed 2026-07-24; Linux, macOS, and Windows are required by `.github/workflows/ecosystem.yml` before merge |
119
+
120
+ A pin is updated only after repeating repository-status review, migration preview, source/idempotency checks, utilities-only comparison, and the three-OS workflow run.
121
+
122
+ ### Production CLI smoke
123
+
124
+ One React+Vite CSS fixture also exercises the installed CLI and production build boundary. It captures a clean production baseline, runs package-wide migration through the installed binary, verifies a second CLI no-op, deletes generated output, rebuilds, and compares the production preview.
125
+
126
+ Targeted controlled cases continue to use the installed public `migrate()` API because its structured report supports exact assertions.
127
+
128
+ ## Browser Oracle
129
+
130
+ ### Dynamic baseline
131
+
132
+ Each case captures its baseline immediately before migration on the same OS and Chromium installation used for the post-migration capture. The suite does not commit computed-style golden files. This avoids treating platform-specific browser serialization as a product regression.
133
+
134
+ ### Computed-style comparison
135
+
136
+ For every matched element, the harness reads every property exposed by `getComputedStyle()`, sorts property names, and compares exact values. Properties beginning with `--` are excluded because Tailwind uses internal custom properties whose representation can change while the resulting standard properties remain equal.
137
+
138
+ The exclusion does not permit a no-op migration to pass. Controlled cases pair the browser comparison with exact candidates, reports, and source bytes.
139
+
140
+ ### Causal witness
141
+
142
+ Before migration, the harness temporarily withholds the target stylesheet and captures every probe. Each probe must differ from its baseline in at least one standard computed-style property. This proves that the selected stylesheet contributes to every asserted state.
143
+
144
+ After migration, the harness also captures the app with the migrated legacy stylesheet withheld. That utilities-only capture must equal the baseline. Retained legacy CSS therefore cannot hide missing or incorrect generated utilities.
145
+
146
+ ### Capture stability
147
+
148
+ Each probe runs in a fresh detached Playwright page. The harness:
149
+
150
+ 1. sets the viewport before navigation;
151
+ 2. navigates to the declared route;
152
+ 3. waits for the declared readiness selector and fonts;
153
+ 4. disables transitions and animations;
154
+ 5. performs one trusted Playwright interaction when declared;
155
+ 6. waits for layout to settle;
156
+ 7. checks cardinality and identity;
157
+ 8. records standard computed styles.
158
+
159
+ Only capture operations may retry. They receive one initial attempt and at most three retries, each with a fresh page. Migration, source, report, idempotency, and semantic comparison failures fail immediately.
160
+
161
+ ## Package Isolation
162
+
163
+ The suite tests the publication boundary rather than the checkout boundary.
164
+
165
+ For each operating system, one package job:
166
+
167
+ 1. builds the native addon;
168
+ 2. packs the root package and current-platform native package;
169
+ 3. records package names, versions, commit, platform, addon digest, and tarball digests in a provenance manifest;
170
+ 4. uploads those exact artifacts for the case jobs on that OS.
171
+
172
+ A case job publishes the artifacts to a fresh Verdaccio registry, installs a temporary driver with `--ignore-scripts`, and verifies the resolved root package, native package, and addon against provenance. It rejects checkout paths, workspace links, wrong-platform addons, stale artifacts, and digest mismatches.
173
+
174
+ Verdaccio may proxy normal application dependencies to npm. It must not fetch `tw-migrate` or `tw-migrate-*` packages from upstream. Publishing is enabled only during bootstrap; the install phase uses sealed storage. The registry stops before application code starts.
175
+
176
+ ## Case Lifecycle
177
+
178
+ A targeted case follows this order:
179
+
180
+ 1. Validate the manifest before executing project data.
181
+ 2. Install and verify packed packages.
182
+ 3. Start the baseline server and capture every probe.
183
+ 4. Stop the server.
184
+ 5. Capture the stylesheet-withheld causal witness.
185
+ 6. Restore the source and run the first migration.
186
+ 7. Verify reports, candidates, changed files, and expected bytes.
187
+ 8. Snapshot migration-owned sources and run migration again.
188
+ 9. Verify the second report and source tree are unchanged.
189
+ 10. Clear generated output and framework caches.
190
+ 11. Capture the utilities-only result with migrated legacy CSS withheld.
191
+ 12. Restore migrated source, start a fresh server, and capture post-migration probes.
192
+ 13. Compare baseline and post-migration cardinality, identity, and computed styles.
193
+ 14. Stop all processes and delete temporary successful-run data.
194
+
195
+ Every child process has a bounded timeout and bounded process-tree termination. A teardown failure fails an otherwise successful case and does not replace an earlier primary failure.
196
+
197
+ ## Manifest Contract
198
+
199
+ `ecosystem-ci/projects.json` is typed data, not executable configuration. Controlled, production-smoke, and external entries use separate schemas.
200
+
201
+ Validation rejects:
202
+
203
+ - unknown fields or duplicate IDs;
204
+ - duplicate controlled runtime/style cells;
205
+ - class-based selectors;
206
+ - missing readiness, cardinality, source, or probe declarations;
207
+ - non-full external commit SHAs;
208
+ - local, SSH, or non-HTTPS external repository URLs;
209
+ - command strings, shell fragments, custom environment variables, and eval predicates;
210
+ - absolute paths, traversal, and symlinks escaping the case root;
211
+ - package-manager and lockfile mismatches.
212
+
213
+ External commands come from reviewed identifiers and argument arrays. Package managers use exact numeric npm or pnpm versions. Install steps are limited to script-free locked installs and named pnpm workspace build scripts; servers are limited to `run <script>`. Manifest values never become shell programs, runner labels, action names, artifact roots, or workflow expressions.
214
+
215
+ An external entry may declare up to three reviewed `runtimeWrites` paths for framework startup behavior. The harness snapshots those tracked files at the pinned SHA, rejects every undeclared tracked write after each server phase, and restores declared writes to exact bytes. Runtime writes cannot overlap the lockfile, migration source, or Tailwind entry. All other external projects must keep the tracked checkout clean.
216
+
217
+ ## CI and Contributor Workflow
218
+
219
+ The browser suite runs in a separate GitHub Actions workflow on:
220
+
221
+ - a designated pull-request label;
222
+ - pushes to `main`;
223
+ - manual dispatch.
224
+
225
+ It does not run on a cron schedule. The workflow uses GitHub-hosted Ubuntu, macOS, and Windows runners with Chromium. Package jobs run once per OS; case jobs run independently for each OS and case with `fail-fast: false`.
226
+
227
+ The workflow uses `pull_request`, never `pull_request_target`. Repository permissions are limited to `contents: read`, checkout credentials are not persisted, and external child processes receive an explicit environment containing only path, home, temporary-directory, required Windows system variables, and `CI=true`. They receive no GitHub, Actions, OIDC, cloud, registry, or repository credentials. The verified installed migration module is loaded before any external command runs.
228
+
229
+ Contributors can run one controlled case with:
230
+
231
+ ```bash
232
+ pnpm test:ecosystem --case react-vite-css
233
+ ```
234
+
235
+ Running the complete controlled corpus requires an explicit `--all`. External cases are CI-only because they execute pinned third-party source and require network cloning. Contributors use manual workflow dispatch when they need to reproduce an external case in the supported environment.
236
+
237
+ ## Failure Diagnostics
238
+
239
+ Each case receives a workflow-owned artifact root and writes a phase ledger before setup begins. On failure, the suite may retain only explicitly declared regular files under that root:
240
+
241
+ - phase ledger;
242
+ - computed-style JSON;
243
+ - migration reports or CLI output;
244
+ - source diff;
245
+ - per-attempt screenshots;
246
+ - browser console and page errors;
247
+ - registry, install, build, and server logs.
248
+
249
+ Upload preparation rejects undeclared files, symlinks, paths outside the root, and oversized artifacts. Raw external child output is written to inert files; workflow logs contain harness-owned summaries.
250
+
251
+ ## Version Contract
252
+
253
+ The harness pins compatible versions rather than inheriting application toolchain drift:
254
+
255
+ - Vitest and `@vitest/browser-playwright`: 4.1.10;
256
+ - Playwright: 1.61.1;
257
+ - Verdaccio: 6.8.0;
258
+ - Tailwind CSS integrations: 4.3.3;
259
+ - Vite: 8.1.5;
260
+ - Next.js: 15.5.21;
261
+ - React and ReactDOM: 19.2.8;
262
+ - `next-with-less`: 3.0.1.
263
+
264
+ External projects retain their own pinned dependencies.
265
+
266
+ ## Delivery
267
+
268
+ ### PR 1: Harness and runtime proof
269
+
270
+ The first PR provides:
271
+
272
+ - strict manifest and case selection;
273
+ - package staging, provenance, and sealed registry installation;
274
+ - browser lifecycle and computed-style oracle;
275
+ - React+Vite, Next.js, and Vite+HTML plain CSS fixtures;
276
+ - the initial three-OS workflow.
277
+
278
+ This slice proves the shared architecture with one stylesheet language across all runtimes.
279
+
280
+ ### PR 2: Matrix and ecosystem completion
281
+
282
+ The second PR adds:
283
+
284
+ - the remaining nine SCSS, Sass, and Less controlled cells;
285
+ - the production CLI smoke;
286
+ - two to five admitted external projects with CI-only checkout;
287
+ - final contributor and diagnostics documentation.
288
+
289
+ External projects do not retire or reduce the controlled matrix.
290
+
291
+ ## Testing Strategy
292
+
293
+ Harness unit tests cover:
294
+
295
+ - strict manifest validation and case selection;
296
+ - complete controlled inventory;
297
+ - package provenance and installed layout checks;
298
+ - source-scoped idempotency;
299
+ - retry boundaries and process teardown;
300
+ - artifact allowlisting;
301
+ - workflow-to-manifest matrix consistency.
302
+
303
+ Focused browser tests cover one case at a time through the installed package boundary. The full workflow covers every admitted case on all three operating systems.
304
+
305
+ Existing commands retain their current scope:
306
+
307
+ - `pnpm test` runs Rust and Node regression tests without a browser or clone;
308
+ - `pnpm test:snapshots` runs packaged CLI snapshots;
309
+ - `pnpm test:ecosystem --case <id>` runs one controlled browser case;
310
+ - the dedicated workflow owns external project checkout and execution.
311
+
312
+ ## Success Criteria
313
+
314
+ 1. All twelve controlled runtime/style cells pass exact source, report, idempotency, and browser checks on Ubuntu, macOS, and Windows.
315
+ 2. Every controlled probe proves a stylesheet-dependent baseline and utilities-only equivalence.
316
+ 3. The production CLI smoke passes clean pre/post builds through packed packages.
317
+ 4. Two to five unmodified external projects pass at reviewed full SHAs on all three operating systems.
318
+ 5. Package provenance proves that no case loaded the checkout entrypoint, checkout addon, workspace link, or upstream product package.
319
+ 6. External code runs only in ephemeral, read-only, no-secret CI jobs.
320
+ 7. Failures retain enough bounded evidence to identify the reached phase without uploading arbitrary workspace contents.
321
+ 8. Default tests and packaged snapshots require no Chromium, Verdaccio, or external repository checkout.
322
+
323
+ ## Accepted Trade-offs
324
+
325
+ 1. Controlled fixtures cost maintenance but provide deterministic Cartesian coverage that public projects cannot supply.
326
+ 2. External pins may fail because an upstream dependency or registry is unavailable; visible failure is preferred to silent skipping.
327
+ 3. Chromium-only coverage does not prove browser-engine equivalence. Firefox and WebKit remain follow-up work until Chromium coverage is stable.
328
+ 4. Exact standard computed-style comparison may expose OS serialization differences. Runtime-local baselines reduce this risk without weakening equality on one runner.
329
+ 5. Excluding custom properties can miss a custom-property-only regression, so controlled source and report assertions remain mandatory.
330
+ 6. OS-by-case jobs cost more than grouped jobs but isolate native-package, project, and diagnostic failures.
331
+
332
+ ## Deferred Work
333
+
334
+ - Firefox and WebKit coverage.
335
+ - Automated external pin updates.
336
+ - Remote dependency caches or measured sharding.
337
+ - Shared package-staging code with the packaged snapshot runner.
338
+ - Screenshot-based visual regression coverage for differences that computed styles cannot represent.