forma-arch 0.11.0 → 0.11.2
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 +20 -14
- package/lib/viewer/c4-hologram.html +17 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,16 +9,19 @@ Forma is the companion to `arbiter`: arbiter governs the process, Forma shows th
|
|
|
9
9
|
guarantees the picture matches reality.
|
|
10
10
|
|
|
11
11
|
**[▶ Live demo](https://LucaDominici.github.io/forma/)** — **not** this repo. It is `haben`, a
|
|
12
|
-
private 53-package Go application, generated by forma at haben commit `
|
|
13
|
-
53 packages,
|
|
14
|
-
feature matrix.
|
|
12
|
+
private 53-package Go application, generated by forma at haben commit `4c9b6880`: six domains over
|
|
13
|
+
53 packages, 193 edges read from real `import` blocks, and box text quoted from that repo's own
|
|
14
|
+
feature matrix. Its five-checkpoint architecture timeline is governed by haben's current product
|
|
15
|
+
boundary: the 63-node AS-IS baseline grows cumulatively to 66 nodes and 196 edges, while the final
|
|
16
|
+
operational checkpoint honestly reports no architecture change. A tool proving itself on its own
|
|
17
|
+
28 nodes proves nothing you care about.
|
|
15
18
|
|
|
16
19
|
What you are looking at is checked, not asserted: at that commit `node scripts/presentable.mjs`
|
|
17
20
|
exits 0 on the model — every level under 24 boxes, every box carrying prose rather than a file
|
|
18
21
|
count, every level with more than one box drawing at least one arrow — and `forma check` exits 0
|
|
19
22
|
against haben's source at the same commit. The curation that turns 53 flat packages into six
|
|
20
|
-
domains is one human decision, checked in
|
|
21
|
-
the commands to rebuild it.
|
|
23
|
+
domains and maps the governed boundary to typed timeline patches is one human decision, checked in
|
|
24
|
+
at [`docs/demo/curate.mjs`](docs/demo/curate.mjs) with the commands to rebuild it.
|
|
22
25
|
|
|
23
26
|
The one thing the demo does not do is regenerate itself: haben is private, so Pages publishes a
|
|
24
27
|
snapshot committed from a local run rather than cloning it in CI. Nothing automated will notice
|
|
@@ -88,7 +91,7 @@ and validates every cumulative state before replacing the last model.
|
|
|
88
91
|
"timeline": {
|
|
89
92
|
"source": "docs/architecture/FUTURE.md",
|
|
90
93
|
"checkpoints": [{
|
|
91
|
-
"id": "g1", "label": "G1 · UAT", "badge": "9
|
|
94
|
+
"id": "g1", "label": "G1 · UAT", "badge": "9 board · 1 P0",
|
|
92
95
|
"patch": {
|
|
93
96
|
"nodes": {
|
|
94
97
|
"add": [{ "node": {
|
|
@@ -112,14 +115,17 @@ and validates every cumulative state before replacing the last model.
|
|
|
112
115
|
```
|
|
113
116
|
|
|
114
117
|
Node patches support `add`, non-structural `update`, and child-first `remove`; edge patches support
|
|
115
|
-
`add`, exact-match `rewire`, and `remove`. Every operation carries
|
|
116
|
-
|
|
117
|
-
and
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
118
|
+
`add`, exact-match `rewire`, and `remove`. Every operation carries governed change prose; a live
|
|
119
|
+
node changed locally exposes it as “Change from previous”, while the graph accents changed nodes
|
|
120
|
+
and relations without keeping a second change log or drawing ghosts for removals. IDs, parents,
|
|
121
|
+
levels and kinds cannot be edited in place, ambiguous edge selectors fail, and a node cannot
|
|
122
|
+
disappear while children or incident relations survive. The source path must exist. `AS-IS` is
|
|
123
|
+
implicit and reserved; the last checkpoint is the target, so `target` is forbidden inside timeline
|
|
124
|
+
patches. With a timeline the viewer replaces CURRENT/TARGET with the checkpoints, supports
|
|
125
|
+
`?checkpoint=g1`, accents only the local patch, and keeps drill, layout, re-verification and export
|
|
126
|
+
working on the cumulative graph. A future checkpoint with no local patch says “no architecture
|
|
127
|
+
changes”; its display-only badge is not interpreted. Without a timeline, the legacy controls and
|
|
128
|
+
model are unchanged.
|
|
123
129
|
|
|
124
130
|
**Curated state, verified against reality.** `forma verify` asks your `gh` CLI for the state of every issue the model references (`--gh-repo owner/repo`, or `meta.ghRepo` in the topology), marks the nodes whose issues are closed as done, and prefixes their `current` with dated evidence. It touches state, never structure, and re-running it never stacks the evidence. It is opt-in and separate on purpose: `gen` and `check` never open a socket. In the served viewer, **RE-VERIFY** re-reads the model without losing your level, layout or mode.
|
|
125
131
|
|
|
@@ -41,9 +41,6 @@ select#skin option{background:#08202e;color:var(--nm)}
|
|
|
41
41
|
.cpstep.active .dot{border-color:var(--cyan);background:var(--bg);box-shadow:0 0 10px rgba(62,231,255,.8)}
|
|
42
42
|
#checkpoint-summary{text-align:right;color:var(--ter2);font-size:8.5px;letter-spacing:.08em;margin-top:5px;min-height:11px}
|
|
43
43
|
#checkpoint-summary b{color:var(--cyan);font-weight:600}
|
|
44
|
-
#checkpoint-changes{display:grid;gap:3px;margin-top:7px;max-height:92px;overflow:auto}
|
|
45
|
-
.cpchange{color:var(--text);font-size:9px;line-height:1.35;padding-left:8px;border-left:2px solid var(--cyan)}
|
|
46
|
-
.cpchange b{color:var(--cyan);font-weight:600;letter-spacing:.08em}.cpchange span{color:var(--ter2)}
|
|
47
44
|
#crumb{font-size:11px;color:var(--sec);margin:8px 0}
|
|
48
45
|
#crumb .agg{float:right;color:var(--ter2);letter-spacing:.05em}
|
|
49
46
|
#crumb .agg i{display:inline-block;width:7px;height:7px;border-radius:50%;margin:0 3px 0 8px;vertical-align:baseline}
|
|
@@ -190,9 +187,8 @@ html[data-skin="blueprint"] .nd.delta rect,html[data-skin="blueprint"] .edge.del
|
|
|
190
187
|
</div>
|
|
191
188
|
</div>
|
|
192
189
|
<nav id="timeline">
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
<div id="checkpoint-changes"></div>
|
|
190
|
+
<div id="timeline-track"></div>
|
|
191
|
+
<div id="checkpoint-summary"></div>
|
|
196
192
|
</nav>
|
|
197
193
|
<div id="crumb"></div>
|
|
198
194
|
<div id="stamp"></div>
|
|
@@ -229,7 +225,7 @@ var STRINGS={
|
|
|
229
225
|
detDecl:"Declared in", detCover:"Coverage", detRoll:"Rolled up from", rollPart:"mean of the {n} of {t} parts inside that anyone ruled on", coverWhole:"the document names this module itself",
|
|
230
226
|
coverPart:"{n} of {t} units named by the document",
|
|
231
227
|
legHint:"— animated arrow = active flow · dashed = to build", legCheckpoint:"CHANGE IN THIS CHECKPOINT",
|
|
232
|
-
timelineLabel:"Architecture checkpoints", asIs:"AS-IS", asIsBadge:"verified code", cumulativeHint:"the graph is always cumulative",
|
|
228
|
+
timelineLabel:"Architecture checkpoints", asIs:"AS-IS", asIsBadge:"verified code", cumulativeHint:"the graph is always cumulative", noArchChanges:"no architecture changes",
|
|
233
229
|
deltaAdd:"ADD", deltaUpdate:"UPDATE", deltaRewire:"REWIRE", deltaRemove:"REMOVE",
|
|
234
230
|
crumbRoot:"Context", crumbLevel:"C4", lvlContext:"CONTEXT",
|
|
235
231
|
lvlNames:{context:"Context",container:"Container",component:"Components",leaf:"Leaves"},
|
|
@@ -253,7 +249,7 @@ var STRINGS={
|
|
|
253
249
|
detDecl:"Dichiarato in", detCover:"Copertura", detRoll:"Somma dal basso", rollPart:"media delle {n} parti su {t} qui dentro su cui qualcuno si e pronunciato", coverWhole:"il documento nomina il modulo stesso",
|
|
254
250
|
coverPart:"{n} unità su {t} nominate dal documento",
|
|
255
251
|
legHint:"— freccia animata = flusso attivo · tratteggio = da costruire", legCheckpoint:"CAMBIO IN QUESTO CHECKPOINT",
|
|
256
|
-
timelineLabel:"Checkpoint architetturali", asIs:"AS-IS", asIsBadge:"codice verificato", cumulativeHint:"il grafo è sempre cumulativo",
|
|
252
|
+
timelineLabel:"Checkpoint architetturali", asIs:"AS-IS", asIsBadge:"codice verificato", cumulativeHint:"il grafo è sempre cumulativo", noArchChanges:"nessun cambio architetturale",
|
|
257
253
|
deltaAdd:"ADD", deltaUpdate:"UPDATE", deltaRewire:"REWIRE", deltaRemove:"REMOVE",
|
|
258
254
|
crumbRoot:"Contesto", crumbLevel:"C4", lvlContext:"CONTESTO",
|
|
259
255
|
lvlNames:{context:"Contesto",container:"Container",component:"Componenti",leaf:"Foglie"},
|
|
@@ -396,6 +392,14 @@ function timelineStates(base){
|
|
|
396
392
|
}
|
|
397
393
|
return {errors:errors,states:states};
|
|
398
394
|
}
|
|
395
|
+
function timelineSummary(id,delta){
|
|
396
|
+
var c=(delta&&delta.counts)||{},parts=[];
|
|
397
|
+
if(c.add)parts.push({key:"deltaAdd",count:c.add});
|
|
398
|
+
if(c.update)parts.push({key:"deltaUpdate",count:c.update});
|
|
399
|
+
if(c.rewire)parts.push({key:"deltaRewire",count:c.rewire});
|
|
400
|
+
if(c.remove)parts.push({key:"deltaRemove",count:c.remove});
|
|
401
|
+
return {parts:parts,empty:id!=="as-is"&&!parts.length};
|
|
402
|
+
}
|
|
399
403
|
var BASE=null,M=null,TSTATES=[],CPID="as-is",CPDELTA={nodes:[],edges:[],removedNodes:[],removedEdges:[],counts:{add:0,update:0,rewire:0,remove:0}}
|
|
400
404
|
var stack=[null],mode="now",POS={},POSALL={},drag=null,justDragged=false,CATNODES={},FETCHED=false
|
|
401
405
|
var VBOX={}; // last fitted viewBox per level — frozen while dragging so the canvas does not rescale
|
|
@@ -462,7 +466,6 @@ function makeCatalogNode(pid,g){
|
|
|
462
466
|
}
|
|
463
467
|
function hasKids(id){for(var i=0;i<M.nodes.length;i++)if(M.nodes[i].parent===id)return true;return false;}
|
|
464
468
|
function node(id){for(var i=0;i<M.nodes.length;i++)if(M.nodes[i].id===id)return M.nodes[i];return null;}
|
|
465
|
-
function nameOf(id){var n=node(id);return n?n.name:id;}
|
|
466
469
|
function srcOf(n){if(CPID!=="as-is"&&nodeProjected(n.id)&&BASE&&BASE.timeline)return BASE.timeline.source;
|
|
467
470
|
if(n.verify){if(typeof n.verify==="string")return n.verify;if(n.verify.source)return n.verify.source;if(n.verify.ref)return n.verify.ref;if(n.verify.issue)return "#"+n.verify.issue;}
|
|
468
471
|
var ev=(n.evidence||[]).filter(function(e){return e.type==="doc"||e.type==="path";})[0];
|
|
@@ -475,25 +478,16 @@ function edgeChange(e){
|
|
|
475
478
|
function emptyDelta(){return {nodes:[],edges:[],removedNodes:[],removedEdges:[],counts:{add:0,update:0,rewire:0,remove:0}};}
|
|
476
479
|
function renderTimeline(){
|
|
477
480
|
var nav=$("timeline"),legacy=$("legacytime"),legend=$("lg-checkpoint-wrap");
|
|
478
|
-
if(!BASE||!BASE.timeline){nav.style.display="none";legacy.style.display="inline-flex";legend.style.display="none"
|
|
481
|
+
if(!BASE||!BASE.timeline){nav.style.display="none";legacy.style.display="inline-flex";legend.style.display="none";return;}
|
|
479
482
|
nav.style.display="block";legacy.style.display="none";legend.style.display="inline";
|
|
480
483
|
nav.setAttribute("aria-label",STR.timelineLabel);
|
|
481
484
|
var html='<button class="cpstep real'+(CPID==="as-is"?" active":"")+'" data-checkpoint="as-is" type="button"><span class="dot"></span>'+esc(STR.asIs)+'<span class="cpbadge">'+esc(STR.asIsBadge)+'</span></button>';
|
|
482
485
|
for(var i=0;i<TSTATES.length;i++){var s=TSTATES[i];html+='<button class="cpstep'+(CPID===s.id?" active":"")+'" data-checkpoint="'+esc(s.id)+'" type="button"><span class="dot"></span>'+esc(s.label)+(s.badge?'<span class="cpbadge">'+esc(s.badge)+'</span>':'')+'</button>';}
|
|
483
486
|
$("timeline-track").innerHTML=html;
|
|
484
|
-
var
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
if(c.remove)parts.push('<b>'+STR.deltaRemove+'</b> '+c.remove);
|
|
489
|
-
$("checkpoint-summary").innerHTML=(parts.length?parts.join(" · ")+" · ":"")+esc(STR.cumulativeHint);
|
|
490
|
-
var changes=[],d=CPDELTA,i,nm,subject;
|
|
491
|
-
function row(type,what,why){changes.push('<div class="cpchange"><b>'+esc(type)+'</b> '+esc(what)+' <span>— '+esc(why)+'</span></div>');}
|
|
492
|
-
for(i=0;i<d.nodes.length;i++){nm=node(d.nodes[i].id);row(d.nodes[i].type,nm?nm.name:d.nodes[i].id,d.nodes[i].change);}
|
|
493
|
-
for(i=0;i<d.edges.length;i++){subject=nameOf(d.edges[i].from)+" → "+nameOf(d.edges[i].to)+(d.edges[i].label?" · "+d.edges[i].label:"");row(d.edges[i].type,subject,d.edges[i].change);}
|
|
494
|
-
for(i=0;i<d.removedEdges.length;i++){subject=nameOf(d.removedEdges[i].from)+" → "+nameOf(d.removedEdges[i].to)+(d.removedEdges[i].label?" · "+d.removedEdges[i].label:"");row(d.removedEdges[i].type,subject,d.removedEdges[i].change);}
|
|
495
|
-
for(i=0;i<d.removedNodes.length;i++)row(d.removedNodes[i].type,d.removedNodes[i].name||d.removedNodes[i].id,d.removedNodes[i].change);
|
|
496
|
-
$("checkpoint-changes").innerHTML=changes.join("");
|
|
487
|
+
var summary=timelineSummary(CPID,CPDELTA),parts=[];
|
|
488
|
+
for(i=0;i<summary.parts.length;i++)parts.push('<b>'+STR[summary.parts[i].key]+'</b> '+summary.parts[i].count);
|
|
489
|
+
$("checkpoint-summary").innerHTML=parts.length?parts.join(" · ")+" · "+esc(STR.cumulativeHint)
|
|
490
|
+
:(summary.empty?'<b>'+esc(STR.noArchChanges)+'</b> · ':'')+esc(STR.cumulativeHint);
|
|
497
491
|
}
|
|
498
492
|
function checkpointUrl(id){
|
|
499
493
|
try{var u=new URL(location.href);if(id==="as-is")u.searchParams.delete("checkpoint");else u.searchParams.set("checkpoint",id);history.replaceState(null,"",u.toString());}catch(e){}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forma-arch",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"description": "Forma — present your architecture instead of slides. An interactive, stack-agnostic C4 explorer (context → container → component → leaf) generated from your code and kept true to it by a deterministic drift check.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"architecture",
|