aeon-ui-engine 1.2.0 → 1.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.
package/CHANGELOG.md CHANGED
@@ -1,10 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.3.1] - 2026-07-30
4
+
5
+ ### Changed
6
+
7
+ - Describe this release.
8
+
3
9
  All notable changes to **aeon-ui-engine** are documented here.
4
10
 
5
11
  Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
12
  Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
13
 
14
+ ## [1.3.0] - 2026-07-30
15
+
16
+ ### Changed
17
+
18
+ - **Consumer shell defaults** aligned with dense Electron wallet chrome:
19
+ - Headless Dialog backdrop / positioner / content (so Prompt works without `@aeon-ui/ui` recipe classes)
20
+ - Prompt title + description typography (`--aeon-font-display`)
21
+ - StatusBanner sticky top placement
22
+ - AppNav dense 28px breadcrumb / back rail
23
+ - **`applyBrandPalette` fonts** also set `--fonts-ui`, `--fonts-body`, `--fonts-display` so recipes pick up brand typefaces
24
+ - **Electron CSS** — fill-height `.app-shell` / `.stage`, `.titlebar` alias, compact 3px scrollbars, hover-reveal scroll helper
25
+
26
+ ### Docs
27
+
28
+ - [PRODUCT_SHELL.md](./docs/PRODUCT_SHELL.md) — headless Prompt requires consumer-shell; do not re-copy shell CSS into products
29
+
8
30
  ## [1.2.0] - 2026-07-30
9
31
 
10
32
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aeon-ui-engine",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -35,7 +35,12 @@
35
35
  "test:headless": "pnpm run build:headless && pnpm --filter @aeon-ui/core --filter @aeon-ui/primitives --filter @aeon-ui/react run test",
36
36
  "changeset": "changeset",
37
37
  "version-packages": "changeset version",
38
- "release": "pnpm build && changeset publish"
38
+ "release": "pnpm build && changeset publish",
39
+ "version:patch": "node scripts/bump-version.mjs patch",
40
+ "version:minor": "node scripts/bump-version.mjs minor",
41
+ "version:major": "node scripts/bump-version.mjs major",
42
+ "version:push": "node scripts/bump-version.mjs patch --push",
43
+ "version:check": "node scripts/require-version-bump.mjs"
39
44
  },
40
45
  "devDependencies": {
41
46
  "@changesets/cli": "^2.29.0",
@@ -623,9 +623,19 @@ function brandPaletteToCssVars(palette) {
623
623
  "--shadows-focus-ring": palette.focusRing,
624
624
  "--aeon-grid-line": palette.gridLine
625
625
  };
626
- if (palette.font) vars["--aeon-font"] = palette.font;
627
- if (palette.fontDisplay) vars["--aeon-font-display"] = palette.fontDisplay;
628
- if (palette.radius) vars["--aeon-radius"] = palette.radius;
626
+ if (palette.font) {
627
+ vars["--aeon-font"] = palette.font;
628
+ vars["--fonts-ui"] = palette.font;
629
+ vars["--fonts-body"] = palette.font;
630
+ }
631
+ if (palette.fontDisplay) {
632
+ vars["--aeon-font-display"] = palette.fontDisplay;
633
+ vars["--fonts-display"] = palette.fontDisplay;
634
+ }
635
+ if (palette.radius) {
636
+ vars["--aeon-radius"] = palette.radius;
637
+ vars["--radii-aeon"] = palette.radius;
638
+ }
629
639
  return vars;
630
640
  }
631
641
  function applyBrandPalette(input, options = {}) {
@@ -97,9 +97,20 @@ export function brandPaletteToCssVars(palette: BrandPalette): Record<string, str
97
97
  '--shadows-focus-ring': palette.focusRing,
98
98
  '--aeon-grid-line': palette.gridLine,
99
99
  }
100
- if (palette.font) vars['--aeon-font'] = palette.font
101
- if (palette.fontDisplay) vars['--aeon-font-display'] = palette.fontDisplay
102
- if (palette.radius) vars['--aeon-radius'] = palette.radius
100
+ if (palette.font) {
101
+ // Recipes read --fonts-ui / --fonts-body; also expose --aeon-font for shells.
102
+ vars['--aeon-font'] = palette.font
103
+ vars['--fonts-ui'] = palette.font
104
+ vars['--fonts-body'] = palette.font
105
+ }
106
+ if (palette.fontDisplay) {
107
+ vars['--aeon-font-display'] = palette.fontDisplay
108
+ vars['--fonts-display'] = palette.fontDisplay
109
+ }
110
+ if (palette.radius) {
111
+ vars['--aeon-radius'] = palette.radius
112
+ vars['--radii-aeon'] = palette.radius
113
+ }
103
114
  return vars
104
115
  }
105
116
 
@@ -1,21 +1,93 @@
1
1
  /**
2
- * Consumer shell recipes — baseline look for Prompt, StatusBanner, AppNav chrome.
2
+ * Consumer shell recipes — baseline look for Prompt, StatusBanner, AppNav,
3
+ * and headless Dialog chrome.
3
4
  * Opt into brand color via applyBrandPalette(); these rules only use Aeon CSS vars.
4
5
  * Intentionally restrained — products still own marketing atmosphere.
5
6
  */
6
7
 
8
+ /* ── Headless Dialog (Prompt composes Dialog without recipe class names) ── */
9
+
10
+ [data-aeon-scope='dialog'][data-aeon-part='backdrop'] {
11
+ position: fixed;
12
+ inset: 0;
13
+ z-index: 1000;
14
+ background: color-mix(in srgb, #000 72%, transparent);
15
+ animation: aeon-shell-rise 200ms ease both;
16
+ }
17
+
18
+ [data-aeon-scope='dialog'][data-aeon-part='positioner'] {
19
+ position: fixed;
20
+ inset: 0;
21
+ z-index: 1001;
22
+ display: flex;
23
+ align-items: center;
24
+ justify-content: center;
25
+ padding: 16px;
26
+ overflow-y: auto;
27
+ overscroll-behavior: contain;
28
+ }
29
+
30
+ [data-aeon-scope='dialog'][data-aeon-part='content'] {
31
+ position: relative;
32
+ width: min(440px, 100%);
33
+ margin: auto;
34
+ flex-shrink: 0;
35
+ max-height: calc(100vh - 32px);
36
+ overflow-y: auto;
37
+ border-radius: calc(var(--aeon-radius, 0.5rem) + 4px);
38
+ border: 1px solid var(--colors-aeon-border);
39
+ background: var(--colors-aeon-surface, var(--colors-aeon-bg));
40
+ color: var(--colors-aeon-text);
41
+ box-shadow: 0 24px 64px rgba(0, 0, 0, 0.55);
42
+ padding: 20px 22px;
43
+ }
44
+
45
+ @keyframes aeon-shell-rise {
46
+ from {
47
+ opacity: 0;
48
+ transform: translateY(6px);
49
+ }
50
+ to {
51
+ opacity: 1;
52
+ transform: none;
53
+ }
54
+ }
55
+
56
+ /* ── Prompt ── */
57
+
7
58
  [data-aeon-scope='prompt'][data-aeon-part='root'] {
8
59
  display: contents;
9
60
  }
10
61
 
62
+ [data-aeon-scope='dialog'][data-aeon-part='title'],
63
+ [data-aeon-scope='prompt'][data-aeon-part='title'] {
64
+ margin: 0;
65
+ font-family: var(--aeon-font-display, var(--fonts-display, inherit));
66
+ font-size: 1.05rem;
67
+ font-weight: 700;
68
+ letter-spacing: 0.06em;
69
+ text-transform: uppercase;
70
+ color: var(--colors-aeon-text);
71
+ line-height: 1.25;
72
+ }
73
+
11
74
  [data-aeon-scope='prompt'][data-aeon-part='eyebrow'] {
12
75
  margin: 0 0 8px;
13
76
  color: var(--colors-aeon-accent);
77
+ font-family: var(--aeon-font-display, var(--fonts-display, inherit));
14
78
  font-size: 0.72rem;
15
79
  letter-spacing: 0.14em;
16
80
  text-transform: uppercase;
17
81
  }
18
82
 
83
+ [data-aeon-scope='prompt'][data-aeon-part='description'],
84
+ [data-aeon-scope='dialog'][data-aeon-part='description'] {
85
+ margin: 8px 0 0;
86
+ color: var(--colors-aeon-muted);
87
+ font-size: 0.9rem;
88
+ line-height: 1.45;
89
+ }
90
+
19
91
  [data-aeon-scope='prompt'][data-aeon-part='meta'] {
20
92
  display: grid;
21
93
  gap: 10px;
@@ -45,6 +117,8 @@
45
117
  border-radius: var(--aeon-radius, 10px);
46
118
  padding: 10px 14px;
47
119
  border: 1px solid transparent;
120
+ letter-spacing: 0.02em;
121
+ text-transform: none;
48
122
  }
49
123
 
50
124
  [data-aeon-scope='prompt'][data-aeon-part='primary'] {
@@ -59,7 +133,19 @@
59
133
  border-color: var(--colors-aeon-border);
60
134
  }
61
135
 
136
+ /* Product `.btn` inside Prompt / StatusBanner — sentence case, not all-caps CTAs */
137
+ [data-aeon-scope='statusBanner'] button.btn,
138
+ [data-aeon-scope='prompt'] button.btn {
139
+ text-transform: none;
140
+ letter-spacing: 0.02em;
141
+ }
142
+
143
+ /* ── StatusBanner (sticky top chrome) ── */
144
+
62
145
  [data-aeon-scope='statusBanner'][data-aeon-part='root'] {
146
+ position: sticky;
147
+ top: 0;
148
+ z-index: 40;
63
149
  display: flex;
64
150
  align-items: center;
65
151
  justify-content: space-between;
@@ -90,11 +176,24 @@
90
176
  flex-shrink: 0;
91
177
  }
92
178
 
179
+ /* ── AppNav breadcrumb / back (dense 28px rail) ── */
180
+
93
181
  [data-aeon-scope='appNav'][data-aeon-part='breadcrumb'] {
94
182
  display: flex;
95
- flex-wrap: wrap;
96
- gap: 6px;
183
+ flex-wrap: nowrap;
97
184
  align-items: center;
185
+ gap: 0;
186
+ min-width: 0;
187
+ overflow: hidden;
188
+ margin: 0;
189
+ height: 28px;
190
+ font-family: var(--aeon-font-display, var(--fonts-display, inherit));
191
+ font-size: 0.78rem;
192
+ font-weight: 700;
193
+ letter-spacing: 0.1em;
194
+ text-transform: uppercase;
195
+ line-height: 28px;
196
+ color: var(--colors-aeon-text);
98
197
  }
99
198
 
100
199
  [data-aeon-scope='appNav'][data-aeon-part='crumb'] {
@@ -104,26 +203,48 @@
104
203
  color: var(--colors-aeon-muted);
105
204
  cursor: pointer;
106
205
  padding: 0;
206
+ height: 28px;
207
+ line-height: 28px;
208
+ letter-spacing: inherit;
209
+ text-transform: inherit;
210
+ }
211
+
212
+ [data-aeon-scope='appNav'][data-aeon-part='crumb']:hover {
213
+ color: var(--colors-aeon-accent);
107
214
  }
108
215
 
109
216
  [data-aeon-scope='appNav'][data-aeon-part='crumb'][data-aeon-state~='current'] {
110
217
  color: var(--colors-aeon-text);
111
- font-weight: 600;
218
+ font-weight: 700;
112
219
  cursor: default;
113
220
  }
114
221
 
115
222
  [data-aeon-scope='appNav'][data-aeon-part='crumb']:not(:last-child)::after {
116
223
  content: '/';
117
- margin-left: 6px;
118
- color: var(--colors-aeon-border);
224
+ margin: 0 0.45em;
225
+ color: var(--colors-aeon-muted);
226
+ font-weight: 500;
119
227
  pointer-events: none;
120
228
  }
121
229
 
122
230
  [data-aeon-scope='appNav'][data-aeon-part='back'] {
123
- font: inherit;
124
- background: transparent;
231
+ appearance: none;
232
+ flex-shrink: 0;
233
+ width: 28px;
234
+ height: 28px;
235
+ margin: 0;
236
+ padding: 0;
125
237
  border: 0;
126
- color: var(--colors-aeon-accent);
238
+ border-radius: var(--aeon-radius, 0.5rem);
239
+ background: transparent;
240
+ color: var(--colors-aeon-muted);
241
+ display: inline-grid;
242
+ place-items: center;
127
243
  cursor: pointer;
128
- padding: 0;
244
+ transition: color 140ms ease, background 140ms ease;
245
+ }
246
+
247
+ [data-aeon-scope='appNav'][data-aeon-part='back']:hover {
248
+ color: var(--colors-aeon-accent);
249
+ background: color-mix(in srgb, var(--colors-aeon-accent) 10%, transparent);
129
250
  }
@@ -1,9 +1,66 @@
1
1
  /**
2
2
  * Optional Electron chrome helpers.
3
3
  * Import only in desktop shells: import '@aeon-ui/panda/electron.css'
4
+ *
5
+ * Defaults aligned with dense wallet / Electron fill-height layouts.
4
6
  */
5
7
 
6
- .aeon-titlebar {
8
+ /* Compact scrollbars on Electron platforms */
9
+ html[data-aeon-platform],
10
+ html.platform-darwin {
11
+ --aeon-scrollbar-size: 3px;
12
+ --aeon-scrollbar-track: transparent;
13
+ --aeon-scrollbar-thumb: color-mix(in srgb, var(--colors-aeon-muted, #a1a1aa) 35%, transparent);
14
+ --aeon-scrollbar-thumb-hover: color-mix(in srgb, var(--colors-aeon-muted, #a1a1aa) 55%, transparent);
15
+ --aeon-scrollbar-corner: transparent;
16
+ }
17
+
18
+ html[data-aeon-platform],
19
+ html[data-aeon-platform] body,
20
+ html[data-aeon-platform] #root,
21
+ html.platform-darwin,
22
+ html.platform-darwin body,
23
+ html.platform-darwin #root {
24
+ height: 100%;
25
+ min-height: 100%;
26
+ }
27
+
28
+ html[data-aeon-platform] body,
29
+ html.platform-darwin body {
30
+ margin: 0;
31
+ overflow: hidden;
32
+ font-family: var(--aeon-font, var(--fonts-ui, system-ui, sans-serif));
33
+ color: var(--colors-aeon-text);
34
+ background: var(--colors-aeon-bg);
35
+ -webkit-font-smoothing: antialiased;
36
+ letter-spacing: 0.01em;
37
+ }
38
+
39
+ .aeon-app-shell,
40
+ .app-shell {
41
+ height: 100%;
42
+ display: grid;
43
+ grid-template-rows: auto 1fr;
44
+ min-height: 0;
45
+ }
46
+
47
+ .aeon-stage,
48
+ .stage {
49
+ height: 100%;
50
+ min-height: 0;
51
+ overflow: auto;
52
+ padding: 20px clamp(14px, 3vw, 28px) 28px;
53
+ }
54
+
55
+ @media (max-width: 640px) {
56
+ .aeon-stage,
57
+ .stage {
58
+ padding: 16px 12px 24px;
59
+ }
60
+ }
61
+
62
+ .aeon-titlebar,
63
+ .titlebar {
7
64
  display: flex;
8
65
  align-items: center;
9
66
  justify-content: space-between;
@@ -18,13 +75,40 @@
18
75
  .aeon-titlebar button,
19
76
  .aeon-titlebar a,
20
77
  .aeon-titlebar input,
21
- .aeon-titlebar [data-aeon-no-drag] {
78
+ .aeon-titlebar [data-aeon-no-drag],
79
+ .titlebar button,
80
+ .titlebar a,
81
+ .titlebar input,
82
+ .titlebar [data-aeon-no-drag] {
22
83
  -webkit-app-region: no-drag;
23
84
  app-region: no-drag;
24
85
  }
25
86
 
26
87
  html.platform-darwin .aeon-titlebar,
27
- html[data-aeon-platform='darwin'] .aeon-titlebar {
88
+ html.platform-darwin .titlebar,
89
+ html[data-aeon-platform='darwin'] .aeon-titlebar,
90
+ html[data-aeon-platform='darwin'] .titlebar {
28
91
  /* hiddenInset traffic lights */
29
92
  padding-top: 36px;
30
93
  }
94
+
95
+ /* Hover-reveal scrollbars for dense nav stages */
96
+ .aeon-scroll-hover,
97
+ [data-aeon-scroll='hover'] {
98
+ scrollbar-color: transparent transparent;
99
+ }
100
+
101
+ .aeon-scroll-hover::-webkit-scrollbar-thumb,
102
+ [data-aeon-scroll='hover']::-webkit-scrollbar-thumb {
103
+ background: transparent;
104
+ }
105
+
106
+ .aeon-scroll-hover:hover,
107
+ [data-aeon-scroll='hover']:hover {
108
+ scrollbar-color: var(--aeon-scrollbar-thumb) transparent;
109
+ }
110
+
111
+ .aeon-scroll-hover:hover::-webkit-scrollbar-thumb,
112
+ [data-aeon-scroll='hover']:hover::-webkit-scrollbar-thumb {
113
+ background: var(--aeon-scrollbar-thumb);
114
+ }
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Bump aeon-ui-engine semver, changelog, tag, optionally push + npm publish.
4
+ *
5
+ * node scripts/bump-version.mjs patch|minor|major [--push] [--publish] [--no-commit]
6
+ */
7
+ import { execSync } from 'node:child_process'
8
+ import fs from 'node:fs'
9
+ import path from 'node:path'
10
+ import { fileURLToPath } from 'node:url'
11
+
12
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
13
+ const pkgPath = path.join(root, 'package.json')
14
+ const changelogPath = path.join(root, 'CHANGELOG.md')
15
+
16
+ const args = process.argv.slice(2)
17
+ const bump = args.find((a) => a === 'patch' || a === 'minor' || a === 'major')
18
+ const noCommit = args.includes('--no-commit')
19
+ const doPush = args.includes('--push')
20
+ const doPublish = args.includes('--publish')
21
+
22
+ if (!bump) {
23
+ console.error('Usage: bump-version.mjs patch|minor|major [--push] [--publish] [--no-commit]')
24
+ process.exit(1)
25
+ }
26
+
27
+ function parseSemver(v) {
28
+ const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v.trim())
29
+ if (!m) throw new Error(`Invalid semver: ${v}`)
30
+ return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]) }
31
+ }
32
+
33
+ function format({ major, minor, patch }) {
34
+ return `${major}.${minor}.${patch}`
35
+ }
36
+
37
+ function bumpSemver(v, kind) {
38
+ const s = parseSemver(v)
39
+ if (kind === 'major') return format({ major: s.major + 1, minor: 0, patch: 0 })
40
+ if (kind === 'minor') return format({ major: s.major, minor: s.minor + 1, patch: 0 })
41
+ return format({ major: s.major, minor: s.minor, patch: s.patch + 1 })
42
+ }
43
+
44
+ function today() {
45
+ return new Date().toISOString().slice(0, 10)
46
+ }
47
+
48
+ function prependChangelog(version) {
49
+ const entry = `## [${version}] - ${today()}\n\n### Changed\n\n- Describe this release.\n\n`
50
+ if (!fs.existsSync(changelogPath)) {
51
+ fs.writeFileSync(changelogPath, `# Changelog\n\n${entry}`)
52
+ return
53
+ }
54
+ const existing = fs.readFileSync(changelogPath, 'utf8')
55
+ if (existing.includes(`## [${version}]`)) return
56
+ if (existing.startsWith('# Changelog')) {
57
+ fs.writeFileSync(changelogPath, existing.replace('# Changelog\n\n', `# Changelog\n\n${entry}`))
58
+ } else {
59
+ fs.writeFileSync(changelogPath, `# Changelog\n\n${entry}${existing}`)
60
+ }
61
+ }
62
+
63
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
64
+ const prev = pkg.version
65
+ const next = bumpSemver(prev, bump)
66
+ pkg.version = next
67
+ fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`)
68
+ prependChangelog(next)
69
+ console.log(`aeon-ui-engine ${prev} → ${next}`)
70
+
71
+ if (!noCommit) {
72
+ execSync('git add package.json CHANGELOG.md', { cwd: root, stdio: 'inherit' })
73
+ execSync(`git commit -m "Release aeon-ui-engine v${next}"`, { cwd: root, stdio: 'inherit' })
74
+ try {
75
+ execSync(`git tag v${next}`, { cwd: root, stdio: 'inherit' })
76
+ } catch {
77
+ console.warn(`Tag v${next} already exists`)
78
+ }
79
+ }
80
+
81
+ if (doPush) {
82
+ execSync('git push origin HEAD', { cwd: root, stdio: 'inherit' })
83
+ execSync(`git push origin v${next}`, { cwd: root, stdio: 'inherit' })
84
+ }
85
+
86
+ if (doPublish) {
87
+ execSync('npm publish --access public', { cwd: root, stdio: 'inherit' })
88
+ }
89
+
90
+ if (!doPush) {
91
+ console.log(`Tagged v${next}. Push: git push && git push origin v${next}`)
92
+ console.log('Or: npm run version:push')
93
+ }
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Fail if package.json version is not strictly newer than the latest remote tag (v*).
4
+ */
5
+ import { execSync } from 'node:child_process'
6
+ import fs from 'node:fs'
7
+ import path from 'node:path'
8
+ import { fileURLToPath } from 'node:url'
9
+
10
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
11
+ const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'))
12
+ const local = pkg.version
13
+
14
+ function parse(v) {
15
+ const m = /^(\d+)\.(\d+)\.(\d+)/.exec(String(v).replace(/^v/, ''))
16
+ if (!m) return null
17
+ return [Number(m[1]), Number(m[2]), Number(m[3])]
18
+ }
19
+
20
+ function gt(a, b) {
21
+ for (let i = 0; i < 3; i++) {
22
+ if (a[i] > b[i]) return true
23
+ if (a[i] < b[i]) return false
24
+ }
25
+ return false
26
+ }
27
+
28
+ try {
29
+ execSync('git fetch --tags --quiet', { cwd: root, stdio: 'ignore' })
30
+ } catch {
31
+ /* offline */
32
+ }
33
+
34
+ let remoteTag = ''
35
+ try {
36
+ remoteTag = execSync(
37
+ 'git describe --tags --abbrev=0 origin/main 2>/dev/null || git describe --tags --abbrev=0',
38
+ { cwd: root, encoding: 'utf8' },
39
+ ).trim()
40
+ } catch {
41
+ remoteTag = ''
42
+ }
43
+
44
+ const localP = parse(local)
45
+ if (!localP) {
46
+ console.error(`Invalid package.json version: ${local}`)
47
+ process.exit(1)
48
+ }
49
+
50
+ if (!remoteTag) {
51
+ console.log(`No remote tag yet — allowing push of v${local}`)
52
+ process.exit(0)
53
+ }
54
+
55
+ const remoteP = parse(remoteTag)
56
+ if (!remoteP) {
57
+ console.log(`Unparsed remote tag ${remoteTag} — allowing push`)
58
+ process.exit(0)
59
+ }
60
+
61
+ if (!gt(localP, remoteP)) {
62
+ console.error(`
63
+ Refusing push: aeon-ui-engine ${local} is not newer than remote tag ${remoteTag}.
64
+
65
+ npm run version:push
66
+ `)
67
+ process.exit(1)
68
+ }
69
+
70
+ console.log(`Version OK: ${local} > ${remoteTag.replace(/^v/, '')}`)