@flyingrobots/bijou 0.3.0 → 0.5.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/README.md CHANGED
@@ -4,10 +4,11 @@ Themed terminal components for CLIs, loggers, and scripts — graceful degradati
4
4
 
5
5
  **Zero dependencies. Hexagonal architecture. Works everywhere.**
6
6
 
7
- ## What's New in 0.3.0?
7
+ ## What's New in 0.5.0?
8
8
 
9
- - **`dag()`**ASCII DAG renderer with auto-layout (Sugiyama-Lite), edge routing, badges, per-node tokens, path highlighting, and graceful degradation
10
- - **`dagSlice()`** extract subgraphs (ancestors/descendants/neighborhood) with ghost boundary nodes
9
+ - **`DagSource` adapter** bring your own graph representation (database, API, adjacency matrix). DAG rendering no longer requires `DagNode[]`
10
+ - **Traversal-based slicing** — `dagSlice()` BFS-walks via `has()`/`children()`/`parents()`, never enumerates the full graph
11
+ - **Composable slices** — `dagSlice()` returns a `SlicedDagSource` when given `DagSource`, enabling slice-of-slice chains
11
12
 
12
13
  See the [CHANGELOG](https://github.com/flyingrobots/bijou/blob/main/docs/CHANGELOG.md) for the full release history.
13
14
 
@@ -40,7 +41,7 @@ console.log(box('Hello, world!'));
40
41
  `badge()`, `alert()`, `kbd()`, `skeleton()` — status indicators and UI primitives.
41
42
 
42
43
  ### Data
43
- `table()`, `tree()`, `accordion()`, `timeline()` — structured data display.
44
+ `table()`, `tree()`, `accordion()`, `timeline()`, `dag()`, `dagSlice()`, `dagLayout()` — structured data display and DAG rendering with `DagSource` adapter for external graphs.
44
45
 
45
46
  ### Navigation
46
47
  `tabs()`, `breadcrumb()`, `stepper()`, `paginator()` — wayfinding components.
@@ -0,0 +1,64 @@
1
+ import type { TokenValue } from '../theme/tokens.js';
2
+ import type { DagNode } from './dag.js';
3
+ /**
4
+ * Adapter interface for accessing graph data. Decouples DAG rendering
5
+ * from any specific in-memory representation. Implementations can wrap
6
+ * arrays, databases, APIs, or any other graph store.
7
+ *
8
+ * A DagSource may represent an unbounded graph — it has no enumeration
9
+ * method. Use `dagSlice()` to BFS-walk from a focus node and produce
10
+ * a bounded `SlicedDagSource` that the renderer can consume.
11
+ */
12
+ export interface DagSource {
13
+ /** Returns true if a node with this ID exists in the graph. */
14
+ has(id: string): boolean;
15
+ /** Human-readable label for a node. */
16
+ label(id: string): string;
17
+ /** Child node IDs (outgoing edges). */
18
+ children(id: string): readonly string[];
19
+ /**
20
+ * Parent node IDs (incoming edges). Required for ancestor traversal
21
+ * in `dagSlice()`. Without this, only `direction: 'descendants'` is
22
+ * supported.
23
+ */
24
+ parents?(id: string): readonly string[];
25
+ /** Optional badge text for a node. */
26
+ badge?(id: string): string | undefined;
27
+ /** Optional per-node color/style token. */
28
+ token?(id: string): TokenValue | undefined;
29
+ }
30
+ /**
31
+ * A bounded DagSource produced by `sliceSource()`. Extends DagSource
32
+ * with `ids()` so the renderer can enumerate the finite node set.
33
+ */
34
+ export interface SlicedDagSource extends DagSource {
35
+ /** All node IDs in this bounded slice. */
36
+ ids(): readonly string[];
37
+ /** Whether this node is a ghost (boundary placeholder). */
38
+ ghost(id: string): boolean;
39
+ /** Ghost label override (e.g., "... 3 ancestors"). */
40
+ ghostLabel(id: string): string | undefined;
41
+ }
42
+ /** Options for `dagSlice()`. */
43
+ export interface DagSliceOptions {
44
+ direction?: 'ancestors' | 'descendants' | 'both';
45
+ depth?: number;
46
+ }
47
+ /** Returns true if the value implements the `DagSource` interface. */
48
+ export declare function isDagSource(value: unknown): value is DagSource;
49
+ /** Returns true if the value is a `SlicedDagSource` (bounded, with `ids()`). */
50
+ export declare function isSlicedDagSource(value: unknown): value is SlicedDagSource;
51
+ /** Wraps a `DagNode[]` as a `SlicedDagSource`. Already bounded. */
52
+ export declare function arraySource(nodes: DagNode[]): SlicedDagSource;
53
+ /** @internal Converts a `SlicedDagSource` to `DagNode[]` for internal renderers. */
54
+ export declare function materialize(source: SlicedDagSource): DagNode[];
55
+ /**
56
+ * BFS-walks a `DagSource` from a focus node and returns a bounded
57
+ * `SlicedDagSource` containing only the neighborhood. Ghost nodes are
58
+ * injected at depth boundaries.
59
+ *
60
+ * Never calls `ids()` on the source — works purely via traversal.
61
+ * For ancestor/both directions, `source.parents()` must be provided.
62
+ */
63
+ export declare function sliceSource(source: DagSource, focus: string, opts?: DagSliceOptions): SlicedDagSource;
64
+ //# sourceMappingURL=dag-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dag-source.d.ts","sourceRoot":"","sources":["../../../src/core/components/dag-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAIxC;;;;;;;;GAQG;AACH,MAAM,WAAW,SAAS;IACxB,+DAA+D;IAC/D,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IAEzB,uCAAuC;IACvC,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IAE1B,uCAAuC;IACvC,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IAExC;;;;OAIG;IACH,OAAO,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IAExC,sCAAsC;IACtC,KAAK,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAEvC,2CAA2C;IAC3C,KAAK,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;CAC5C;AAED;;;GAGG;AACH,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAChD,0CAA0C;IAC1C,GAAG,IAAI,SAAS,MAAM,EAAE,CAAC;IAEzB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IAE3B,sDAAsD;IACtD,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CAC5C;AAED,gCAAgC;AAChC,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,WAAW,GAAG,aAAa,GAAG,MAAM,CAAC;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAID,sEAAsE;AACtE,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAS9D;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,eAAe,CAO1E;AAID,mEAAmE;AACnE,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,eAAe,CA2B7D;AAID,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,EAAE,CAqB9D;AAuBD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,eAAe,GACrB,eAAe,CAsIjB"}
@@ -0,0 +1,227 @@
1
+ // ── Type Guard ──────────────────────────────────────────────────────
2
+ /** Returns true if the value implements the `DagSource` interface. */
3
+ export function isDagSource(value) {
4
+ return (typeof value === 'object' &&
5
+ value !== null &&
6
+ !Array.isArray(value) &&
7
+ typeof value.has === 'function' &&
8
+ typeof value.label === 'function' &&
9
+ typeof value.children === 'function');
10
+ }
11
+ /** Returns true if the value is a `SlicedDagSource` (bounded, with `ids()`). */
12
+ export function isSlicedDagSource(value) {
13
+ return (isDagSource(value) &&
14
+ typeof value.ids === 'function' &&
15
+ typeof value.ghost === 'function' &&
16
+ typeof value.ghostLabel === 'function');
17
+ }
18
+ // ── arraySource ─────────────────────────────────────────────────────
19
+ /** Wraps a `DagNode[]` as a `SlicedDagSource`. Already bounded. */
20
+ export function arraySource(nodes) {
21
+ const map = new Map();
22
+ for (const n of nodes)
23
+ map.set(n.id, n);
24
+ // Pre-compute parent map
25
+ const parentMap = new Map();
26
+ for (const n of nodes) {
27
+ if (!parentMap.has(n.id))
28
+ parentMap.set(n.id, []);
29
+ for (const c of n.edges ?? []) {
30
+ if (!parentMap.has(c))
31
+ parentMap.set(c, []);
32
+ parentMap.get(c).push(n.id);
33
+ }
34
+ }
35
+ const idList = Object.freeze(nodes.map(n => n.id));
36
+ return {
37
+ ids: () => idList,
38
+ has: (id) => map.has(id),
39
+ label: (id) => map.get(id)?.label ?? id,
40
+ children: (id) => [...(map.get(id)?.edges ?? [])],
41
+ parents: (id) => [...(parentMap.get(id) ?? [])],
42
+ badge: (id) => map.get(id)?.badge,
43
+ token: (id) => map.get(id)?.token,
44
+ ghost: (id) => map.get(id)?._ghost ?? false,
45
+ ghostLabel: (id) => map.get(id)?._ghostLabel,
46
+ };
47
+ }
48
+ // ── materialize ─────────────────────────────────────────────────────
49
+ /** @internal Converts a `SlicedDagSource` to `DagNode[]` for internal renderers. */
50
+ export function materialize(source) {
51
+ const ids = source.ids();
52
+ const nodes = [];
53
+ for (const id of ids) {
54
+ const children = source.children(id);
55
+ const node = {
56
+ id,
57
+ label: source.label(id),
58
+ edges: children.length > 0 ? [...children] : undefined,
59
+ };
60
+ const badge = source.badge?.(id);
61
+ if (badge !== undefined)
62
+ node.badge = badge;
63
+ const token = source.token?.(id);
64
+ if (token !== undefined)
65
+ node.token = token;
66
+ if (source.ghost(id)) {
67
+ node._ghost = true;
68
+ node._ghostLabel = source.ghostLabel(id);
69
+ }
70
+ nodes.push(node);
71
+ }
72
+ return nodes;
73
+ }
74
+ // ── emptySource ─────────────────────────────────────────────────────
75
+ const EMPTY_IDS = Object.freeze([]);
76
+ /** @internal Singleton empty source for missing focus nodes. */
77
+ const EMPTY_SOURCE = Object.freeze({
78
+ ids: () => EMPTY_IDS,
79
+ has: () => false,
80
+ label: () => '',
81
+ children: () => EMPTY_IDS,
82
+ ghost: () => false,
83
+ ghostLabel: () => undefined,
84
+ });
85
+ // ── Ghost ID Prefixes ───────────────────────────────────────────────
86
+ const GHOST_ANCESTORS_PREFIX = '__ghost_ancestors_';
87
+ const GHOST_DESCENDANTS_PREFIX = '__ghost_descendants_';
88
+ // ── sliceSource ─────────────────────────────────────────────────────
89
+ /**
90
+ * BFS-walks a `DagSource` from a focus node and returns a bounded
91
+ * `SlicedDagSource` containing only the neighborhood. Ghost nodes are
92
+ * injected at depth boundaries.
93
+ *
94
+ * Never calls `ids()` on the source — works purely via traversal.
95
+ * For ancestor/both directions, `source.parents()` must be provided.
96
+ */
97
+ export function sliceSource(source, focus, opts) {
98
+ const explicitDirection = opts?.direction;
99
+ const direction = explicitDirection ?? 'both';
100
+ const maxDepth = opts?.depth ?? Infinity;
101
+ if (!source.has(focus))
102
+ return EMPTY_SOURCE;
103
+ const included = new Set();
104
+ const ghostNodes = new Map();
105
+ // Precomputed reverse map for when source.parents is absent
106
+ const derivedParents = new Map();
107
+ // BFS ancestors
108
+ if (direction === 'ancestors' || direction === 'both') {
109
+ if (!source.parents) {
110
+ if (explicitDirection === 'ancestors') {
111
+ throw new Error('[bijou] dagSlice(): source.parents() is required for ancestor traversal');
112
+ }
113
+ // direction defaulted to 'both' — silently downgrade to descendants-only
114
+ }
115
+ else {
116
+ const getParents = source.parents.bind(source);
117
+ const queue = [[focus, 0]];
118
+ const visited = new Set();
119
+ while (queue.length > 0) {
120
+ const [id, depth] = queue.shift();
121
+ if (visited.has(id))
122
+ continue;
123
+ visited.add(id);
124
+ included.add(id);
125
+ if (depth < maxDepth) {
126
+ for (const p of getParents(id)) {
127
+ if (!visited.has(p) && source.has(p))
128
+ queue.push([p, depth + 1]);
129
+ }
130
+ }
131
+ else {
132
+ const boundaryParents = [...getParents(id)].filter(p => !visited.has(p) && source.has(p));
133
+ if (boundaryParents.length > 0) {
134
+ const ghostId = `${GHOST_ANCESTORS_PREFIX}${id}`;
135
+ included.add(ghostId);
136
+ const count = boundaryParents.length;
137
+ ghostNodes.set(ghostId, {
138
+ label: `... ${count} ancestor${count !== 1 ? 's' : ''}`,
139
+ edges: [id],
140
+ });
141
+ }
142
+ }
143
+ }
144
+ }
145
+ }
146
+ // BFS descendants
147
+ if (direction === 'descendants' || direction === 'both') {
148
+ const queue = [[focus, 0]];
149
+ const visited = new Set();
150
+ while (queue.length > 0) {
151
+ const [id, depth] = queue.shift();
152
+ if (visited.has(id))
153
+ continue;
154
+ visited.add(id);
155
+ included.add(id);
156
+ const ch = [...source.children(id)].filter(c => source.has(c));
157
+ // Track reverse edges for derived parent map
158
+ if (!source.parents) {
159
+ for (const c of ch) {
160
+ if (!derivedParents.has(c))
161
+ derivedParents.set(c, []);
162
+ derivedParents.get(c).push(id);
163
+ }
164
+ }
165
+ if (depth < maxDepth) {
166
+ for (const c of ch) {
167
+ if (!visited.has(c))
168
+ queue.push([c, depth + 1]);
169
+ }
170
+ }
171
+ else {
172
+ const boundaryChildren = ch.filter(c => !visited.has(c));
173
+ if (boundaryChildren.length > 0) {
174
+ const ghostId = `${GHOST_DESCENDANTS_PREFIX}${id}`;
175
+ included.add(ghostId);
176
+ const count = boundaryChildren.length;
177
+ ghostNodes.set(ghostId, {
178
+ label: `... ${count} descendant${count !== 1 ? 's' : ''}`,
179
+ edges: [],
180
+ });
181
+ }
182
+ }
183
+ }
184
+ }
185
+ // Build the sliced DagSource
186
+ const slicedIds = Object.freeze([...included]);
187
+ // Inherit ghost status from input when slicing a SlicedDagSource
188
+ const inheritGhost = isSlicedDagSource(source) ? source : null;
189
+ return {
190
+ ids: () => slicedIds,
191
+ has: (id) => included.has(id),
192
+ label: (id) => {
193
+ const ghost = ghostNodes.get(id);
194
+ if (ghost)
195
+ return ghost.label;
196
+ return source.label(id);
197
+ },
198
+ children: (id) => {
199
+ const ghost = ghostNodes.get(id);
200
+ if (ghost)
201
+ return [...ghost.edges];
202
+ // Filter to included IDs, plus any descendant ghost for this node
203
+ const ch = [...source.children(id)].filter(c => included.has(c));
204
+ const descGhostId = `${GHOST_DESCENDANTS_PREFIX}${id}`;
205
+ if (ghostNodes.has(descGhostId))
206
+ ch.push(descGhostId);
207
+ return ch;
208
+ },
209
+ parents: (id) => {
210
+ if (ghostNodes.has(id)) {
211
+ if (id.startsWith(GHOST_ANCESTORS_PREFIX))
212
+ return [];
213
+ const boundaryId = id.replace(GHOST_DESCENDANTS_PREFIX, '');
214
+ return included.has(boundaryId) ? [boundaryId] : [];
215
+ }
216
+ if (source.parents) {
217
+ return [...source.parents(id)].filter(p => included.has(p));
218
+ }
219
+ return (derivedParents.get(id) ?? []).filter(p => included.has(p));
220
+ },
221
+ badge: (id) => ghostNodes.has(id) ? undefined : source.badge?.(id),
222
+ token: (id) => ghostNodes.has(id) ? undefined : source.token?.(id),
223
+ ghost: (id) => ghostNodes.has(id) || (inheritGhost !== null && inheritGhost.ghost(id)),
224
+ ghostLabel: (id) => ghostNodes.get(id)?.label ?? inheritGhost?.ghostLabel(id),
225
+ };
226
+ }
227
+ //# sourceMappingURL=dag-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dag-source.js","sourceRoot":"","sources":["../../../src/core/components/dag-source.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import type { BijouContext } from '../../ports/context.js';
2
2
  import type { TokenValue } from '../theme/tokens.js';
3
+ import type { DagSource, SlicedDagSource, DagSliceOptions } from './dag-source.js';
3
4
  export interface DagNode {
4
5
  id: string;
5
6
  label: string;
@@ -15,14 +16,29 @@ export interface DagOptions {
15
16
  edgeToken?: TokenValue;
16
17
  highlightPath?: string[];
17
18
  highlightToken?: TokenValue;
19
+ selectedId?: string;
20
+ selectedToken?: TokenValue;
18
21
  nodeWidth?: number;
19
22
  maxWidth?: number;
20
23
  direction?: 'down' | 'right';
21
24
  ctx?: BijouContext;
22
25
  }
23
- export declare function dagSlice(nodes: DagNode[], focus: string, opts?: {
24
- direction?: 'ancestors' | 'descendants' | 'both';
25
- depth?: number;
26
- }): DagNode[];
26
+ export interface DagNodePosition {
27
+ readonly row: number;
28
+ readonly col: number;
29
+ readonly width: number;
30
+ readonly height: number;
31
+ }
32
+ export interface DagLayout {
33
+ readonly output: string;
34
+ readonly nodes: ReadonlyMap<string, DagNodePosition>;
35
+ readonly width: number;
36
+ readonly height: number;
37
+ }
38
+ export declare function dagSlice(source: DagSource, focus: string, opts?: DagSliceOptions): SlicedDagSource;
39
+ export declare function dagSlice(nodes: DagNode[], focus: string, opts?: DagSliceOptions): DagNode[];
40
+ export declare function dagLayout(source: SlicedDagSource, options?: DagOptions): DagLayout;
41
+ export declare function dagLayout(nodes: DagNode[], options?: DagOptions): DagLayout;
42
+ export declare function dag(source: SlicedDagSource, options?: DagOptions): string;
27
43
  export declare function dag(nodes: DagNode[], options?: DagOptions): string;
28
44
  //# sourceMappingURL=dag.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dag.d.ts","sourceRoot":"","sources":["../../../src/core/components/dag.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAKrD,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,cAAc,CAAC,EAAE,UAAU,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC7B,GAAG,CAAC,EAAE,YAAY,CAAC;CACpB;AA4hBD,wBAAgB,QAAQ,CACtB,KAAK,EAAE,OAAO,EAAE,EAChB,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IACL,SAAS,CAAC,EAAE,WAAW,GAAG,aAAa,GAAG,MAAM,CAAC;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GACA,OAAO,EAAE,CAuHX;AAID,wBAAgB,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,OAAO,GAAE,UAAe,GAAG,MAAM,CAgBtE"}
1
+ {"version":3,"file":"dag.d.ts","sourceRoot":"","sources":["../../../src/core/components/dag.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGrD,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAInF,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,cAAc,CAAC,EAAE,UAAU,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,UAAU,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC7B,GAAG,CAAC,EAAE,YAAY,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACrD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAiiBD,wBAAgB,QAAQ,CACtB,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,eAAe,GACrB,eAAe,CAAC;AACnB,wBAAgB,QAAQ,CACtB,KAAK,EAAE,OAAO,EAAE,EAChB,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,eAAe,GACrB,OAAO,EAAE,CAAC;AAgBb,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;AACpF,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;AAmB7E,wBAAgB,GAAG,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;AAC3E,wBAAgB,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { getDefaultContext } from '../../context.js';
2
+ import { isDagSource, isSlicedDagSource, arraySource, materialize, sliceSource } from './dag-source.js';
2
3
  // ── Helpers ────────────────────────────────────────────────────────
3
4
  function resolveCtx(ctx) {
4
5
  if (ctx)
@@ -226,9 +227,9 @@ function renderNodeBox(label, badgeText, width, ghost) {
226
227
  return [top, mid, bot];
227
228
  }
228
229
  // ── Interactive Renderer ───────────────────────────────────────────
229
- function renderInteractive(nodes, options, ctx) {
230
+ function renderInteractiveLayout(nodes, options, ctx) {
230
231
  if (nodes.length === 0)
231
- return '';
232
+ return { output: '', nodes: new Map(), width: 0, height: 0 };
232
233
  const nodeMap = new Map();
233
234
  for (const n of nodes)
234
235
  nodeMap.set(n.id, n);
@@ -356,6 +357,7 @@ function renderInteractive(nodes, options, ctx) {
356
357
  }
357
358
  }
358
359
  // Write node boxes
360
+ const positions = new Map();
359
361
  for (const n of nodes) {
360
362
  const layer = layerMap.get(n.id);
361
363
  const col = colIndex.get(n.id);
@@ -363,9 +365,13 @@ function renderInteractive(nodes, options, ctx) {
363
365
  continue;
364
366
  const startCol = col * colStride;
365
367
  const startRow = layer * RS;
368
+ positions.set(n.id, { row: startRow, col: startCol, width: nodeWidth, height: 3 });
366
369
  const boxLines = renderNodeBox(n.label, n.badge, nodeWidth, n._ghost === true);
367
370
  let nToken;
368
- if (highlightSet.has(n.id) && options.highlightToken) {
371
+ if (options.selectedId === n.id) {
372
+ nToken = options.selectedToken ?? ctx.theme.theme.ui.cursor;
373
+ }
374
+ else if (highlightSet.has(n.id) && options.highlightToken) {
369
375
  nToken = options.highlightToken;
370
376
  }
371
377
  else if (n.token) {
@@ -417,7 +423,7 @@ function renderInteractive(nodes, options, ctx) {
417
423
  while (lines.length > 0 && lines[lines.length - 1].trim() === '') {
418
424
  lines.pop();
419
425
  }
420
- return lines.join('\n');
426
+ return { output: lines.join('\n'), nodes: positions, width: gridCols, height: gridRows };
421
427
  }
422
428
  // ── Pipe Renderer ──────────────────────────────────────────────────
423
429
  function renderPipe(nodes) {
@@ -474,131 +480,32 @@ function renderAccessible(nodes, layerMap) {
474
480
  lines.pop();
475
481
  return lines.join('\n');
476
482
  }
477
- // ── dagSlice ───────────────────────────────────────────────────────
478
- export function dagSlice(nodes, focus, opts) {
479
- const direction = opts?.direction ?? 'both';
480
- const maxDepth = opts?.depth ?? Infinity;
481
- const nodeMap = new Map();
482
- for (const n of nodes)
483
- nodeMap.set(n.id, n);
484
- if (!nodeMap.has(focus))
485
- return [];
486
- const included = new Set();
487
- const ghostIds = new Set();
488
- // Build parent map
489
- const parentsMap = new Map();
490
- for (const n of nodes) {
491
- if (!parentsMap.has(n.id))
492
- parentsMap.set(n.id, []);
493
- for (const c of n.edges ?? []) {
494
- if (!parentsMap.has(c))
495
- parentsMap.set(c, []);
496
- parentsMap.get(c).push(n.id);
497
- }
483
+ export function dagSlice(input, focus, opts) {
484
+ if (isDagSource(input)) {
485
+ return sliceSource(input, focus, opts);
498
486
  }
499
- // BFS ancestors
500
- if (direction === 'ancestors' || direction === 'both') {
501
- const queue = [[focus, 0]];
502
- const visited = new Set();
503
- while (queue.length > 0) {
504
- const entry = queue.shift();
505
- const id = entry[0];
506
- const depth = entry[1];
507
- if (visited.has(id))
508
- continue;
509
- visited.add(id);
510
- included.add(id);
511
- if (depth < maxDepth) {
512
- for (const p of parentsMap.get(id) ?? []) {
513
- if (!visited.has(p))
514
- queue.push([p, depth + 1]);
515
- }
516
- }
517
- else {
518
- const boundaryParents = (parentsMap.get(id) ?? []).filter(p => !visited.has(p));
519
- if (boundaryParents.length > 0) {
520
- const ghostId = `__ghost_ancestors_${id}`;
521
- ghostIds.add(ghostId);
522
- included.add(ghostId);
523
- }
524
- }
525
- }
526
- }
527
- // BFS descendants
528
- if (direction === 'descendants' || direction === 'both') {
529
- const queue = [[focus, 0]];
530
- const visited = new Set();
531
- while (queue.length > 0) {
532
- const entry = queue.shift();
533
- const id = entry[0];
534
- const depth = entry[1];
535
- if (visited.has(id))
536
- continue;
537
- visited.add(id);
538
- included.add(id);
539
- const ch = nodeMap.get(id)?.edges ?? [];
540
- if (depth < maxDepth) {
541
- for (const c of ch) {
542
- if (!visited.has(c) && nodeMap.has(c))
543
- queue.push([c, depth + 1]);
544
- }
545
- }
546
- else {
547
- const boundaryChildren = ch.filter(c => !visited.has(c) && nodeMap.has(c));
548
- if (boundaryChildren.length > 0) {
549
- const ghostId = `__ghost_descendants_${id}`;
550
- ghostIds.add(ghostId);
551
- included.add(ghostId);
552
- }
553
- }
554
- }
555
- }
556
- // Build result
557
- const result = [];
558
- for (const gid of ghostIds) {
559
- if (gid.startsWith('__ghost_ancestors_')) {
560
- const boundaryId = gid.replace('__ghost_ancestors_', '');
561
- const allParents = (parentsMap.get(boundaryId) ?? []).filter(p => !included.has(p));
562
- const count = allParents.length;
563
- result.push({
564
- id: gid,
565
- label: `... ${count} ancestor${count !== 1 ? 's' : ''}`,
566
- edges: [boundaryId],
567
- _ghost: true,
568
- _ghostLabel: `... ${count} ancestor${count !== 1 ? 's' : ''}`,
569
- });
570
- }
571
- }
572
- for (const n of nodes) {
573
- if (!included.has(n.id))
574
- continue;
575
- const filteredEdges = (n.edges ?? []).filter(e => included.has(e));
576
- const descGhostId = `__ghost_descendants_${n.id}`;
577
- if (ghostIds.has(descGhostId)) {
578
- filteredEdges.push(descGhostId);
579
- }
580
- result.push({ ...n, edges: filteredEdges.length > 0 ? filteredEdges : undefined });
581
- }
582
- for (const gid of ghostIds) {
583
- if (gid.startsWith('__ghost_descendants_')) {
584
- const boundaryId = gid.replace('__ghost_descendants_', '');
585
- const boundaryNode = nodeMap.get(boundaryId);
586
- const allChildren = (boundaryNode?.edges ?? []).filter(c => !included.has(c) && nodeMap.has(c));
587
- const count = allChildren.length;
588
- result.push({
589
- id: gid,
590
- label: `... ${count} descendant${count !== 1 ? 's' : ''}`,
591
- _ghost: true,
592
- _ghostLabel: `... ${count} descendant${count !== 1 ? 's' : ''}`,
593
- });
594
- }
487
+ // Array path: wrap, slice, materialize back for backward compat
488
+ const source = arraySource(input);
489
+ return materialize(sliceSource(source, focus, opts));
490
+ }
491
+ export function dagLayout(input, options = {}) {
492
+ if (isDagSource(input) && !isSlicedDagSource(input)) {
493
+ throw new Error('[bijou] dagLayout(): received an unbounded DagSource. Use dagSlice() to produce a SlicedDagSource first.');
595
494
  }
596
- return result;
495
+ const ctx = resolveCtx(options.ctx);
496
+ const nodes = isSlicedDagSource(input) ? materialize(input) : input;
497
+ if (nodes.length === 0)
498
+ return { output: '', nodes: new Map(), width: 0, height: 0 };
499
+ const result = renderInteractiveLayout(nodes, options, ctx);
500
+ return { output: result.output, nodes: result.nodes, width: result.width, height: result.height };
597
501
  }
598
- // ── Main Entry Point ───────────────────────────────────────────────
599
- export function dag(nodes, options = {}) {
502
+ export function dag(input, options = {}) {
503
+ if (isDagSource(input) && !isSlicedDagSource(input)) {
504
+ throw new Error('[bijou] dag(): received an unbounded DagSource. Use dagSlice() to produce a SlicedDagSource first.');
505
+ }
600
506
  const ctx = resolveCtx(options.ctx);
601
507
  const mode = ctx.mode;
508
+ const nodes = isSlicedDagSource(input) ? materialize(input) : input;
602
509
  if (nodes.length === 0)
603
510
  return '';
604
511
  if (mode === 'pipe') {
@@ -608,6 +515,6 @@ export function dag(nodes, options = {}) {
608
515
  const layerMap = assignLayers(nodes);
609
516
  return renderAccessible(nodes, layerMap);
610
517
  }
611
- return renderInteractive(nodes, options, ctx);
518
+ return renderInteractiveLayout(nodes, options, ctx).output;
612
519
  }
613
520
  //# sourceMappingURL=dag.js.map