@basou/cli 0.71.2 → 0.73.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/dist/index.js CHANGED
@@ -842,11 +842,13 @@ import {
842
842
  assertBasouRootSafe as assertBasouRootSafe3,
843
843
  BOARD_INIT_SAVE,
844
844
  basouPaths as basouPaths4,
845
+ boardDeclarationSnapshot,
845
846
  boardGuide,
846
847
  boardInitText,
847
848
  buildRecord,
848
849
  byCodePoint,
849
850
  diffCells,
851
+ diffDeclarations,
850
852
  diffObserved,
851
853
  displayPath as displayPath3,
852
854
  effortStartOf,
@@ -2345,9 +2347,10 @@ async function doRunBoardMeasure(options, ctx) {
2345
2347
  throw new Error("--model must name a model.");
2346
2348
  }
2347
2349
  const loaded = await loadBoard(options, ctx, "board measure");
2348
- const measurement = await measureLoaded(loaded, ctx, options.model, await previousOf(loaded));
2350
+ const previous = await previousOf(loaded);
2351
+ const measurement = await measureLoaded(loaded, ctx, options.model, previous);
2349
2352
  if (options.json === true) console.log(JSON.stringify(measurement, null, 2));
2350
- else printMeasurementText(measurement, hasRecords(loaded));
2353
+ else printMeasurementText(measurement, hasRecords(loaded), declarationChanges(loaded, previous));
2351
2354
  if (!measurement.complete) process.exitCode = 1;
2352
2355
  return measurement;
2353
2356
  }
@@ -2362,16 +2365,17 @@ async function runBoardRecord(options, ctx = {}) {
2362
2365
  var RecordRefusal = class extends Error {
2363
2366
  };
2364
2367
  async function doRunBoardRecord(options, ctx) {
2365
- let result;
2368
+ let checked;
2366
2369
  try {
2367
- result = await checkAndRecord(options, ctx);
2370
+ checked = await checkAndRecord(options, ctx);
2368
2371
  } catch (error) {
2369
2372
  if (error instanceof RecordRefusal || !(error instanceof Error)) throw error;
2370
2373
  throw new RecordRefusal(`${error.message}
2371
2374
  Nothing was written.`, { cause: error.cause });
2372
2375
  }
2376
+ const { result, declared } = checked;
2373
2377
  if (options.json === true) console.log(JSON.stringify(result, null, 2));
2374
- else printRecordText(result);
2378
+ else printRecordText(result, declared ?? null);
2375
2379
  return result;
2376
2380
  }
2377
2381
  async function checkAndRecord(options, ctx) {
@@ -2412,6 +2416,7 @@ ${parsed.errors.map((e) => ` - ${displayPath3(e)}`).join("\n")}`
2412
2416
  recordedAt,
2413
2417
  recordedWith: { basou: BASOU_CLI_VERSION, build: BASOU_BUILD?.commit ?? null }
2414
2418
  });
2419
+ const declared = options.json === true ? void 0 : declarationChanges(loaded, previous);
2415
2420
  let written = null;
2416
2421
  if (options.dryRun !== true) {
2417
2422
  let name;
@@ -2438,14 +2443,22 @@ ${parsed.errors.map((e) => ` - ${displayPath3(e)}`).join("\n")}`
2438
2443
  };
2439
2444
  }
2440
2445
  return {
2441
- record: written,
2442
- dry_run: options.dryRun === true,
2443
- complete: measurement.complete,
2444
- not_found: measurement.not_found,
2445
- order_anomalies: record.order_anomalies,
2446
- diff
2446
+ result: {
2447
+ record: written,
2448
+ dry_run: options.dryRun === true,
2449
+ complete: measurement.complete,
2450
+ not_found: measurement.not_found,
2451
+ order_anomalies: record.order_anomalies,
2452
+ diff
2453
+ },
2454
+ ...declared === void 0 ? {} : { declared }
2447
2455
  };
2448
2456
  }
2457
+ function declarationChanges(loaded, previous) {
2458
+ const { last } = previous;
2459
+ if (last.status !== "found") return null;
2460
+ return diffDeclarations(last.record.declaration, boardDeclarationSnapshot(loaded.declaration));
2461
+ }
2449
2462
  var INPUT_KEYS = /* @__PURE__ */ new Set([
2450
2463
  "measure_digest",
2451
2464
  "observed",
@@ -2595,7 +2608,7 @@ async function checkRecordsPlace(loaded, recordsDir, shown, notPrivate) {
2595
2608
  }
2596
2609
  }
2597
2610
  }
2598
- function printRecordText(result) {
2611
+ function printRecordText(result, declared) {
2599
2612
  const lines = [
2600
2613
  result.record === null ? "Dry run: the record checked out (nothing was written)." : `Recorded ${displayPath3(result.record)}`
2601
2614
  ];
@@ -2616,7 +2629,11 @@ function printRecordText(result) {
2616
2629
  ` observed ${displayPath3(o.name)}: ${observation(o.before)} -> ${observation(o.after)}`
2617
2630
  );
2618
2631
  }
2619
- lines.push(...diffLines(result.diff.measure, cells.length + observed.length > 0));
2632
+ const declaration = declarationLines(declared);
2633
+ lines.push(
2634
+ ...diffLines(result.diff.measure, cells.length + observed.length + declaration.length > 0),
2635
+ ...declaration
2636
+ );
2620
2637
  }
2621
2638
  if (!result.complete) {
2622
2639
  lines.push(`The measurement recorded is not complete (${result.not_found.length}):`);
@@ -2802,6 +2819,16 @@ function diffLines(diff, more) {
2802
2819
  if (lines.length === 0 && !more) lines.push(" nothing moved");
2803
2820
  return lines;
2804
2821
  }
2822
+ function declarationLines(changes) {
2823
+ if (changes === null) {
2824
+ return [
2825
+ " declaration: not compared (the previous record holds no declaration basou can compare with)"
2826
+ ];
2827
+ }
2828
+ return changes.map(
2829
+ (c) => ` declaration ${displayPath3(c.at)}: ${c.change === "reordered" ? "the order changed" : c.change}`
2830
+ );
2831
+ }
2805
2832
  function shownValue(value) {
2806
2833
  if (value === null) return "not measured";
2807
2834
  if (typeof value === "number") return String(Math.round(value * 1e4) / 1e4);
@@ -2933,12 +2960,18 @@ function axisLines(m) {
2933
2960
  for (const u of a.unjudged) lines.push(` (${u.trigger}) not judged: ${u.why}`);
2934
2961
  return lines;
2935
2962
  }
2936
- function printMeasurementText(m, hasRecords2) {
2963
+ function printMeasurementText(m, hasRecords2, declared) {
2937
2964
  const lines = [displayPath3(m.title)];
2938
2965
  const build = m.measured_with.build === null ? "" : ` (build ${m.measured_with.build})`;
2939
2966
  lines.push(`Measured ${m.measured_at} with basou ${m.measured_with.basou}${build}`);
2940
2967
  if (m.diff !== null) {
2941
- lines.push("", `Since the previous record ${m.diff.against}:`, ...diffLines(m.diff, false));
2968
+ const declaration = declarationLines(declared);
2969
+ lines.push(
2970
+ "",
2971
+ `Since the previous record ${m.diff.against}:`,
2972
+ ...diffLines(m.diff, declaration.length > 0),
2973
+ ...declaration
2974
+ );
2942
2975
  } else if (m.not_found.some((n) => n.at === "diff")) {
2943
2976
  lines.push("", "The previous record could not be read (see Not measured).");
2944
2977
  } else {
@@ -13938,6 +13971,17 @@ var BOARD_HTML = `<!doctype html>
13938
13971
  table.matrix th.stage { font-weight: 400; font-size: 12px; text-align: center; min-width: 64px; }
13939
13972
  table.matrix th.stage b { display: block; font-size: 13px; }
13940
13973
  table.matrix td.cell { text-align: center; font-size: 18px; line-height: 1; }
13974
+ .stage-meanings { display: none; list-style: none; padding: 0; margin: 6px 0 0; font-size: 12px; color: var(--fg-2); }
13975
+ /* Six stages do not fit beside their meanings below about 600px: each is
13976
+ drawn by its id, the meanings are listed under the matrix, and a lane
13977
+ name wraps. After the rules above, which it overrides. */
13978
+ @media (max-width: 600px) {
13979
+ table.matrix th.stage { min-width: 0; padding: 4px 2px; }
13980
+ table.matrix th.stage .meaning { display: none; }
13981
+ table.matrix td.cell { font-size: 16px; padding: 4px 2px; }
13982
+ table.matrix th:first-child { max-width: 10em; overflow-wrap: anywhere; }
13983
+ .stage-meanings { display: block; }
13984
+ }
13941
13985
  .st::before { display: inline-block; width: 1.2em; }
13942
13986
  .st.done::before { content: "\\25CF"; color: var(--done); }
13943
13987
  .st.part::before { content: "\\25D0"; color: var(--done); }
@@ -13958,6 +14002,11 @@ var BOARD_HTML = `<!doctype html>
13958
14002
  .chip.blocked { background: var(--chip-blocked); }
13959
14003
  .chip.unverified { background: var(--chip-unverified); }
13960
14004
  .muted { color: var(--fg-2); }
14005
+ .sub { font-size: 12px; color: var(--fg-2); overflow-wrap: anywhere; }
14006
+ ul.sub { margin: 0; padding-left: 18px; }
14007
+ td.wrap { overflow-wrap: anywhere; min-width: 10em; }
14008
+ tr.notes td { padding-top: 0; }
14009
+ .chip.changed { margin-left: 0; margin-right: 6px; background: var(--chip-unverified); }
13961
14010
  .bar { height: 10px; background: var(--fill); border-radius: 5px; min-width: 120px; position: relative; overflow: hidden; }
13962
14011
  .bar span { position: absolute; left: 0; top: 0; bottom: 0; background: var(--s1); }
13963
14012
  figure { margin: 16px 0; }
@@ -14087,6 +14136,60 @@ var BOARD_HTML = `<!doctype html>
14087
14136
  return JSON.stringify(k) + ':' + v;
14088
14137
  }).join(',') + '}';
14089
14138
  }
14139
+ // The parts of a value drawn on one line, and what an http value says of each word.
14140
+ var DOT = ' ' + String.fromCharCode(183) + ' ';
14141
+ var FOUND = String.fromCharCode(10003);
14142
+ var NOT_FOUND = String.fromCharCode(10007);
14143
+ // An http value read as its parts: the status, the redirects, where it
14144
+ // landed, and what it read off the body (when the status is 2xx) or that it
14145
+ // did not. Numbers and strings are drawn as they are.
14146
+ function httpText(h) {
14147
+ var parts = [String(h.status), fill(S.observed.redirects, { n: h.redirects }), h.final_url];
14148
+ if (h.body === null) parts.push(S.observed.bodyNotRead);
14149
+ else if (h.body) {
14150
+ if (h.body.words) {
14151
+ parts.push(h.body.words.map(function (w) { return w.word + ' ' + (w.found ? FOUND : NOT_FOUND); }).join(' '));
14152
+ }
14153
+ if (h.body.count !== undefined) parts.push(fill(S.observed.count, { n: h.body.count }));
14154
+ if (h.body.capture !== undefined) {
14155
+ parts.push(h.body.capture === null ? S.observed.captureNone : fill(S.observed.capture, { value: JSON.stringify(h.body.capture) }));
14156
+ }
14157
+ }
14158
+ return parts.join(DOT);
14159
+ }
14160
+ // A value of an observation as the page draws it: an http value in its
14161
+ // shape read as its parts, one with words beside what it says of each, and
14162
+ // any other as it is (a string) or as JSON. A test lifts this function.
14163
+ function observedText(value, words, http) {
14164
+ if (http) return httpText(http);
14165
+ if (words) return withWords(value, words);
14166
+ return shown(value);
14167
+ }
14168
+ // The value of an observation that moved, and what the previous record
14169
+ // held: a value whose type alone moved ("12" and 12) is drawn as JSON, so
14170
+ // that the two do not read the same. A test lifts this function.
14171
+ function movedText(o) {
14172
+ var m = o.moved;
14173
+ var now = observedText(o.value, o.words, o.http);
14174
+ if (m.status === 'none') return { now: now, then: S.observed.beforeNone };
14175
+ if (m.value === null) {
14176
+ return { now: now, then: S.observed.beforeNotObserved + (m.error ? ' (' + m.error + ')' : '') };
14177
+ }
14178
+ var then = observedText(m.value, m.words, m.http);
14179
+ if ((typeof m.value === 'string') !== (typeof o.value === 'string')) {
14180
+ then = JSON.stringify(m.value);
14181
+ if (then === now) now = JSON.stringify(o.value);
14182
+ }
14183
+ return { now: now, then: fill(S.observed.before, { value: then }) + (m.error ? ' (' + m.error + ')' : '') };
14184
+ }
14185
+ // A measure's value: the paths of a file_paths by how many, a number with
14186
+ // its unit, and a string (a file's name, a capture), which is no amount,
14187
+ // without it.
14188
+ function measureText(m) {
14189
+ if (Array.isArray(m.value)) return num(m.value.length) + ' ' + m.unit;
14190
+ return num(m.value) + (typeof m.value === 'number' ? ' ' + m.unit : '');
14191
+ }
14192
+ function signed(n) { return (n > 0 ? '+' : '') + num(n); }
14090
14193
  function stateName(state) { return S.states[state] || state; }
14091
14194
  function setStatus(message, isErr) {
14092
14195
  var s = $('status');
@@ -14167,21 +14270,47 @@ var BOARD_HTML = `<!doctype html>
14167
14270
  k, el('div', { class: 'v', text: num(t.value) }), sub === null ? null : el('div', { class: 's', text: sub })
14168
14271
  ]));
14169
14272
  });
14273
+ return section(S.sections.summary, false, [el('div', null, [prose(s.text), reported()]), tiles]);
14274
+ }
14275
+
14276
+ // The observations outside, which the record holds in its summary, in a
14277
+ // section of their own after the lanes.
14278
+ function observations(b) {
14279
+ var s = b.summary;
14170
14280
  var observed;
14171
14281
  if (s.observed.length === 0) {
14172
14282
  observed = el('p', { class: 'muted', text: S.observed.none });
14173
14283
  } else {
14174
- var rows = s.observed.map(function (o) {
14284
+ var rows = [];
14285
+ s.observed.forEach(function (o) {
14286
+ var cell = el('td', { class: 'wrap' });
14175
14287
  var value;
14176
- if (o.value !== null) value = o.words ? withWords(o.value, o.words) : shown(o.value);
14177
- else if (o.previous && o.previous.status === 'value') value = fill(S.observed.missing, { value: shown(o.previous.value) });
14178
- else if (o.previous && o.previous.status === 'unreadable') value = S.observed.missingPreviousUnreadable;
14179
- else value = S.observed.missingNoPrevious;
14180
- if (o.error) value += ' (' + o.error + ')';
14181
- return el('tr', null, [
14182
- el('td', { text: o.name }), el('td', { text: value }),
14183
- el('td', { text: o.observed_at }), el('td', { text: o.source })
14184
- ]);
14288
+ if (o.value !== null && o.moved) {
14289
+ var pair = movedText(o);
14290
+ cell.appendChild(el('span', { class: 'chip changed', text: S.observed.changed }));
14291
+ value = pair.now;
14292
+ cell.appendChild(el('span', { text: value + (o.error ? ' (' + o.error + ')' : '') }));
14293
+ cell.appendChild(el('div', { class: 'sub', text: pair.then }));
14294
+ } else {
14295
+ if (o.value !== null) value = observedText(o.value, o.words, o.http);
14296
+ else if (o.previous && o.previous.status === 'value') value = fill(S.observed.missing, { value: shown(o.previous.value) });
14297
+ else if (o.previous && o.previous.status === 'unreadable') value = S.observed.missingPreviousUnreadable;
14298
+ else value = S.observed.missingNoPrevious;
14299
+ if (o.error) value += ' (' + o.error + ')';
14300
+ cell.appendChild(el('span', { text: value }));
14301
+ }
14302
+ // What the record's declaration says it is and looks at, under its name.
14303
+ var what = o.kind ? [o.kind].concat(o.looks_at || []).join(DOT) : null;
14304
+ rows.push(el('tr', null, [
14305
+ el('td', null, [el('div', { text: o.name }), what === null ? null : el('div', { class: 'sub', text: what })]),
14306
+ cell,
14307
+ el('td', { text: o.observed_at }), el('td', { class: 'wrap', text: o.source })
14308
+ ]));
14309
+ if (o.notes && o.notes.length > 0) {
14310
+ rows.push(el('tr', { class: 'notes' }, [el('td', { colspan: '4' }, [
14311
+ el('ul', { class: 'sub' }, o.notes.map(function (n) { return el('li', null, [prose(n, 'span')]); }))
14312
+ ])]));
14313
+ }
14185
14314
  });
14186
14315
  observed = el('div', { class: 'scroll' }, [el('table', { class: 'grid' }, [
14187
14316
  el('tr', null, [
@@ -14190,13 +14319,11 @@ var BOARD_HTML = `<!doctype html>
14190
14319
  ])
14191
14320
  ].concat(rows))]);
14192
14321
  }
14193
- var observedHeading = el('h3', { text: S.observed.heading }, [reported()]);
14194
- return section(S.sections.summary, false, [
14195
- el('div', null, [prose(s.text), reported()]),
14196
- tiles,
14197
- observedHeading,
14198
- observed
14199
- ]);
14322
+ var children = [observed];
14323
+ if (s.previous_record === 'unreadable' && s.observed.length > 0) {
14324
+ children.push(el('p', { class: 'muted', text: S.observed.previousUnreadable }));
14325
+ }
14326
+ return section(S.observed.heading, true, children);
14200
14327
  }
14201
14328
 
14202
14329
  var SVGNS = 'http://www.w3.org/2000/svg';
@@ -14428,16 +14555,24 @@ var BOARD_HTML = `<!doctype html>
14428
14555
  ]);
14429
14556
  };
14430
14557
  var union = e.active_ms.union;
14558
+ // Over the daily rows, today's included (one more than the days elapsed),
14559
+ // so that the first day is a day; the sub-text names the divisor.
14431
14560
  var perDay = union === null || !e.period_days ? null : union / e.period_days;
14432
14561
  var perWorked = union === null || !e.active_days ? null : union / e.active_days;
14562
+ var perDaySub = [
14563
+ perDay === null ? null : fill(S.effort.perDayOver, { days: num(e.period_days) }),
14564
+ perWorked === null ? null : fill(S.effort.perDayWorked, { time: hm(perWorked) })
14565
+ ].filter(function (part) { return part !== null; }).join(DOT);
14433
14566
  var tokens = e.sessions_without_tokens ? fill(S.effort.withoutTokens, { n: e.sessions_without_tokens }) : null;
14567
+ // Each tool's time is the union of its sessions' intervals, as Active is.
14568
+ var unionOf = function (ms) { return ms === null ? null : S.effort.toolUnion; };
14434
14569
  var tiles = el('div', { class: 'tiles' }, [
14435
14570
  tile(S.effort.elapsed, e.elapsed_days === null ? S.effort.notMeasured : fill(S.effort.days, { days: e.elapsed_days }),
14436
14571
  e.time_zone === null ? fill(S.effort.from, { date: e.start }) : fill(S.effort.fromZone, { date: e.start, zone: e.time_zone })),
14437
14572
  tile(S.effort.active, hm(union), e.active_days === null ? null : fill(S.effort.daysWorked, { n: e.active_days })),
14438
- tile(S.effort.claude, hm(e.active_ms.claude), null),
14439
- tile(S.effort.codex, e.active_ms.codex === null && union !== null ? S.effort.noCodex : hm(e.active_ms.codex), null),
14440
- tile(S.effort.perDay, hm(perDay), perWorked === null ? null : fill(S.effort.perDayWorked, { time: hm(perWorked) })),
14573
+ tile(S.effort.claude, hm(e.active_ms.claude), unionOf(e.active_ms.claude)),
14574
+ tile(S.effort.codex, e.active_ms.codex === null && union !== null ? S.effort.noCodex : hm(e.active_ms.codex), unionOf(e.active_ms.codex)),
14575
+ tile(S.effort.perDay, hm(perDay), perDaySub),
14441
14576
  tile(S.effort.outputTokens, num(e.output_tokens), tokens)
14442
14577
  ]);
14443
14578
  var commits = e.commits === null ? S.effort.notMeasured : e.commits.map(function (c) {
@@ -14475,8 +14610,13 @@ var BOARD_HTML = `<!doctype html>
14475
14610
  })
14476
14611
  )));
14477
14612
  }
14613
+ // The meaning is the head's title too, as a screen reader reads it and a
14614
+ // pointer that hovers shows it, and the text beside the id is left out of
14615
+ // what a screen reader reads, so that it is read once.
14478
14616
  var head = el('tr', null, [el('th', { text: S.matrix.lane })].concat(x.stages.map(function (s) {
14479
- return el('th', { class: 'stage' }, [el('b', { text: s.id }), s.meaning]);
14617
+ return el('th', { class: 'stage', title: s.meaning }, [
14618
+ el('b', { text: s.id }), el('span', { class: 'meaning', 'aria-hidden': 'true', text: s.meaning })
14619
+ ]);
14480
14620
  })));
14481
14621
  var rows = x.lanes.map(function (lane) {
14482
14622
  return el('tr', null, [el('th', { text: lane.name })].concat(lane.cells.map(function (c) {
@@ -14488,6 +14628,11 @@ var BOARD_HTML = `<!doctype html>
14488
14628
  })));
14489
14629
  });
14490
14630
  children.push(el('div', { class: 'scroll' }, [el('table', { class: 'matrix' }, [head].concat(rows))]));
14631
+ // What each stage means, drawn where a narrow screen gives a stage its id
14632
+ // alone (and hidden where the heads say it).
14633
+ children.push(el('ul', { class: 'stage-meanings' }, x.stages.map(function (s) {
14634
+ return el('li', null, [el('b', { text: s.id }), ' ' + s.meaning]);
14635
+ })));
14491
14636
  var legend = el('div', { class: 'legend' });
14492
14637
  ['done', 'part', 'blocked', 'shelved', 'none', 'unverified'].forEach(function (s) {
14493
14638
  legend.appendChild(el('span', null, [el('span', { class: 'st ' + s }), stateName(s)]));
@@ -14535,6 +14680,27 @@ var BOARD_HTML = `<!doctype html>
14535
14680
  }));
14536
14681
  }
14537
14682
 
14683
+ // Every measure of the record, with the lanes it is drawn in and how much a
14684
+ // number moved since the previous record.
14685
+ function measures(b) {
14686
+ if (b.measures.length === 0) {
14687
+ return section(S.sections.measures, false, [el('p', { class: 'muted', text: S.measures.none })]);
14688
+ }
14689
+ var head = el('tr', null, [
14690
+ el('th', { text: S.measures.id }), el('th', { text: S.measures.value }),
14691
+ el('th', { text: S.measures.lanes }), el('th', { class: 'n', text: S.measures.delta })
14692
+ ]);
14693
+ var rows = b.measures.map(function (m) {
14694
+ return el('tr', { 'data-measure': m.id }, [
14695
+ el('td', null, [el('code', { text: m.id })]),
14696
+ el('td', { class: 'wrap', text: measureText(m) }),
14697
+ el('td', { text: m.lanes.length === 0 ? '-' : m.lanes.join(', ') }),
14698
+ el('td', { class: 'n', text: m.delta === null || m.delta === undefined ? '' : signed(m.delta) })
14699
+ ]);
14700
+ });
14701
+ return section(S.sections.measures, false, [el('div', { class: 'scroll' }, [el('table', { class: 'grid' }, [head].concat(rows))])]);
14702
+ }
14703
+
14538
14704
  function composition(b) {
14539
14705
  if (b.composition.length === 0) {
14540
14706
  return section(S.sections.composition, false, [el('p', { class: 'muted', text: S.composition.none })]);
@@ -14622,7 +14788,10 @@ var BOARD_HTML = `<!doctype html>
14622
14788
  var b = page.board;
14623
14789
  // The board's own title, so that boards open in other tabs can be told apart.
14624
14790
  document.title = b.heading.title;
14625
- [heading(b), summary(b), effort(b, false), matrix(b), lanes(b), composition(b), turns(b), footnotes(b)]
14791
+ // Where each lane stops, right under the summary's tiles, and the lanes,
14792
+ // before the observations and the charts of the effort.
14793
+ [heading(b), summary(b), matrix(b), lanes(b), observations(b), effort(b, false), measures(b), composition(b),
14794
+ turns(b), recordRepos(b), recordComponents(b), footnotes(b)]
14626
14795
  .forEach(function (node) { root.appendChild(node); });
14627
14796
  }
14628
14797
 
@@ -14648,12 +14817,14 @@ var BOARD_HTML = `<!doctype html>
14648
14817
  function missingIn(l) {
14649
14818
  return function (at) { return l.footnotes.not_found.some(function (n) { return n.at === at; }); };
14650
14819
  }
14651
- function liveRepos(l) {
14652
- var missing = missingIn(l);
14653
- var labels = [S.live.repo, S.live.branch, S.live.head, S.live.lastCommit, S.live.commits, S.live.uncommitted, S.live.base, S.live.behind, S.live.ahead, S.live.fetchedAt];
14654
- var numeric = [4, 5, 7, 8];
14820
+ // The repositories as a table. older: a record of version 1 or 2, which
14821
+ // measured no base, ahead or fetch time (drawn as '-') and held the
14822
+ // commits behind as behind_main.
14823
+ function reposTable(list, missing, older) {
14824
+ var labels = [S.live.repo, S.live.branch, S.live.head, S.live.lastCommit, S.live.commits, S.live.files, S.live.uncommitted, S.live.base, S.live.behind, S.live.ahead, S.live.fetchedAt];
14825
+ var numeric = [4, 5, 6, 8, 9];
14655
14826
  var head = el('tr', null, labels.map(function (label, i) { return el('th', { class: numeric.indexOf(i) !== -1 ? 'n' : '', text: label }); }));
14656
- var rows = l.repos.map(function (r) {
14827
+ var rows = list.map(function (r) {
14657
14828
  var at = 'repos[' + r.path + ']';
14658
14829
  // A null the measurement says it could not measure, or one that means
14659
14830
  // so (a detached HEAD, no commit yet, no base, no fetch of it on
@@ -14662,20 +14833,76 @@ var BOARD_HTML = `<!doctype html>
14662
14833
  if (v !== null) return show(v);
14663
14834
  return missing(at) || missing(at + '.' + field) ? S.tiles.notMeasured : '-';
14664
14835
  };
14836
+ var unmeasured = function (field, v, show) { return older ? '-' : value(field, v, show); };
14665
14837
  return el('tr', null, [
14666
14838
  el('td', { text: r.name === null ? r.path : r.name }),
14667
14839
  el('td', { text: value('branch', r.branch, String) }),
14668
14840
  el('td', { text: value('head', r.head, function (h) { return h.slice(0, 7); }) }),
14669
14841
  el('td', { text: value('last_commit', r.last_commit, when) }),
14670
14842
  el('td', { class: 'n', text: num(r.commits) }),
14843
+ el('td', { class: 'n', text: num(r.files) }),
14671
14844
  el('td', { class: 'n', text: num(r.uncommitted) }),
14672
- el('td', { text: value('base', r.base, String) }),
14673
- el('td', { class: 'n', text: value('behind', r.behind, num) }),
14674
- el('td', { class: 'n', text: value('ahead', r.ahead, num) }),
14675
- el('td', { text: value('fetched_at', r.fetched_at, when) })
14845
+ el('td', { text: unmeasured('base', r.base, String) }),
14846
+ el('td', { class: 'n', text: value(older ? 'behind_main' : 'behind', r.behind, num) }),
14847
+ el('td', { class: 'n', text: unmeasured('ahead', r.ahead, num) }),
14848
+ el('td', { text: unmeasured('fetched_at', r.fetched_at, when) })
14676
14849
  ]);
14677
14850
  });
14678
- return section(S.live.repos, false, [el('div', { class: 'scroll' }, [el('table', { class: 'grid' }, [head].concat(rows))])]);
14851
+ return el('div', { class: 'scroll' }, [el('table', { class: 'grid' }, [head].concat(rows))]);
14852
+ }
14853
+ function liveRepos(l) {
14854
+ return section(S.live.repos, false, [reposTable(l.repos, missingIn(l), false)]);
14855
+ }
14856
+ // The repositories as the record measured them, by its version.
14857
+ function recordRepos(b) {
14858
+ var r = b.repos;
14859
+ if (!r) return section(S.live.repos, false, [el('p', { class: 'muted', text: S.record.reposUnread })]);
14860
+ var children = [reposTable(r.list, missingIn(b), !r.base_measured)];
14861
+ if (!r.base_measured) children.push(el('p', { class: 'muted', text: fill(S.record.notMeasuredByVersion, { version: r.record_version }) }));
14862
+ return section(S.live.repos, false, children);
14863
+ }
14864
+ // The components the record found, by the lanes its registry counts them
14865
+ // in, then those in no lane (folded), those not registered, and those gone.
14866
+ function recordComponents(b) {
14867
+ var c = b.components;
14868
+ if (!c) return section(S.live.components, false, [el('p', { class: 'muted', text: S.record.componentsUnread })]);
14869
+ if (!c.measured) {
14870
+ return section(S.live.components, false, [
14871
+ el('p', { class: 'muted', text: S.record.componentsNotMeasured }),
14872
+ el('ul', null, c.reasons.map(function (reason) { return el('li', { text: reason }); }))
14873
+ ]);
14874
+ }
14875
+ var item = function (x) {
14876
+ var kinds = x.kinds === null ? S.record.notFound : x.kinds.join(', ');
14877
+ var parts = [el('code', { text: x.key }), el('span', { class: 'muted', text: ' ' + kinds })];
14878
+ if (x.kind_changed) {
14879
+ parts.push(el('span', { class: 'chip changed', text: fill(S.record.kindChanged, { before: x.kind_changed.before.join(', ') }) }));
14880
+ }
14881
+ if (x.note) parts.push(el('div', { class: 'sub', text: x.note }));
14882
+ return el('li', { 'data-component': x.key }, parts);
14883
+ };
14884
+ var group = function (title, list, empty) {
14885
+ return [el('h3', { text: title }), list.length === 0 ? el('p', { class: 'muted', text: empty }) : el('ul', null, list.map(item))];
14886
+ };
14887
+ var children = [];
14888
+ c.lanes.forEach(function (lane) {
14889
+ children = children.concat(group(lane.name, lane.components, S.record.noComponentInLane));
14890
+ });
14891
+ // Those in no lane can be most of them: folded under how many they are.
14892
+ if (c.uncounted.length > 0) {
14893
+ children.push(el('details', null, [
14894
+ el('summary', { text: fill(S.record.uncountedFolded, { n: num(c.uncounted.length) }) }),
14895
+ el('ul', null, c.uncounted.map(item))
14896
+ ]));
14897
+ } else {
14898
+ children = children.concat(group(S.record.uncounted, c.uncounted, S.record.none));
14899
+ }
14900
+ children = children.concat(group(S.record.unacknowledged, c.unacknowledged, S.record.none));
14901
+ children.push(el('h3', { text: S.record.gone }));
14902
+ children.push(c.gone === null ? el('p', { class: 'muted', text: S.record.goneUnknown })
14903
+ : c.gone.length === 0 ? el('p', { class: 'muted', text: S.record.none })
14904
+ : el('ul', null, c.gone.map(function (key) { return el('li', null, [el('code', { text: key })]); })));
14905
+ return section(S.live.components, false, children);
14679
14906
  }
14680
14907
  function liveTrail(l) {
14681
14908
  var t = l.trail;
@@ -16336,7 +16563,7 @@ function installExperimentalNotice(program2, names = EXPERIMENTAL_COMMANDS, writ
16336
16563
  function readBuildStamp() {
16337
16564
  if (false) return void 0;
16338
16565
  try {
16339
- return JSON.parse('{"version":"0.71.2","commit":"c7fc0ed","committedAt":"2026-10-19T20:00:28+09:00"}');
16566
+ return JSON.parse('{"version":"0.73.0","commit":"fdfa3a5","committedAt":"2026-10-20T20:30:59+09:00"}');
16340
16567
  } catch {
16341
16568
  return void 0;
16342
16569
  }