forma-arch 0.11.0 → 0.11.1
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 +12 -9
- package/lib/viewer/c4-hologram.html +17 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,7 +88,7 @@ and validates every cumulative state before replacing the last model.
|
|
|
88
88
|
"timeline": {
|
|
89
89
|
"source": "docs/architecture/FUTURE.md",
|
|
90
90
|
"checkpoints": [{
|
|
91
|
-
"id": "g1", "label": "G1 · UAT", "badge": "9
|
|
91
|
+
"id": "g1", "label": "G1 · UAT", "badge": "9 board · 1 P0",
|
|
92
92
|
"patch": {
|
|
93
93
|
"nodes": {
|
|
94
94
|
"add": [{ "node": {
|
|
@@ -112,14 +112,17 @@ and validates every cumulative state before replacing the last model.
|
|
|
112
112
|
```
|
|
113
113
|
|
|
114
114
|
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
|
-
|
|
115
|
+
`add`, exact-match `rewire`, and `remove`. Every operation carries governed change prose; a live
|
|
116
|
+
node changed locally exposes it as “Change from previous”, while the graph accents changed nodes
|
|
117
|
+
and relations without keeping a second change log or drawing ghosts for removals. IDs, parents,
|
|
118
|
+
levels and kinds cannot be edited in place, ambiguous edge selectors fail, and a node cannot
|
|
119
|
+
disappear while children or incident relations survive. The source path must exist. `AS-IS` is
|
|
120
|
+
implicit and reserved; the last checkpoint is the target, so `target` is forbidden inside timeline
|
|
121
|
+
patches. With a timeline the viewer replaces CURRENT/TARGET with the checkpoints, supports
|
|
122
|
+
`?checkpoint=g1`, accents only the local patch, and keeps drill, layout, re-verification and export
|
|
123
|
+
working on the cumulative graph. A future checkpoint with no local patch says “no architecture
|
|
124
|
+
changes”; its display-only badge is not interpreted. Without a timeline, the legacy controls and
|
|
125
|
+
model are unchanged.
|
|
123
126
|
|
|
124
127
|
**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
128
|
|
|
@@ -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.1",
|
|
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",
|