@intentius/behold 0.29.10 → 0.29.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/behold",
3
- "version": "0.29.10",
3
+ "version": "0.29.12",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "description": "behold — a live control plane on chant. See your whole estate (every substrate in one graph), coloured by drift; act through delegated, gated Ops.",
@@ -123,7 +123,7 @@ export const ANNOTATIONS = [
123
123
  {
124
124
  id: "T12", product: "chant", feature: "Waiting for approval",
125
125
  pattern: String.raw`^Nothing in wave \d+ or after it ran\. (?:Read the plans, then|It waits on .+\.) approve: (chant approve \S+ \S+ --plan \S+) \(with --resume, the approval also starts this job again\)$`,
126
- note: "The job ended without applying this wave or any later one; the printed chant approve command records the approval and can restart the job.",
126
+ note: "The job ends at this wave; the printed chant approve command records the approval and can restart the job.",
127
127
  docs: { product: "chant", path: "/guide/op-waves/#resume-the-waiting-job" },
128
128
  source: "chant:packages/core/src/op/op-waves-run.ts:1587",
129
129
  sample: "Nothing in wave 2 or after it ran. Read the plans, then approve: chant approve tf-apply wave-2 --plan jcs1-sha256:95b79f4946fffd67b2d3da8971084852c4182623d8223b4a307f7067375fe37a (with --resume, the approval also starts this job again)",
@@ -163,7 +163,7 @@ export const ANNOTATIONS = [
163
163
  {
164
164
  id: "T17", product: "terragucci", feature: "Changed after approval",
165
165
  pattern: String.raw`^wave \d+ changed after it was approved, so nothing in it was applied\. approved: (\S+); planned now: (\S+)\. `,
166
- note: "The wave planned to a different digest than the one approved, so nothing applied; the new plan needs its own approval.",
166
+ note: "The wave planned to a different digest than the one approved; the new plan needs its own approval.",
167
167
  docs: { product: "terragucci", path: "/guides/fix-a-refused-wave/" },
168
168
  source: "terragucci:packages/terragucci/src/apply.ts:2983",
169
169
  sample: "wave 1 changed after it was approved, so nothing in it was applied. approved: jcs1-sha256:c7d57fcf0bf559242e2104f4d0ed562888f6bd99ed8c5af350db462771169877; planned now: jcs1-sha256:0be8353d2143e463577e05eca00b62cabd51791aaa383051930e384340812052. Read the new plan, then approve it: terragucci approve wave-1 --plan jcs1-sha256:0be8353d2143e463577e05eca00b62cabd51791aaa383051930e384340812052",
@@ -171,7 +171,7 @@ export const ANNOTATIONS = [
171
171
  {
172
172
  id: "T18", product: "terragucci", feature: "Changed after approval",
173
173
  pattern: String.raw`^wave \d+ of \d+: approved by (\S+), but these roots planned differently since: (.+)$`,
174
- note: "Names the roots whose plans differ from the approved ones, which is why the approval no longer covers the wave.",
174
+ note: "Names the roots whose plans differ from the approved ones, so the wave needs a new approval.",
175
175
  docs: { product: "terragucci", path: "/tutorial/changed-wave/" },
176
176
  source: "terragucci:packages/terragucci/src/apply.ts:2984",
177
177
  sample: "wave 1 of 3: approved by terragucci-admin, but these roots planned differently since: network",
@@ -187,7 +187,7 @@ export const ANNOTATIONS = [
187
187
  {
188
188
  id: "T20", product: "terragucci", feature: "Unreviewed outputs",
189
189
  pattern: String.raw`^wave \d+ of \d+: nobody reviewed the plan it made on those outputs, so it waits for an approval of it(?: although gate is \S+)?$`,
190
- note: "This wave planned on outputs nobody could see in review, so it waits for an approval even when its gate would let it through.",
190
+ note: "This wave planned on outputs that got their values during the apply, so a person approves it even when its gate would let it through.",
191
191
  docs: { product: "terragucci", path: "/concepts/waves-and-approvals/#linked-roots" },
192
192
  source: "terragucci:packages/terragucci/src/apply.ts:2562",
193
193
  sample: "wave 1 of 3: nobody reviewed the plan it made on those outputs, so it waits for an approval of it although gate is on-destructive",
@@ -203,7 +203,7 @@ export const ANNOTATIONS = [
203
203
  {
204
204
  id: "T22", product: "terragucci", feature: "Unreviewed outputs",
205
205
  pattern: String.raw`^wave \d+ of \d+: (\S+) reads? no output of (\S+) that (.+) left unknown$`,
206
- note: "Every output this root reads was already known when it was planned, so its plan holds no values nobody reviewed.",
206
+ note: "Every output this root reads was known when it was planned, so its plan holds only reviewed values.",
207
207
  docs: { product: "terragucci", path: "/concepts/waves-and-approvals/#linked-roots" },
208
208
  source: "terragucci:packages/terragucci/src/unknown-reads.ts:142",
209
209
  sample: "wave 2 of 3: app reads no output of network that wave 1's plan of network in this push (2026/10/e8863ee426177316d84499a5b8401c4b6adfb546/tf-apply-wave-1) left unknown",
@@ -235,7 +235,7 @@ export const ANNOTATIONS = [
235
235
  {
236
236
  id: "T26", product: "terragucci", feature: "Unreviewed outputs",
237
237
  pattern: String.raw`^wave \d+ of \d+: (\S+) reads (.+), unknown at plan time and computed in this apply$`,
238
- note: "Inside a composed run this root reads a value that only gets computed during the apply, so nobody saw it in the plan.",
238
+ note: "Inside a composed run this root reads a value computed during the apply.",
239
239
  docs: { product: "terragucci", path: "/concepts/waves-and-approvals/#approval" },
240
240
  source: "terragucci:packages/terragucci/src/apply.ts:2022",
241
241
  sample: "wave 1 of 3: app reads subnet_id of network, unknown at plan time and computed in this apply",
@@ -275,7 +275,7 @@ export const ANNOTATIONS = [
275
275
  {
276
276
  id: "T31", product: "chant", feature: "Apply failed",
277
277
  pattern: String.raw`^wave \d+ \(([^)]+)\): apply failed, so none of (.+) counts as applied$`,
278
- note: "The wave's apply failed. None of its roots count as applied and later waves do not run.",
278
+ note: "The wave's apply failed. Its roots and the later waves wait for a re-run.",
279
279
  docs: { product: "terragucci", path: "/reference/responses/#apply-failed" },
280
280
  source: "chant:packages/core/src/op/op-waves-run.ts:2092",
281
281
  sample: "wave 2 (wave-2): apply failed, so none of app counts as applied",
@@ -379,7 +379,7 @@ export const ANNOTATIONS = [
379
379
  {
380
380
  id: "T44", product: "terragucci", feature: "Eligible approvers",
381
381
  pattern: String.raw`^wave \d+ of \d+: (\S+) (.+), so \1's (approval|review) doesn't count$`,
382
- note: "This person had a hand in the change. Their approval or review does not count toward the gate.",
382
+ note: "This person worked on the change, so the gate counts approvals from someone else.",
383
383
  docs: { product: "terragucci", path: "/guides/approve-a-wave/#eligible-approvers" },
384
384
  source: "terragucci:packages/terragucci/src/involved.ts:175",
385
385
  sample: "wave 2 of 3: demo-author opened pull request 1086, so demo-author's review doesn't count", // made from the source, no run printed it yet
@@ -387,7 +387,7 @@ export const ANNOTATIONS = [
387
387
  {
388
388
  id: "T45", product: "terragucci", feature: "Eligible approvers",
389
389
  pattern: String.raw`^wave \d+ of \d+: an approval counts only from someone who had no hand in the change, so not from (.+)$`,
390
- note: "An approval from the people named here does not count because they had a hand in the change.",
390
+ note: "The people named here worked on the change, so the gate counts approvals from someone else.",
391
391
  docs: { product: "terragucci", path: "/guides/approve-a-wave/#eligible-approvers" },
392
392
  source: "terragucci:packages/terragucci/src/apply.ts:3001",
393
393
  sample: "wave 2 of 3: an approval counts only from someone who had no hand in the change, so not from demo-author", // made from the source, no run printed it yet
@@ -123,7 +123,7 @@ export function approvalModel({ demo, runs = [], history = null, lifecycle = nul
123
123
  commit: requested && requested.source ? requested.source.commit || null : null,
124
124
  line: reqLine,
125
125
  });
126
- } else Object.assign(reqRow, { state: "dim", text: `Wave ${wave} has not asked for an approval yet.` });
126
+ } else Object.assign(reqRow, { state: "dim", text: `Wave ${wave} asks for an approval when it reaches its gate.` });
127
127
 
128
128
  // Approved.
129
129
  const apRow = { id: "approved", label: "Approved" };
@@ -159,7 +159,7 @@ export function approvalModel({ demo, runs = [], history = null, lifecycle = nul
159
159
  line: start2 ? { run: resume.id, ...start2 } : found,
160
160
  });
161
161
  } else if (resume) {
162
- Object.assign(rsRow, { state: "going", text: `/terragucci apply started the waves again; wave ${wave} has not reached its gate yet.`, at: resume.started_at || resume.created_at, runId: resume.id, line: start2 ? { run: resume.id, ...start2 } : null });
162
+ Object.assign(rsRow, { state: "going", text: `/terragucci apply started the waves again; wave ${wave} reaches its gate next.`, at: resume.started_at || resume.created_at, runId: resume.id, line: start2 ? { run: resume.id, ...start2 } : null });
163
163
  } else {
164
164
  Object.assign(rsRow, { state: "dim", text: driver && driver.comment ? `Then a comment of ${driver.comment} on ${driver.on} starts the waves again.` : "A comment of /terragucci apply starts the waves again." });
165
165
  }
@@ -31,9 +31,9 @@ export const ACTION_HEAD = {
31
31
 
32
32
  export const WORDS = {
33
33
  same: "The plan leaves this resource as it is.",
34
- untouched: "This plan does not touch its root, so nothing changes here.",
34
+ untouched: "This plan leaves its root as it is.",
35
35
  noPlan: "No plan yet for this resource.",
36
- unknown: "known after apply, so no review shows this value",
36
+ unknown: "known after apply",
37
37
  long: "too long to show here",
38
38
  noneYet: "none yet",
39
39
  gone: "gone",
@@ -49,11 +49,11 @@ describe("what the popover says about a resource (behold#571)", () => {
49
49
  expect(p.applied).toBeNull();
50
50
  });
51
51
 
52
- it("says an attribute known only after apply in the unknown tone, and that no review shows it", () => {
52
+ it("says an attribute known only after apply in the unknown tone", () => {
53
53
  const p = resourcePopover(answer(), "repo/app/terraform_data.listener_port");
54
54
  expect(p.unknown).toBe(true);
55
- expect(p.lines).toEqual([{ text: "input: 8070 -> known after apply, so no review shows this value", tone: "unknown" }]);
56
- expect(attributeLine({ path: "input", unknown: true })).toEqual({ text: "input: known after apply, so no review shows this value", tone: "unknown" });
55
+ expect(p.lines).toEqual([{ text: "input: 8070 -> known after apply", tone: "unknown" }]);
56
+ expect(attributeLine({ path: "input", unknown: true })).toEqual({ text: "input: known after apply", tone: "unknown" });
57
57
  });
58
58
 
59
59
  it("says no change for a card the plan leaves alone, and that a root the plan does not hold is untouched", () => {
package/web/kiosk.js CHANGED
@@ -137,7 +137,7 @@ export function countdownWords(secs) {
137
137
 
138
138
  /** The driver's next action, as the kiosk says it. */
139
139
  export function nextWords(demo, now = new Date()) {
140
- if (!demo.next) return { action: "nothing until the driver resets", when: "the driver stalled; it resets and starts the next cycle", secs: null };
140
+ if (!demo.next) return { action: "reset and start the next cycle", when: "the driver stalled; it resets and starts the next cycle", secs: null };
141
141
  const secs = secondsUntil(demo.next.at, now);
142
142
  return {
143
143
  action: demo.next.action,
@@ -198,9 +198,9 @@ export function runPanel(demo, answer, progress) {
198
198
  if (run) return { stage: "apply", run };
199
199
  if (pr) return { stage: "pull-request", pr };
200
200
  if (stage === "pull-request") {
201
- return { stage: "pull-request", note: demo.pull === null ? "The pull request isn't open yet." : `The plan of pull request #${demo.pull} isn't in the reports yet: the pipeline is planning it.` };
201
+ return { stage: "pull-request", note: demo.pull === null ? "The pull request opens next." : `The pipeline is planning pull request #${demo.pull}.` };
202
202
  }
203
- return { stage: "apply", note: demo.commit ? `The run view of ${demo.commit.slice(0, 8)} isn't in the reports yet: the pipeline writes it as the waves start.` : "No run yet." };
203
+ return { stage: "apply", note: demo.commit ? `The pipeline writes the run view of ${demo.commit.slice(0, 8)} as the waves start.` : "The first run starts next." };
204
204
  }
205
205
 
206
206
  /** The pull request's waves and reads, when the panel shows its plan. */
@@ -457,7 +457,7 @@ export function factLines(demo, answer) {
457
457
  if (totals) out.push(`pull request #${pr.run.pull_request} changes ${rootsCount(pr.totals_roots) || "across roots"}: ${totals}`);
458
458
  const b = blastRows(answer);
459
459
  if (b && b.changed.length) out.push(`blast radius: ${b.changed.join(", ")}${b.downstream.length ? `; downstream, nearest first: ${b.downstream.join(", ")}` : ""}`);
460
- if (b && b.unplanned.length) out.push(`not touched: ${b.unplanned.join(", ")}`);
460
+ if (b && b.unplanned.length) out.push(`unchanged: ${b.unplanned.join(", ")}`);
461
461
  const cost = costLine(pr.cost);
462
462
  if (cost) out.push(cost);
463
463
  return out;
@@ -644,7 +644,7 @@ export function approvalPanel(model, s = {}) {
644
644
  }
645
645
 
646
646
  /** behold#571: the header's line when this behold serves another repo than the page's. */
647
- export const NO_GRAPH_WORDS = "This page shows the runs and their logs; the graph of this repo isn't served here.";
647
+ export const NO_GRAPH_WORDS = "This page shows the runs and their logs.";
648
648
 
649
649
  /** behold#571: a tool's name, and its logo where its owner allows one, linking to its official site. */
650
650
  function brandEl(vendor) {
@@ -691,7 +691,7 @@ export const APPROVAL_POLL_MS = 4_000;
691
691
  export const LEDGER_POLL_MS = 10_000;
692
692
 
693
693
  /** behold#571: the approval panel's words for the approval the driver gives next, over its command. */
694
- export const DRIVER_SIGNS = "Next the driver plays a reviewer with no hand in the change and signs this plan with that reviewer's key:";
694
+ export const DRIVER_SIGNS = "Next the driver plays a reviewer outside the change and signs this plan with that reviewer's key:";
695
695
 
696
696
  /** behold#571: "What is this?" says what behold is; the demo's own intro stays on the card between scenarios. */
697
697
  export const WHAT_TEXT = "This is behold, a live view of infrastructure. It draws the setup as a graph and shows its pipeline runs, their logs and the approvals they wait for, as they happen. Here it watches a demo pipeline that runs one change through several Terraform states in a loop.";
package/web/kiosk.test.js CHANGED
@@ -49,7 +49,7 @@ describe("frameSrc (#569)", () => {
49
49
 
50
50
  describe("factLines (#569)", () => {
51
51
  it("says the pull request's totals and blast radius", () => {
52
- expect(factLines(demo("planned"), answer)).toEqual(["pull request #7 changes across 3 roots: 3 update", "blast radius: app, edge, network", "not touched: other"]);
52
+ expect(factLines(demo("planned"), answer)).toEqual(["pull request #7 changes across 3 roots: 3 update", "blast radius: app, edge, network", "unchanged: other"]);
53
53
  });
54
54
  });
55
55
 
@@ -81,7 +81,7 @@ describe("before the merge, no other cycle's plan (#569)", () => {
81
81
  expect(matchingPull(ours, now(step))).toBe(ours.pull_request);
82
82
  expect(focusRoots(now(step), older, null)).toBe(null);
83
83
  expect(factLines(now(step), older)).toEqual([]);
84
- expect(runPanel(now(step), older, null).note).toBe("The plan of pull request #989 isn't in the reports yet: the pipeline is planning it.");
84
+ expect(runPanel(now(step), older, null).note).toBe("The pipeline is planning pull request #989.");
85
85
  }
86
86
  });
87
87
  it("from the merge on, takes the plan by its number: demo.json's commit is the merge's", () => {
@@ -23,7 +23,7 @@
23
23
  import { ago, clock, hrefOf } from "./terragucci.js";
24
24
 
25
25
  export const STATE_WORDS = {
26
- "not-started": "not started",
26
+ "not-started": "queued",
27
27
  planned: "planned",
28
28
  waiting: "waiting for an approval",
29
29
  applying: "applying",
@@ -119,13 +119,13 @@ export function readCardIds(answer, e) {
119
119
  return [`${r.member}/${r.name}/data.terraform_remote_state.${data}`, `${r.member}/${r.name}/data.terraform_estate_outputs.${data}`];
120
120
  }
121
121
 
122
- /** "5 create · 1 update", no-ops and zeros left out; "no changes" when nothing moves. */
122
+ /** "5 create · 1 update", no-ops and zeros left out; "unchanged" when nothing moves. */
123
123
  export function totalsLine(totals) {
124
124
  if (!totals) return null;
125
125
  const order = ["create", "update", "replace", "delete", "forget", "import", "read"];
126
126
  const keys = [...order.filter((k) => k in totals), ...Object.keys(totals).filter((k) => !order.includes(k) && k !== "no-op").sort()];
127
127
  const parts = keys.filter((k) => totals[k] > 0).map((k) => `${totals[k]} ${k}`);
128
- return parts.length ? parts.join(" · ") : "no changes";
128
+ return parts.length ? parts.join(" · ") : "unchanged";
129
129
  }
130
130
 
131
131
  /** #568 step 2: "across 3 roots", from `totals_roots` (report minor 35); null on an older report, which has no count. */
@@ -216,7 +216,7 @@ export function composeLine(pr) {
216
216
  return null;
217
217
  }
218
218
 
219
- const APPLY_WORDS = { applied: "applied", partial: "applied in part", failed: "failed", "not-started": "not started", "no-changes": "no changes" };
219
+ const APPLY_WORDS = { applied: "applied", partial: "applied in part", failed: "failed", "not-started": "queued", "no-changes": "unchanged" };
220
220
  const STATE_AS = { written: "state written", unchanged: "state as it was, nothing to write", "not-written": "state not written" };
221
221
 
222
222
  /** One member of a composed run as planned: "app as module.app: 1 create". */
@@ -494,7 +494,7 @@ export function rootStates(answer, progress) {
494
494
  const blast = pr.blast && Array.isArray(pr.blast.roots) ? pr.blast.roots : null;
495
495
  const readsUnknown = (pr.reads || []).some((r) => r.root === root && r.unknown && r.unknown.length) || !!(c && c.held_by_unknown && c.held_by_unknown.includes(root));
496
496
  if (blast && !blast.includes(root) && !readsUnknown) {
497
- roots[root] = { wave: w.number, state: "unchanged", tone: STATE_TONE.unchanged, words: `wave ${w.number} · no changes`, facts: [] };
497
+ roots[root] = { wave: w.number, state: "unchanged", tone: STATE_TONE.unchanged, words: `wave ${w.number} · unchanged`, facts: [] };
498
498
  continue;
499
499
  }
500
500
  const facts = [];
@@ -515,7 +515,7 @@ export function rootStates(answer, progress) {
515
515
  const inRun = [...composedOf].filter(([, n]) => n === w.number).map(([r]) => r);
516
516
  for (const root of [...new Set([...w.roots, ...(c ? c.roots : []), ...inRun])]) {
517
517
  if (!rootChanges(w, root)) {
518
- roots[root] = { wave: w.number, state: "unchanged", tone: STATE_TONE.unchanged, words: `wave ${w.number} · no changes`, facts: [] };
518
+ roots[root] = { wave: w.number, state: "unchanged", tone: STATE_TONE.unchanged, words: `wave ${w.number} · unchanged`, facts: [] };
519
519
  continue;
520
520
  }
521
521
  const m = c && c.members ? c.members.find((x) => x.root === root) : null;
@@ -529,7 +529,7 @@ export function rootStates(answer, progress) {
529
529
  if (m && m.apply) facts.push(memberApplyLine(m).replace(`${root}: `, "")); // this root's own part, not the wave's
530
530
  else if (c && !composedHere) {
531
531
  // Beside a composed run in its wave: the wave's figures are the run's, not this root's.
532
- } else if (totals && state !== "not-started") facts.push(totals === "no changes" ? `${together ? "the wave: " : ""}no changes` : `${together ? "the wave " : ""}${state === "applied" ? "applied" : state === "failed" || state === "refused" ? "planned" : "plans"} ${totals}`);
532
+ } else if (totals && state !== "not-started") facts.push(totals === "unchanged" ? `${together ? "the wave: " : ""}unchanged` : `${together ? "the wave " : ""}${state === "applied" ? "applied" : state === "failed" || state === "refused" ? "planned" : "plans"} ${totals}`);
533
533
  if (state === "failed" || state === "refused") {
534
534
  const before = progress.states.filter((x) => x.number < w.number && x.state === "applied").map((x) => x.number);
535
535
  if (before.length) facts.push(`wave ${list(before)} stays applied`);
@@ -60,7 +60,7 @@ describe("rootStates, a root that plans no change (#569 review)", () => {
60
60
  const p = run([{ number: 1, state: "waiting", roots: ["network", "other"], reads: [], digest: "jcs1-sha256:365a4f0d54d7aa", totals: { create: 5 }, changed: ["network"] }], []);
61
61
  const { roots: r } = rootStates(prAnswer, p);
62
62
  expect(r.network.state).toBe("waiting");
63
- expect(r.other).toMatchObject({ state: "unchanged", tone: "idle", words: "wave 1 · no changes", facts: [] });
63
+ expect(r.other).toMatchObject({ state: "unchanged", tone: "idle", words: "wave 1 · unchanged", facts: [] });
64
64
  });
65
65
  it("takes a composed member's own no-changes over the wave's state", () => {
66
66
  const p = run([{ number: 1, state: "failed", roots: ["network"], reads: [], composed: { roots: ["network", "edge"], members: [{ root: "network", module: "m", apply: "failed" }, { root: "edge", module: "e", apply: "no-changes" }] } }], []);
@@ -30,13 +30,13 @@ export const JOB_TONE = { queued: "idle", running: "busy", waiting: "wait", succ
30
30
  /** A card's state in one or two words, when the full words don't fit the card. */
31
31
  export const SHORT_WORDS = {
32
32
  planned: "planned",
33
- "not-started": "not started",
33
+ "not-started": "queued",
34
34
  waiting: "waiting",
35
35
  applying: "applying",
36
36
  applied: "applied",
37
37
  refused: "refused",
38
38
  failed: "failed",
39
- unchanged: "no changes",
39
+ unchanged: "unchanged",
40
40
  outside: "not in this run",
41
41
  };
42
42
 
@@ -98,7 +98,7 @@ describe("the component marks (#571)", () => {
98
98
  network: ["applied", "ok", "applied"],
99
99
  other: ["outside", "idle", "not in this run"],
100
100
  app: ["waiting", "wait", "waiting for an approval"],
101
- edge: ["not-started", "idle", "not started"],
101
+ edge: ["not-started", "idle", "queued"],
102
102
  });
103
103
  expect(m.cards.app.short).toBe("waiting");
104
104
  expect(m.waves[2]).toMatchObject({ job: "apply-wave-2", state: "waiting", tone: "wait", title: "apply-wave-2 · held at the gate" });
@@ -211,7 +211,7 @@ export function renderEstateSvg(answer, now = new Date(), doc = document) {
211
211
  const run = runLine(p, now);
212
212
  g.appendChild(titled(doc, svgEl(doc, "text", { x: b.x + PAD, y: b.y + 55, class: `tg-project-run tg-${run.tone}` }, run.text), run.title));
213
213
  for (const w of b.waves) {
214
- g.appendChild(svgEl(doc, "text", { x: w.x, y: w.y + 14, class: `tg-wave-label tg-wave-${w.state}`, "data-wave": w.number }, `wave ${w.number} · ${w.state === "not-started" ? "not started" : w.state}`));
214
+ g.appendChild(svgEl(doc, "text", { x: w.x, y: w.y + 14, class: `tg-wave-label tg-wave-${w.state}`, "data-wave": w.number }, `wave ${w.number} · ${w.state === "not-started" ? "queued" : w.state}`));
215
215
  for (const r of w.roots) {
216
216
  const node = svgEl(doc, "g", { class: "tg-root-node", "data-root": r.root });
217
217
  node.appendChild(svgEl(doc, "rect", { x: r.x, y: r.y, width: r.w, height: r.h, rx: 4 }));
package/web/terragucci.js CHANGED
@@ -86,7 +86,7 @@ export function stageCell(verdict, stage, now = new Date()) {
86
86
  if (verdict.approval === "waiting") return { text: `wave ${verdict.run.wave} waiting, ${age}`, tone: "wave", title };
87
87
  return { text: `wave ${verdict.run.wave} ${verdict.applied ? "applied" : verdict.approval || "ran"}, ${age}`, tone: "ok", title };
88
88
  }
89
- if (verdict.changes === 0) return { text: `${stage === "tf-drift" ? "none" : "no changes"}, ${age}`, tone: "ok", title };
89
+ if (verdict.changes === 0) return { text: `${stage === "tf-drift" ? "none" : "unchanged"}, ${age}`, tone: "ok", title };
90
90
  const noun = stage === "tf-drift" ? "drifted" : `change${verdict.changes === 1 ? "" : "s"}`;
91
91
  const pr = verdict.run.pull_request ? ` (PR #${verdict.run.pull_request})` : "";
92
92
  return { text: `${verdict.changes} ${noun}${pr}, ${age}`, tone: stage === "tf-drift" ? "drift" : "plan", title };