toolroll 0.9.86 → 0.9.87

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 (171) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/dist/agents-board.d.ts +12 -0
  3. package/dist/agents-board.js +26 -0
  4. package/dist/approval-rules-ui.d.ts +4 -1
  5. package/dist/approval-rules-ui.js +31 -15
  6. package/dist/assignment-ui.js +1 -1
  7. package/dist/backup-ui.d.ts +5 -4
  8. package/dist/backup-ui.js +65 -32
  9. package/dist/browser/terminal.css +1 -1
  10. package/dist/browser/terminal.js +57 -54
  11. package/dist/browser/workspace.css +2 -2
  12. package/dist/browser/workspace.js +35 -70
  13. package/dist/browser-crew.js +1 -1
  14. package/dist/browser-workspace.d.ts +329 -183
  15. package/dist/browser-workspace.js +10 -9
  16. package/dist/chat-controls.d.ts +1 -1
  17. package/dist/chat-controls.js +1 -1
  18. package/dist/chat-settings-page.d.ts +29 -9
  19. package/dist/chat-settings-page.js +59 -28
  20. package/dist/check-levels-ui.d.ts +2 -1
  21. package/dist/check-levels-ui.js +67 -58
  22. package/dist/contracts/cli.d.ts +43 -0
  23. package/dist/contracts/cli.js +4 -0
  24. package/dist/contracts/console-api.d.ts +1 -1
  25. package/dist/contracts/console-api.js +1 -1
  26. package/dist/credentials-ui.d.ts +5 -3
  27. package/dist/credentials-ui.js +58 -31
  28. package/dist/design/kit.css +685 -0
  29. package/dist/design/kit.d.ts +208 -0
  30. package/dist/design/kit.js +177 -0
  31. package/dist/design/tokens.css +19 -1
  32. package/dist/discord-settings.js +8 -4
  33. package/dist/export-ui.d.ts +6 -4
  34. package/dist/export-ui.js +18 -8
  35. package/dist/flow-changed.d.ts +14 -0
  36. package/dist/flow-changed.js +53 -0
  37. package/dist/flow-gallery-ui.d.ts +41 -4
  38. package/dist/flow-gallery-ui.js +95 -63
  39. package/dist/flow-starters-ui.d.ts +3 -9
  40. package/dist/flow-starters-ui.js +12 -23
  41. package/dist/flow-unattended-words.d.ts +20 -0
  42. package/dist/flow-unattended-words.js +24 -0
  43. package/dist/flow-unattended.d.ts +9 -5
  44. package/dist/flow-unattended.js +45 -23
  45. package/dist/flows-cli.js +7 -3
  46. package/dist/flows-ui.d.ts +12 -6
  47. package/dist/flows-ui.js +141 -30
  48. package/dist/integrations-ui.d.ts +6 -4
  49. package/dist/integrations-ui.js +34 -40
  50. package/dist/integrations.d.ts +2 -2
  51. package/dist/integrations.js +4 -4
  52. package/dist/kits-ui.d.ts +8 -10
  53. package/dist/kits-ui.js +29 -33
  54. package/dist/kits.js +1 -1
  55. package/dist/knowledge-ui.js +76 -19
  56. package/dist/ledger-view.d.ts +2 -0
  57. package/dist/ledger-view.js +41 -38
  58. package/dist/models-ui.d.ts +8 -3
  59. package/dist/models-ui.js +82 -59
  60. package/dist/monitoring-ui.d.ts +4 -2
  61. package/dist/monitoring-ui.js +44 -13
  62. package/dist/needs-you.d.ts +1 -1
  63. package/dist/needs-you.js +8 -0
  64. package/dist/operate-remote-arguments.js +1 -0
  65. package/dist/operate-remote.js +1 -1
  66. package/dist/operate.d.ts +1 -1
  67. package/dist/operate.js +38 -4
  68. package/dist/passwordless.d.ts +1 -1
  69. package/dist/passwordless.js +1 -1
  70. package/dist/people-audit-ui.d.ts +2 -1
  71. package/dist/people-audit-ui.js +25 -23
  72. package/dist/policy-ui.d.ts +3 -2
  73. package/dist/policy-ui.js +27 -21
  74. package/dist/policy.d.ts +3 -0
  75. package/dist/policy.js +14 -1
  76. package/dist/project-delete-ui.d.ts +4 -2
  77. package/dist/project-delete-ui.js +135 -126
  78. package/dist/project-knowledge.d.ts +10 -10
  79. package/dist/publish.d.ts +11 -0
  80. package/dist/publish.js +16 -3
  81. package/dist/pull-request-ui.d.ts +2 -1
  82. package/dist/pull-request-ui.js +39 -28
  83. package/dist/recipe-ui.d.ts +4 -2
  84. package/dist/recipe-ui.js +14 -4
  85. package/dist/release-adapters.d.ts +3 -0
  86. package/dist/release-adapters.js +22 -2
  87. package/dist/release-cli.js +81 -9
  88. package/dist/release.d.ts +21 -0
  89. package/dist/release.js +40 -4
  90. package/dist/releases.js +1 -1
  91. package/dist/request-budget-ui.d.ts +4 -2
  92. package/dist/request-budget-ui.js +30 -19
  93. package/dist/retention-ui.d.ts +4 -3
  94. package/dist/retention-ui.js +15 -12
  95. package/dist/scope.d.ts +3 -0
  96. package/dist/scope.js +6 -0
  97. package/dist/serve.d.ts +0 -1
  98. package/dist/serve.js +51 -7
  99. package/dist/server/agents-computers.d.ts +9 -0
  100. package/dist/server/agents-computers.js +67 -0
  101. package/dist/server/chat.js +16 -16
  102. package/dist/server/chrome.d.ts +1 -1
  103. package/dist/server/chrome.js +19 -9
  104. package/dist/server/console.css +2 -2
  105. package/dist/server/flows.js +64 -36
  106. package/dist/server/home.d.ts +35 -0
  107. package/dist/server/home.js +104 -0
  108. package/dist/server/http.d.ts +3 -0
  109. package/dist/server/http.js +5 -0
  110. package/dist/server/kit-standalone.d.ts +22 -0
  111. package/dist/server/kit-standalone.js +69 -0
  112. package/dist/server/kit.d.ts +405 -0
  113. package/dist/server/kit.js +361 -0
  114. package/dist/server/pages.d.ts +2 -1
  115. package/dist/server/pages.js +213 -174
  116. package/dist/server/people-tokens.js +26 -85
  117. package/dist/server/render-history.d.ts +51 -0
  118. package/dist/server/render-history.js +228 -0
  119. package/dist/server/render-new-task.d.ts +29 -0
  120. package/dist/server/render-new-task.js +96 -0
  121. package/dist/server/render-next.d.ts +63 -0
  122. package/dist/server/render-next.js +109 -0
  123. package/dist/server/render-pages.d.ts +22 -190
  124. package/dist/server/render-pages.js +123 -575
  125. package/dist/server/render-people.d.ts +24 -3
  126. package/dist/server/render-people.js +93 -72
  127. package/dist/server/render-projects.d.ts +103 -0
  128. package/dist/server/render-projects.js +247 -0
  129. package/dist/server/render-results.d.ts +5 -24
  130. package/dist/server/render-results.js +48 -185
  131. package/dist/server/render-settings.d.ts +5 -74
  132. package/dist/server/render-settings.js +102 -493
  133. package/dist/server/render-tasks.d.ts +35 -5
  134. package/dist/server/render-tasks.js +114 -85
  135. package/dist/server/route-table.js +20 -5
  136. package/dist/server/settings.js +276 -255
  137. package/dist/server/task-views.d.ts +8 -0
  138. package/dist/server/task-views.js +85 -2
  139. package/dist/server/tasks.d.ts +8 -0
  140. package/dist/server/tasks.js +95 -40
  141. package/dist/server/terminals.js +1 -1
  142. package/dist/server/welcome.d.ts +22 -0
  143. package/dist/server/welcome.js +69 -0
  144. package/dist/settings-groups-ui.d.ts +279 -0
  145. package/dist/settings-groups-ui.js +615 -0
  146. package/dist/settings-kit.d.ts +42 -6
  147. package/dist/settings-kit.js +69 -7
  148. package/dist/setup-guide.d.ts +2 -2
  149. package/dist/skills-ui.js +59 -7
  150. package/dist/slack-settings.js +12 -5
  151. package/dist/spend-ui.d.ts +45 -26
  152. package/dist/spend-ui.js +124 -56
  153. package/dist/sso-ui.d.ts +5 -2
  154. package/dist/sso-ui.js +63 -31
  155. package/dist/storage-ui.d.ts +3 -2
  156. package/dist/storage-ui.js +56 -50
  157. package/dist/store.d.ts +112 -3
  158. package/dist/store.js +292 -20
  159. package/dist/surface.js +5 -1
  160. package/dist/teams-settings.d.ts +2 -1
  161. package/dist/teams-settings.js +24 -41
  162. package/dist/telegram-settings.d.ts +0 -4
  163. package/dist/telegram-settings.js +52 -25
  164. package/dist/toolroll-update-ui.d.ts +8 -4
  165. package/dist/toolroll-update-ui.js +55 -45
  166. package/dist/tools-ui.d.ts +5 -3
  167. package/dist/tools-ui.js +107 -42
  168. package/dist/work-index.js +11 -7
  169. package/dist/workspace-ui.d.ts +1 -1
  170. package/dist/workspace-ui.js +7 -4
  171. package/package.json +1 -1
@@ -1,42 +1,28 @@
1
1
  /**
2
- * Settings → Storage: how much space task checkouts use, what a clean-up would free now, a Clean up button whose
3
- * preview (what goes, what stays and why, with paths) sits behind the password, what the last storage sweep removed
4
- * and when (its paths in a disclosure) and why the next one waits, what landed work's teardown did and still waits on (in a disclosure, with the
5
- * one-time command for older work), and when a finished task's clean checkout goes by itself. An instance operator's page.
2
+ * Settings → Storage: how much space task checkouts use, what a clean-up would free now, the clean-up's preview (what
3
+ * goes, what stays and why, with paths) and its Remove behind the password, what the last storage sweep removed and
4
+ * when (its paths in a disclosure) and why the next one waits, what landed work's teardown did and still waits on (in a
5
+ * disclosure, with the one-time command for older work), and when a finished task's clean checkout goes by itself. An
6
+ * instance operator's page, built from the revamp kit; the Settings frame adds the title.
6
7
  */
7
8
  import { homedir } from "node:os";
8
9
  import { basename } from "node:path";
9
- import { html, postForm, reauth } from "./html.js";
10
+ import { html, joinHtml } from "./html.js";
10
11
  import { previewDigest, whyWords } from "./checkout-cleanup.js";
11
12
  import { CLEANUP_CHOICES, bytesWords } from "./storage.js";
12
13
  import { sweepDetails, sweepWords } from "./storage-sweep.js";
13
14
  import { sweepScheduleWords } from "./storage-sweep-schedule.js";
15
+ import { codeLine, disclose, formRow, icon, infoLine, note, notice, select, settingsRecord, settingsSection } from "./server/kit.js";
16
+ import { kitSectionForm } from "./settings-kit.js";
14
17
  const plural = (count, one, many = `${one}s`) => `${count} ${count === 1 ? one : many}`;
15
- export const STORAGE_CSS = `.storage{max-width:720px;min-width:0;overflow-wrap:anywhere}.storage h2{font-size:1.0625rem;margin:24px 0 4px}` +
16
- `.storage .storage-state{display:grid;gap:12px}.storage .storage-state h2{margin:0}.storage .storage-state p{margin:0}` +
17
- `.storage details.clean-up,.storage details.discard{border:0;padding:0;margin:0;background:transparent}` +
18
- `.storage details.clean-up>summary{list-style:none;display:inline-flex;align-items:center;min-height:2.125rem;padding:.35rem .75rem;border-radius:calc(var(--radius) - 3px);` +
19
- `background:var(--primary);color:var(--primary-foreground);font-weight:600;font-size:.8125rem;cursor:pointer;white-space:nowrap}` +
20
- `.storage details.clean-up>summary::-webkit-details-marker{display:none}.storage details.clean-up[open]>summary{background:var(--card);color:var(--foreground);border:1px solid var(--input)}` +
21
- `.storage .preview{display:grid;gap:12px;margin-top:12px;padding-top:12px;border-top:1px solid var(--border)}.storage .preview h3{font-size:.875rem;margin:0}` +
22
- `.storage ul.checkouts{list-style:none;padding:0;margin:0}` +
23
- `.storage ul.checkouts li{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:2px 12px;padding:8px 0;border-top:1px solid var(--border);font-size:.8125rem}` +
24
- `.storage ul.checkouts .name{font-weight:500;min-width:0}.storage ul.checkouts .size{font-variant-numeric:tabular-nums;white-space:nowrap}` +
25
- `.storage ul.checkouts .path{grid-column:1/-1;font-family:var(--font-mono, ui-monospace, monospace);font-size:.6875rem;color:var(--muted-foreground);word-break:break-all}` +
26
- `.storage ul.checkouts .why{grid-column:1/-1}` +
27
- `.storage details.discard{grid-column:1/-1}.storage details.discard>summary{cursor:pointer;color:var(--destructive);min-height:2.25rem;display:inline-flex;align-items:center}` +
28
- `.storage form{display:grid;gap:10px;margin:0}.storage label{display:grid;gap:4px;font-size:.8125rem;font-weight:500;max-width:20rem}` +
29
- `.storage select,.storage input[type=password]{width:100%;box-sizing:border-box;min-height:2.125rem}.storage form button{justify-self:start;white-space:nowrap}` +
30
- `.storage form.setting button[type=submit],.storage .preview form.remove button[type=submit]{background:var(--primary);color:var(--primary-foreground);border-color:var(--primary);font-weight:600}` +
31
- `.storage .sweep{display:grid;gap:4px}.storage .sweep p{margin:0}.storage .sweep .problem{color:var(--destructive)}` +
32
- `.storage details.sweep-details>summary{cursor:pointer;min-height:2.25rem;display:inline-flex;align-items:center;font-size:.8125rem;color:var(--muted-foreground)}` +
33
- `.storage details.landed>summary{cursor:pointer;min-height:2.25rem;display:inline-flex;align-items:center;font-size:.8125rem;color:var(--muted-foreground)}` +
34
- `.storage details.landed .landed-body{display:grid;gap:6px;margin-top:4px;font-size:.8125rem}.storage details.landed p{margin:0}` +
35
- `.storage details.landed ul{margin:0;padding-left:18px;overflow-wrap:anywhere}.storage details.landed .target{font-family:var(--font-mono, ui-monospace, monospace);font-size:.6875rem}` +
36
- `.storage details.landed code{font-family:var(--font-mono, ui-monospace, monospace);font-size:.75rem;overflow-wrap:anywhere}` +
37
- `.storage details.sweep-details ul{margin:4px 0 0;padding-left:18px;font-family:var(--font-mono, ui-monospace, monospace);font-size:.6875rem;color:var(--muted-foreground);overflow-wrap:anywhere}` +
38
- `@media (max-width:900px){.storage details.sweep-details>summary,.storage details.landed>summary{min-height:44px}.storage details.clean-up>summary,.storage form button{min-height:44px}.storage label{max-width:none}.storage select,.storage input[type=password]{min-height:44px;font-size:16px}.storage .preview form.remove button{width:100%}}`;
39
- /** Paths under the home folder as ~/…, as the checkout list shows them. */
18
+ /** The page is kit pieces now; nothing of the old look is left. Kept as an export for the shared stylesheet. */
19
+ export const STORAGE_CSS = "";
20
+ /** A path under the home folder as ~/…. */
21
+ function tilde(path) {
22
+ const home = homedir();
23
+ return home !== "" && home !== "/" && path.startsWith(`${home}/`) ? `~${path.slice(home.length)}` : path;
24
+ }
25
+ /** Paths under the home folder as ~/… anywhere in a line, as the checkout list shows them. */
40
26
  function homeAsTilde(line) {
41
27
  const home = homedir();
42
28
  return home === "" || home === "/" ? line : line.split(`${home}/`).join("~/");
@@ -49,41 +35,61 @@ function whenWords(at) {
49
35
  /** When the last sweep ran and what it did, then why the next one waits or when it is due; paths and problems behind Details. */
50
36
  function sweepHtml(sweep) {
51
37
  const last = sweep.last;
52
- const schedule = sweep.schedule === undefined ? (sweep.off ? html `<p class="meta" data-sweep-state="off">Automatic sweep: off.</p>` : "")
53
- : html `<p class="meta" data-sweep-state="${sweep.schedule.state}">Automatic sweep: ${sweepScheduleWords(sweep.schedule, whenWords)}</p>`;
38
+ const schedule = sweep.schedule === undefined ? (sweep.off ? html `<p class="k-note" data-sweep-state="off">Automatic sweep: off.</p>` : "")
39
+ : html `<p class="k-note" data-sweep-state="${sweep.schedule.state}">Automatic sweep: ${sweepScheduleWords(sweep.schedule, whenWords)}</p>`;
54
40
  if (last === null)
55
- return html `<div class="sweep" data-last-sweep=""><p>Not swept yet.</p>${schedule}</div>`;
41
+ return html `<div data-last-sweep="">${note("Not swept yet.")}${schedule}</div>`;
56
42
  const who = last.source === "manual" ? `Cleaned up by ${last.actor}` : "Swept";
57
43
  const details = sweepDetails(last.parts);
58
44
  const problem = last.parts.some(one => one.failed > 0 || one.problem !== undefined);
59
- return html `<div class="sweep" data-last-sweep="${last.at}"><p${problem ? html ` class="problem"` : ""}>${who} <time datetime="${last.at}">${whenWords(last.at)}</time>. ${sweepWords(last.parts)}</p>${schedule}${details.length === 0 ? "" : html `<details class="sweep-details"><summary>Details</summary><ul>${details.map(line => html `<li>${homeAsTilde(line)}</li>`)}</ul></details>`}</div>`;
45
+ const said = html `${who} <time datetime="${last.at}">${whenWords(last.at)}</time>. ${sweepWords(last.parts)}`;
46
+ return html `<div data-last-sweep="${last.at}">${problem ? html `<p class="k-notice" role="alert">${icon("alert", true)}<span>${said}</span></p>` : note(said)}${schedule}${details.length === 0 ? "" : disclose("Details", joinHtml(details.map(line => codeLine(homeAsTilde(line)))))}</div>`;
60
47
  }
61
48
  /** Landed work, on demand: what its teardown did, what it waits on and why, and the one-time command for older work. */
62
49
  function landedHtml(landed) {
63
- const home = homedir();
64
50
  const done = landed.completed + landed.checkouts + landed.branches === 0 ? "No landed work cleaned up yet."
65
51
  : `${plural(landed.completed, "task")} marked complete; ${plural(landed.checkouts, "checkout")} and ${plural(landed.branches, "local branch", "local branches")} removed.`;
66
- const waiting = landed.waiting.length === 0 ? "" : html `<p>Waiting (${landed.waiting.length}):</p><ul data-landed-waiting>${landed.waiting.map(one => html `<li><span class="target">${home !== "" && one.target.startsWith(`${home}/`) ? `~${one.target.slice(home.length)}` : one.target}</span>${one.why === null ? "" : html `: ${one.why}`}</li>`)}</ul>`;
67
- return html `<details class="landed" data-landed><summary>Landed work</summary><div class="landed-body"><p>${done}</p>${waiting}<p class="meta">For work that landed before this: <code>toolroll storage clean --landed</code> shows what would go; add <code>--yes</code> to do it.</p></div></details>`;
52
+ const waiting = landed.waiting.length === 0 ? "" : html `<div data-landed-waiting>${note(`Waiting (${landed.waiting.length}):`)}${landed.waiting.map(one => settingsRecord({ title: html `<span class="k-mono">${tilde(one.target)}</span>`, sub: one.why ?? "" }))}</div>`;
53
+ return html `<div data-landed>${disclose("Landed work", joinHtml([note(done), waiting,
54
+ note(html `For work that landed before this: <code class="k-mono">toolroll storage clean --landed</code> shows what would go; add <code class="k-mono">--yes</code> to do it.`)]))}</div>`;
68
55
  }
56
+ /** One checkout: the task it was for, its size, where it is (the home folder as ~), why it stays, and Discard when it has changes. */
69
57
  function item(one) {
58
+ const why = one.why === null ? "" : `${whyWords(one)[0].toUpperCase()}${whyWords(one).slice(1)}`;
70
59
  const discard = one.why === "has changes"
71
- ? html `<details class="discard"><summary>Discard</summary>${postForm("/settings/storage/discard", html `<p class="meta">Throws away this checkout and its uncommitted changes. Its branch stays.</p>${reauth(html `<label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label>`)}<button type="submit" class="danger">Discard changes</button>`, { hidden: { path: one.path } })}</details>`
60
+ ? disclose("Discard", kitSectionForm({ proof: "storage-discard", action: "/settings/storage/discard", id: `discard-${previewId(one.path)}`, hidden: { path: one.path },
61
+ save: { label: "Discard changes", variant: "danger" }, body: note("Throws away this checkout and its uncommitted changes. Its branch stays.") }))
72
62
  : "";
73
- // The task it was for, then where it is (the home folder as ~), then why it stays.
74
- const home = homedir();
75
- const where = home !== "" && one.path.startsWith(`${home}/`) ? `~${one.path.slice(home.length)}` : one.path;
76
- return html `<li data-checkout="${one.path}"><span class="name">${one.taskId ?? basename(one.path)}</span><span class="size">${bytesWords(one.bytes)}</span><span class="path">${where}</span>${one.why === null ? "" : html `<span class="why">${whyWords(one)[0].toUpperCase() + whyWords(one).slice(1)}</span>`}${discard}</li>`;
63
+ return settingsRecord({ title: one.taskId ?? basename(one.path), attrs: { "data-checkout": one.path }, when: bytesWords(one.bytes),
64
+ sub: html `<span class="k-mono">${tilde(one.path)}</span>${why === "" ? "" : ` · ${why}`}`, more: discard });
77
65
  }
78
- export function storageHtml(view, notice) {
66
+ /** A short id for a checkout's form, from its path. */
67
+ const previewId = (path) => path.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "").slice(-48) || "checkout";
68
+ export function storageHtml(view, said) {
79
69
  const { plan } = view;
80
- const note = notice.problem ? html `<p class="problem" role="alert">${notice.problem}</p>` : notice.said ? html `<p role="status">${notice.said}</p>` : "";
81
70
  const facts = [plural(plan.count, "checkout"), `${plan.waitingReview} waiting for review`, `${plan.withChanges} kept for their changes`].join(" · ");
82
71
  const frees = plan.go.length === 0 ? "Nothing to clean up now." : `Cleaning up now frees about ${bytesWords(plan.freeBytes)}.`;
83
- const remove = plan.go.length === 0 ? "" :
84
- postForm("/settings/storage/clean", html `${reauth(html `<label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label>`)}<button type="submit">Remove ${plural(plan.go.length, "checkout")}</button>`, { attrs: { class: "remove" }, hidden: { preview: previewDigest(plan) } });
85
- const preview = html `<details class="clean-up" data-clean-up${notice.problem?.startsWith("Enter your Toolroll password to clean") ? html ` open` : ""}><summary>Clean up</summary><div class="preview">${plan.go.length === 0 ? html `<p>Nothing to remove now.</p>` :
86
- html `<h3>Removes ${plural(plan.go.length, "checkout")}, about ${bytesWords(plan.freeBytes)}</h3><p class="meta">Their branches stay, so nothing committed is lost.</p><ul class="checkouts" data-goes>${plan.go.map(one => item(one))}</ul>`}${plan.stay.length === 0 ? "" : html `<h3>Stays (${plan.stay.length})</h3><ul class="checkouts" data-stays>${plan.stay.map(one => item(one))}</ul>`}${remove}</div></details>`;
87
- const options = CLEANUP_CHOICES.map(one => html `<option value="${one.value}"${one.value === plan.cleanup ? html ` selected` : ""}>${one.label}</option>`);
88
- return html `<section class="storage">${note}<div class="card storage-state"><h2 data-checkout-bytes="${plan.totalBytes}">Checkouts use ${bytesWords(plan.totalBytes)}</h2><p class="meta">${facts}</p>${view.measured ?? ""}<p data-clean-bytes="${plan.freeBytes}">${frees}</p>${view.sweep === undefined ? "" : sweepHtml(view.sweep)}${view.landed === undefined ? "" : landedHtml(view.landed)}${preview}</div><h2>Automatic cleanup</h2>${postForm("/settings/storage", html `<label>Remove a finished task's clean checkout<select name="cleanup">${options}</select></label><p class="meta">Its branch stays. Checkouts with changes, commits on no branch, in use, or waiting for review are kept. When a task's work is already in main, Toolroll marks it complete and removes its checkout and local branches.</p>${reauth(html `<label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label>`)}<button type="submit">Save</button>`, { attrs: { class: "setting" } })}</section>`;
72
+ const checkouts = settingsSection({ id: "checkouts", title: `Checkouts use ${bytesWords(plan.totalBytes)}`,
73
+ body: joinHtml([
74
+ said.problem ? notice(said.problem) : said.said ? infoLine(said.said) : "",
75
+ html `<p class="k-note" data-checkout-bytes="${plan.totalBytes}">${facts}</p>`,
76
+ view.measured ?? "",
77
+ html `<p class="k-note" data-clean-bytes="${plan.freeBytes}">${frees}</p>`,
78
+ view.sweep === undefined ? "" : sweepHtml(view.sweep),
79
+ view.landed === undefined ? "" : landedHtml(view.landed),
80
+ ]) });
81
+ const remove = plan.go.length === 0 ? "" : kitSectionForm({ proof: "storage-clean", action: "/settings/storage/clean", id: "clean-form", hidden: { preview: previewDigest(plan) },
82
+ save: { label: `Remove ${plural(plan.go.length, "checkout")}`, variant: "danger" }, body: html `` });
83
+ const goes = plan.go.length === 0 ? note("Nothing to remove now.")
84
+ : html `<div data-goes>${plan.go.map(one => item(one))}</div>`;
85
+ const stays = plan.stay.length === 0 ? "" : disclose(`Stays (${plan.stay.length})`, html `<div data-stays>${plan.stay.map(one => item(one))}</div>`);
86
+ const cleanUp = settingsSection({ id: "clean-up", title: "Clean up now",
87
+ lead: plan.go.length === 0 ? "Nothing to remove now." : `Removes ${plural(plan.go.length, "checkout")}, about ${bytesWords(plan.freeBytes)}. Their branches stay, so nothing committed is lost.`,
88
+ body: html `<div data-clean-up>${joinHtml([plan.go.length === 0 ? "" : goes, remove, stays])}</div>` });
89
+ const automatic = settingsSection({ id: "automatic-cleanup", title: "Automatic cleanup",
90
+ lead: "Its branch stays. Checkouts with changes, commits on no branch, in use, or waiting for review are kept. When a task's work is already in main, Toolroll marks it complete and removes its checkout and local branches.",
91
+ body: kitSectionForm({ proof: "storage-cleanup", action: "/settings/storage", id: "cleanup-form", gate: true,
92
+ body: formRow("Remove a finished task's clean checkout", null, select({ id: "storage-cleanup", name: "cleanup", label: "Remove a finished task's clean checkout", current: plan.cleanup,
93
+ items: CLEANUP_CHOICES.map(one => [one.value, one.label]) }), "inline") }) });
94
+ return joinHtml([checkouts, cleanUp, automatic]);
89
95
  }
package/dist/store.d.ts CHANGED
@@ -233,7 +233,7 @@ export type SealedLedgerEntry = LedgerEntry & {
233
233
  hash: string;
234
234
  } | null;
235
235
  };
236
- export declare const SCHEMA_VERSION = 145;
236
+ export declare const SCHEMA_VERSION = 146;
237
237
  /** v118: the tables merged into `credential`, in the order they move (a person's API tokens keep their ids first). */
238
238
  export declare const V118_MERGED_TABLES: Readonly<Record<string, CredentialKind>>;
239
239
  /**
@@ -405,6 +405,12 @@ export declare const V143_ADDED_COLUMNS: Readonly<Record<string, Readonly<Record
405
405
  * the watched branch of the watched repository) the "checks failed" trigger saw, green or red, so tidy can tell a
406
406
  * failure notice was fixed by a later commit. */
407
407
  export declare const V145_SCHEMA = "\nCREATE TABLE IF NOT EXISTS task_archive (\n task_ref INTEGER PRIMARY KEY REFERENCES task_ref(id),\n by TEXT NOT NULL,\n by_kind TEXT NOT NULL CHECK (by_kind IN ('person','tidy')),\n rule TEXT,\n reason TEXT NOT NULL,\n at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS branch_check_run (\n repo TEXT NOT NULL,\n run_id INTEGER NOT NULL,\n branch TEXT NOT NULL,\n workflow TEXT NOT NULL,\n head_sha TEXT NOT NULL,\n conclusion TEXT NOT NULL CHECK (conclusion IN ('success','failure')),\n run_created_at TEXT NOT NULL,\n observed_at TEXT NOT NULL,\n PRIMARY KEY (repo, run_id)\n);\nCREATE INDEX IF NOT EXISTS branch_check_run_by_workflow ON branch_check_run (repo, branch, workflow, run_created_at);\nCREATE TABLE IF NOT EXISTS merge_in_flight (\n publication INTEGER PRIMARY KEY REFERENCES publication(id),\n task_ref INTEGER NOT NULL REFERENCES task_ref(id),\n token TEXT NOT NULL,\n by TEXT NOT NULL,\n at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS deploy_in_flight (\n task_ref INTEGER PRIMARY KEY REFERENCES task_ref(id),\n run INTEGER NOT NULL,\n at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS flow_step_task (\n card INTEGER NOT NULL,\n entry INTEGER NOT NULL,\n task_id TEXT NOT NULL,\n PRIMARY KEY (card, entry, task_id)\n);\n";
408
+ /** v146: `ci_check` is one pull request check (a GitHub check run or commit status) as the plane's winning observation of
409
+ * that head read it: written only with the observation that won its generation, replacing that head's rows wholesale.
410
+ * `release_step` is one step of a release (release.ts RELEASE_STEPS) as `toolroll release` moved through it: when it
411
+ * started, when it ended and how; recording it never changes the release. Both are read-only history for the Ship and
412
+ * Release views and their CLI twins. */
413
+ export declare const V146_SCHEMA = "\nCREATE TABLE IF NOT EXISTS ci_check (\n github_repo TEXT NOT NULL,\n pr_number INTEGER NOT NULL,\n head_sha TEXT NOT NULL,\n -- A check's identity: its kind (check run or commit status), its workflow and its name, so two workflows' checks\n -- with the same name stay two rows (a failing one never hides behind a passing namesake).\n ident TEXT NOT NULL,\n name TEXT NOT NULL,\n workflow TEXT,\n state TEXT NOT NULL CHECK (state IN ('queued','running','passed','failed','skipped','cancelled','neutral')),\n started_at TEXT,\n completed_at TEXT,\n url TEXT,\n observed_at TEXT NOT NULL,\n PRIMARY KEY (github_repo, pr_number, head_sha, ident)\n);\nCREATE TABLE IF NOT EXISTS release_step (\n task_id TEXT NOT NULL,\n tag TEXT NOT NULL,\n version TEXT NOT NULL,\n pr INTEGER,\n step TEXT NOT NULL,\n state TEXT NOT NULL CHECK (state IN ('running','done','failed','waiting')),\n started_at TEXT NOT NULL,\n ended_at TEXT,\n detail TEXT,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (task_id, tag, step)\n);\nCREATE INDEX IF NOT EXISTS release_step_by_task ON release_step (task_id, updated_at);\n";
408
414
  /**
409
415
  * v135: one approval per release (docs/plans/release-once.md). `release_authority` is the single source of truth for a
410
416
  * release a person approved: one row per generation, changed only by compare-and-swap on `rev`, with a lease whose
@@ -6083,8 +6089,13 @@ export declare class Store {
6083
6089
  file?: string | null;
6084
6090
  held?: string | null;
6085
6091
  }, now: Date): number;
6086
- /** A person approves a held script (one an imported flow brought): from now on it runs. False when it wasn't held. */
6087
- approveFlowScript(repo: string, name: string): boolean;
6092
+ /** A person approves a held script (one an imported flow brought): from now on it runs. The approval covers exactly
6093
+ * the version and content the person read (`seen`): the check and the release of the hold are one statement in one
6094
+ * transaction, so a script removed and brought back under the same name is never approved unseen. */
6095
+ approveFlowScript(repo: string, name: string, seen: {
6096
+ version: number;
6097
+ digest: string;
6098
+ }): "approved" | "changed" | "not-held";
6088
6099
  removeFlowScript(repo: string, name: string, by?: string, now?: Date): boolean;
6089
6100
  /** Claim a card's visit to a step zone. False when another pass holds it or it already finished. */
6090
6101
  claimFlowStep(step: {
@@ -8132,3 +8143,101 @@ export declare function restoreTaskFamily(store: Store, taskId: string, by: stri
8132
8143
  reason: ArchiveRefusal;
8133
8144
  message: string;
8134
8145
  };
8146
+ /**
8147
+ * What a LIVE invite grants, for its own join page (Wave E, "Your access"): the role, who invited, the projects and
8148
+ * when it expires. Read without spending an attempt and under exactly inviteIsLive's predicate, so every dead shape
8149
+ * reads as null and still gets the one indistinguishable page (D6). Only a holder of the live token sees it.
8150
+ */
8151
+ export declare function liveInviteTerms(store: Store, tokenValue: string, now: Date): {
8152
+ role: "approver" | "viewer";
8153
+ mintedBy: string;
8154
+ expiresAt: string;
8155
+ projects: ProjectAccess;
8156
+ } | null;
8157
+ /** One pull request check as GitHub last reported it for the observed head (v146 ci_check). */
8158
+ /** `kind`: a check run or a commit status (part of the check's identity); a row built without it reads as a run when it
8159
+ * names a workflow, else as a status. */
8160
+ export type CiCheckRow = {
8161
+ name: string;
8162
+ workflow: string | null;
8163
+ state: "queued" | "running" | "passed" | "failed" | "skipped" | "cancelled" | "neutral";
8164
+ startedAt: string | null;
8165
+ completedAt: string | null;
8166
+ url: string | null;
8167
+ kind?: "run" | "status";
8168
+ };
8169
+ /** `observed`: the authoritative state of the same winning observation (ci_observation), which the overall status
8170
+ * always comes from; the rows only show each check's own timing. */
8171
+ export type ShipChecks = {
8172
+ githubRepo: string;
8173
+ prNumber: number;
8174
+ headSha: string;
8175
+ observedAt: string;
8176
+ observed: "passing" | "failing" | "running" | "none";
8177
+ checks: CiCheckRow[];
8178
+ };
8179
+ /** At most this many checks are kept for one head; GitHub's rollup is far smaller in practice. */
8180
+ export declare const CI_CHECKS_MAX = 200;
8181
+ /**
8182
+ * GitHub's statusCheckRollup (gh pr view --json statusCheckRollup) as check rows: CheckRun items (name, workflowName,
8183
+ * status, conclusion, startedAt, completedAt, detailsUrl) and StatusContext items (context, state, startedAt, targetUrl).
8184
+ * Strict: an item that is not one of those shapes, has no usable name, or names a state it doesn't know is dropped; one
8185
+ * row per identity (kind, workflow and name), and when one identity appears twice (a re-run) the worse state stands;
8186
+ * at most CI_CHECKS_MAX.
8187
+ */
8188
+ export declare function parseCiRollup(rollup: unknown): CiCheckRow[];
8189
+ /**
8190
+ * Replace one head's checks wholesale (v146). Call ONLY with an observation that won its generation (publish.ts settles
8191
+ * and records in one transaction): a losing, stale answer never writes here.
8192
+ */
8193
+ export declare function recordCiChecks(store: Store, githubRepo: string, prNumber: number, headSha: string, rows: readonly CiCheckRow[], now: Date): void;
8194
+ /** A pull request's checks for the head the plane's last winning observation recorded, or null when none was. */
8195
+ export declare function ciChecksFor(store: Store, githubRepo: string, prNumber: number): ShipChecks | null;
8196
+ /** A task's pull request checks: its family's newest publication that has a pull request number, and that pull
8197
+ * request's checks (ciChecksFor). Null: no pull request, or none observed yet. */
8198
+ export declare function shipChecksForTask(store: Store, taskId: string): ShipChecks | null;
8199
+ /** One release step as `toolroll release` moved through it (v146 release_step). */
8200
+ export type ReleaseStepRow = {
8201
+ step: string;
8202
+ state: "running" | "done" | "failed" | "waiting";
8203
+ startedAt: string;
8204
+ endedAt: string | null;
8205
+ detail: string | null;
8206
+ };
8207
+ export type ReleaseProgress = {
8208
+ tag: string;
8209
+ version: string;
8210
+ pr: number | null;
8211
+ startedAt: string;
8212
+ updatedAt: string;
8213
+ steps: ReleaseStepRow[];
8214
+ stopped: {
8215
+ step: string;
8216
+ message: string;
8217
+ at: string;
8218
+ } | null;
8219
+ };
8220
+ /** What a release coordinator says about one step. */
8221
+ export type ReleaseStepEntry = {
8222
+ taskId: string;
8223
+ tag: string;
8224
+ version: string;
8225
+ pr: number | null;
8226
+ step: string;
8227
+ state: ReleaseStepRow["state"];
8228
+ at: Date;
8229
+ detail?: string | null;
8230
+ };
8231
+ /**
8232
+ * Record a release step (v146): `running` or `waiting` opens it (its first start is kept), `done` or `failed` ends it.
8233
+ * A step that runs again after a stop starts its row again. A failed write throws here; the release's adapter swallows
8234
+ * it, so recording never changes a release.
8235
+ */
8236
+ export declare function recordReleaseStep(store: Store, entry: ReleaseStepEntry): void;
8237
+ /** A release's steps, for the release task: the newest tag recorded for it, its steps in the order they ran, and the
8238
+ * step it stopped at when the last thing recorded was a failure. Null: no step recorded. */
8239
+ export declare function releaseProgressFor(store: Store, taskId: string): ReleaseProgress | null;
8240
+ /** The release task whose steps were recorded most recently (v146), or null when none was. */
8241
+ export declare function latestReleaseStepTask(store: Store): string | null;
8242
+ /** Seconds a step or check took (to its end, or to `now` while it runs); null without a start. */
8243
+ export declare function elapsedSeconds(startedAt: string | null, endedAt: string | null, now: Date): number | null;