aeon-ui-engine 1.4.0 → 1.5.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 (35) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/CONSUMER.md +67 -27
  3. package/INTEGRATION.md +1 -1
  4. package/README.md +17 -8
  5. package/package.json +4 -1
  6. package/packages/cli/dist/bin.js +29 -3
  7. package/packages/cli/dist/{chunk-5L47SLNV.js → chunk-JMNRJCWF.js} +63 -9
  8. package/packages/cli/dist/index.d.ts +15 -1
  9. package/packages/cli/dist/index.js +3 -1
  10. package/packages/cli/src/bin.ts +30 -2
  11. package/packages/cli/src/index.ts +1 -0
  12. package/packages/cli/src/init.ts +10 -4
  13. package/packages/cli/src/link.ts +61 -0
  14. package/packages/core/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  15. package/packages/primitives/dist/{chunk-PB3LFHXU.js → chunk-VUPWYBGU.js} +14 -0
  16. package/packages/primitives/dist/index.js +1 -1
  17. package/packages/primitives/dist/machines/range-slider.d.ts +3 -3
  18. package/packages/primitives/dist/machines/slider.d.ts +22 -0
  19. package/packages/primitives/dist/machines/slider.js +1 -1
  20. package/packages/primitives/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  21. package/packages/primitives/src/machines/slider.ts +17 -0
  22. package/packages/react/dist/index.d.ts +15 -1
  23. package/packages/react/dist/index.js +89 -21
  24. package/packages/react/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  25. package/packages/react/src/floating/FloatingPositioner.tsx +8 -0
  26. package/packages/react/src/hooks/use-anchor-position.ts +88 -17
  27. package/packages/react/src/menu/index.tsx +37 -3
  28. package/packages/react/src/popover/index.tsx +7 -1
  29. package/packages/react/src/slider/index.tsx +54 -14
  30. package/packages/react/src/tooltip/index.tsx +7 -1
  31. package/packages/schemas/prompts/cursorrules.md +3 -1
  32. package/packages/schemas/prompts/statecharts.md +1 -1
  33. package/packages/schemas/src/prompts.ts +3 -1
  34. package/scripts/link-scoped-packages.mjs +206 -0
  35. package/scripts/vite-aliases.mjs +6 -8
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.5.1] - 2026-08-18
4
+
5
+ ### Added
6
+
7
+ - **Scoped package linking (Next.js works without aliases)** — `postinstall` and `npx aeon-ui link` symlink `node_modules/@aeon-ui/*` → `aeon-ui-engine/packages/*`. Next.js consumers now need only `transpilePackages` plus `"use client"`; the Vite plugin is optional (optimizeDeps convenience).
8
+ - **`aeon-ui link` CLI command** — `--cwd`, `--force`, `--quiet`; also runs at the end of `npx aeon-ui init`. Exported as `aeon-ui-engine/link`.
9
+
10
+ ### Changed
11
+
12
+ - CONSUMER.md, README, INTEGRATION, VITE_CONSUMER, MIGRATION_FROM_ARK, PUBLISHING: install once, link automatically, Vite plugin optional; `file:` link workaround for Next.js removed.
13
+ - Landing FAQ and Cursor rules snippet state that Aeon is React-first with Next.js supported, and that Vue / Solid / Svelte are thin adapters rather than the main catalog.
14
+
15
+ ## [1.4.1] - 2026-08-17
16
+
17
+ ### Fixed
18
+
19
+ - **Floating surfaces are collision aware** — `Menu` / `Popover` / `Tooltip` / `Select` positioners now accept `bottom-end` / `top-end` / `*-center` placements, flip when the preferred side has no room, and clamp inside the viewport. Right-aligned triggers (avatar menus in a sticky header) no longer render offscreen.
20
+ - **Positioner stacking is configurable** — `gutter` and `zIndex` props on `Menu.Positioner`, `Popover.Positioner`, `Tooltip.Positioner` replace the hardcoded 4px gap and `z-index: 50`, so menus can sit above app chrome.
21
+ - **Menu keyboard behavior** — `Menu.Content` takes focus while open, restores focus to the trigger on dismiss, and closes on Escape from anywhere on the page (previously only when focus was already inside the surface).
22
+ - **Controlled `Slider` reports interaction** — pointer and keyboard input now call `onValueChange` even when `value` is set, so a controlled thumb (audio seek bar) follows its prop instead of Aeon's internal value. `onValueChange` no longer fires on mount or echoes the controlled prop.
23
+ - **`sliderMachine` bounds follow the host** — new `SET_BOUNDS` / `SET_DISABLED` events, sent by `Slider.Root` on prop change, so a range that grows later (track duration arriving) stops clamping to the initial `max`.
24
+
25
+ ### Added
26
+
27
+ - Positioners publish `data-side`, `data-align`, `--aeon-available-height`, and `--aeon-anchor-width` for recipes that cap their own scroll area.
28
+
3
29
  ## [1.4.0] - 2026-08-17
4
30
 
5
31
  ### Added
@@ -13,6 +39,7 @@
13
39
  ### Changed
14
40
 
15
41
  - COMPONENTS / MACHINES / PRIMITIVE_COVERAGE / API_PATTERNS updated for 1.4 primitives.
42
+ - **aeon-ui.com** hero, FAQ, Cursor docs, CONSUMER, and README now lead with `npx aeon-ui migrate` for existing apps.
16
43
 
17
44
  ## [1.3.6] - 2026-07-31
18
45
 
package/CONSUMER.md CHANGED
@@ -1,6 +1,16 @@
1
1
  # Using `aeon-ui-engine` from npm
2
2
 
3
- One package. One plugin. Done.
3
+ One package. Install once. `@aeon-ui/*` resolves automatically.
4
+
5
+ Aeon is **React-first** (Next.js included). Vue / Solid / Svelte adapters are thin — not the main product.
6
+
7
+ ## 0. Existing app? Migrate first
8
+
9
+ ```bash
10
+ npx aeon-ui migrate
11
+ ```
12
+
13
+ Scans for known local workarounds (custom dropzones, range sliders, toast bridges, hand-rolled link buttons) and writes `AEON_MIGRATION_CHECKLIST.md`. Use `--check` to print findings only. New projects can skip straight to install + `npx aeon-ui init`.
4
14
 
5
15
  ## 1. Install
6
16
 
@@ -8,51 +18,81 @@ One package. One plugin. Done.
8
18
  npm install aeon-ui-engine react react-dom xstate @xstate/react
9
19
  ```
10
20
 
11
- ## 2. Vite plugin
21
+ **Postinstall** runs `scripts/link-scoped-packages.mjs`, which symlinks:
12
22
 
13
- ```ts
14
- // vite.config.ts
15
- import react from '@vitejs/plugin-react'
16
- import { defineConfig } from 'vite'
17
- import { aeonUiVitePlugin } from 'aeon-ui-engine/vite'
23
+ `node_modules/@aeon-ui/*` → `node_modules/aeon-ui-engine/packages/*`
18
24
 
19
- export default defineConfig({
20
- plugins: [react(), aeonUiVitePlugin()],
21
- })
25
+ Re-run manually anytime:
26
+
27
+ ```bash
28
+ npx aeon-ui link
29
+ # or
30
+ node node_modules/aeon-ui-engine/scripts/link-scoped-packages.mjs
22
31
  ```
23
32
 
24
- ## 3. Import CSS (one line)
33
+ No bundler aliases are required for `@aeon-ui/ui` / `@aeon-ui/react` imports.
34
+
35
+ ## 2. Import CSS (one line)
25
36
 
26
37
  ```tsx
27
- // main.tsx or app entry
38
+ // app/layout.tsx (Next.js) or main.tsx (Vite)
28
39
  import 'aeon-ui-engine/aeon.css'
29
40
  ```
30
41
 
31
- ## 4. Use components
42
+ ## 3. Use components
32
43
 
33
44
  ```tsx
34
45
  import { Button, Dialog, Menu, Toast } from '@aeon-ui/ui'
35
46
  ```
36
47
 
37
- That's it. The plugin handles all `@aeon-ui/*` aliases, TypeScript paths, and optimizeDeps.
48
+ ---
49
+
50
+ ## Next.js
51
+
52
+ ```js
53
+ // next.config.mjs
54
+ export default {
55
+ transpilePackages: [
56
+ '@aeon-ui/ui',
57
+ '@aeon-ui/react',
58
+ '@aeon-ui/core',
59
+ '@aeon-ui/primitives',
60
+ '@aeon-ui/panda',
61
+ ],
62
+ }
63
+ ```
64
+
65
+ Mark interactive surfaces as Client Components:
66
+
67
+ ```tsx
68
+ 'use client'
69
+ import { Button } from '@aeon-ui/ui'
70
+ ```
71
+
72
+ No `resolve.alias` / webpack alias block. If Turbopack mis-resolves after install, run `npx aeon-ui link` and prefer `next dev --webpack` while evaluating.
38
73
 
39
74
  ---
40
75
 
41
- ## Optional: TypeScript
76
+ ## Vite (optional plugin)
42
77
 
43
- If your tsconfig doesn't extend the engine base:
78
+ Scoped imports already work via the link step. The Vite plugin is optional — it still helps with `optimizeDeps` and monorepo `packagesRoot`:
44
79
 
45
- ```json
46
- {
47
- "compilerOptions": {
48
- "paths": {
49
- "@aeon-ui/*": ["./node_modules/aeon-ui-engine/packages/*/src"]
50
- }
51
- }
52
- }
80
+ ```ts
81
+ // vite.config.ts
82
+ import react from '@vitejs/plugin-react'
83
+ import { defineConfig } from 'vite'
84
+ import { aeonUiVitePlugin } from 'aeon-ui-engine/vite'
85
+
86
+ export default defineConfig({
87
+ plugins: [react(), aeonUiVitePlugin()],
88
+ })
53
89
  ```
54
90
 
55
- Or extend directly:
91
+ ---
92
+
93
+ ## Optional: TypeScript
94
+
95
+ Linked packages usually typecheck via each package’s `types` field. If needed:
56
96
 
57
97
  ```json
58
98
  {
@@ -117,7 +157,7 @@ Style with any CSS system using `data-aeon-scope`, `data-aeon-part`, `data-aeon-
117
157
 
118
158
  ## Optional: Local monorepo
119
159
 
120
- Point the plugin at a local checkout:
160
+ Point the Vite plugin at a local checkout (or set `AEON_UI_ROOT` before `aeon-ui link`):
121
161
 
122
162
  ```ts
123
163
  aeonUiVitePlugin({ packagesRoot: '/path/to/aeon-ui/packages' })
@@ -125,4 +165,4 @@ aeonUiVitePlugin({ packagesRoot: '/path/to/aeon-ui/packages' })
125
165
 
126
166
  ## Reference consumer
127
167
 
128
- [Casino-games](https://github.com/GenericCPU/Casino-games) uses this exact pattern.
168
+ [Casino-games](https://github.com/GenericCPU/Casino-games) uses the engine install pattern. Next.js consumers: install + link + `transpilePackages` (see above).
package/INTEGRATION.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Agents:** [AGENTS.md](./AGENTS.md) · [docs/API_PATTERNS.md](./docs/API_PATTERNS.md)
4
4
 
5
- **npm consumers:** start with **[CONSUMER.md](./CONSUMER.md)** — install `aeon-ui-engine`, peer deps, and `aeonUiViteAliases()` before any `@aeon-ui/*` import works.
5
+ **npm consumers:** start with **[CONSUMER.md](./CONSUMER.md)** — install `aeon-ui-engine` (postinstall links `@aeon-ui/*`), peer deps, then import. Vite plugin is optional; Next.js needs `transpilePackages` only.
6
6
 
7
7
  Aeon UI is **three layers**. You can use one or all.
8
8
 
package/README.md CHANGED
@@ -16,7 +16,7 @@ Aeon UI models interactive behavior as executable [statecharts](https://www.thes
16
16
  | `@aeon-ui/primitives` | XState machines (toggle, dialog, tabs, accordion, async, button lifecycle, field) |
17
17
  | `@aeon-ui/react` | React headless bindings |
18
18
  | `@aeon-ui/schemas` | JSON Schema catalog + agent system prompts |
19
- | `@aeon-ui/cli` | `npx aeon-ui init` — Cursor rules, schemas, starter |
19
+ | `@aeon-ui/cli` | `npx aeon-ui init` · `link` · `migrate` — Cursor rules, scoped package link, migration checklist |
20
20
  | `@aeon-ui/panda` | Design tokens, `defineSlotRecipe` styles, generated `styled-system` |
21
21
  | `@aeon-ui/ui` | Styled components (`aeonButton`, `aeonAccordion`, …) |
22
22
  | `@aeon-ui/vue` | Vue 3 headless adapter (v0.1: Switch + `useAeonMachine`) |
@@ -33,13 +33,27 @@ Teach an AI agent first (optional but recommended):
33
33
  npx aeon-ui init
34
34
  ```
35
35
 
36
- The monorepo is published on npm as **`aeon-ui-engine`** (bundles all `@aeon-ui/*` workspace packages):
36
+ Migrating an existing app? Scan for known workarounds and write a checklist:
37
+
38
+ ```bash
39
+ npx aeon-ui migrate
40
+ ```
41
+
42
+ The monorepo is published on npm as **`aeon-ui-engine`** (bundles all `@aeon-ui/*` workspace packages). Postinstall links `node_modules/@aeon-ui/*` so **Next.js and Vite** resolve imports without aliases:
37
43
 
38
44
  ```bash
39
45
  npm install aeon-ui-engine react react-dom xstate @xstate/react
46
+ # re-link if needed: npx aeon-ui link
40
47
  ```
41
48
 
42
- Add the Vite plugin and import CSS:
49
+ ```tsx
50
+ // app/layout.tsx or main.tsx
51
+ import 'aeon-ui-engine/aeon.css'
52
+ ```
53
+
54
+ **Next.js** — add `transpilePackages` for `@aeon-ui/ui`, `@aeon-ui/react`, `@aeon-ui/core`, `@aeon-ui/primitives`, `@aeon-ui/panda`, and use `"use client"` for interactive components.
55
+
56
+ **Vite (optional)** — `aeonUiVitePlugin()` still helps with optimizeDeps; linking alone is enough for `@aeon-ui/*` resolution:
43
57
 
44
58
  ```ts
45
59
  // vite.config.ts
@@ -49,11 +63,6 @@ export default defineConfig({
49
63
  })
50
64
  ```
51
65
 
52
- ```tsx
53
- // main.tsx
54
- import 'aeon-ui-engine/aeon.css'
55
- ```
56
-
57
66
  Full setup guide: **[CONSUMER.md](./CONSUMER.md)**.
58
67
 
59
68
  Open [https://github.com/GenericCPU/aeon-ui](https://github.com/GenericCPU/aeon-ui) for the repo. The marketing demo lives at [https://aeon-ui.com](https://aeon-ui.com) — set `VITE_SITE_ORIGIN` to that origin in Vercel so Open Graph previews resolve the social image in `apps/demo/public/` correctly.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aeon-ui-engine",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,6 +8,7 @@
8
8
  },
9
9
  "exports": {
10
10
  "./vite": "./scripts/vite-aliases.mjs",
11
+ "./link": "./scripts/link-scoped-packages.mjs",
11
12
  "./aeon.css": "./packages/panda/aeon.css",
12
13
  "./tsconfig.base.json": "./tsconfig.base.json",
13
14
  "./CONSUMER.md": "./CONSUMER.md",
@@ -25,6 +26,8 @@
25
26
  "CHANGELOG.md"
26
27
  ],
27
28
  "scripts": {
29
+ "postinstall": "node ./scripts/link-scoped-packages.mjs",
30
+ "link:scoped": "node ./scripts/link-scoped-packages.mjs",
28
31
  "build": "pnpm --filter @aeon-ui/panda run codegen && pnpm -r run build",
29
32
  "build:headless": "pnpm --filter @aeon-ui/core --filter @aeon-ui/primitives --filter @aeon-ui/react run build",
30
33
  "codegen": "pnpm --filter @aeon-ui/panda codegen",
@@ -1,37 +1,63 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  initAeon,
4
+ linkAeon,
4
5
  migrateAeon
5
- } from "./chunk-5L47SLNV.js";
6
+ } from "./chunk-JMNRJCWF.js";
6
7
 
7
8
  // src/bin.ts
8
9
  var args = process.argv.slice(2);
9
10
  var cmd = args[0] ?? "help";
10
11
  var force = args.includes("--force") || args.includes("-f");
11
12
  var checkOnly = args.includes("--check");
13
+ var quiet = args.includes("--quiet") || args.includes("-q");
14
+ function argValue(flag) {
15
+ const i = args.indexOf(flag);
16
+ if (i >= 0 && args[i + 1] && !args[i + 1].startsWith("-")) return args[i + 1];
17
+ return void 0;
18
+ }
19
+ var cwdArg = argValue("--cwd");
12
20
  function help() {
13
21
  console.log(`aeon-ui \u2014 AI-optimized CLI for Aeon UI
14
22
 
15
23
  Usage:
16
24
  npx aeon-ui init [--force]
25
+ npx aeon-ui link [--quiet] [--force] [--cwd <dir>]
17
26
  npx aeon-ui migrate [--check]
18
27
 
19
28
  Commands:
20
29
  init Inject Cursor rules, JSON schemas, system prompt, and a starter component
30
+ link Symlink node_modules/@aeon-ui/* \u2192 aeon-ui-engine/packages/* (also runs on postinstall)
21
31
  migrate Scan for known workarounds and write AEON_MIGRATION_CHECKLIST.md
22
32
  help Show this message
23
33
 
24
34
  Options:
25
- --force, -f Overwrite existing generated files (init)
35
+ --force, -f Overwrite existing generated files (init); replace existing @aeon-ui links (link)
26
36
  --check Report findings only; do not write checklist (migrate)
37
+ --quiet, -q Suppress link output (link)
38
+ --cwd <dir> Project root for link (defaults to process.cwd())
27
39
  `);
28
40
  }
29
41
  if (cmd === "init") {
30
- const result = initAeon({ force });
42
+ const result = initAeon({ force, cwd: cwdArg });
31
43
  console.log(`Aeon UI init \u2192 ${result.cwd}`);
32
44
  for (const line of result.written) console.log(` ${line}`);
45
+ try {
46
+ linkAeon({ cwd: result.cwd, quiet: true });
47
+ console.log(" link @aeon-ui/* (scoped packages)");
48
+ } catch {
49
+ console.log(" link skipped (install aeon-ui-engine first, then: npx aeon-ui link)");
50
+ }
33
51
  console.log("\nNext: npm install aeon-ui-engine react react-dom xstate @xstate/react");
34
52
  console.log("Docs: AEON_AI.md \xB7 paste aeon.cursorrules.md into your agent system prompt");
53
+ } else if (cmd === "link") {
54
+ try {
55
+ const result = linkAeon({ quiet, force, cwd: cwdArg });
56
+ if (quiet) console.log(`Aeon UI link \u2192 ${result.cwd}`);
57
+ } catch (err) {
58
+ console.error(err instanceof Error ? err.message : err);
59
+ process.exitCode = 1;
60
+ }
35
61
  } else if (cmd === "migrate") {
36
62
  const result = migrateAeon({ write: !checkOnly });
37
63
  console.log(`Aeon UI migrate \u2192 ${result.cwd}`);
@@ -85,10 +85,16 @@ Generated by \`npx aeon-ui init\`.
85
85
  ## Next steps
86
86
 
87
87
  1. Install: \`npm install aeon-ui-engine react react-dom xstate @xstate/react\`
88
- 2. CSS: \`import 'aeon-ui-engine/aeon.css'\`
89
- 3. Vite: add \`aeonUiVitePlugin()\` from \`aeon-ui-engine/vite\`
90
- 4. Paste \`aeon.cursorrules.md\` into Cursor rules / system prompt (already mirrored to \`.cursor/rules/aeon-ui.mdc\`)
91
- 5. Constrain generation with JSON under \`aeon.schemas/\`
88
+ - Postinstall links \`@aeon-ui/*\` into \`node_modules\` (re-run with \`npx aeon-ui link\` if needed).
89
+ 2. CSS: \`import 'aeon-ui-engine/aeon.css'\` (e.g. root layout).
90
+ 3. **Next.js:** add \`transpilePackages\` for \`@aeon-ui/ui\`, \`@aeon-ui/react\`, \`@aeon-ui/core\`, \`@aeon-ui/primitives\`, \`@aeon-ui/panda\`. Use \`"use client"\` for interactive components. No webpack aliases required.
91
+ 4. **Vite (optional):** \`aeonUiVitePlugin()\` from \`aeon-ui-engine/vite\` still helps with optimizeDeps; scoped imports resolve via the link step alone.
92
+ 5. Paste \`aeon.cursorrules.md\` into Cursor rules / system prompt (already mirrored to \`.cursor/rules/aeon-ui.mdc\`)
93
+ 6. Constrain generation with JSON under \`aeon.schemas/\`
94
+
95
+ ## Frameworks
96
+
97
+ Aeon is **React-first** (full catalog + styled layer). Next.js is supported. Vue / Solid / Svelte adapters exist but are thin \u2014 not the main product.
92
98
 
93
99
  ## Principle
94
100
 
@@ -98,9 +104,56 @@ Generated by \`npx aeon-ui init\`.
98
104
  return { cwd, written };
99
105
  }
100
106
 
107
+ // src/link.ts
108
+ import { spawnSync } from "child_process";
109
+ import { createRequire } from "module";
110
+ import { existsSync as existsSync2 } from "fs";
111
+ import { dirname, join as join2 } from "path";
112
+ import { fileURLToPath } from "url";
113
+ function resolveLinkScript() {
114
+ const here = dirname(fileURLToPath(import.meta.url));
115
+ const fromMonorepo = join2(here, "../../../scripts/link-scoped-packages.mjs");
116
+ if (existsSync2(fromMonorepo)) return fromMonorepo;
117
+ try {
118
+ const require2 = createRequire(import.meta.url);
119
+ const engineRoot = dirname(require2.resolve("aeon-ui-engine/package.json"));
120
+ return join2(engineRoot, "scripts/link-scoped-packages.mjs");
121
+ } catch {
122
+ throw new Error(
123
+ "Could not find scripts/link-scoped-packages.mjs (aeon-ui-engine not installed?)"
124
+ );
125
+ }
126
+ }
127
+ function linkAeon(options = {}) {
128
+ const script = resolveLinkScript();
129
+ const args = [script];
130
+ if (options.cwd) {
131
+ args.push("--cwd", options.cwd);
132
+ }
133
+ if (options.quiet) {
134
+ args.push("--quiet");
135
+ }
136
+ if (options.force) {
137
+ args.push("--force");
138
+ }
139
+ const result = spawnSync(process.execPath, args, {
140
+ stdio: options.quiet ? "pipe" : "inherit",
141
+ env: process.env,
142
+ cwd: options.cwd ?? process.cwd()
143
+ });
144
+ if (result.status !== 0 && result.status !== null) {
145
+ const err = result.stderr?.toString() || `link exited with code ${result.status}`;
146
+ throw new Error(err);
147
+ }
148
+ return {
149
+ cwd: options.cwd ?? process.cwd(),
150
+ status: result.status ?? 0
151
+ };
152
+ }
153
+
101
154
  // src/migrate.ts
102
- import { existsSync as existsSync2, readFileSync, writeFileSync as writeFileSync2 } from "fs";
103
- import { join as join2 } from "path";
155
+ import { existsSync as existsSync3, readFileSync, writeFileSync as writeFileSync2 } from "fs";
156
+ import { join as join3 } from "path";
104
157
  var FINDINGS = [
105
158
  {
106
159
  id: "preview-window-machine",
@@ -156,8 +209,8 @@ function walkHints(cwd) {
156
209
  const hits = [];
157
210
  let corpus = "";
158
211
  for (const rel of CANDIDATES) {
159
- const abs = join2(cwd, rel);
160
- if (!existsSync2(abs)) continue;
212
+ const abs = join3(cwd, rel);
213
+ if (!existsSync3(abs)) continue;
161
214
  try {
162
215
  corpus += `
163
216
  ${rel}
@@ -228,7 +281,7 @@ function migrateAeon(options = {}) {
228
281
  const findings = walkHints(cwd);
229
282
  const written = [];
230
283
  if (write) {
231
- const path = join2(cwd, "AEON_MIGRATION_CHECKLIST.md");
284
+ const path = join3(cwd, "AEON_MIGRATION_CHECKLIST.md");
232
285
  const findingsBlock = findings.length === 0 ? "\n## Scan results\n\nNo known local workarounds detected in the quick scan.\n" : `
233
286
  ## Scan results
234
287
 
@@ -243,5 +296,6 @@ ${findingsBlock}`);
243
296
 
244
297
  export {
245
298
  initAeon,
299
+ linkAeon,
246
300
  migrateAeon
247
301
  };
@@ -7,6 +7,20 @@ declare function initAeon(options?: InitOptions): {
7
7
  written: string[];
8
8
  };
9
9
 
10
+ type LinkOptions = {
11
+ cwd?: string;
12
+ quiet?: boolean;
13
+ force?: boolean;
14
+ };
15
+ /**
16
+ * Symlink `node_modules/@aeon-ui/*` → `aeon-ui-engine/packages/*`.
17
+ * Prefer the postinstall hook; this command re-runs linking manually.
18
+ */
19
+ declare function linkAeon(options?: LinkOptions): {
20
+ cwd: string;
21
+ status: number;
22
+ };
23
+
10
24
  type MigrateOptions = {
11
25
  cwd?: string;
12
26
  write?: boolean;
@@ -23,4 +37,4 @@ declare function migrateAeon(options?: MigrateOptions): {
23
37
  findings: Finding[];
24
38
  };
25
39
 
26
- export { type InitOptions, type MigrateOptions, initAeon, migrateAeon };
40
+ export { type InitOptions, type LinkOptions, type MigrateOptions, initAeon, linkAeon, migrateAeon };
@@ -1,8 +1,10 @@
1
1
  import {
2
2
  initAeon,
3
+ linkAeon,
3
4
  migrateAeon
4
- } from "./chunk-5L47SLNV.js";
5
+ } from "./chunk-JMNRJCWF.js";
5
6
  export {
6
7
  initAeon,
8
+ linkAeon,
7
9
  migrateAeon
8
10
  };
@@ -1,36 +1,64 @@
1
1
  #!/usr/bin/env node
2
2
  import { initAeon } from './init.js'
3
+ import { linkAeon } from './link.js'
3
4
  import { migrateAeon } from './migrate.js'
4
5
 
5
6
  const args = process.argv.slice(2)
6
7
  const cmd = args[0] ?? 'help'
7
8
  const force = args.includes('--force') || args.includes('-f')
8
9
  const checkOnly = args.includes('--check')
10
+ const quiet = args.includes('--quiet') || args.includes('-q')
11
+
12
+ function argValue(flag: string): string | undefined {
13
+ const i = args.indexOf(flag)
14
+ if (i >= 0 && args[i + 1] && !args[i + 1].startsWith('-')) return args[i + 1]
15
+ return undefined
16
+ }
17
+
18
+ const cwdArg = argValue('--cwd')
9
19
 
10
20
  function help() {
11
21
  console.log(`aeon-ui — AI-optimized CLI for Aeon UI
12
22
 
13
23
  Usage:
14
24
  npx aeon-ui init [--force]
25
+ npx aeon-ui link [--quiet] [--force] [--cwd <dir>]
15
26
  npx aeon-ui migrate [--check]
16
27
 
17
28
  Commands:
18
29
  init Inject Cursor rules, JSON schemas, system prompt, and a starter component
30
+ link Symlink node_modules/@aeon-ui/* → aeon-ui-engine/packages/* (also runs on postinstall)
19
31
  migrate Scan for known workarounds and write AEON_MIGRATION_CHECKLIST.md
20
32
  help Show this message
21
33
 
22
34
  Options:
23
- --force, -f Overwrite existing generated files (init)
35
+ --force, -f Overwrite existing generated files (init); replace existing @aeon-ui links (link)
24
36
  --check Report findings only; do not write checklist (migrate)
37
+ --quiet, -q Suppress link output (link)
38
+ --cwd <dir> Project root for link (defaults to process.cwd())
25
39
  `)
26
40
  }
27
41
 
28
42
  if (cmd === 'init') {
29
- const result = initAeon({ force })
43
+ const result = initAeon({ force, cwd: cwdArg })
30
44
  console.log(`Aeon UI init → ${result.cwd}`)
31
45
  for (const line of result.written) console.log(` ${line}`)
46
+ try {
47
+ linkAeon({ cwd: result.cwd, quiet: true })
48
+ console.log(' link @aeon-ui/* (scoped packages)')
49
+ } catch {
50
+ console.log(' link skipped (install aeon-ui-engine first, then: npx aeon-ui link)')
51
+ }
32
52
  console.log('\nNext: npm install aeon-ui-engine react react-dom xstate @xstate/react')
33
53
  console.log('Docs: AEON_AI.md · paste aeon.cursorrules.md into your agent system prompt')
54
+ } else if (cmd === 'link') {
55
+ try {
56
+ const result = linkAeon({ quiet, force, cwd: cwdArg })
57
+ if (quiet) console.log(`Aeon UI link → ${result.cwd}`)
58
+ } catch (err) {
59
+ console.error(err instanceof Error ? err.message : err)
60
+ process.exitCode = 1
61
+ }
34
62
  } else if (cmd === 'migrate') {
35
63
  const result = migrateAeon({ write: !checkOnly })
36
64
  console.log(`Aeon UI migrate → ${result.cwd}`)
@@ -1,2 +1,3 @@
1
1
  export { initAeon, type InitOptions } from './init.js'
2
+ export { linkAeon, type LinkOptions } from './link.js'
2
3
  export { migrateAeon, type MigrateOptions } from './migrate.js'
@@ -92,10 +92,16 @@ Generated by \`npx aeon-ui init\`.
92
92
  ## Next steps
93
93
 
94
94
  1. Install: \`npm install aeon-ui-engine react react-dom xstate @xstate/react\`
95
- 2. CSS: \`import 'aeon-ui-engine/aeon.css'\`
96
- 3. Vite: add \`aeonUiVitePlugin()\` from \`aeon-ui-engine/vite\`
97
- 4. Paste \`aeon.cursorrules.md\` into Cursor rules / system prompt (already mirrored to \`.cursor/rules/aeon-ui.mdc\`)
98
- 5. Constrain generation with JSON under \`aeon.schemas/\`
95
+ - Postinstall links \`@aeon-ui/*\` into \`node_modules\` (re-run with \`npx aeon-ui link\` if needed).
96
+ 2. CSS: \`import 'aeon-ui-engine/aeon.css'\` (e.g. root layout).
97
+ 3. **Next.js:** add \`transpilePackages\` for \`@aeon-ui/ui\`, \`@aeon-ui/react\`, \`@aeon-ui/core\`, \`@aeon-ui/primitives\`, \`@aeon-ui/panda\`. Use \`"use client"\` for interactive components. No webpack aliases required.
98
+ 4. **Vite (optional):** \`aeonUiVitePlugin()\` from \`aeon-ui-engine/vite\` still helps with optimizeDeps; scoped imports resolve via the link step alone.
99
+ 5. Paste \`aeon.cursorrules.md\` into Cursor rules / system prompt (already mirrored to \`.cursor/rules/aeon-ui.mdc\`)
100
+ 6. Constrain generation with JSON under \`aeon.schemas/\`
101
+
102
+ ## Frameworks
103
+
104
+ Aeon is **React-first** (full catalog + styled layer). Next.js is supported. Vue / Solid / Svelte adapters exist but are thin — not the main product.
99
105
 
100
106
  ## Principle
101
107
 
@@ -0,0 +1,61 @@
1
+ import { spawnSync } from 'node:child_process'
2
+ import { createRequire } from 'node:module'
3
+ import { existsSync } from 'node:fs'
4
+ import { dirname, join } from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+
7
+ export type LinkOptions = {
8
+ cwd?: string
9
+ quiet?: boolean
10
+ force?: boolean
11
+ }
12
+
13
+ function resolveLinkScript(): string {
14
+ const here = dirname(fileURLToPath(import.meta.url))
15
+ const fromMonorepo = join(here, '../../../scripts/link-scoped-packages.mjs')
16
+ if (existsSync(fromMonorepo)) return fromMonorepo
17
+
18
+ try {
19
+ const require = createRequire(import.meta.url)
20
+ const engineRoot = dirname(require.resolve('aeon-ui-engine/package.json'))
21
+ return join(engineRoot, 'scripts/link-scoped-packages.mjs')
22
+ } catch {
23
+ throw new Error(
24
+ 'Could not find scripts/link-scoped-packages.mjs (aeon-ui-engine not installed?)',
25
+ )
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Symlink `node_modules/@aeon-ui/*` → `aeon-ui-engine/packages/*`.
31
+ * Prefer the postinstall hook; this command re-runs linking manually.
32
+ */
33
+ export function linkAeon(options: LinkOptions = {}) {
34
+ const script = resolveLinkScript()
35
+ const args = [script]
36
+ if (options.cwd) {
37
+ args.push('--cwd', options.cwd)
38
+ }
39
+ if (options.quiet) {
40
+ args.push('--quiet')
41
+ }
42
+ if (options.force) {
43
+ args.push('--force')
44
+ }
45
+
46
+ const result = spawnSync(process.execPath, args, {
47
+ stdio: options.quiet ? 'pipe' : 'inherit',
48
+ env: process.env,
49
+ cwd: options.cwd ?? process.cwd(),
50
+ })
51
+
52
+ if (result.status !== 0 && result.status !== null) {
53
+ const err = result.stderr?.toString() || `link exited with code ${result.status}`
54
+ throw new Error(err)
55
+ }
56
+
57
+ return {
58
+ cwd: options.cwd ?? process.cwd(),
59
+ status: result.status ?? 0,
60
+ }
61
+ }
@@ -1 +1 @@
1
- {"version":"3.2.4","results":[[":src/machine.test.ts",{"duration":2.9991249999999923,"failed":false}],[":src/theme.test.ts",{"duration":2.946333999999993,"failed":false}],[":../../apps/demo/src/aeonSchemaCompiler.test.ts",{"duration":7.737041999999974,"failed":false}],[":src/identicon.test.ts",{"duration":4.611709000000005,"failed":false}]]}
1
+ {"version":"3.2.4","results":[[":src/machine.test.ts",{"duration":7.0908339999999725,"failed":false}],[":src/theme.test.ts",{"duration":3.866333999999995,"failed":false}],[":../../apps/demo/src/aeonSchemaCompiler.test.ts",{"duration":7.737041999999974,"failed":false}],[":src/identicon.test.ts",{"duration":9.875333000000012,"failed":false}]]}
@@ -29,6 +29,14 @@ var sliderMachine = setup({
29
29
  }),
30
30
  end: assign({
31
31
  value: ({ context }) => context.max
32
+ }),
33
+ setBounds: assign(({ context, event }) => {
34
+ if (event.type !== "SET_BOUNDS") return context;
35
+ const { min, max, step } = event;
36
+ return { ...context, min, max, step, value: snap(context.value, min, max, step) };
37
+ }),
38
+ setDisabled: assign({
39
+ disabled: ({ context, event }) => event.type === "SET_DISABLED" ? event.disabled : context.disabled
32
40
  })
33
41
  }
34
42
  }).createMachine({
@@ -41,6 +49,12 @@ var sliderMachine = setup({
41
49
  disabled: input.disabled ?? false
42
50
  }),
43
51
  initial: "interaction",
52
+ // Bounds and disabled follow the host regardless of interaction state, so a
53
+ // range that widens (audio duration arriving) stops clamping to stale limits.
54
+ on: {
55
+ SET_BOUNDS: { actions: "setBounds" },
56
+ SET_DISABLED: { actions: "setDisabled" }
57
+ },
44
58
  states: {
45
59
  interaction: {
46
60
  initial: "idle",