staysfixed 0.2.1 → 0.2.2

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/watch/panel.js +78 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "staysfixed",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Prove that what already worked still works after an agent changed the code. Picture checks, guards for fixed bugs, a pre-release walkthrough, and known-good markers — as a CLI and as an MCP server.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -190,6 +190,10 @@ export function panelHtml(plan = {}) {
190
190
  '<div class="track" id="track"><div class="fill" id="fill"></div></div>',
191
191
  '<span class="counts mono" id="counts"></span>',
192
192
  '</div>',
193
+ // Said once, plainly. Without it the list of names and times reads as a
194
+ // speed report — which is exactly how it read to the first person who
195
+ // looked at it. The times are how long a check took; the check is this.
196
+ '<p class="what" id="what"></p>',
193
197
  '</header>',
194
198
 
195
199
  // --- the two columns. One on a narrow panel, two on a wide one. ---------
@@ -487,6 +491,19 @@ button { font: inherit; color: inherit; }
487
491
  from { opacity: 0; transform: translateY(7px); filter: blur(4px); }
488
492
  to { opacity: 1; transform: none; filter: blur(0); }
489
493
  }
494
+ .what {
495
+ margin: 8px 0 0; font-size: var(--t-small); color: var(--faint);
496
+ line-height: 1.45; overflow-wrap: anywhere;
497
+ }
498
+ .rverdict {
499
+ flex: 0 0 auto; font-size: var(--t-small); color: var(--faint);
500
+ white-space: nowrap; padding-left: 10px;
501
+ }
502
+ .item.held .rverdict { color: var(--faint); }
503
+ .item.moved .rverdict { color: var(--moved); }
504
+ .item.broke .rverdict { color: var(--broke); }
505
+ .item.waiting .rverdict { color: var(--waiting); }
506
+ .item.pending .rverdict, .item.skip .rverdict { color: var(--faintest, var(--faint)); }
490
507
  .note { margin-top: 5px; font-size: var(--t-body); color: var(--faint); overflow-wrap: anywhere; }
491
508
 
492
509
  /* One hairline, not a row of blocks. Each finished check adds its own slice of
@@ -750,6 +767,7 @@ button { font: inherit; color: inherit; }
750
767
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
751
768
  }
752
769
  .item.pending .rname, .item.skip .rname { color: var(--faint); }
770
+ .rtime { margin-left: 12px; }
753
771
  .rtime {
754
772
  flex: 0 0 auto;
755
773
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
@@ -1102,7 +1120,7 @@ const SCRIPT = `
1102
1120
  var ui = {
1103
1121
  clock: el('clock'), project: el('project'), app: el('app'), targetsep: el('targetsep'),
1104
1122
  state: el('state'), note: el('note'),
1105
- track: el('track'), fill: el('fill'), counts: el('counts'),
1123
+ track: el('track'), fill: el('fill'), counts: el('counts'), what: el('what'),
1106
1124
  stage: el('stage'), shot: el('shot'), layerA: el('layerA'), layerB: el('layerB'), blank: el('blank'),
1107
1125
  caption: el('caption'), shotname: el('shotname'), shotout: el('shotout'), tabs: el('tabs'),
1108
1126
  scroll: el('scroll'), follow: el('follow'), nothing: el('nothing'),
@@ -1203,6 +1221,54 @@ const SCRIPT = `
1203
1221
  }
1204
1222
  }
1205
1223
 
1224
+ /**
1225
+ * Two or three words for the row itself.
1226
+ *
1227
+ * The long sentence stays in the detail; this is what a person reads while
1228
+ * scanning the column. A check that held has to say so — silence next to a
1229
+ * duration is what made the panel look like it was timing page loads.
1230
+ */
1231
+ function shortOutcome(kind, ev) {
1232
+ var status = ev.status;
1233
+ if (kind === 'guard') {
1234
+ if (status === 'passed') return 'still holds';
1235
+ if (status === 'skipped') return 'left out';
1236
+ return 'broken again';
1237
+ }
1238
+ switch (status) {
1239
+ case 'passed': return 'matches';
1240
+ case 'changed':
1241
+ var n = ev.diffPixels || 0;
1242
+ return commas(n) + ' ' + plural(n, 'pixel', 'pixels') + ' moved';
1243
+ case 'new': return 'needs your eyes';
1244
+ case 'missing': return 'no approved picture';
1245
+ case 'failed': return 'no picture taken';
1246
+ case 'flaky': return 'would not settle';
1247
+ case 'skipped': return 'left out';
1248
+ default: return '';
1249
+ }
1250
+ }
1251
+
1252
+ /**
1253
+ * The one line that says what a run is, in the words a person would use.
1254
+ */
1255
+ function describeWork(screens, guards) {
1256
+ var bits = [];
1257
+ if (screens) {
1258
+ bits.push(
1259
+ screens + ' ' + plural(screens, 'screen', 'screens') +
1260
+ ' photographed and compared, pixel for pixel, with the picture you approved',
1261
+ );
1262
+ }
1263
+ if (guards) {
1264
+ bits.push(
1265
+ guards + ' ' + plural(guards, 'guard', 'guards') +
1266
+ ' — one per bug that was already fixed once',
1267
+ );
1268
+ }
1269
+ return bits.join(' · ');
1270
+ }
1271
+
1206
1272
  // -------------------------------------------------------------------------
1207
1273
  // Numbers count rather than jump.
1208
1274
  //
@@ -1312,10 +1378,13 @@ const SCRIPT = `
1312
1378
  var rname = document.createElement('span');
1313
1379
  rname.className = 'rname';
1314
1380
  rname.textContent = name;
1381
+ var rverdict = document.createElement('span');
1382
+ rverdict.className = 'rverdict';
1315
1383
  var rtime = document.createElement('span');
1316
1384
  rtime.className = 'rtime';
1317
1385
  row.appendChild(dot);
1318
1386
  row.appendChild(rname);
1387
+ row.appendChild(rverdict);
1319
1388
  row.appendChild(rtime);
1320
1389
  row.insertAdjacentHTML('beforeend', CHEVRON);
1321
1390
 
@@ -1327,7 +1396,7 @@ const SCRIPT = `
1327
1396
  root.appendChild(detail);
1328
1397
 
1329
1398
  var entry = {
1330
- kind: kind, name: name, root: root, row: row, time: rtime,
1399
+ kind: kind, name: name, root: root, row: row, time: rtime, verdict: rverdict,
1331
1400
  detail: detail, describe: describe || '', open: false, hasDetail: false
1332
1401
  };
1333
1402
 
@@ -1522,6 +1591,12 @@ const SCRIPT = `
1522
1591
  }
1523
1592
  ui.countScreens.textContent = countIn('picture');
1524
1593
  ui.countGuards.textContent = countIn('guard');
1594
+ var nScreens = 0, nGuards = 0;
1595
+ for (var w = 0; w < order.length; w++) {
1596
+ if (order[w].kind === 'guard') nGuards++; else nScreens++;
1597
+ }
1598
+ ui.what.textContent = describeWork(nScreens, nGuards);
1599
+ ui.what.hidden = order.length === 0;
1525
1600
  ui.nothing.hidden = order.length > 0;
1526
1601
  paintMeter();
1527
1602
  }
@@ -2344,6 +2419,7 @@ const SCRIPT = `
2344
2419
  preloadAll(entry.pics);
2345
2420
  }
2346
2421
  entry.outText = outcomeText(kind, ev);
2422
+ if (entry.verdict) entry.verdict.textContent = shortOutcome(kind, ev);
2347
2423
  entry.failedAt = (kind === 'guard' && ev.status === 'failed' && ev.failedAt) ? ev.failedAt : '';
2348
2424
  entry.story = (kind === 'guard' && ev.status === 'failed' && ev.because) ? ev.because : '';
2349
2425
  if (typeof ev.durationMs === 'number') tweenTo(entry.time, ev.durationMs, fmt);