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 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 item · 1 P0",
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 the prose shown as “Change from
116
- previous”. IDs, parents, levels and kinds cannot be edited in place, ambiguous edge selectors fail,
117
- and a node cannot disappear while children or incident relations survive. The source path must
118
- exist. `AS-IS` is implicit and reserved; the last checkpoint is the target, so `target` is forbidden
119
- inside timeline patches. With a timeline the viewer replaces CURRENT/TARGET with the checkpoints,
120
- supports `?checkpoint=g1`, accents only the local patch, and keeps drill, layout, re-verification
121
- and export working on the cumulative graph. Without one, the legacy controls and model are
122
- unchanged.
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
- <div id="timeline-track"></div>
194
- <div id="checkpoint-summary"></div>
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";$("checkpoint-changes").innerHTML="";return;}
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 c=CPDELTA.counts,parts=[];
485
- if(c.add)parts.push('<b>'+STR.deltaAdd+'</b> '+c.add);
486
- if(c.update)parts.push('<b>'+STR.deltaUpdate+'</b> '+c.update);
487
- if(c.rewire)parts.push('<b>'+STR.deltaRewire+'</b> '+c.rewire);
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.0",
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",