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