@adia-ai/web-components 0.8.4 → 0.8.5

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 (53) hide show
  1. package/CHANGELOG.md +18 -1
  2. package/USAGE.md +60 -25
  3. package/components/adia-mark/adia-mark.a2ui.json +123 -0
  4. package/components/adia-mark/adia-mark.class.js +64 -0
  5. package/components/adia-mark/adia-mark.css +48 -0
  6. package/components/adia-mark/adia-mark.d.ts +20 -0
  7. package/components/adia-mark/adia-mark.examples.md +26 -0
  8. package/components/adia-mark/adia-mark.js +17 -0
  9. package/components/adia-mark/adia-mark.test.js +69 -0
  10. package/components/adia-mark/adia-mark.yaml +121 -0
  11. package/components/agent-questions/agent-questions.a2ui.json +2 -1
  12. package/components/agent-questions/agent-questions.class.js +96 -53
  13. package/components/agent-questions/agent-questions.css +27 -106
  14. package/components/agent-questions/agent-questions.yaml +1 -0
  15. package/components/button/button.class.js +5 -1
  16. package/components/calendar-grid/calendar-grid.a2ui.json +3 -1
  17. package/components/calendar-grid/calendar-grid.class.js +11 -6
  18. package/components/calendar-grid/calendar-grid.css +42 -20
  19. package/components/calendar-grid/calendar-grid.yaml +5 -0
  20. package/components/calendar-picker/calendar-picker.a2ui.json +3 -1
  21. package/components/calendar-picker/calendar-picker.class.js +11 -6
  22. package/components/calendar-picker/calendar-picker.css +39 -19
  23. package/components/calendar-picker/calendar-picker.yaml +5 -0
  24. package/components/chart/chart.class.js +17 -14
  25. package/components/color-input/color-input.class.js +8 -5
  26. package/components/color-picker/color-picker.class.js +6 -3
  27. package/components/field/field.class.js +10 -7
  28. package/components/fields/fields.class.js +15 -12
  29. package/components/icon/icon.class.js +9 -5
  30. package/components/index.js +1 -0
  31. package/components/nav/nav.class.js +2 -1
  32. package/components/noodles/noodles.class.js +14 -10
  33. package/components/page/page.class.js +3 -2
  34. package/components/select/select.class.js +3 -2
  35. package/components/swatch/swatch.class.js +27 -24
  36. package/components/swiper/swiper.class.js +4 -1
  37. package/components/swiper/swiper.css +13 -4
  38. package/components/toolbar/toolbar.class.js +11 -6
  39. package/components/tree/tree.class.js +5 -2
  40. package/core/data-stream.js +21 -15
  41. package/core/element.js +36 -1
  42. package/core/element.test.js +67 -0
  43. package/core/icons-phosphor.js +86 -22
  44. package/core/icons.js +22 -8
  45. package/dist/theme-provider.min.js +2 -2
  46. package/dist/web-components.min.css +1 -1
  47. package/dist/web-components.min.js +107 -102
  48. package/dist/web-components.sheet.js +1 -1
  49. package/package.json +1 -5
  50. package/styles/components.css +1 -0
  51. package/traits/error-shake/error-shake.js +23 -17
  52. package/traits/fade-presence/fade-presence.js +19 -15
  53. package/traits/success-checkmark/success-checkmark.js +21 -16
package/CHANGELOG.md CHANGED
@@ -1,6 +1,23 @@
1
1
  # Changelog — @adia-ai/web-components
2
2
 
3
- ## [Unreleased]
3
+ ## [0.8.5] — 2026-07-17
4
+
5
+ ### Added
6
+ - **`<adia-mark-ui>`** (`components/adia-mark/adia-mark.{class.js,css,yaml}`; gh#283): the Adia brand mark as a token-driven primitive — background always reads `--a-canvas-surface-inverse`, the wordmark always reads `--a-fg-inverse`, so one component replaces the four hand-maintained static `agent-ui-{light,dark,light-transparent,dark-transparent}.svg` exports and adapts to scheme changes with zero asset swapping. Delivery shape (inlined custom element, not a bare SVG file or `icon-ui` registry entry) was constrained by the ticket's own finding: an `<img src="...svg">` or CSS `background-image: url(...)` reference cannot resolve the host page's CSS custom properties — only inlined markup can. `size` prop (xs/sm/md/lg/xl) matches `avatar-ui`'s scale. Verified live in both color schemes (Playwright) — background/foreground correctly invert together with no code change. Existing static-asset consumers (`public/icons/*.svg`, the a2ui corpus chunks, `apps/embedded-app`'s CSS-background brand mark) were deliberately left untouched — the static assets aren't deleted, so nothing breaks; migrating those ~35+ call sites is a separate, larger follow-up, out of scope for this ticket's `size:small` label.
7
+ - **`scripts/verify/exports-wildcard-resolution.mjs`** (gh#296, shared with `@adia-ai/web-modules`): live-resolution regression gate asserting every documented short-form CSS/JS subpath specifier resolves to a real on-disk file. Removed the dead `./components/*/*.css` exports-map key — confirmed via live `import.meta.resolve()` testing that it never actually resolved (Node's pattern matching always picked the single-wildcard `./components/*.css` sibling instead). Documented the underlying trap (a redundant nested specifier like `components/button/button.css` silently resolves to a garbage, nonexistent quadrupled path instead of throwing) in `USAGE.md` — not fixable in the exports map itself, since Node's `*` matches across `/` with no way to constrain it to one path segment.
8
+ - **`scripts/dev/audit-template-child-conflict.mjs`** (gh#284): static audit flagging a component whose `static template` is non-null AND whose yaml declares a `slots.default` entry (the container shape — nested CE children silently wiped by `stamp()`'s first-mount `replaceChildren`) or whose usage example shows body-text content (the projected-text shape, advisory). Verified against the current 168 components: zero findings — every component accepting light-DOM children currently uses `static template = () => null`. Catches a FUTURE regression rather than patching `stamp()`/`connectedCallback` itself; wired into `adia-author`'s structural-gate sequence (`npm run audit:template-child-conflict[:strict]`).
9
+
10
+ ### Changed
11
+ - **`button-ui` role-forwarding sweep (gh issue 276 wave 4, operator ruling)**: `button-ui` now defaults `role=button` only when the author set nothing, so composites can stamp `role=tab`/`gridcell` semantics onto the primitive. `components/calendar-grid/calendar-grid.class.js` + `components/calendar-picker/calendar-picker.class.js` stamp `<button-ui variant="ghost">` for all five shapes (prev/next nav, leading/main/trailing day cells), fixing two latent bugs at the root during migration — a `:disabled` pseudo-class selector that only matches native controls (now `[disabled]`), and a today-indicator dot that collided with `button-ui`'s own `::after`-rendered label (relocated to `::before`). `components/agent-questions/agent-questions.class.js` migrates its stamped native `<button role=radio|checkbox>` cards to `option-card-ui`; multi-select mode intercepts click/keydown at the container's capture phase to preserve toggle-off (the primitive is architecturally radio-only). `components/button/button.class.js` + `components/swiper/swiper.class.js`: swiper's pagination dots migrate from a native `<button role=tab>` to `<button-ui variant=ghost role=tab>`. Live-probed in all four components; `scripts/dev/audit-stamped-native-buttons.mjs` SANCTIONED entries removed for swiper and agent-questions.
12
+
13
+ ### Fixed
14
+ - **`icons-phosphor.js`'s auto-discovery glob breaks under pnpm workspace hoisting (gh#287)**: the baked `import.meta.glob('/node_modules/@phosphor-icons/…')` resolved as a literal path join against Vite's configured project root — under pnpm's default (non-hoisted, symlinked) linking, `@phosphor-icons/core` (a transitive dependency of this package, not necessarily the consumer's) is linked only inside this package's own resolution scope, so the glob silently matched zero files and every icon rendered blank. Switched to the entry-file-relative form (`../node_modules/…`, resolved against `icons-phosphor.js`'s own directory, one level inside the package) — confirmed empirically against a real hoisting-gap install (root cause + fix verified with a from-scratch pnpm workspace reproduction, not assumed). Also documented a second, independent failure mode named in the same issue — Vite's dev-server `optimizeDeps` pre-bundling this package empties the glob via a different mechanism (esbuild's scan transforms the glob into a literal empty object, no exception) — with the `optimizeDeps.exclude` fix and a scoped explicit-import fallback, in `icons-phosphor.js`'s JSDoc, `icons.js`'s JSDoc, `icon.class.js`'s fail-loud diagnostic, and `USAGE.md` § Icons.
15
+ - **Every browser-only API call site guarded for SSR (gh#285)**: `UIElement`'s constructor called `attachInternals()` unconditionally — the highest-leverage crash site, throwing before any subclass code runs — now feature-detected with a no-op `ElementInternals` shim server-side. `adoptStyles()`'s write to `document.adoptedStyleSheets` is now guarded on `'adoptedStyleSheets' in document`. Every `new ResizeObserver/IntersectionObserver/MutationObserver/PerformanceObserver` construction site across `core/element.js`, `core/data-stream.js`, and the shipped `components/{chart,color-input,color-picker,field,fields,nav,noodles,page,select,swatch,toolbar,tree}` + `traits/{error-shake,fade-presence,success-checkmark}` sources is now feature-detected (22 sites needed the guard; 10 more were already correctly guarded). Real-browser behavior unchanged — confirmed via Playwright and the full test suite; 4 new `core/element.test.js` cases simulate the actual API absence (deleting the constructors, not mocking).
16
+
17
+ ### Maintenance
18
+ - **`styles/components.css` barrel** gained the `<adia-mark-ui>` `@import` entry (see Added, above) — without it the component would have shipped with zero CSS. **`dist/` bundles rebuilt** (`web-components.min.{css,js}`, `.sheet.js`, `theme-provider.min.js`) to pick up the `<adia-mark-ui>`, exports-map, and SSR-guard source changes above.
19
+
20
+ ## [0.8.4] — 2026-07-16
4
21
 
5
22
  ### Fixed
6
23
  - **Focus rings pair with transparent borders** (operator ruling 2026-07-16): a control showing its `--a-focus-ring` box-shadow sets `border-color: transparent` in the same rule — border + gap + ring double-drew the edge (visible on the chat composer). Applied to the 21 ring-bearing controls with visible borders (input, textarea, select, combobox, check, radio, switch, range, tag[tone=outline], tags-input's missing invalid-state half, time/calendar/date-range pickers, upload dropzone, option-card, accordion[contained], agent-artifact header, button, toggle-group, chat-input); the pairing law is stated in `styles/colors/semantics/features.css` and the component-token contract.
package/USAGE.md CHANGED
@@ -50,11 +50,20 @@ import '@adia-ai/web-components/components/button';
50
50
  import '@adia-ai/web-components/components/button.css';
51
51
  ```
52
52
 
53
- > **No `.js` suffix on component subpaths.** Import the directory name
54
- > `…/components/button`, never `…/components/button/button.js`. The
55
- > `exports` wildcard matches only the directory form; appending the
56
- > explicit filename fails to resolve under Vite 8 (rolldown). The on-disk
57
- > filename is irrelevant to the subpath specifier.
53
+ > **Don't repeat the directory name for `.js` OR `.css` subpaths.**
54
+ > Import the short form — `…/components/button`, never
55
+ > `…/components/button/button.js`; `…/components/button.css`, never
56
+ > `…/components/button/button.css`. Every component lives at
57
+ > `components/<name>/<name>.<ext>` on disk, so the nested form looks like
58
+ > the natural specifier — it isn't. The `exports` wildcard's single `*`
59
+ > captures the WHOLE nested subpath (it matches across `/`, with no way to
60
+ > constrain it to one segment) and pastes it into both `*`s of the target,
61
+ > quadrupling the path. Under Vite 8 (rolldown) the `.js` case fails at
62
+ > bundle time when it tries to read the resulting nonexistent file; the
63
+ > `.css` case resolves to a URL that also doesn't exist, with no clear
64
+ > "wrong specifier" error either way — the on-disk filename repetition is
65
+ > irrelevant to the subpath specifier; use the short form. Tracked as
66
+ > [gh#296](https://github.com/adiahealth/gen-ui-kit/issues/296).
58
67
 
59
68
  Subpath exports also available:
60
69
 
@@ -100,34 +109,60 @@ For the live demo of every component: <https://ui-kit.exe.xyz/site/components/>.
100
109
  ## Icons — wiring the registry
101
110
 
102
111
  `<icon-ui>` (and every component that renders an icon — `<button-ui icon>`,
103
- `<badge-ui>`, menus, etc.) draws from an **icon registry** that your app
104
- must wire **once, from your own entry module**. Out of the box the registry
105
- is empty; an un-wired registry renders every icon **blank**.
106
-
107
- **Why the app has to do it.** The zero-config glob in the package's own
108
- `icons-phosphor.js` uses `import.meta.glob('/node_modules/@phosphor-icons/…')`,
109
- which is a **build-time macro your bundler rewrites** and under Vite 8 /
110
- rolldown that rewrite **does not happen for code imported from inside a
111
- published dependency**. The glob has to be evaluated in *your* source graph.
112
- `@phosphor-icons/core` ships as a dependency of this package (so the SVG
113
- assets resolve), but the glob that reads them must live in your app.
114
-
115
- **The one-time wiring (do this in your app entry, before first paint):**
112
+ `<badge-ui>`, menus, etc.) draws from an **icon registry**. Zero-config
113
+ consumers get every Phosphor name for free: the main barrel
114
+ (`import '@adia-ai/web-components'`) side-effect-imports the package's own
115
+ `icons-phosphor.js`, which globs the full Phosphor set at build time. Most
116
+ apps need no wiring at all.
117
+
118
+ **Two Vite-specific gaps can leave the registry empty (gh#287) both silent,
119
+ both fixed without touching your component code:**
120
+
121
+ 1. **Vite's dev-server `optimizeDeps` pre-bundles this package.** Vite
122
+ pre-bundles bare-specifier dependencies with esbuild for faster cold
123
+ starts, before its own glob transform ever runs — that turns the
124
+ auto-discovery glob into a literal empty object, no exception, no
125
+ per-icon warning. Fix in your Vite config:
126
+
127
+ ```js
128
+ // vite.config.js
129
+ export default defineConfig({
130
+ optimizeDeps: { exclude: ['@adia-ai/web-components', '@adia-ai/web-modules'] },
131
+ });
132
+ ```
133
+
134
+ 2. **A packaging step changes where this file lands, or you're on a version
135
+ older than this fix.** The auto-glob resolves `@phosphor-icons/core`
136
+ relative to its own file location, one level inside
137
+ `@adia-ai/web-components` — hoisting-safe under pnpm's default (non-hoisted)
138
+ linking, but it assumes the package's own internal layout is preserved. A
139
+ bundler step that re-nests `core/icons-phosphor.js` elsewhere, a Yarn Berry
140
+ PnP install, or `@phosphor-icons/core` missing entirely can still leave it
141
+ empty.
142
+
143
+ If neither fix applies to your setup — or you want a smaller bundle than the
144
+ full ~9000-chunk auto-discovery set — wire icons explicitly from your own
145
+ entry module instead, with plain bare-specifier imports (never affected by
146
+ `optimizeDeps`, since it's your own first-party source, not a dependency):
116
147
 
117
148
  ```js
118
149
  import { installIconLoaders } from '@adia-ai/web-components/core/icons';
150
+ import caretRight from '@phosphor-icons/core/assets/regular/caret-right.svg?raw';
151
+ import x from '@phosphor-icons/core/assets/regular/x.svg?raw';
152
+ // ...one import per icon you use
119
153
 
120
154
  installIconLoaders({
121
- regular: import.meta.glob(
122
- '/node_modules/@phosphor-icons/core/assets/regular/*.svg',
123
- { query: '?raw', import: 'default', eager: true },
124
- ),
125
- // add other weights only if you use them:
126
- // bold: import.meta.glob('/node_modules/@phosphor-icons/core/assets/bold/*.svg', { query: '?raw', import: 'default', eager: true }),
127
- // fill: import.meta.glob('/node_modules/@phosphor-icons/core/assets/fill/*.svg', { query: '?raw', import: 'default', eager: true }),
155
+ regular: {
156
+ '/node_modules/@phosphor-icons/core/assets/regular/caret-right.svg': caretRight,
157
+ '/node_modules/@phosphor-icons/core/assets/regular/x.svg': x,
158
+ },
128
159
  });
129
160
  ```
130
161
 
162
+ The map's keys only need to end with `/<weight>/<filename>.svg` — lookup
163
+ matches by suffix — so the `/node_modules/...` prefix above is just for
164
+ consistency with the glob form, not a real path.
165
+
131
166
  `installIconLoaders` is idempotent, may be called before or after the
132
167
  elements connect (icons already on the page repaint when it runs), and
133
168
  accepts either the per-weight map above or a flat `path → svg-string` map
@@ -0,0 +1,123 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/AdiaMark.json",
4
+ "title": "AdiaMark",
5
+ "description": "The Adia brand mark — a rounded square with the \"Adia\" wordmark cut out in a contrasting color. Token-driven: the background always reads as the current scheme's inverse surface, and the wordmark always reads as the current scheme's inverse foreground, so a single asset replaces separate light/dark/transparent static exports (gh#283). Not an `icon-ui` registry entry — icon-ui is the Phosphor-glyph registry only; this is a fixed, one-off brand image, a separate delivery shape.",
6
+ "type": "object",
7
+ "allOf": [
8
+ {
9
+ "$ref": "common_types.json#/$defs/ComponentCommon"
10
+ },
11
+ {
12
+ "$ref": "common_types.json#/$defs/CatalogComponentCommon"
13
+ }
14
+ ],
15
+ "properties": {
16
+ "component": {
17
+ "const": "AdiaMark"
18
+ },
19
+ "inline": {
20
+ "description": "Renders as `display:inline-block` instead of the block-level default (ADR-0037) — set this when the mark sits beside a text run (a topbar brand slot) rather than standing alone.",
21
+ "type": "boolean",
22
+ "default": false
23
+ },
24
+ "size": {
25
+ "description": "Diameter of the square mark. Matches avatar-ui's size ladder for visual consistency when the two sit side by side (e.g. an app switcher row).",
26
+ "type": "string",
27
+ "enum": [
28
+ "xs",
29
+ "sm",
30
+ "md",
31
+ "lg",
32
+ "xl"
33
+ ],
34
+ "default": "md"
35
+ }
36
+ },
37
+ "required": [
38
+ "component"
39
+ ],
40
+ "unevaluatedProperties": false,
41
+ "x-adiaui": {
42
+ "anti_patterns": [
43
+ {
44
+ "fix": "<adia-mark-ui size=\"sm\"></adia-mark-ui>\n",
45
+ "why": "icon-ui only resolves Phosphor glyph names from the registry; there\nis no \"adia\" Phosphor icon, and registering one would misuse a\nthird-party glyph set for a proprietary brand mark.\n",
46
+ "wrong": "<icon-ui name=\"adia\"></icon-ui>\n"
47
+ },
48
+ {
49
+ "fix": "<adia-mark-ui size=\"sm\"></adia-mark-ui>\n",
50
+ "why": "Hardcoding the fill colors defeats the entire point of gh#283 —\nthe mark is supposed to track the inverse-surface/inverse-foreground\ntokens automatically across scheme changes, not be pinned to one\nscheme's static values.\n",
51
+ "wrong": "<adia-mark-ui style=\"--adia-mark-bg: #171818; --adia-mark-fg: #E2E3E4;\"></adia-mark-ui>\n"
52
+ }
53
+ ],
54
+ "category": "display",
55
+ "composes": [],
56
+ "events": {},
57
+ "examples": [
58
+ {
59
+ "description": "Small brand mark in an admin-shell topbar, beside the product name.",
60
+ "a2ui": "[\n {\n \"id\": \"brand-row\",\n \"component\": \"Row\",\n \"gap\": \"2\",\n \"align\": \"center\",\n \"children\": [\"mark\", \"name\"]\n },\n {\n \"id\": \"mark\",\n \"component\": \"AdiaMark\",\n \"size\": \"sm\"\n },\n {\n \"id\": \"name\",\n \"component\": \"Text\",\n \"variant\": \"heading\",\n \"text\": \"Adia\"\n }\n]",
61
+ "name": "topbar-brand-mark"
62
+ }
63
+ ],
64
+ "keywords": [
65
+ "adia",
66
+ "brand",
67
+ "logo",
68
+ "wordmark",
69
+ "mark",
70
+ "identity",
71
+ "favicon"
72
+ ],
73
+ "name": "UIAdiaMark",
74
+ "related": [
75
+ "Icon",
76
+ "Avatar",
77
+ "Image"
78
+ ],
79
+ "slots": {},
80
+ "states": [
81
+ {
82
+ "description": "The only state — a static, non-interactive brand mark.",
83
+ "name": "default"
84
+ }
85
+ ],
86
+ "status": "stable",
87
+ "synonyms": {
88
+ "brand": [
89
+ "adia",
90
+ "logo",
91
+ "mark",
92
+ "identity"
93
+ ],
94
+ "logo": [
95
+ "adia",
96
+ "brand",
97
+ "mark",
98
+ "wordmark"
99
+ ]
100
+ },
101
+ "tag": "adia-mark-ui",
102
+ "tokens": {
103
+ "--adia-mark-bg": {
104
+ "description": "Background fill — always the inverse surface token, regardless of variant or scheme.",
105
+ "default": "var(--a-canvas-surface-inverse)"
106
+ },
107
+ "--adia-mark-fg": {
108
+ "description": "Wordmark fill — always the inverse foreground token, regardless of variant or scheme.",
109
+ "default": "var(--a-fg-inverse)"
110
+ },
111
+ "--adia-mark-radius": {
112
+ "description": "Corner radius of the background square.",
113
+ "default": "var(--a-radius-lg)"
114
+ },
115
+ "--adia-mark-size": {
116
+ "description": "Width and height of the square mark.",
117
+ "default": "2.5rem"
118
+ }
119
+ },
120
+ "traits": [],
121
+ "version": 1
122
+ }
123
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Non-side-effect class export for `<adia-mark-ui>`.
3
+ *
4
+ * Importing this file gives you the class without auto-registering the
5
+ * tag. Useful for test isolation, subclassing with tag-name override, or
6
+ * selective composition.
7
+ *
8
+ * The auto-register path stays at `@adia-ai/web-components/components/adia-mark`
9
+ * (which imports this file + calls `defineIfFree()`).
10
+ *
11
+ * @see ../../USAGE.md#registration--auto-vs-explicit
12
+ */
13
+
14
+ /**
15
+ * <adia-mark-ui></adia-mark-ui>
16
+ * <adia-mark-ui size="sm"></adia-mark-ui>
17
+ * <adia-mark-ui size="xl"></adia-mark-ui>
18
+ *
19
+ * The Adia brand mark — a rounded square with the "Adia" wordmark cut
20
+ * out in a contrasting color. Background and wordmark are ALWAYS the
21
+ * inverse-surface/inverse-foreground token pair (gh#283); this replaces
22
+ * the four separate static light/dark/transparent SVG exports
23
+ * (`public/icons/agent-ui-*.svg`) that had to be hand-maintained per
24
+ * scheme. Not themeable — see adia-mark.yaml's anti_patterns entry for
25
+ * why a color/tone override defeats the point.
26
+ *
27
+ * Geometry (`viewBox="0 0 100 100"`) is unchanged from the original
28
+ * static asset — same five wordmark paths, same background square —
29
+ * only the `fill` values became token references. `[data-mark-glyph]`
30
+ * marks the five wordmark paths so the CSS token (`--adia-mark-fg`) can
31
+ * target them without repeating the selector five times.
32
+ *
33
+ * Static content, zero props affect the template output — `static
34
+ * template` returns the same markup every render. Safe under SSR:
35
+ * `stamp()`'s first-mount `replaceChildren` only ever regenerates
36
+ * identical, template-owned content (see gh#284's survey — this
37
+ * component follows the same "template content never depends on
38
+ * light-DOM children" shape as check-ui/switch-ui, just with zero
39
+ * interpolated values instead of property-derived ones).
40
+ */
41
+
42
+ import { UIElement, html } from '../../core/element.js';
43
+
44
+ export class UIAdiaMark extends UIElement {
45
+ static properties = {
46
+ size: { type: String, default: 'md', reflect: true },
47
+ inline: { type: Boolean, default: false, reflect: true },
48
+ };
49
+
50
+ static template = () => html`
51
+ <svg viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
52
+ <path data-mark-glyph d="M61.3692 41.2755C61.9898 41.8703 62.7139 42.1677 63.5414 42.1677C64.3948 42.1677 65.1189 41.8703 65.7137 41.2755C66.3343 40.6548 66.6446 39.9307 66.6446 39.1032C66.6446 38.2757 66.3343 37.5516 65.7137 36.931C65.1189 36.3103 64.3948 36 63.5414 36C62.7139 36 61.9898 36.3103 61.3692 36.931C60.7744 37.5516 60.477 38.2757 60.477 39.1032C60.477 39.9307 60.7744 40.6548 61.3692 41.2755Z"></path>
53
+ <path data-mark-glyph d="M61.0588 44.4951V63.8902H66.0628V44.4951H61.0588Z"></path>
54
+ <path data-mark-glyph fill-rule="evenodd" clip-rule="evenodd" d="M90 44.4929H84.9961V46.7815C83.4962 44.8937 81.3886 43.9498 78.6732 43.9498C76.0872 43.9498 73.8633 44.9454 72.0013 46.9366C70.1652 48.902 69.2472 51.3199 69.2472 54.1904C69.2472 57.0609 70.1652 59.4918 72.0013 61.483C73.8633 63.4484 76.0872 64.431 78.6732 64.431C81.3886 64.431 83.4962 63.4872 84.9961 61.5994V63.888H90V44.4929ZM79.6042 59.6598C78.0526 59.6598 76.7725 59.1556 75.764 58.147C74.7554 57.1126 74.2512 55.7938 74.2512 54.1904C74.2512 52.5871 74.7554 51.2812 75.764 50.2726C76.7725 49.2382 78.0526 48.721 79.6042 48.721C81.1558 48.721 82.4359 49.2382 83.4445 50.2726C84.4789 51.2812 84.9961 52.5871 84.9961 54.1904C84.9961 55.7938 84.4789 57.1126 83.4445 58.147C82.4359 59.1556 81.1558 59.6598 79.6042 59.6598Z"></path>
55
+ <path data-mark-glyph fill-rule="evenodd" clip-rule="evenodd" d="M51.8676 36.7388H56.8715V63.892H51.8676V61.6034C50.3936 63.4912 48.2989 64.4351 45.5836 64.4351C42.9717 64.4351 40.7348 63.4524 38.8729 61.487C37.0368 59.4958 36.1188 57.0649 36.1188 54.1944C36.1188 51.324 37.0368 48.906 38.8729 46.9407C40.7348 44.9494 42.9717 43.9538 45.5836 43.9538C48.2989 43.9538 50.3936 44.8977 51.8676 46.7855V36.7388ZM42.6355 58.151C43.6699 59.1596 44.9629 59.6639 46.5145 59.6639C48.0662 59.6639 49.3462 59.1596 50.3548 58.151C51.3633 57.1166 51.8676 55.7978 51.8676 54.1944C51.8676 52.5911 51.3633 51.2852 50.3548 50.2766C49.3462 49.2422 48.0662 48.725 46.5145 48.725C44.9629 48.725 43.6699 49.2422 42.6355 50.2766C41.627 51.2852 41.1227 52.5911 41.1227 54.1944C41.1227 55.7978 41.627 57.1166 42.6355 58.151Z"></path>
56
+ <path data-mark-glyph fill-rule="evenodd" clip-rule="evenodd" d="M28.2314 59.0044L29.8606 63.892H35.6792L26.1368 36.7388H19.5036L10 63.892H15.7797L17.4089 59.0044H28.2314ZM22.8396 42.9065L26.5634 54.0005H19.1157L22.8396 42.9065Z"></path>
57
+ </svg>
58
+ `;
59
+
60
+ connected() {
61
+ this.setAttribute('role', 'img');
62
+ if (!this.hasAttribute('aria-label')) this.setAttribute('aria-label', 'Adia');
63
+ }
64
+ }
@@ -0,0 +1,48 @@
1
+ @scope (adia-mark-ui) {
2
+ :where(:scope) {
3
+ /* ── Tokens ── */
4
+ --adia-mark-size: var(--a-size, 2.5rem);
5
+ --adia-mark-radius: var(--a-radius-lg);
6
+ /* Fixed by design — always the inverse pairing, never a themeable
7
+ variant. See adia-mark.yaml's anti_patterns entry. */
8
+ --adia-mark-bg: var(--a-canvas-surface-inverse);
9
+ --adia-mark-fg: var(--a-fg-inverse);
10
+
11
+ --adia-mark-size-xs: 1.25rem;
12
+ --adia-mark-size-sm: 1.75rem;
13
+ --adia-mark-size-md: 2.5rem;
14
+ --adia-mark-size-lg: 3.5rem;
15
+ --adia-mark-size-xl: 4.5rem;
16
+ }
17
+
18
+ :scope {
19
+ /* ADR-0037 — block-level by default; [inline] opts into inline-block
20
+ for the common "beside a text run" case (a topbar brand slot). */
21
+ display: block;
22
+ width: var(--adia-mark-size);
23
+ height: var(--adia-mark-size);
24
+ border-radius: var(--adia-mark-radius);
25
+ background: var(--adia-mark-bg);
26
+ overflow: hidden;
27
+ flex-shrink: 0;
28
+ }
29
+
30
+ :scope[inline] { display: inline-block; }
31
+
32
+ :scope > svg {
33
+ display: block;
34
+ width: 100%;
35
+ height: 100%;
36
+ }
37
+
38
+ :scope > svg [data-mark-glyph] {
39
+ fill: var(--adia-mark-fg);
40
+ }
41
+
42
+ /* ── Size ladder ── */
43
+ :scope[size="xs"] { --adia-mark-size: var(--adia-mark-size-xs); }
44
+ :scope[size="sm"] { --adia-mark-size: var(--adia-mark-size-sm); }
45
+ :scope[size="md"] { --adia-mark-size: var(--adia-mark-size-md); }
46
+ :scope[size="lg"] { --adia-mark-size: var(--adia-mark-size-lg); }
47
+ :scope[size="xl"] { --adia-mark-size: var(--adia-mark-size-xl); }
48
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `<adia-mark-ui>` — The Adia brand mark — a rounded square with the "Adia" wordmark cut out in a contrasting color. Token-driven: the background always reads as the current scheme's inverse surface, and the wordmark always reads as the current scheme's inverse foreground, so a single asset replaces separate light/dark/transparent static exports (gh#283). Not an `icon-ui` registry entry — icon-ui is the Phosphor-glyph registry only; this is a fixed, one-off brand image, a separate delivery shape.
3
+ *
4
+ * @see https://ui-kit.exe.xyz/site/components/adia-mark
5
+ *
6
+ * Type declarations generated by scripts/build/dts-codegen.mjs from
7
+ * the component's `.a2ui.json` sidecar(s). Edit the source `.yaml`,
8
+ * run `npm run build:components`, then `npm run codegen:dts` to
9
+ * regenerate; or hand-author this file fully if rich event types are
10
+ * needed beyond what the yaml `events:` block can express.
11
+ */
12
+
13
+ import { UIElement } from '../../core/element.js';
14
+
15
+ export class UIAdiaMark extends UIElement {
16
+ /** Renders as `display:inline-block` instead of the block-level default (ADR-0037) — set this when the mark sits beside a text run (a topbar brand slot) rather than standing alone. */
17
+ inline: boolean;
18
+ /** Diameter of the square mark. Matches avatar-ui's size ladder for visual consistency when the two sit side by side (e.g. an app switcher row). */
19
+ size: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
20
+ }
@@ -0,0 +1,26 @@
1
+ # adia-mark — Examples
2
+
3
+ ## Default
4
+
5
+ ```html
6
+ <adia-mark-ui></adia-mark-ui>
7
+ ```
8
+
9
+ ## Sizes
10
+
11
+ ```html
12
+ <adia-mark-ui size="xs"></adia-mark-ui>
13
+ <adia-mark-ui size="sm"></adia-mark-ui>
14
+ <adia-mark-ui size="md"></adia-mark-ui>
15
+ <adia-mark-ui size="lg"></adia-mark-ui>
16
+ <adia-mark-ui size="xl"></adia-mark-ui>
17
+ ```
18
+
19
+ ## Topbar brand slot
20
+
21
+ ```html
22
+ <row-ui gap="2" align="center">
23
+ <adia-mark-ui size="sm"></adia-mark-ui>
24
+ <text-ui variant="heading">Adia</text-ui>
25
+ </row-ui>
26
+ ```
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `<adia-mark-ui>` — auto-registers the tag on import.
3
+ *
4
+ * For non-side-effect class import (test isolation, tag override), use
5
+ * the `class` subpath:
6
+ *
7
+ * import { UIAdiaMark } from '@adia-ai/web-components/components/adia-mark/class';
8
+ *
9
+ * @see ../../USAGE.md#registration--auto-vs-explicit
10
+ */
11
+
12
+ import { defineIfFree } from '../../core/register.js';
13
+ import { UIAdiaMark } from './adia-mark.class.js';
14
+
15
+ defineIfFree('adia-mark-ui', UIAdiaMark);
16
+
17
+ export { UIAdiaMark };
@@ -0,0 +1,69 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import '../../core/element.js';
3
+ import './adia-mark.js';
4
+
5
+ const tick = () => new Promise((r) => queueMicrotask(r));
6
+
7
+ function mount(html) {
8
+ const wrap = document.createElement('div');
9
+ wrap.innerHTML = html;
10
+ document.body.appendChild(wrap);
11
+ return wrap.firstElementChild;
12
+ }
13
+
14
+ describe('adia-mark-ui', () => {
15
+ beforeEach(() => { document.body.innerHTML = ''; });
16
+
17
+ it('registers adia-mark-ui as a custom element', () => {
18
+ expect(customElements.get('adia-mark-ui')).toBeDefined();
19
+ });
20
+
21
+ it('defaults to size="md"', async () => {
22
+ const el = mount('<adia-mark-ui></adia-mark-ui>');
23
+ await tick();
24
+ expect(el.size).toBe('md');
25
+ });
26
+
27
+ it('reflects an explicit [size]', async () => {
28
+ const el = mount('<adia-mark-ui size="xl"></adia-mark-ui>');
29
+ await tick();
30
+ expect(el.size).toBe('xl');
31
+ });
32
+
33
+ it('defaults to inline=false and reflects an explicit [inline]', async () => {
34
+ const a = mount('<adia-mark-ui></adia-mark-ui>');
35
+ const b = mount('<adia-mark-ui inline></adia-mark-ui>');
36
+ await tick();
37
+ expect(a.inline).toBe(false);
38
+ expect(b.inline).toBe(true);
39
+ expect(b.getAttribute('inline')).toBe('');
40
+ });
41
+
42
+ it('sets role="img" and a default aria-label on connect', async () => {
43
+ const el = mount('<adia-mark-ui></adia-mark-ui>');
44
+ await tick();
45
+ expect(el.getAttribute('role')).toBe('img');
46
+ expect(el.getAttribute('aria-label')).toBe('Adia');
47
+ });
48
+
49
+ it('does not overwrite a consumer-authored aria-label', async () => {
50
+ const el = mount('<adia-mark-ui aria-label="Adia — home"></adia-mark-ui>');
51
+ await tick();
52
+ expect(el.getAttribute('aria-label')).toBe('Adia — home');
53
+ });
54
+
55
+ it('stamps the wordmark SVG with five glyph paths, no consumer-content conflict', async () => {
56
+ const el = mount('<adia-mark-ui></adia-mark-ui>');
57
+ await tick();
58
+ const svg = el.querySelector('svg');
59
+ expect(svg).toBeTruthy();
60
+ expect(el.querySelectorAll('[data-mark-glyph]').length).toBe(5);
61
+ });
62
+
63
+ it('renders identically regardless of size — static template, no interpolation', async () => {
64
+ const a = mount('<adia-mark-ui size="sm"></adia-mark-ui>');
65
+ const b = mount('<adia-mark-ui size="xl"></adia-mark-ui>');
66
+ await tick();
67
+ expect(a.innerHTML).toBe(b.innerHTML);
68
+ });
69
+ });
@@ -0,0 +1,121 @@
1
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
2
+ name: UIAdiaMark
3
+ tag: adia-mark-ui
4
+ status: stable
5
+ component: AdiaMark
6
+ category: display
7
+ version: 1
8
+ description: >-
9
+ The Adia brand mark — a rounded square with the "Adia" wordmark cut out
10
+ in a contrasting color. Token-driven: the background always reads as
11
+ the current scheme's inverse surface, and the wordmark always reads as
12
+ the current scheme's inverse foreground, so a single asset replaces
13
+ separate light/dark/transparent static exports (gh#283). Not an
14
+ `icon-ui` registry entry — icon-ui is the Phosphor-glyph registry only;
15
+ this is a fixed, one-off brand image, a separate delivery shape.
16
+ props:
17
+ size:
18
+ description: Diameter of the square mark. Matches avatar-ui's size ladder for visual consistency when the two sit side by side (e.g. an app switcher row).
19
+ type: string
20
+ default: md
21
+ enum:
22
+ - xs
23
+ - sm
24
+ - md
25
+ - lg
26
+ - xl
27
+ reflect: true
28
+ inline:
29
+ description: Renders as `display:inline-block` instead of the block-level default (ADR-0037) — set this when the mark sits beside a text run (a topbar brand slot) rather than standing alone.
30
+ type: boolean
31
+ default: false
32
+ reflect: true
33
+ events: {}
34
+ slots: {}
35
+ states:
36
+ - name: default
37
+ description: The only state — a static, non-interactive brand mark.
38
+ traits: []
39
+ tokens:
40
+ --adia-mark-size:
41
+ description: Width and height of the square mark.
42
+ default: 2.5rem
43
+ --adia-mark-radius:
44
+ description: Corner radius of the background square.
45
+ default: var(--a-radius-lg)
46
+ --adia-mark-bg:
47
+ description: Background fill — always the inverse surface token, regardless of variant or scheme.
48
+ default: var(--a-canvas-surface-inverse)
49
+ --adia-mark-fg:
50
+ description: Wordmark fill — always the inverse foreground token, regardless of variant or scheme.
51
+ default: var(--a-fg-inverse)
52
+ a2ui:
53
+ rules:
54
+ - Use <AdiaMark> for the Adia brand/app-identity mark — a favicon-style app-switcher tile, a splash/loading screen, an about-panel header. Do not use it as a generic icon; it has no [name] prop and always renders the same wordmark.
55
+ - Do not wrap <AdiaMark> in <Icon> or pass it a color/tone override — the background/foreground pairing is fixed by design (always inverse-on-inverse for contrast), not a themeable variant.
56
+ - Prefer <AdiaMark size="sm"> in a nav/topbar brand slot (a Row alongside the product name); reserve larger sizes (lg, xl) for splash screens or empty-state headers. Set [inline] only when the mark sits directly inside a run of prose text, not inside a Row/flex layout (block-level children compose fine as flex items already).
57
+ anti_patterns:
58
+ - wrong: |
59
+ <icon-ui name="adia"></icon-ui>
60
+ why: |
61
+ icon-ui only resolves Phosphor glyph names from the registry; there
62
+ is no "adia" Phosphor icon, and registering one would misuse a
63
+ third-party glyph set for a proprietary brand mark.
64
+ fix: |
65
+ <adia-mark-ui size="sm"></adia-mark-ui>
66
+ - wrong: |
67
+ <adia-mark-ui style="--adia-mark-bg: #171818; --adia-mark-fg: #E2E3E4;"></adia-mark-ui>
68
+ why: |
69
+ Hardcoding the fill colors defeats the entire point of gh#283 —
70
+ the mark is supposed to track the inverse-surface/inverse-foreground
71
+ tokens automatically across scheme changes, not be pinned to one
72
+ scheme's static values.
73
+ fix: |
74
+ <adia-mark-ui size="sm"></adia-mark-ui>
75
+ examples:
76
+ - name: topbar-brand-mark
77
+ description: Small brand mark in an admin-shell topbar, beside the product name.
78
+ a2ui: >-
79
+ [
80
+ {
81
+ "id": "brand-row",
82
+ "component": "Row",
83
+ "gap": "2",
84
+ "align": "center",
85
+ "children": ["mark", "name"]
86
+ },
87
+ {
88
+ "id": "mark",
89
+ "component": "AdiaMark",
90
+ "size": "sm"
91
+ },
92
+ {
93
+ "id": "name",
94
+ "component": "Text",
95
+ "variant": "heading",
96
+ "text": "Adia"
97
+ }
98
+ ]
99
+ keywords:
100
+ - adia
101
+ - brand
102
+ - logo
103
+ - wordmark
104
+ - mark
105
+ - identity
106
+ - favicon
107
+ synonyms:
108
+ logo:
109
+ - adia
110
+ - brand
111
+ - mark
112
+ - wordmark
113
+ brand:
114
+ - adia
115
+ - logo
116
+ - mark
117
+ - identity
118
+ related:
119
+ - Icon
120
+ - Avatar
121
+ - Image
@@ -45,7 +45,8 @@
45
45
  "anti_patterns": [],
46
46
  "category": "agent",
47
47
  "composes": [
48
- "button-ui"
48
+ "button-ui",
49
+ "option-card-ui"
49
50
  ],
50
51
  "events": {
51
52
  "questions-answer": {