Skip to content

feat(cli): adopt an app from Astryx onto ui-common, with doctor and an agent skill - #57

Draft
yomybaby wants to merge 2 commits into
mainfrom
feat/adopt-from-astryx
Draft

yomybaby wants to merge 2 commits into
mainfrom
feat/adopt-from-astryx

Conversation

@yomybaby

@yomybaby yomybaby commented Oct 2, 2026

Copy link
Copy Markdown
Member

Summary

Tooling that moves an app using Astryx directly (@astryxdesign/*) onto @lablup/ui-common in one guided pass, so the next adopter does not repeat the first migration by hand.

ui-common adopt --from astryx [--dry-run | --check] [--diff] [--report <path>] [--ignore <path>]… [paths…]

  • Rewrites every @astryxdesign/core[/X], @astryxdesign/lab, @astryxdesign/theme-neutral[/built|/theme.css] module specifier (incl. theme/tokens.stylex, CSS entry points, locales/*.json) to the ui-common mirror, checked against the target ui-common's exports map. Covers import, export … from, import(), typeof import(), require(), require.resolve(), import.meta.resolve(), vi.mock/jest.* (nested generics included), CSS/SCSS @import/@use. Package names in other strings, declare module augmentations, @astryxdesign/cli and @generated files are left alone.
  • Dialog/AlertDialog → Modal/AlertModal with jscodeshift: imports and references renamed (alias kept when the module already binds Modal; a module's own re-export keeps its public name); names with no counterpart (useImperativeDialog, …) are split onto the Astryx subpath and reported.
  • Layer order: ui-common added to every Astryx @layer statement; an entry stylesheet / index.html without one gets the full statement first (not when another page of the project already declares it).
  • package.json, per workspace member that uses Astryx: @lablup/ui-common + @lablup/ui-common-cli at the CLI's exact version (library: peer + dev), @astryxdesign/core kept only where declared and moved to ui-common's exact pin (never added; a library's core peer dropped), theme-neutral and an unused lab removed, a used lab pinned to ui-common's canary plus the lab override (reusing upgrade's override code) when nothing else gives its core peer a target, pnpm allowBuilds, a minimumReleaseAgeExclude hint. catalog:/workspace: specs are never edited.
  • Writes ui-common-adopt-report.md (same renderer as upgrade): Astryx version moves (alert, with the Astryx codemod command), imports left, Modal refs/HTMLDialogElement, local Astryx patches (and which ui-common fork covers them), body overlays needing MODAL_LIVE_ATTRIBUTE, global shortcuts needing MODAL_OPEN_ATTRIBUTE, Escape handlers, ASTRYX agent blocks and tools anchored on <!-- ASTRYX:END -->, astryx CLI calls, InternationalizationProvider without uiCommonMessages.
  • Idempotent. --check writes nothing and exits 1 while a file would change or an Astryx import is left. Exit codes 0 / 1 / 2. Decision: adopt is its own command (there is no ui-common version to upgrade from); upgrade --from astryx is an alias.

ui-common doctor [--json] [--verbose]

Read-only, exit 1 on any FAIL (WARN does not fail), each with a fix and a docs/adopting-from-astryx.md#<id> section: node, versions, single-core (pnpm lockfile packages/snapshots, npm package-lock, and on-disk resolution from ui-common, lab and each package), lab-core, lab-override, imports (the adopt scanner), layer-order (present, first, with ui-common, identical across index.html / entry CSS / Storybook copies), vite-prebundle, vitest-inline, i18n, agents (block current, no ASTRYX block). Works on a single package or a workspace; run from a member it reads the workspace root. doctor <subcommand> still passes through to Astryx's doctor.

Agent skill

packages/cli/skill/ui-common-adopt/SKILL.md (Claude Code format, ~120 lines): preflight → dry run → apply → install → doctor loop → per-report-section decision rules → build/tests/cold dev start → light/dark screenshot diff → done criteria. Installed with ui-common agents --skill [--dir ~/.claude/skills]; the UI-COMMON block names it once installed in the project. Shipped in the CLI tarball (check:pack requires it).

Docs

docs/adopting-from-astryx.md (problem → fix, the sequence, a copy-paste agent prompt, the ESLint ban, one section per doctor check), cross-linked with migrating-to-0.2.md and from README (CLI table + intro), CLI README, docs/astryx.md, CONTRIBUTING, CHANGELOG [Unreleased]. The boundary/disclosure check now also scans docs/** and the shipped skill. No version bump.

Proof

Fixture app (Vite 8 + React 19.2 + @stylexjs/unplugin + Astryx core/lab/theme-neutral 0.6.2 + Vitest + Storybook config + index.html + InternationalizationProvider + a local pnpm patch on core's ComplexSelector), baseline committed with build/tests green:

  1. adopt --from astryx --dry-run, then apply: 7 files, 18 specifiers; report listed the Modal ref, the patch (covered by the ComplexSelector fork), the window shortcut, the ASTRYX block, the astryx theme build script, the i18n provider.
  2. pnpm install, doctor: 4 FAIL (vite-prebundle, vitest-inline, i18n, agents) + 1 WARN. Confirmed real before fixing: Vitest failed with Unknown file extension ".css", tsc failed on the HTMLDialogElement ref, cold dev start took 24 s to first render.
  3. Fixed per doctor and report → doctor 11/11 PASS, adopt --check exit 0, tsc clean, vite build OK, Vitest passes, cold dev start (node_modules/.vite removed) renders in 2.7 s with 0 page/console errors (Playwright).
  4. Before/after pixel diff of the production build: main screen 0 px different in light and dark; dialog open differs only in the backdrop (Astryx Dialog blurs the page; Modal does not) — expected.

Backend.AI WebUI

  • main (copy): adopt --from astryx --check → exit 0, nothing to change; doctor → 11/11 PASS from the workspace root, and from the react and backend.ai-ui members.
  • Pre-migration tree (6ece69b5ce^): adopt --check exit 1; dry run rewrites 1,427 specifiers in 533 files and finds both local Astryx patches as covered by ui-common forks (what the migration later did). Applying it and comparing per-file import specifiers with the real migration commit: identical in 644 of 680 files; the rest are hand-written component moves the commit also made. doctor on that tree flags 1,436 direct imports, the layer order, Vite, Vitest, i18n and the ASTRYX blocks.

Tests: packages/cli/test/adopt/ (fixtures: app, pnpm workspace with library + app + non-member package, npm + lab; second run no-op; --check/--dry-run/report guard/version-move alert/ignore/generated), packages/cli/test/doctor/ (healthy project passes; each check fails on its breakage; npm lockfile; workspace member), agents --skill in test/cli/. pnpm run verify passes.

Follow-ups

  • Run Astryx's own codemods automatically when adopt detects an Astryx version move (today: an alert with the command).
  • Ship the ESLint ban as a shareable config instead of a doc snippet.
  • A doctor check for the astryx theme build .css stub, and for nested <Theme> without mode.
  • Visual parity is a skill step, not a command; a ui-common screenshot-diff helper could make it one.

…ent skill

ui-common adopt --from astryx moves an app that imports @astryxdesign/core,
lab or theme-neutral onto @lablup/ui-common in one pass: it rewrites module
specifiers in scripts and stylesheets, renames Dialog and AlertDialog to
Modal and AlertModal, adds the ui-common layer to the cascade order, edits
package.json in every workspace member and writes a manual-review report in
the upgrade report's format. --check exits 1 while anything is left, for CI;
upgrade --from astryx is an alias.

ui-common doctor runs read-only checks of the wiring the first adopters
learned the hard way (one Astryx core, lab on it, no direct imports, the
layer order, the Vite pre-bundle fix, Vitest inlining, i18n, the agent
block, matching versions, Node), with a fix and a doc section per failure.
doctor with a subcommand still reaches Astryx's doctor.

agents --skill installs the ui-common-adopt skill shipped in the CLI package,
and the agent block names it once installed.
docs/adopting-from-astryx.md covers the command sequence, a prompt for a
coding agent, the problems a direct-Astryx app meets and one section per
doctor check. README, the CLI README, CHANGELOG, CONTRIBUTING and the 0.2
migration notes point to it. The boundary check now scans docs/ and the
shipped skill.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant