forma-arch 0.8.0 → 0.9.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/lib/docmap.mjs CHANGED
@@ -147,16 +147,36 @@ export function indexByNode(rows, nodes) {
147
147
  const paths = subtree(n.id)
148
148
  if (!paths.length) continue
149
149
  const hit = rows.filter((r) => r.refs.some((ref) => paths.some((p) => touches(ref, p))))
150
- if (hit.length) idx.set(n.id, hit)
150
+ if (hit.length) idx.set(n.id, { rows: hit, cover: coverOf(n, byId, kids, hit, paths) })
151
151
  }
152
152
  return idx
153
153
  }
154
154
 
155
+ // How much of a node the rows actually reach. `done / rows.length` answers "of the capabilities
156
+ // somebody wrote down, how many are finished" — never "how much of this module is finished". A
157
+ // container holding 8 files that a matrix names 3 of, all done, reads 100%: the other 5 are not
158
+ // counted as unfinished, they are not counted at all. So the number ships with its own reach.
159
+ // `whole` means a row names the module itself, i.e. the document IS talking about all of it.
160
+ function coverOf(node, byId, kids, hit, paths) {
161
+ const own = (node.evidence || []).filter((e) => e.type === 'path').map((e) => normRef(e.ref))
162
+ const whole = hit.some((r) => r.refs.some((ref) => own.some((p) => ref === p || p.startsWith(ref + '/'))))
163
+ const leaves = (kids.get(node.id) || []).map((k) => byId.get(k)).filter((k) => k && k.kind === 'leaf')
164
+ const glob = (node.evidence || []).find((e) => e.type === 'glob')
165
+ // Units = the boxes below it, or — for a node whose children are internal detail (a Go package) —
166
+ // the file count its own drift anchor already carries.
167
+ const total = leaves.length || (glob && glob.count) || paths.length
168
+ if (whole) return { named: total, total, whole: true }
169
+ const named = new Set()
170
+ for (const r of hit) for (const ref of r.refs) if (own.some((p) => ref.startsWith(p + '/'))) named.add(ref)
171
+ return { named: Math.min(named.size, total), total, whole: false }
172
+ }
173
+
155
174
  // The rows that DESCRIBE a node (past the cap it is only touched by them — see MAX_ROWS).
156
175
  export const describingRows = (idx, id) => {
157
- const rows = idx.get(id)
158
- return rows && rows.length <= MAX_ROWS ? rows : null
176
+ const e = idx.get(id)
177
+ return e && e.rows.length <= MAX_ROWS ? e.rows : null
159
178
  }
179
+ export const coverageFor = (idx, id) => (idx.get(id) || {}).cover || null
160
180
 
161
181
  // Programme state a document states outright: how many of the capabilities living in this node are
162
182
  // finished. Derived, never curated — the c4-status.json overlay still overrides it (gen.mjs §WP-A1),
@@ -165,9 +185,12 @@ export function statusFor(idx, id) {
165
185
  const rows = describingRows(idx, id)
166
186
  if (!rows || rows.some((r) => r.done == null)) return null // no status column: describe only
167
187
  const done = rows.filter((r) => r.done).length
188
+ const c = coverageFor(idx, id)
168
189
  return {
169
190
  status2: done === rows.length ? 'done' : done === 0 ? 'planned' : 'in-progress',
170
191
  completion: Math.round((done / rows.length) * 100),
171
- source: `${rows[0].from} (${done}/${rows.length} done)`,
192
+ // The citation says what it is: a repo document declaring itself finished, not a verification.
193
+ source: `${rows[0].from} (${done}/${rows.length} declared done)`,
194
+ ...(c ? { coverage: c } : {}),
172
195
  }
173
196
  }
package/lib/gen.mjs CHANGED
@@ -165,7 +165,10 @@ for (const n of nodes) {
165
165
  const ds = !n.status2 && n.kind !== 'system' ? statusFor(docIndex, n.id) : null
166
166
  // `derived: true` is the marker `check` keys off to know this state must keep re-deriving. A
167
167
  // string match on the citation would break the moment anyone rewords it.
168
- if (ds) { n.status2 = ds.status2; if (n.completion == null) n.completion = ds.completion; if (!n.verify) n.verify = { source: ds.source, derived: true } }
168
+ // `coverage` rides in `verify` (additionalProperties: true there, so no schema churn) and says how
169
+ // much of the node the citation actually reaches — the difference between "3 of 3 rows are done"
170
+ // and "this module is done".
171
+ if (ds) { n.status2 = ds.status2; if (n.completion == null) n.completion = ds.completion; if (!n.verify) n.verify = { source: ds.source, derived: true, ...(ds.coverage ? { coverage: ds.coverage } : {}) } }
169
172
  // Code can prove a file EXISTS; it cannot prove the work behind it is finished. Marking every
170
173
  // undecorated node done/100 turned a virgin repo into a board reading "10/10 complete" — the
171
174
  // exact false green this tool exists to kill. No overlay (§WP-A1), no verdict: `unknown`, and
@@ -89,7 +89,7 @@ svg.dense .edge.hot{opacity:1;stroke-width:2.6}
89
89
  #etip{position:absolute;pointer-events:none;display:none;z-index:6;background:var(--detailbg);border:1px solid var(--border);
90
90
  color:var(--nm);font-size:10px;letter-spacing:.03em;padding:3px 8px;border-radius:5px;white-space:nowrap;box-shadow:0 3px 12px rgba(0,0,0,.45)}
91
91
  #legend{display:flex;gap:13px;font-size:9.5px;color:var(--sec);margin:10px 0 0;flex-wrap:wrap}
92
- #legend i{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:4px}
92
+ #legend i{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:4px;box-sizing:border-box}
93
93
  .hint{color:var(--ter2)}
94
94
  #detail{margin-top:12px;border:1px solid var(--border);border-radius:10px;padding:13px;background:var(--detailbg);display:none}
95
95
  #detail h3{color:var(--cyan);font-size:12.5px;margin-bottom:8px;letter-spacing:.05em}
@@ -165,6 +165,10 @@ html[data-skin="blueprint"] button.on{text-shadow:none}
165
165
  <div id="stage"><div id="etip"></div><div id="err" style="display:none"></div></div>
166
166
  <div id="legend">
167
167
  <span><i style="background:#2fe08a"></i><span id="lg-done"></span></span>
168
+ <!-- Same colour, hollow: a green derived from a repo document saying it is finished is not the
169
+ same evidence as a green from a closed issue, and the legend is where a reader learns that
170
+ one mark means two things. -->
171
+ <span><i style="border:1.5px solid #2fe08a"></i><span id="lg-decl"></span></span>
168
172
  <span><i style="background:#ffc74d"></i><span id="lg-prog"></span></span>
169
173
  <span><i style="background:#3ee7ff"></i><span id="lg-next"></span></span>
170
174
  <span><i style="background:#2d5a72"></i><span id="lg-plan"></span></span>
@@ -185,8 +189,10 @@ var STRINGS={
185
189
  labels:"LABELS", labelsTitle:"arrow labels (auto-off above 14 arrows)",
186
190
  resetLayout:"RESET LAYOUT", resetLayoutTitle:"re-arrange",
187
191
  printExport:"PRINT / EXPORT", exportSvg:"Export SVG", exportPng:"Export PNG", exportLayout:"Export layout JSON", print:"Print",
188
- legDone:"DONE (proven)", legProg:"IN PROGRESS", legNext:"NEXT", legPlan:"PLAN ONLY", legProb:"PROBLEM",
192
+ legDone:"DONE (proven)", legDecl:"DONE (declared)", legProg:"IN PROGRESS", legNext:"NEXT", legPlan:"PLAN ONLY", legProb:"PROBLEM",
189
193
  legUnk:"UNKNOWN (no curated state)", stUnk:"?",
194
+ detDecl:"Declared in", detCover:"Coverage", coverWhole:"the document names this module itself",
195
+ coverPart:"{n} of {t} units named by the document",
190
196
  legHint:"— animated arrow = active flow · dashed = to build",
191
197
  crumbRoot:"Context", crumbLevel:"C4", lvlContext:"CONTEXT",
192
198
  lvlNames:{context:"Context",container:"Container",component:"Components",leaf:"Leaves"},
@@ -205,8 +211,10 @@ var STRINGS={
205
211
  labels:"ETICHETTE", labelsTitle:"etichette sugli archi (spente sopra i 14 archi)",
206
212
  resetLayout:"RESET LAYOUT", resetLayoutTitle:"ridisponi",
207
213
  printExport:"STAMPA / ESPORTA", exportSvg:"Esporta SVG", exportPng:"Esporta PNG", exportLayout:"Esporta layout JSON", print:"Stampa",
208
- legDone:"FATTO (provato)", legProg:"IN CORSO", legNext:"PROSSIMO", legPlan:"SOLO PROGETTO", legProb:"PROBLEMA",
214
+ legDone:"FATTO (provato)", legDecl:"FATTO (dichiarato)", legProg:"IN CORSO", legNext:"PROSSIMO", legPlan:"SOLO PROGETTO", legProb:"PROBLEMA",
209
215
  legUnk:"IGNOTO (stato non curato)", stUnk:"?",
216
+ detDecl:"Dichiarato in", detCover:"Copertura", coverWhole:"il documento nomina il modulo stesso",
217
+ coverPart:"{n} unità su {t} nominate dal documento",
210
218
  legHint:"— freccia animata = flusso attivo · tratteggio = da costruire",
211
219
  crumbRoot:"Contesto", crumbLevel:"C4", lvlContext:"CONTESTO",
212
220
  lvlNames:{context:"Contesto",container:"Container",component:"Componenti",leaf:"Foglie"},
@@ -491,6 +499,13 @@ function echoesName(desc,name){
491
499
  for(i=0;i<w.length;i++){if(!w[i]||own[w[i]]||DESC_NOISE[w[i]]||/^\d+$/.test(w[i]))continue;return false;}
492
500
  return true;
493
501
  }
502
+ // Pure, so it is testable without a DOM. Empty when nothing was derived — a curated or a
503
+ // gh-verified state has no document reach to report.
504
+ function coverText(n){
505
+ var c=n&&n.verify&&n.verify.coverage;if(!c)return "";
506
+ if(c.whole)return STR.coverWhole;
507
+ return STR.coverPart.replace("{n}",c.named).replace("{t}",c.total);
508
+ }
494
509
  function wrapDesc(desc,cpl,max){
495
510
  if(max<1||cpl<2)return [];
496
511
  var words=String(desc==null?"":desc).split(" "),all=[],line="",i;
@@ -525,7 +540,12 @@ function showDetail(n){
525
540
  +(n.func?'<div class="row"><span class="lbl">'+esc(STR.detFunc)+'</span><span class="func">'+esc(n.func)+'</span></div>':'')
526
541
  +(cur&&cur!==n.func?'<div class="row"><span class="lbl">'+esc(STR.detNow)+'</span><span class="now">'+esc(cur)+'</span></div>':'')
527
542
  +((n.target&&n.target!=="—")?'<div class="row"><span class="lbl">'+esc(STR.detTgt)+'</span><span class="tgt">'+esc(n.target)+'</span></div>':'')
528
- +(showSrc?'<div class="row"><span class="lbl">'+esc(STR.detSrc)+'</span><span class="src">'+esc(src)+'</span></div>':'')
543
+ // A number DERIVED from a repo document is a declaration, not a verification: the label says which
544
+ // one, and the coverage says how much of this module the citation actually reaches. "3 of 3 rows
545
+ // declared done" and "this module is done" are not the same sentence when the module holds 22
546
+ // files and the document names 3 of them.
547
+ +(showSrc?'<div class="row"><span class="lbl">'+esc((n.verify&&n.verify.derived)?STR.detDecl:STR.detSrc)+'</span><span class="src">'+esc(src)+'</span></div>':'')
548
+ +(coverText(n)?'<div class="row"><span class="lbl">'+esc(STR.detCover)+'</span><span class="now">'+esc(coverText(n))+'</span></div>':'')
529
549
  +((n.issues&&n.issues.length)?'<div class="row"><span class="lbl">'+esc(STR.detIssue)+'</span><span class="now">'+esc(n.issues.join(", "))+'</span></div>':'');
530
550
  $("detail").style.display="block";
531
551
  var dc=$("dc");if(dc)dc.addEventListener("click",function(){$("detail").style.display="none";});
@@ -645,7 +665,7 @@ function applyStrings(){
645
665
  set("blbl",STR.labels);var bl=$("blbl");if(bl)bl.title=STR.labelsTitle;
646
666
  set("pexp",STR.printExport);set("pexpSvg",STR.exportSvg);set("pexpPng",STR.exportPng);
647
667
  set("pexpLayout",STR.exportLayout);set("pexpPrint",STR.print);
648
- set("lg-done",STR.legDone);set("lg-prog",STR.legProg);set("lg-next",STR.legNext);
668
+ set("lg-done",STR.legDone);set("lg-decl",STR.legDecl);set("lg-prog",STR.legProg);set("lg-next",STR.legNext);
649
669
  set("lg-plan",STR.legPlan);set("lg-prob",STR.legProb);set("lg-unk",STR.legUnk);set("lg-hint",STR.legHint);
650
670
  }
651
671
  // ---- FEATURE #2: PRINT / EXPORT (all client-side, offline) ----
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forma-arch",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
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",