@leftoff/cli 0.9.18-beta.4 → 0.9.18-beta.6

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/ui/semdiff.js CHANGED
@@ -216,7 +216,20 @@ export function alsoIn(st, g, shared) {
216
216
  }
217
217
  export const CHANGED_TIP = 'Its files changed since this walkthrough was written'
218
218
 
219
- function stepHtml(st, { esc, icon, done, cur, open, sid, sf = {}, skip = null, stat = null, shared = null, foldShared = true }) {
219
+ // 2026-11-03 decision memory: the accepted decisions a step cites, as small chips that open the decision with its chain
220
+ export function decisionsOf(data) { return new Map(((data && data.decisions) || []).map((d) => [d.id, d])) }
221
+ export function stepDecisionsHtml(st, data, { esc }) {
222
+ const ids = Array.isArray(st.decisions) ? st.decisions : []
223
+ if (!ids.length) return ''
224
+ const by = decisionsOf(data)
225
+ const gone = new Set((data && data.stale && data.stale.decisions) || [])
226
+ return '<div class="wt-decs" role="group" aria-label="Decisions behind this step">' + ids.map((id) => {
227
+ const d = by.get(id)
228
+ return '<button type="button" class="dec-chip wt-dec' + (gone.has(id) ? ' wt-dec-gone' : '') + '" data-action="wt-dec" data-id="' + esc(id) + '"' + (data && data.decisionRepo ? ' data-repo="' + esc(data.decisionRepo) + '"' : '') +
229
+ ' data-tip="' + esc((gone.has(id) ? 'Changed since this walkthrough · ' : '') + (d ? d.text : 'Decision ' + id)) + '"><span class="wt-dec-id">' + esc(id) + '</span>' + (d ? '<span class="wt-dec-t">' + esc(d.text) + '</span>' : '') + (gone.has(id) ? '<span class="wt-dec-w"> · changed since</span>' : '') + '</button>'
230
+ }).join('') + '</div>'
231
+ }
232
+ function stepHtml(st, { esc, icon, done, cur, open, sid, sf = {}, skip = null, stat = null, shared = null, foldShared = true, data = null }) {
220
233
  const core = st.tag === 'Core'
221
234
  // 2026-10-29 several repos: the step's repo leads ("cube_cloud · src/x.ts"); one repo: the file names
222
235
  const rp = st.repo ? [st.repo] : []
@@ -246,7 +259,7 @@ function stepHtml(st, { esc, icon, done, cur, open, sid, sf = {}, skip = null, s
246
259
  : '<button type="button" class="wt-showc" data-action="wt-code" data-step="' + esc(st.id) + '" aria-expanded="false">' + icon('chevron-right') + 'Show code <span class="faint">· ' + plural(lines, 'line') + '</span></button>'
247
260
  // callouts whose line isn't in a drawn snippet (folded code) still read here
248
261
  const loose = !show && st.callouts.length ? '<div class="wt-cos">' + st.callouts.map((c) => calloutHtml(c, { esc, icon })).join('') + '</div>' : ''
249
- return '<article class="wt-st ' + (core ? 'wt-core' : 'wt-sup') + (done ? ' done' : '') + (cur ? ' cur' : '') + '" id="wt-' + esc(st.id) + '" data-step="' + esc(st.id) + '" tabindex="-1" aria-label="' + esc('Step ' + st.n + ': ' + mdPlain(st.title)) + '">' + head + why + loose + code + '</article>'
262
+ return '<article class="wt-st ' + (core ? 'wt-core' : 'wt-sup') + (done ? ' done' : '') + (cur ? ' cur' : '') + '" id="wt-' + esc(st.id) + '" data-step="' + esc(st.id) + '" tabindex="-1" aria-label="' + esc('Step ' + st.n + ': ' + mdPlain(st.title)) + '">' + head + why + stepDecisionsHtml(st, data, { esc }) + loose + code + '</article>'
250
263
  }
251
264
 
252
265
  function groupHtml(g, { esc, icon, open, data = null }) {
@@ -389,6 +402,8 @@ function modeSegHtml(show, split) {
389
402
  // "3 files changed since this walkthrough · Update"
390
403
  export function staleWords(st) {
391
404
  if (!st) return ''
405
+ // 2026-11-03 a decision it cites has changed (superseded, rejected or edited) since it was written
406
+ if (Array.isArray(st.decisions) && st.decisions.length && !st.count) return (st.decisions.length === 1 ? 'Decision ' + st.decisions[0] + ' has' : 'Decisions ' + st.decisions.join(', ') + ' have') + ' changed since this walkthrough'
392
407
  if (st.count == null) return 'The change has moved on since this walkthrough'
393
408
  if (!st.count) return 'The change has moved on since this walkthrough'
394
409
  return st.count + (st.count === 1 ? ' file changed' : ' files changed') + ' since this walkthrough'
@@ -477,7 +492,7 @@ export function walkthroughHtml({ s, data, ui, reviewed = new Set(), esc, icon,
477
492
  const core = steps.filter((x) => x.tag === 'Core' && vis.has(x.id)), sup = steps.filter((x) => x.tag !== 'Core' && vis.has(x.id))
478
493
  const bd = data.stats.breakdown || {}
479
494
  const shared = sharedSnips(data), skip = skipFiles(data), stat = statusMap(data)
480
- const one = (st) => stepHtml(st, { esc, icon, done: reviewed.has(st.id), cur: ui.cur === st.id, open: ui.open && ui.open.has(st.id), sid, sf: ui.sf || {}, skip, stat, shared })
495
+ const one = (st) => stepHtml(st, { esc, icon, done: reviewed.has(st.id), cur: ui.cur === st.id, open: ui.open && ui.open.has(st.id), sid, sf: ui.sf || {}, skip, stat, shared, data })
481
496
  const repos = walkRepos(data)
482
497
  // 2026-10-29 several repos: one section per repo (its header with counts), its steps in reading order
483
498
  if (repos.length) {
@@ -565,7 +580,7 @@ function deckHtml(data, ui, { esc, icon, reviewed, sid }) {
565
580
  slides.forEach((s, i) => {
566
581
  if (s.kind === 'step') {
567
582
  const x = s.step
568
- h += sec(i, stepHtml(x, { esc, icon, done: reviewed.has(x.id), cur: false, open: true, sid, sf: ui.sf || {}, skip: skipFiles(data), stat: statusMap(data), shared, foldShared: false /* a slide shows one step: the earlier one isn't on screen */ }), 'Step ' + x.n + ' of ' + steps.length + ': ' + mdPlain(x.title), ' data-step="' + esc(x.id) + '"')
583
+ h += sec(i, stepHtml(x, { esc, icon, done: reviewed.has(x.id), cur: false, open: true, sid, sf: ui.sf || {}, skip: skipFiles(data), stat: statusMap(data), shared, foldShared: false /* a slide shows one step: the earlier one isn't on screen */, data }), 'Step ' + x.n + ' of ' + steps.length + ': ' + mdPlain(x.title), ' data-step="' + esc(x.id) + '"')
569
584
  } else if (s.kind === 'skip') {
570
585
  const groups = data.groups || []
571
586
  const n = groups.reduce((a, g) => a + g.files.length + (g.kind === 'repeated' ? 1 : 0), 0), l = groups.reduce((a, g) => a + g.lines, 0)
@@ -808,6 +823,7 @@ export function toMarkdown(data, { title = '' } = {}) {
808
823
  if (repos.length && st.repo && st.repo !== lastRepo) { lastRepo = st.repo; const r = repos.find((x) => x.label === st.repo); L.push(''); L.push('### ' + st.repo + (r ? ' (' + repoHeadWords(r, data.steps || []) + ')' : '')) }
809
824
  L.push(''); L.push((repos.length ? '#### ' : '### ') + st.n + '. ' + st.title + ' (' + st.tag + ')'); L.push('')
810
825
  if (st.why) L.push(st.why)
826
+ if (Array.isArray(st.decisions) && st.decisions.length) { const by = decisionsOf(data); L.push(''); L.push('Decisions: ' + st.decisions.map((id) => '`' + id + '`' + (by.get(id) ? ' ' + by.get(id).text : '')).join('; ')) } // 2026-11-03
811
827
  for (const c of st.callouts || []) { L.push(''); L.push('> **' + (CALLOUT[c.kind] || CALLOUT.look).word + '** (`' + pathLabel(c.file, data) + ':' + c.line + '`): ' + c.text) }
812
828
  for (const sn of st.snippets || []) {
813
829
  const fence = /```/.test(sn.patch) ? '~~~~' : '```'
@@ -820,6 +836,7 @@ export function toMarkdown(data, { title = '' } = {}) {
820
836
  for (const g of groups) L.push('- ' + g.title + (g.note ? ' (' + g.note + ')' : '') + ': ' + plural(g.files.length + (g.kind === 'repeated' ? 1 : 0), 'file') + ', ' + plural(g.lines, 'line'))
821
837
  }
822
838
  if (data.coverage) { L.push(''); L.push('_Every changed line is in a step or a group: ' + n0(data.coverage.covered) + ' of ' + n0(data.coverage.total) + '._') }
839
+ if (Array.isArray(data.decisions) && data.decisions.length) { L.push(''); L.push('### Decisions cited'); L.push(''); for (const d of data.decisions) L.push('- `' + d.id + '` ' + d.text + (d.why ? ' (' + d.why + ')' : '')) }
823
840
  return L.join('\n') + '\n'
824
841
  }
825
842
 
@@ -865,6 +882,7 @@ export function toHtmlDoc(data, { title = '', repo = '', when = '' } = {}) {
865
882
  const agHead = (s) => { if (!repos.length || !s.st || !s.st.repo || s.st.repo === agRepo) return ''; agRepo = s.st.repo; const r = repos.find((x) => x.label === s.st.repo); return '<li class="rh"><h3>' + escH(s.st.repo) + (r ? ' <span class="f">· ' + repoHeadWords(r, steps) + '</span>' : '') + '</h3></li>' }
866
883
  o += '<h2>Reading order</h2><ol class="ag">' + slides.map((s, i) => i === 0 ? '' : agHead(s) + '<li><label for="s' + i + '">' + (s.st ? s.st.n + '. ' : '') + walkInline(s.t) + (s.st ? ' <span class="tag ' + (s.st.tag === 'Core' ? 'core' : '') + '">' + escH(s.st.tag) + '</span>' : '') + '</label></li>').join('') + '</ol>'
867
884
  if (data.coverage) o += '<p class="f">Every changed line is in a step or a group: ' + n0(data.coverage.covered) + ' of ' + n0(data.coverage.total) + '.</p>'
885
+ if (Array.isArray(data.decisions) && data.decisions.length) o += '<h2>Decisions cited</h2><ul>' + data.decisions.map((d) => '<li><code>' + escH(d.id) + '</code> ' + escH(d.text) + (d.why ? ' <span class="f">(' + escH(d.why) + ')</span>' : '') + '</li>').join('') + '</ul>' // 2026-11-03
868
886
  b += '<section class="sl" data-i="0">' + o + nav(0) + '</section>'
869
887
  slides.forEach((s, i) => {
870
888
  if (!i) return
@@ -873,6 +891,7 @@ export function toHtmlDoc(data, { title = '', repo = '', when = '' } = {}) {
873
891
  const st = s.st
874
892
  if (repos.length && st.repo) { const r = repos.find((y) => y.label === st.repo); x += '<h3 class="rp">' + escH(st.repo) + (r ? ' <span class="f">· ' + repoHeadWords(r, steps) + '</span>' : '') + '</h3>' } // 2026-10-29 the step's repo
875
893
  x += '<h2>' + st.n + '. ' + walkInline(st.title) + '<span class="tag ' + (st.tag === 'Core' ? 'core' : '') + '">' + escH(st.tag) + '</span></h2>' + (st.why ? '<div class="why">' + walkBlock(st.why, { exported: true }) + '</div>' : '')
894
+ if (Array.isArray(st.decisions) && st.decisions.length) { const by = decisionsOf(data); x += '<p class="f decs">Decisions: ' + st.decisions.map((id) => '<code>' + escH(id) + '</code>' + (by.get(id) ? ' ' + escH(by.get(id).text) : '')).join('; ') + '</p>' } // 2026-11-03
876
895
  const drawn = new Set()
877
896
  for (const sn of st.snippets || []) {
878
897
  const cs = (st.callouts || []).filter((c) => c.hunk === sn.hunk)