@signal9/era-ui 25.0.0 → 25.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## [25.1.0](https://github.com/sig-nine/era-ui/compare/v25.0.0...v25.1.0) (2026-08-12)
2
+
3
+ ### Features
4
+
5
+ * **tree:** export ancestorPath standalone, and record the real scale number ([57650ca](https://github.com/sig-nine/era-ui/commit/57650ca9b7106afdc23ab9bdc02ae1a64c65fc55))
6
+
7
+ ### Bug Fixes
8
+
9
+ * **test:** ancestorPath test runs in Node, not through a Vite import path ([20bae4e](https://github.com/sig-nine/era-ui/commit/20bae4e4b8330ae2e220f7243c53c343cfa0a39a))
10
+ * **tree:** ancestorPath is its own runes-free module ([93e427d](https://github.com/sig-nine/era-ui/commit/93e427d715d58c4aa69f5d0fdfa38b167be8e3b0))
11
+
1
12
  ## [25.0.0](https://github.com/sig-nine/era-ui/compare/v24.0.3...v25.0.0) (2026-08-12)
2
13
 
3
14
  ### ⚠ BREAKING CHANGES
@@ -65,7 +65,7 @@ export const rules = {
65
65
  'no-hardcoded-motion': makeRule('Take transition timing from the motion axis, not from a literal.', findHardcodedMotion)
66
66
  };
67
67
  const plugin = {
68
- meta: { name: '@sig-nine/era-ui/eslint' },
68
+ meta: { name: '@signal9/era-ui/eslint' },
69
69
  rules,
70
70
  configs: {}
71
71
  };
@@ -0,0 +1,22 @@
1
+ import type { TreeNode } from './context.svelte.js';
2
+ /**
3
+ * The ids of every ancestor of `id`, outermost first. Empty if not found.
4
+ *
5
+ * ITS OWN MODULE, WITH NO RUNES, on purpose. The whole claim of this function is
6
+ * that it needs no state, no context and no DOM — the commonest deep-link flow
7
+ * runs BEFORE a tree exists, turning a URL into the `expanded` array the
8
+ * component is handed on first render. Living in context.svelte.ts would have
9
+ * made it unimportable from a plain Node context (that file declares $state
10
+ * fields), and living in the barrel drags every .svelte component in with it.
11
+ * Same reasoning as docs/consumer-import.ts, which is standalone so the node doc
12
+ * builder can load it.
13
+ *
14
+ * A consumer previously had to construct a throwaway TreeState, assign nodes,
15
+ * call revealPath and discard the instance — "reaching around the component for
16
+ * something the component owns", as they put it. The giveaway is that the walk
17
+ * is pure over `nodes`.
18
+ *
19
+ * TreeState.revealPath is the stateful wrapper over this: one implementation,
20
+ * two surfaces.
21
+ */
22
+ export declare function ancestorPath(nodes: TreeNode[], id: string): string[];
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The ids of every ancestor of `id`, outermost first. Empty if not found.
3
+ *
4
+ * ITS OWN MODULE, WITH NO RUNES, on purpose. The whole claim of this function is
5
+ * that it needs no state, no context and no DOM — the commonest deep-link flow
6
+ * runs BEFORE a tree exists, turning a URL into the `expanded` array the
7
+ * component is handed on first render. Living in context.svelte.ts would have
8
+ * made it unimportable from a plain Node context (that file declares $state
9
+ * fields), and living in the barrel drags every .svelte component in with it.
10
+ * Same reasoning as docs/consumer-import.ts, which is standalone so the node doc
11
+ * builder can load it.
12
+ *
13
+ * A consumer previously had to construct a throwaway TreeState, assign nodes,
14
+ * call revealPath and discard the instance — "reaching around the component for
15
+ * something the component owns", as they put it. The giveaway is that the walk
16
+ * is pure over `nodes`.
17
+ *
18
+ * TreeState.revealPath is the stateful wrapper over this: one implementation,
19
+ * two surfaces.
20
+ */
21
+ export function ancestorPath(nodes, id) {
22
+ const path = [];
23
+ const find = (list, trail) => {
24
+ for (const n of list) {
25
+ if (n.id === id) {
26
+ path.push(...trail);
27
+ return true;
28
+ }
29
+ if (n.children?.length && find(n.children, [...trail, n.id]))
30
+ return true;
31
+ }
32
+ return false;
33
+ };
34
+ find(nodes, []);
35
+ return path;
36
+ }
@@ -48,6 +48,13 @@ export declare class TreeState {
48
48
  /**
49
49
  * VISIBLE nodes in render order, which is also keyboard order.
50
50
  *
51
+ * The whole tree can be handed to `nodes`; only what is open gets flattened.
52
+ * Measured in the field on the catalog this was built for: 1,196 nodes in,
53
+ * ~58 visible, recomputed per render, comfortable with no windowing. That is
54
+ * a real number from a consumer rather than an estimate — their earlier cap
55
+ * of 162 was an artifact of the hand-rolled sidebar they replaced, which
56
+ * could only mount one family at a time.
57
+ *
51
58
  * Arrow keys move through what is on screen, not through the data — a
52
59
  * collapsed branch's children are not reachable by ↓ and must not be, or the
53
60
  * focus ring disappears into a closed subtree.
@@ -61,10 +68,11 @@ export declare class TreeState {
61
68
  /**
62
69
  * Open every ancestor of `id` so a deep link lands on a visible node.
63
70
  *
64
- * Deep-linking is the reason `expanded` is controllable at all: a URL like
65
- * /sp800-53r5/ac/ac-2.1 has to open AC and AC-2 before AC-2(1) exists on
66
- * screen. Doing it from the consumer's side means re-deriving the path
67
- * through the tree, which the tree already knows.
71
+ * Deep-linking is why `expanded` is controllable at all: a URL like
72
+ * /sp800-53r5/AC/ac-2.1 has to open AC and AC-2 before AC-2(1) exists on
73
+ * screen. This is the stateful half it mutates `expanded` and notifies.
74
+ * The path computation itself is {@link ancestorPath}, which needs no state
75
+ * and is exported for callers who only want the ids.
68
76
  */
69
77
  revealPath(id: string): string[];
70
78
  }
@@ -1,5 +1,6 @@
1
1
  import { getContext, setContext } from 'svelte';
2
2
  import { SvelteSet } from 'svelte/reactivity';
3
+ import { ancestorPath } from './ancestor-path.js';
3
4
  export class TreeState {
4
5
  nodes = $state([]);
5
6
  // SvelteSet, so membership reads are tracked per-key rather than the whole
@@ -14,6 +15,13 @@ export class TreeState {
14
15
  /**
15
16
  * VISIBLE nodes in render order, which is also keyboard order.
16
17
  *
18
+ * The whole tree can be handed to `nodes`; only what is open gets flattened.
19
+ * Measured in the field on the catalog this was built for: 1,196 nodes in,
20
+ * ~58 visible, recomputed per render, comfortable with no windowing. That is
21
+ * a real number from a consumer rather than an estimate — their earlier cap
22
+ * of 162 was an artifact of the hand-rolled sidebar they replaced, which
23
+ * could only mount one family at a time.
24
+ *
17
25
  * Arrow keys move through what is on screen, not through the data — a
18
26
  * collapsed branch's children are not reachable by ↓ and must not be, or the
19
27
  * focus ring disappears into a closed subtree.
@@ -60,25 +68,14 @@ export class TreeState {
60
68
  /**
61
69
  * Open every ancestor of `id` so a deep link lands on a visible node.
62
70
  *
63
- * Deep-linking is the reason `expanded` is controllable at all: a URL like
64
- * /sp800-53r5/ac/ac-2.1 has to open AC and AC-2 before AC-2(1) exists on
65
- * screen. Doing it from the consumer's side means re-deriving the path
66
- * through the tree, which the tree already knows.
71
+ * Deep-linking is why `expanded` is controllable at all: a URL like
72
+ * /sp800-53r5/AC/ac-2.1 has to open AC and AC-2 before AC-2(1) exists on
73
+ * screen. This is the stateful half it mutates `expanded` and notifies.
74
+ * The path computation itself is {@link ancestorPath}, which needs no state
75
+ * and is exported for callers who only want the ids.
67
76
  */
68
77
  revealPath(id) {
69
- const path = [];
70
- const find = (list, trail) => {
71
- for (const n of list) {
72
- if (n.id === id) {
73
- path.push(...trail);
74
- return true;
75
- }
76
- if (n.children?.length && find(n.children, [...trail, n.id]))
77
- return true;
78
- }
79
- return false;
80
- };
81
- find(this.nodes, []);
78
+ const path = ancestorPath(this.nodes, id);
82
79
  if (path.length) {
83
80
  for (const p of path)
84
81
  this.expanded.add(p);
@@ -1,3 +1,4 @@
1
1
  export { default as Root } from './tree.svelte';
2
2
  export { default as Item } from './tree-item.svelte';
3
3
  export { TreeState, type TreeNode, type FlatNode } from './context.svelte.js';
4
+ export { ancestorPath } from './ancestor-path.js';
@@ -1,3 +1,4 @@
1
1
  export { default as Root } from './tree.svelte';
2
2
  export { default as Item } from './tree-item.svelte';
3
3
  export { TreeState } from './context.svelte.js';
4
+ export { ancestorPath } from './ancestor-path.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signal9/era-ui",
3
- "version": "25.0.0",
3
+ "version": "25.1.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev --host",
6
6
  "build": "vite build && npm run prepack",
@@ -25,13 +25,14 @@
25
25
  "check:classes": "node --experimental-strip-types scripts/check-classes.ts",
26
26
  "check:twmerge": "node --experimental-strip-types scripts/check-twmerge.ts",
27
27
  "build:llm-docs": "node --experimental-strip-types scripts/build-llm-docs.ts",
28
- "prebuild": "npm run check:surfaces && npm run check:tokens && npm run check:routes && npm run check:canonical && npm run check:layers && npm run check:themes && npm run check:classes && npm run check:twmerge && npm run check:doc-tokens && npm run check:audit-controls && npm run check:specimen-coverage && npm run check:eslint-rules && npm run build:llm-docs",
29
- "validate": "npm run check && npm run lint && npm run format:check && npm run check:surfaces && npm run check:tokens && npm run check:routes && npm run check:canonical && npm run check:layers && npm run check:themes && npm run check:classes && npm run check:twmerge && npm run check:doc-tokens && npm run check:audit-controls && npm run check:specimen-coverage && npm run check:eslint-rules",
28
+ "prebuild": "npm run check:surfaces && npm run check:tokens && npm run check:routes && npm run check:canonical && npm run check:layers && npm run check:themes && npm run check:classes && npm run check:twmerge && npm run check:doc-tokens && npm run check:audit-controls && npm run check:specimen-coverage && npm run check:eslint-rules && npm run check:package-name && npm run build:llm-docs",
29
+ "validate": "npm run check && npm run lint && npm run format:check && npm run check:surfaces && npm run check:tokens && npm run check:routes && npm run check:canonical && npm run check:layers && npm run check:themes && npm run check:classes && npm run check:twmerge && npm run check:doc-tokens && npm run check:audit-controls && npm run check:specimen-coverage && npm run check:eslint-rules && npm run check:package-name",
30
30
  "test": "npm run test:visual",
31
31
  "check:doc-tokens": "node --experimental-strip-types scripts/check-doc-tokens.ts",
32
32
  "check:audit-controls": "node --experimental-strip-types scripts/check-audit-controls.ts",
33
33
  "check:specimen-coverage": "node --experimental-strip-types scripts/check-specimen-coverage.ts",
34
- "check:eslint-rules": "node --experimental-strip-types scripts/check-eslint-rules.ts"
34
+ "check:eslint-rules": "node --experimental-strip-types scripts/check-eslint-rules.ts",
35
+ "check:package-name": "node --experimental-strip-types scripts/check-package-name.ts"
35
36
  },
36
37
  "files": [
37
38
  "dist",
package/skill/SKILL.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: era-ui
3
3
  description: |
4
- Design-system methodology for building UIs with @sig-nine/era-ui (Svelte 5 +
4
+ Design-system methodology for building UIs with @signal9/era-ui (Svelte 5 +
5
5
  Tailwind v4). Use when: writing or reviewing any component, layout, or page
6
6
  in a project that uses era-ui — sizing controls, spacing/padding, radii,
7
7
  surfaces, motion, or composing library components. Covers: the named class
@@ -167,7 +167,7 @@ Not everything era exposes is a component — these ride along with the styleshe
167
167
  and need no import. Reach for one before hand-rolling the same styling or bending
168
168
  a component into the role. Full reference (including every named scale above):
169
169
  `/utilities.md`; machine-readable list: `/utilities.json` (or
170
- `@sig-nine/era-ui/utilities.json`).
170
+ `@signal9/era-ui/utilities.json`).
171
171
 
172
172
  | Class | Use it for |
173
173
  | -------------------- | ---------------------------------------------------------------------------------------------------------------------- |
@@ -183,8 +183,8 @@ a component into the role. Full reference (including every named scale above):
183
183
 
184
184
  ```svelte
185
185
  <script lang="ts">
186
- import { Button, Badge, OS, AI } from '@sig-nine/era-ui';
187
- import * as Select from '@sig-nine/era-ui'; // namespaced parts: Select.Root…
186
+ import { Button, Badge, OS, AI } from '@signal9/era-ui';
187
+ import * as Select from '@signal9/era-ui'; // namespaced parts: Select.Root…
188
188
  </script>
189
189
  ```
190
190
 
@@ -192,7 +192,7 @@ a component into the role. Full reference (including every named scale above):
192
192
  through; style via `class` (merged with tailwind-merge, consumer wins — the
193
193
  named classes conflict-resolve against internals, so `class="h-bar"` on a
194
194
  control-tier control just works).
195
- - `OS` (or `@sig-nine/era-ui/os`): WindowManager + workspaces, Desktop,
195
+ - `OS` (or `@signal9/era-ui/os`): WindowManager + workspaces, Desktop,
196
196
  Window, Taskbar (built-in command bar). `AI` (or `/ai`): Conversation,
197
197
  Message, Response, PromptInput, Tool/Reasoning/Task over the Step primitive.
198
198
  - List-item keyboard highlight is `data-[highlighted]:bg-hover` — never