boundry 0.5.0 → 0.6.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
@@ -4,6 +4,28 @@ All notable changes to Boundry are documented here.
4
4
  Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
5
5
  versioning follows [semver](https://semver.org/).
6
6
 
7
+ ## [0.6.0] — 2026-07-22
8
+
9
+ ### Changed
10
+
11
+ - **`boundry diff` now emits a single "proposed changes" view by default** ([#8]),
12
+ instead of one view per changed layer. The per-layer shape (added in 0.4.0,
13
+ extended per-altitude in 0.5.0) had two failure modes on real changes: it
14
+ multiplied views (one multi-slice change produced ~21), and its per-layer
15
+ `include *` collapsed deeply-nested proposals into their grey ancestor box, so a
16
+ nested proposed leaf silently lost its amber. The new `boundry_diff` view uses
17
+ `include * -> * where tag is #proposed` with **no** bare `include *`, so it pulls
18
+ in exactly the proposed edges and the leaf modules they touch — every change is
19
+ drawn at leaf level, uncollapsed and uniformly coloured, on one landing. A
20
+ `#proposed` box with no edge yet is included by fqn so a proposed-but-unwired
21
+ module still shows, and every endpoint's ancestor chain is included so each
22
+ change nests under its layer/system for context. `approve` still deletes the
23
+ derived file (0.5.0 behaviour).
24
+ - The per-altitude per-layer views from 0.5.0 remain available behind
25
+ **`boundry diff --per-layer`** — useful for a small change, opt-in.
26
+
27
+ [#8]: https://github.com/makspiechota/boundry/issues/8
28
+
7
29
  ## [0.5.0] — 2026-07-21
8
30
 
9
31
  ### Fixed
package/README.md CHANGED
@@ -293,38 +293,41 @@ this styling back out with the marker: a `#proposed` edge returns to bare, a
293
293
  `#proposed` box stays but loses its colour, a `#proposal-delete` is removed
294
294
  outright. Requires **LikeC4 ≥ 1.58** to render.
295
295
 
296
- ### Reviewing a proposal — per-layer diff views (prototype)
296
+ ### Reviewing a proposal — the diff view (prototype)
297
297
 
298
- A proposal nested inside a box is invisible at a wider zoom: at the top level the
299
- box collapses and its inner `#proposed` edge disappears. So `diff` generates a
300
- **focused view for every layer that draws a pending change** — into a derived
301
- `boundry.diff.likec4`:
298
+ `diff` generates a single **proposed-changes** view every pending change on one
299
+ landing into a derived `boundry.diff.likec4`:
302
300
 
303
301
  ```bash
304
302
  boundry diff --arch arch # (re)write boundry.diff.likec4
305
- likec4 serve arch # review each layer, changes coloured
303
+ likec4 serve arch # open 'Boundry diff — proposed changes'
306
304
  ```
307
305
 
308
306
  The highlighting is **generated, not hand-styled** — `diff` emits the LikeC4
309
- style rules into the derived file, so every `#proposed` box fills amber and edge
310
- goes amber + solid, every `#proposal-delete` red, deterministically. That closes
311
- the last manual seam: `annotate` marks, `diff` colours, no agent-dependent
312
- styling step. Boxes are styled *in place* (never force-included), so a nested
313
- proposal stays in its own layer; unchanged elements keep their defaults; and the
314
- rules live only in the generated views, so your own views are untouched.
315
-
316
- A changed edge is drawn at **every altitude it appears**, not only the
317
- common-ancestor layer so a cross-system dependency between two deeply-nested
318
- leaves gets a view at each endpoint's system and container as well as at the
319
- shared root. Open the change at whatever altitude matters (the whole system, a
320
- specific container) instead of being dropped at the noisy root view. `diff` emits
321
- only the layers that genuinely draw the edge: never above the common ancestor
322
- (where it collapses to an undrawn self-loop), never the leaf endpoints themselves.
307
+ rules into the derived file, so every `#proposed` box fills amber and edge goes
308
+ amber + solid, every `#proposal-delete` red, deterministically. That closes the
309
+ last manual seam: `annotate` marks, `diff` colours, no agent-dependent styling
310
+ step. Unchanged elements keep their defaults, and the rules live only in the
311
+ generated view, so your own views are untouched.
312
+
313
+ The view uses `include * -> * where tag is #proposed` (no bare `include *`), so it
314
+ pulls in exactly the proposed edges and the leaf modules they touch — a
315
+ deeply-nested proposal renders as its **own coloured node** instead of collapsing
316
+ into a grey ancestor. A proposed *module* with no edge yet is included too, so a
317
+ proposed-but-unwired box still shows. Each change nests under its layer/system for
318
+ context.
319
+
320
+ For a small change you can ask for the old per-layer shape instead — one focused
321
+ `view … of <scope>` for every layer that draws a change:
322
+
323
+ ```bash
324
+ boundry diff --arch arch --per-layer
325
+ ```
323
326
 
324
327
  The file is a **derived artifact**: overwritten every run, removed when nothing is
325
328
  proposed, so it always matches the current diagram. `approve` deletes it too, as
326
- part of enacting — the moment the last proposal is approved the views are stale,
327
- so the post-approve workspace validates clean. It reads the diagram's own markers
329
+ part of enacting — the moment the last proposal is approved the view is stale, so
330
+ the post-approve workspace validates clean. It reads the diagram's own markers
328
331
  (not the lock), so it frames whatever `annotate` or a human has marked. Being
329
332
  derived, it's a `.gitignore` candidate (`boundry.diff.likec4`).
330
333
 
@@ -340,7 +343,7 @@ boundry generate [--arch <dir>] [--cwd <dir>] [--out <file>]
340
343
  boundry verify [--arch <dir>] [--cwd <dir>]
341
344
  boundry approve [--arch <dir>] [--cwd <dir>]
342
345
  boundry annotate [--arch <dir>]
343
- boundry diff [--arch <dir>]
346
+ boundry diff [--arch <dir>] [--per-layer]
344
347
  ```
345
348
 
346
349
  | Flag | Meaning |
@@ -348,6 +351,7 @@ boundry diff [--arch <dir>]
348
351
  | `--arch <dir>` | LikeC4 workspace directory (all `.likec4` files in it are merged). Default `.`. |
349
352
  | `--cwd <dir>` | Repo root to check. `folder` paths are relative to it. Lets you run from anywhere. |
350
353
  | `--out <file>` | `generate` only: where to write the dependency-cruiser config. Default `.dependency-cruiser.cjs`. |
354
+ | `--per-layer` | `diff` only: emit one focused view per layer instead of the single proposed-changes view. |
351
355
  | `sources...` | `check` only: paths to lint. Default `src`. |
352
356
 
353
357
  - **`check`** compiles the rules and runs the linter. Exits non-zero on any violation.
@@ -360,8 +364,9 @@ boundry diff [--arch <dir>]
360
364
  removes `#proposal-delete`) and writes `boundry.lock`. For humans, not agents.
361
365
  - **`annotate`** rewrites undeclared additions as `#proposed`, diffing against
362
366
  `boundry.lock`.
363
- - **`diff`** generates a focused, colour-coded review view per layer that holds a
364
- pending change, into a derived `boundry.diff.likec4`.
367
+ - **`diff`** generates a single colour-coded "proposed changes" review view into a
368
+ derived `boundry.diff.likec4` (or, with `--per-layer`, one focused view per layer
369
+ that draws a change).
365
370
 
366
371
  Boundry warns (but does not fail) when a mapped folder matches **zero** files, and
367
372
  **fails outright** when a check analysed no files at all — so a passing check can
@@ -46,14 +46,26 @@ export declare class LikeC4Visualizer implements VisualizerPort {
46
46
  */
47
47
  styleMarkers(): Promise<void>;
48
48
  /**
49
- * Generate a focused diff view for every layer that holds a pending change.
50
- * Walks the diagram's own `#proposed` / `#proposal-delete` markers (never the
51
- * lock — a `#proposal-delete` has no lock delta) and writes one `view … of
52
- * <scope>` per layer to a derived `boundry.diff.likec4`. A box is bucketed into
53
- * its parent layer; an edge into *every* layer that draws it (see `edgeScopes`),
54
- * so a cross-system dependency can be reviewed at each altitude, not only the
55
- * common-ancestor layer. The file is overwritten each run and removed when
56
- * nothing is proposed, so it always reflects the current diagram.
49
+ * Generate the review views for the diagram's pending changes, into a derived
50
+ * `boundry.diff.likec4`. Walks the diagram's own `#proposed` / `#proposal-delete`
51
+ * markers (never the lock — a `#proposal-delete` has no lock delta).
52
+ *
53
+ * By default it emits a **single `boundry_diff` view** that draws every change at
54
+ * once, uncollapsed and uniformly highlighted (see `renderSingleView`) the diff
55
+ * landing. With `perLayer`, it instead emits one focused `view … of <scope>` for
56
+ * every layer that draws a change (a box in its parent layer; an edge in every
57
+ * layer that draws it, per `edgeScopes`) — useful for a small change, but it
58
+ * multiplies views and collapses deeply-nested proposals into grey ancestors on
59
+ * a broad `include *`, which is why the single view is the default (issue #8).
60
+ *
61
+ * The file is overwritten each run and removed when nothing is proposed, so it
62
+ * always reflects the current diagram.
63
+ */
64
+ emitDiffViews(perLayer?: boolean): Promise<DiffView[]>;
65
+ /**
66
+ * The `--per-layer` shape: buckets each change into every layer that draws it and
67
+ * renders one scoped `view … of <scope>` per layer. Kept as an opt-in to the
68
+ * single-view default.
57
69
  */
58
- emitDiffViews(): Promise<DiffView[]>;
70
+ private perLayerViews;
59
71
  }
@@ -104,6 +104,61 @@ function renderDiffView(scope, highlight) {
104
104
  const lines = [` title 'Boundry diff · ${scope ?? 'root'}'`, ' include *', ...highlight];
105
105
  return `${opener}\n${lines.join('\n')}\n }`;
106
106
  }
107
+ /** Every proper ancestor of an element, shallowest first — its parent chain up to (not including) the root. */
108
+ function ancestorFqns(fqn) {
109
+ const parts = fqn.split('.');
110
+ const out = [];
111
+ for (let len = 1; len <= parts.length - 1; len++)
112
+ out.push(parts.slice(0, len).join('.'));
113
+ return out;
114
+ }
115
+ /**
116
+ * The single `boundry_diff` view: one landing that draws *every* pending change at
117
+ * once, with uniform highlighting and no empty views. It sidesteps the per-layer
118
+ * shape's two failure modes (issue #8):
119
+ *
120
+ * - **No collapse.** `include * -> * where tag is #proposed` (with NO bare
121
+ * `include *`) pulls in exactly the proposed edges and the leaf modules they
122
+ * touch — so a deeply-nested proposed leaf renders as its own amber node
123
+ * instead of collapsing into a grey ancestor, as it does under `include *`.
124
+ * - **Standalone boxes.** A `#proposed` box with no edge is included by fqn, so
125
+ * a proposed-but-unwired module still shows (and is coloured by the style rule).
126
+ *
127
+ * Every endpoint's and box's full ancestor chain is included too, so each change
128
+ * nests under its layer/system for context (verified against LikeC4 1.58). Rules
129
+ * are emitted only for marker tags actually in use — referencing an undeclared tag
130
+ * is a hard LikeC4 error.
131
+ */
132
+ function renderSingleView(edges, boxes, usedTags) {
133
+ const lines = [` title 'Boundry diff — proposed changes'`];
134
+ // Edge includes: one per marker that appears on an edge. These pull the proposed
135
+ // edges and their endpoint leaves in at leaf level, uncollapsed.
136
+ if (edges.some((e) => e.marker === '#proposed')) {
137
+ lines.push(' include * -> * where tag is #proposed with { color amber; line solid }');
138
+ }
139
+ if (edges.some((e) => e.marker === '#proposal-delete')) {
140
+ lines.push(' include * -> * where tag is #proposal-delete with { color red; line solid }');
141
+ }
142
+ // Element includes: the proposed/proposal-delete boxes themselves (so a standalone
143
+ // one shows), plus the full ancestor chain of every endpoint and box (so each
144
+ // change nests under its containers). Deterministic: sorted, de-duplicated.
145
+ const includeFqns = new Set();
146
+ for (const box of boxes)
147
+ includeFqns.add(box.fqn);
148
+ const anchors = [...edges.flatMap((e) => [e.from, e.to]), ...boxes.map((b) => b.fqn)];
149
+ for (const fqn of anchors)
150
+ for (const ancestor of ancestorFqns(fqn))
151
+ includeFqns.add(ancestor);
152
+ if (includeFqns.size) {
153
+ lines.push(` include ${[...includeFqns].sort().join(', ')}`);
154
+ }
155
+ // Style rules colour the tagged boxes (edges are coloured by their include above).
156
+ if (usedTags.has('proposed'))
157
+ lines.push(' style element.tag = #proposed { color amber }');
158
+ if (usedTags.has('proposal-delete'))
159
+ lines.push(' style element.tag = #proposal-delete { color red }');
160
+ return ` view boundry_diff {\n${lines.join('\n')}\n }`;
161
+ }
107
162
  /**
108
163
  * An exemption pattern has to be a usable regex before it reaches the linter. A
109
164
  * broken or empty one would otherwise be a silent hole: it exempts files from
@@ -582,28 +637,28 @@ export class LikeC4Visualizer {
582
637
  }
583
638
  }
584
639
  /**
585
- * Generate a focused diff view for every layer that holds a pending change.
586
- * Walks the diagram's own `#proposed` / `#proposal-delete` markers (never the
587
- * lock — a `#proposal-delete` has no lock delta) and writes one `view … of
588
- * <scope>` per layer to a derived `boundry.diff.likec4`. A box is bucketed into
589
- * its parent layer; an edge into *every* layer that draws it (see `edgeScopes`),
590
- * so a cross-system dependency can be reviewed at each altitude, not only the
591
- * common-ancestor layer. The file is overwritten each run and removed when
592
- * nothing is proposed, so it always reflects the current diagram.
640
+ * Generate the review views for the diagram's pending changes, into a derived
641
+ * `boundry.diff.likec4`. Walks the diagram's own `#proposed` / `#proposal-delete`
642
+ * markers (never the lock — a `#proposal-delete` has no lock delta).
643
+ *
644
+ * By default it emits a **single `boundry_diff` view** that draws every change at
645
+ * once, uncollapsed and uniformly highlighted (see `renderSingleView`) the diff
646
+ * landing. With `perLayer`, it instead emits one focused `view … of <scope>` for
647
+ * every layer that draws a change (a box in its parent layer; an edge in every
648
+ * layer that draws it, per `edgeScopes`) — useful for a small change, but it
649
+ * multiplies views and collapses deeply-nested proposals into grey ancestors on
650
+ * a broad `include *`, which is why the single view is the default (issue #8).
651
+ *
652
+ * The file is overwritten each run and removed when nothing is proposed, so it
653
+ * always reflects the current diagram.
593
654
  */
594
- async emitDiffViews() {
655
+ async emitDiffViews(perLayer = false) {
595
656
  const likec4 = await LikeC4.fromWorkspace(this.workspaceDir);
596
- // scopeViewId -> { scope, changes } deterministic key, so the same diagram
597
- // always yields the same set of views.
598
- const layers = new Map();
599
- const record = (scope) => {
600
- const key = scopeViewId(scope);
601
- const layer = layers.get(key) ?? { scope, changes: 0 };
602
- layer.changes += 1;
603
- layers.set(key, layer);
604
- };
605
- // Which marker tags actually appear — so the emitted highlight rules only ever
606
- // reference declared tags (an undeclared tag reference is a hard LikeC4 error).
657
+ // Lift every marked edge and box once; both view shapes are built from this.
658
+ const edges = [];
659
+ const boxes = [];
660
+ // Which marker tags actually appear — so the emitted rules only ever reference
661
+ // declared tags (an undeclared tag reference is a hard LikeC4 error).
607
662
  const usedTags = new Set();
608
663
  for (const doc of likec4.LangiumDocuments.all) {
609
664
  const filePath = doc.uri.fsPath;
@@ -619,27 +674,54 @@ export class LikeC4Visualizer {
619
674
  if (statement?.$type === 'Relation') {
620
675
  const from = fqnOf(endpointElement(statement.source));
621
676
  const to = fqnOf(endpointElement(statement.target));
622
- // One view per layer that actually draws this edge, so the reviewer can
623
- // judge it at every altitude — not only the common-ancestor layer (#7).
624
677
  if (from && to)
625
- for (const scope of edgeScopes(from, to))
626
- record(scope);
678
+ edges.push({ from, to, marker });
627
679
  }
628
680
  else if (statement?.$type === 'Element') {
629
681
  const fqn = fqnOf(statement);
630
682
  if (fqn)
631
- record(parentScope(fqn));
683
+ boxes.push({ fqn, marker });
632
684
  }
633
685
  });
634
686
  }
635
687
  const diffPath = join(this.workspaceDir, DIFF_FILE);
636
- if (layers.size === 0) {
688
+ if (edges.length === 0 && boxes.length === 0) {
637
689
  // Nothing proposed — clear any stale views so `serve` never renders a diff
638
690
  // that no longer exists (and never dangles a reference to an approved-away box).
639
691
  if (existsSync(diffPath))
640
692
  rmSync(diffPath);
641
693
  return [];
642
694
  }
695
+ const { header, body, views } = perLayer
696
+ ? this.perLayerViews(edges, boxes, usedTags)
697
+ : {
698
+ header: 'a single landing view of every pending change',
699
+ body: renderSingleView(edges, boxes, usedTags),
700
+ views: [{ id: 'boundry_diff', changes: edges.length + boxes.length }],
701
+ };
702
+ writeFileSync(diffPath, `// Generated by \`boundry diff\` — ${header}.\n` +
703
+ `// Derived artifact: regenerate after approve; do not hand-edit.\n` +
704
+ `views {\n${body}\n}\n`);
705
+ return views;
706
+ }
707
+ /**
708
+ * The `--per-layer` shape: buckets each change into every layer that draws it and
709
+ * renders one scoped `view … of <scope>` per layer. Kept as an opt-in to the
710
+ * single-view default.
711
+ */
712
+ perLayerViews(edges, boxes, usedTags) {
713
+ const layers = new Map();
714
+ const record = (scope) => {
715
+ const key = scopeViewId(scope);
716
+ const layer = layers.get(key) ?? { scope, changes: 0 };
717
+ layer.changes += 1;
718
+ layers.set(key, layer);
719
+ };
720
+ for (const edge of edges)
721
+ for (const scope of edgeScopes(edge.from, edge.to))
722
+ record(scope);
723
+ for (const box of boxes)
724
+ record(parentScope(box.fqn));
643
725
  // Root first, then by fqn, so the output is stable across runs.
644
726
  const ordered = [...layers.values()].sort((a, b) => {
645
727
  if (!a.scope)
@@ -649,14 +731,14 @@ export class LikeC4Visualizer {
649
731
  return a.scope.localeCompare(b.scope);
650
732
  });
651
733
  const highlight = highlightLines(usedTags);
652
- const body = ordered.map((layer) => renderDiffView(layer.scope, highlight)).join('\n');
653
- writeFileSync(diffPath, `// Generated by \`boundry diff\` — one focused view per layer with a pending\n` +
654
- `// change. Derived artifact: regenerate after approve; do not hand-edit.\n` +
655
- `views {\n${body}\n}\n`);
656
- return ordered.map((layer) => ({
657
- id: scopeViewId(layer.scope),
658
- scope: layer.scope,
659
- changes: layer.changes,
660
- }));
734
+ return {
735
+ header: 'one focused view per layer with a pending change',
736
+ body: ordered.map((layer) => renderDiffView(layer.scope, highlight)).join('\n'),
737
+ views: ordered.map((layer) => ({
738
+ id: scopeViewId(layer.scope),
739
+ scope: layer.scope,
740
+ changes: layer.changes,
741
+ })),
742
+ };
661
743
  }
662
744
  }
package/dist/cli/index.js CHANGED
@@ -4,7 +4,7 @@ import { join, relative, resolve } from 'node:path';
4
4
  import { Pipeline } from '../core/pipeline/pipeline.js';
5
5
  import { LikeC4Visualizer } from '../adapters/visualizer/likec4.js';
6
6
  import { DepCruiserEnforcer } from '../adapters/enforcer/depcruiser.js';
7
- const USAGE = 'usage: boundry <generate|check|approve|verify|annotate|diff> [--arch <dir>] [--cwd <dir>] [--out <file>] [sources...]';
7
+ const USAGE = 'usage: boundry <generate|check|approve|verify|annotate|diff> [--arch <dir>] [--cwd <dir>] [--out <file>] [--per-layer] [sources...]';
8
8
  function optValue(args, flag) {
9
9
  const i = args.indexOf(flag);
10
10
  return i >= 0 ? args[i + 1] : undefined;
@@ -139,7 +139,8 @@ async function main() {
139
139
  return;
140
140
  }
141
141
  if (command === 'diff') {
142
- const views = await pipeline.diffViews();
142
+ const perLayer = rest.includes('--per-layer');
143
+ const views = await pipeline.diffViews(perLayer);
143
144
  const diffFile = join(archDir, 'boundry.diff.likec4');
144
145
  if (views.length === 0) {
145
146
  console.log('Boundry: ✓ nothing proposed — no diff views to draw');
@@ -147,10 +148,10 @@ async function main() {
147
148
  }
148
149
  console.log(`Boundry: ✎ wrote ${views.length} diff view(s) to ${relative(process.cwd(), diffFile)}:`);
149
150
  for (const view of views) {
150
- const layer = view.scope ?? 'root';
151
- console.log(` ${view.id} (layer ${layer}, ${view.changes} change(s))`);
151
+ const where = perLayer ? `layer ${view.scope ?? 'root'}, ` : '';
152
+ console.log(` ${view.id} (${where}${view.changes} change(s))`);
152
153
  }
153
- console.log(' Open the diagram in `likec4 serve` to review each layer.');
154
+ console.log(' Open the diagram in `likec4 serve` to review.');
154
155
  return;
155
156
  }
156
157
  console.error(USAGE);
@@ -60,11 +60,11 @@ export declare class Pipeline {
60
60
  */
61
61
  annotate(lock: string): Promise<AnnotateResult>;
62
62
  /**
63
- * (Re)generate a focused diff view per layer that holds a pending change, so a
64
- * reviewer sees each proposal in the scope where it is actually drawn — a
65
- * proposal nested in a box is invisible once that box collapses at a wider
66
- * view. Reads the diagram's own `#proposed` / `#proposal-delete` markers, so it
67
- * composes with `annotate` (annotate marks drift, this frames it).
63
+ * (Re)generate the review views for the diagram's pending changes. By default a
64
+ * single `boundry_diff` landing view draws every `#proposed` / `#proposal-delete`
65
+ * change at once; with `perLayer`, one focused view per layer that draws a change.
66
+ * Reads the diagram's own markers, so it composes with `annotate` (annotate marks
67
+ * drift, this frames it).
68
68
  */
69
- diffViews(): Promise<DiffView[]>;
69
+ diffViews(perLayer?: boolean): Promise<DiffView[]>;
70
70
  }
@@ -69,13 +69,13 @@ export class Pipeline {
69
69
  return { edges, modules };
70
70
  }
71
71
  /**
72
- * (Re)generate a focused diff view per layer that holds a pending change, so a
73
- * reviewer sees each proposal in the scope where it is actually drawn — a
74
- * proposal nested in a box is invisible once that box collapses at a wider
75
- * view. Reads the diagram's own `#proposed` / `#proposal-delete` markers, so it
76
- * composes with `annotate` (annotate marks drift, this frames it).
72
+ * (Re)generate the review views for the diagram's pending changes. By default a
73
+ * single `boundry_diff` landing view draws every `#proposed` / `#proposal-delete`
74
+ * change at once; with `perLayer`, one focused view per layer that draws a change.
75
+ * Reads the diagram's own markers, so it composes with `annotate` (annotate marks
76
+ * drift, this frames it).
77
77
  */
78
- async diffViews() {
79
- return this.visualizer.emitDiffViews();
78
+ async diffViews(perLayer = false) {
79
+ return this.visualizer.emitDiffViews(perLayer);
80
80
  }
81
81
  }
@@ -23,25 +23,26 @@ export interface VisualizerPort {
23
23
  */
24
24
  styleMarkers(): Promise<void>;
25
25
  /**
26
- * (Re)generate a focused diff view for every layer that holds a pending
27
- * change — an edge or box tagged `#proposed` or `#proposal-delete`. Each view
28
- * is scoped to the tightest element that contains the change (the model root
29
- * for a top-level one), because a proposal nested inside a box is invisible
30
- * once that box collapses at a wider scope. A derived artifact: overwritten
26
+ * (Re)generate the review views for the diagram's pending changes edges and
27
+ * boxes tagged `#proposed` or `#proposal-delete`. By default a single
28
+ * `boundry_diff` landing view draws every change at once, uncollapsed and
29
+ * uniformly highlighted. With `perLayer`, one focused `view of <scope>` is
30
+ * emitted per layer that draws a change instead. A derived artifact: overwritten
31
31
  * each run, and removed when nothing is proposed. Returns one entry per view.
32
32
  */
33
- emitDiffViews(): Promise<DiffView[]>;
33
+ emitDiffViews(perLayer?: boolean): Promise<DiffView[]>;
34
34
  }
35
35
  /**
36
- * One emitted diff view: a single layer (element scope) that holds at least one
37
- * pending change, and how many changes fall in it.
36
+ * One emitted diff view. For the default single view there is one entry
37
+ * (`boundry_diff`) counting every pending change; in `perLayer` mode there is one
38
+ * per layer, each scoped to an element and counting the changes that fall in it.
38
39
  */
39
40
  export interface DiffView {
40
- /** The generated view id, e.g. `boundry_diff_root` or `boundry_diff_billing`. */
41
+ /** The generated view id `boundry_diff`, or `boundry_diff_<scope>` per layer. */
41
42
  id: string;
42
- /** The scope element's fqn, or undefined for the model root. */
43
+ /** perLayer only: the scope element's fqn, or undefined for the model root. */
43
44
  scope?: string;
44
- /** How many pending `#proposed` / `#proposal-delete` changes this layer holds. */
45
+ /** How many pending `#proposed` / `#proposal-delete` changes this view holds. */
45
46
  changes: number;
46
47
  }
47
48
  /** A generated linter config artifact. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "boundry",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Compile a C4 architecture diagram into a deterministic dependency linter. Deterministic architectural guardrails for AI agents and humans.",
5
5
  "type": "module",
6
6
  "license": "MIT",