rvlib-shipkit 0.38.0 → 1.0.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.
Files changed (252) hide show
  1. package/CHANGELOG.md +371 -16
  2. package/README.md +26 -14
  3. package/package.json +2 -2
  4. package/src/assets/gen/comfyts-gen.ts +27 -10
  5. package/src/assets/gen/generate-roll.ts +1 -1
  6. package/src/assets/kit-config.ts +6 -6
  7. package/src/assets/run-assets.ts +14 -10
  8. package/src/assets/server/studio-server.ts +6 -5
  9. package/src/check.ts +343 -0
  10. package/src/{rules/SK001-package-json-shape.ts → checks/SK001-package-json-shape.check.ts} +44 -46
  11. package/src/checks/SK002-contracts-fulfillness.check.ts +24 -0
  12. package/src/checks/SK003-claude-md-shape.check.ts +66 -0
  13. package/src/checks/SK004-gitignore-policy.check.ts +309 -0
  14. package/src/{rules/SK005-rvlib-versions.ts → checks/SK005-rvlib-versions.check.ts} +46 -32
  15. package/src/{rules/SK006-npm-publish-safety.ts → checks/SK006-npm-publish-safety.check.ts} +25 -24
  16. package/src/{rules/SK007-github-visibility.ts → checks/SK007-github-visibility.check.ts} +20 -14
  17. package/src/{rules/SK008-reviewer-agent.ts → checks/SK008-reviewer-agent.check.ts} +29 -14
  18. package/src/{rules/SK009-commit-ai-tells.ts → checks/SK009-commit-ai-tells.check.ts} +24 -21
  19. package/src/checks/SK010-undeclared-unit.check.ts +111 -0
  20. package/src/{rules/SK011-sibling-unit-ranges.ts → checks/SK011-sibling-unit-ranges.check.ts} +21 -20
  21. package/src/{rules/SK012-config-barrel-import.ts → checks/SK012-config-barrel-import.check.ts} +18 -23
  22. package/src/{rules/SK013-one-way-in-policy.ts → checks/SK013-one-way-in-policy.check.ts} +58 -50
  23. package/src/{rules/SK014-prose-ste.ts → checks/SK014-prose-ste.check.ts} +27 -13
  24. package/src/{rules/SK015-nested-repo.ts → checks/SK015-nested-repo.check.ts} +15 -12
  25. package/src/checks/SK016-legacy-check-shapes.check.ts +182 -0
  26. package/src/checks/SK017-vscode-hub-task.check.ts +197 -0
  27. package/src/checks/SK018-test-files-run.check.ts +28 -0
  28. package/src/checks/SK019-tests-run-in-units.check.ts +76 -0
  29. package/src/checks/SK020-self-spawning-check.check.ts +147 -0
  30. package/src/checks/contracts.check.ts +20 -0
  31. package/src/cli/cmd_Asks.ts +3 -2
  32. package/src/cli/cmd_Assets.ts +2 -2
  33. package/src/cli/cmd_Bump.ts +1 -1
  34. package/src/cli/cmd_Check.ts +6 -6
  35. package/src/cli/cmd_Checks.ts +95 -0
  36. package/src/cli/cmd_Ci.ts +48 -13
  37. package/src/cli/cmd_Commands.ts +2 -6
  38. package/src/cli/cmd_ConfigKeys.ts +69 -0
  39. package/src/cli/cmd_Contracts.ts +1 -1
  40. package/src/cli/cmd_Fleet.ts +6 -2
  41. package/src/cli/cmd_Hooks.ts +1 -1
  42. package/src/cli/cmd_Keys.ts +43 -18
  43. package/src/cli/cmd_Mv.ts +2 -2
  44. package/src/cli/cmd_Prose.ts +10 -1
  45. package/src/cli/cmd_Releases.ts +2 -2
  46. package/src/cli/cmd_Serve.ts +1 -1
  47. package/src/cli/cmd_Start.ts +6 -6
  48. package/src/cli/cmd_Status.ts +25 -0
  49. package/src/cli/cmd_Tui.ts +2 -2
  50. package/src/cli.ts +7 -4
  51. package/src/collections/actions.ts +4 -4
  52. package/src/collections/checks.ts +258 -0
  53. package/src/collections/collection-types.ts +254 -1
  54. package/src/collections/comfy.ts +23 -0
  55. package/src/collections/md-body.ts +14 -0
  56. package/src/collections/pings.ts +38 -12
  57. package/src/collections/posts.ts +19 -3
  58. package/src/collections/providers.ts +198 -91
  59. package/src/collections/reflections.ts +41 -0
  60. package/src/collections/state-rules.ts +73 -0
  61. package/src/collections/test-time.ts +288 -0
  62. package/src/collections/tests.ts +181 -0
  63. package/src/collections/wikis.ts +66 -0
  64. package/src/comfy/comfy-panel.ts +292 -0
  65. package/src/contracts/apply-contracts.ts +17 -12
  66. package/src/contracts/contract-types.ts +16 -4
  67. package/src/contracts/contracts-engine.ts +78 -42
  68. package/src/contracts/manifests/discord.contract.ts +6 -6
  69. package/src/contracts/manifests/identity.contract.ts +3 -3
  70. package/src/contracts/manifests/itch.contract.ts +12 -15
  71. package/src/contracts/manifests/social.contract.ts +5 -5
  72. package/src/contracts/manifests/steam.contract.ts +16 -16
  73. package/src/contracts/run-contracts.ts +4 -3
  74. package/src/core/attach.ts +96 -0
  75. package/src/core/canonical-scripts.ts +25 -0
  76. package/src/core/check-ctx.ts +204 -14
  77. package/src/core/check-file-names.ts +112 -0
  78. package/src/core/check-pass.ts +84 -0
  79. package/src/core/check-runner.ts +796 -0
  80. package/src/core/config-keys.ts +256 -0
  81. package/src/core/config-rewrite.ts +77 -0
  82. package/src/core/discover-checks.ts +166 -90
  83. package/src/core/discover-legacy-checks.ts +26 -0
  84. package/src/core/doc-block.ts +13 -0
  85. package/src/core/floor-resolve.ts +264 -0
  86. package/src/core/floor.ts +20 -137
  87. package/src/core/git-bin.ts +24 -0
  88. package/src/core/git-root.ts +108 -0
  89. package/src/core/instance.ts +195 -0
  90. package/src/core/jobs.ts +118 -0
  91. package/src/core/layout.ts +52 -0
  92. package/src/core/legacy-shapes.ts +207 -0
  93. package/src/core/lib-root.ts +5 -0
  94. package/src/core/locked-write.ts +110 -39
  95. package/src/core/machine-commit.ts +5 -4
  96. package/src/core/migrate-layout.ts +557 -0
  97. package/src/core/motherhome.ts +2 -8
  98. package/src/core/nesting.ts +4 -12
  99. package/src/core/one-way-in-policy.ts +73 -17
  100. package/src/core/outcome-words.ts +45 -0
  101. package/src/core/package-manager.ts +86 -0
  102. package/src/core/parallel.ts +21 -0
  103. package/src/core/process-tree.ts +44 -0
  104. package/src/core/raw-access.ts +93 -0
  105. package/src/core/read-set.ts +152 -0
  106. package/src/core/run-check.ts +155 -213
  107. package/src/core/run-ci.ts +153 -189
  108. package/src/core/run-fix.ts +96 -0
  109. package/src/core/run-fixers.ts +53 -30
  110. package/src/core/self-update.ts +9 -3
  111. package/src/core/slots.ts +83 -0
  112. package/src/core/spawn-timeout.ts +34 -3
  113. package/src/core/when-block.ts +401 -0
  114. package/src/core/when.ts +135 -0
  115. package/src/core/write-status.ts +5 -2
  116. package/src/hooks/leak-guard.ts +10 -8
  117. package/src/hooks/one-way-in-guard.ts +13 -5
  118. package/src/hooks/root-identity.ts +2 -1
  119. package/src/hooks/run-hooks.ts +35 -23
  120. package/src/init-repo.ts +39 -23
  121. package/src/ledger/check-stats.ts +155 -0
  122. package/src/ledger/check-table.ts +179 -0
  123. package/src/ledger/db.ts +255 -0
  124. package/src/ledger/gate-checks.ts +433 -0
  125. package/src/ledger/git-key.ts +116 -77
  126. package/src/ledger/git-scan.ts +96 -35
  127. package/src/ledger/ledger.ts +116 -42
  128. package/src/ledger/live-runs.ts +111 -0
  129. package/src/ledger/live-timeline.ts +97 -0
  130. package/src/ledger/repo-view.ts +73 -0
  131. package/src/ledger/run-log.ts +448 -0
  132. package/src/ledger/run-timeline.ts +173 -0
  133. package/src/ledger/store.ts +23 -12
  134. package/src/ledger/suite-log.ts +161 -0
  135. package/src/ledger/toolchain-identity.ts +7 -1
  136. package/src/ledger/world-cache.ts +111 -0
  137. package/src/mv/mv-md.ts +16 -6
  138. package/src/problems/extract.ts +182 -0
  139. package/src/problems/problem-board.ts +37 -0
  140. package/src/problems/problem-line.ts +77 -0
  141. package/src/prose/prose-view.ts +90 -0
  142. package/src/prose/run-prose.ts +60 -19
  143. package/src/prose/ste.ts +140 -24
  144. package/src/reflections/reflections-queue.ts +81 -0
  145. package/src/release/git-facts.ts +15 -9
  146. package/src/release/run-bump.ts +6 -5
  147. package/src/release/run-release.ts +25 -11
  148. package/src/release/run-releases.ts +2 -2
  149. package/src/release/unit-gate.ts +7 -54
  150. package/src/release/unit-resolve.ts +282 -0
  151. package/src/release/units.ts +33 -217
  152. package/src/repo-config.ts +155 -85
  153. package/src/rules/rule.ts +36 -96
  154. package/src/rules/untrack-generated.ts +5 -4
  155. package/src/scaffold/files/vscode-tasks.json +10 -31
  156. package/src/scaffold/jsonc-spans.ts +113 -0
  157. package/src/scaffold/project-scaffold.ts +16 -43
  158. package/src/scan.ts +98 -0
  159. package/src/serve/gate-trigger.ts +148 -0
  160. package/src/serve/health-schedule.ts +102 -0
  161. package/src/serve/hub-app/chart-view.ts +179 -0
  162. package/src/serve/hub-app/check-view.tsx +345 -0
  163. package/src/serve/hub-app/check-words.ts +164 -0
  164. package/src/serve/hub-app/checks-svc.ts +280 -0
  165. package/src/serve/hub-app/clock-svc.ts +33 -0
  166. package/src/serve/hub-app/col-card.tsx +23 -39
  167. package/src/serve/hub-app/cols-svc.ts +44 -20
  168. package/src/serve/hub-app/comfy-svc.ts +171 -0
  169. package/src/serve/hub-app/comfy-view.tsx +81 -0
  170. package/src/serve/hub-app/dur.ts +41 -0
  171. package/src/serve/hub-app/hash-route.ts +121 -0
  172. package/src/serve/hub-app/header-bar.tsx +117 -0
  173. package/src/serve/hub-app/health-svc.ts +23 -28
  174. package/src/serve/hub-app/help.tsx +29 -0
  175. package/src/serve/hub-app/hub-app.tsx +122 -119
  176. package/src/serve/hub-app/hub-st.ts +115 -42
  177. package/src/serve/hub-app/hub-types.ts +117 -2
  178. package/src/serve/hub-app/hub.css +534 -192
  179. package/src/serve/hub-app/index.html +2 -0
  180. package/src/serve/hub-app/live-svc.ts +174 -0
  181. package/src/serve/hub-app/md.ts +15 -0
  182. package/src/serve/hub-app/media-svc.ts +4 -1
  183. package/src/serve/hub-app/media-view.tsx +15 -0
  184. package/src/serve/hub-app/next-run.ts +68 -0
  185. package/src/serve/hub-app/project-gutter.css +65 -0
  186. package/src/serve/hub-app/project-gutter.tsx +128 -0
  187. package/src/serve/hub-app/projects-svc.ts +226 -0
  188. package/src/serve/hub-app/prose-hits-st.ts +124 -0
  189. package/src/serve/hub-app/prose-hits-view.tsx +176 -0
  190. package/src/serve/hub-app/prose-hits.css +21 -0
  191. package/src/serve/hub-app/run-strip.ts +47 -0
  192. package/src/serve/hub-app/runs-tabs.ts +24 -0
  193. package/src/serve/hub-app/test-view.tsx +107 -0
  194. package/src/serve/hub-app/tests-svc.ts +62 -0
  195. package/src/serve/hub-app/tests-view.tsx +238 -0
  196. package/src/serve/hub-app/time-ago.tsx +34 -0
  197. package/src/serve/hub-app/timeline-chart.tsx +274 -0
  198. package/src/serve/hub-app/timeline-geometry.ts +314 -0
  199. package/src/serve/hub-app/timeline-svc.ts +62 -0
  200. package/src/serve/hub-app/timeline-view.tsx +245 -0
  201. package/src/serve/hub-app/tree-build.ts +410 -0
  202. package/src/serve/hub-app/tree-row.tsx +42 -0
  203. package/src/serve/hub-app/tree-svc.ts +174 -0
  204. package/src/serve/hub-app/wikis-view.tsx +53 -0
  205. package/src/serve/other-repos.ts +79 -0
  206. package/src/serve/project-gutter-route.ts +350 -0
  207. package/src/serve/project-gutter.ts +449 -0
  208. package/src/serve/run-serve.ts +245 -16
  209. package/src/serve/same-origin.ts +14 -0
  210. package/src/serve/serve-hub.ts +213 -18
  211. package/src/shipkit.ts +19 -12
  212. package/src/smoke/run-smoke.ts +3 -2
  213. package/src/start-repo.ts +75 -36
  214. package/src/suite/judge-suites.ts +216 -18
  215. package/src/suite/kept-timings.ts +58 -0
  216. package/src/suite/runner-shapes.ts +88 -0
  217. package/src/suite/suite-cli.ts +147 -24
  218. package/src/suite/suite-cpu-preload.ts +92 -0
  219. package/src/suite/test-units.ts +243 -0
  220. package/src/ts/config.ts +4 -69
  221. package/src/ts/jsonc.ts +69 -0
  222. package/src/ts/lex.ts +143 -0
  223. package/src/ts/rename.ts +73 -10
  224. package/src/ts/source-graph.ts +28 -8
  225. package/src/ts/tsconfig-projects.ts +6 -97
  226. package/src/ts/tsconfig-resolve.ts +108 -0
  227. package/src/tui/build-steps-panel.ts +61 -0
  228. package/src/tui/checks-panel.ts +24 -16
  229. package/src/tui/run-tui.ts +2 -2
  230. package/src/tui/tui-panel.ts +5 -4
  231. package/src/wiki/graph-colors.ts +68 -0
  232. package/src/wiki/graph-mount.ts +7 -9
  233. package/src/wiki/graph-page.ts +42 -25
  234. package/src/wiki/graph-positions.ts +105 -0
  235. package/src/wiki/run-wiki.ts +1 -1
  236. package/src/wiki/serve-graph.ts +33 -3
  237. package/src/cli/cmd_Rules.ts +0 -51
  238. package/src/cli/cmd_Units.ts +0 -82
  239. package/src/collections/graphs.ts +0 -32
  240. package/src/contracts/contracts-once.ts +0 -43
  241. package/src/core/check-suite.ts +0 -302
  242. package/src/core/repair-contract-dirs.ts +0 -136
  243. package/src/core/task-runner.ts +0 -339
  244. package/src/ledger/gate-units.ts +0 -138
  245. package/src/ledger/unit-table.ts +0 -105
  246. package/src/rules/SK002-contracts-fulfillness.ts +0 -24
  247. package/src/rules/SK003-claude-md-shape.ts +0 -70
  248. package/src/rules/SK004-gitignore-policy.ts +0 -238
  249. package/src/rules/SK010-undeclared-release-unit.ts +0 -127
  250. package/src/rules/rule-registry.ts +0 -64
  251. package/src/rules/run-fix.ts +0 -72
  252. package/src/tui/tasks-panel.ts +0 -60
@@ -1,20 +1,26 @@
1
1
  // HubSt — the ONE root store (build/app-state-tree: one central tree,
2
2
  // instantiated once at boot, services with backrefs, exposed as
3
3
  // window.app). Owns cross-service state: selection, the severity filter
4
- // (an ADDRESS: /?f=needs · /?f=red), hash deep-links (#<asset-id> ·
5
- // #cat-<Category>).
4
+ // (an ADDRESS: /?f=needs · /?f=red), and the hash router: every page and
5
+ // every tab is a selection, every selection has an address (hash-route.ts),
6
+ // and setSel is the one place both move together.
7
+ import { RUNS_ID } from '../../collections/collection-types.ts'
6
8
  import { makeAutoObservable } from 'mobx'
9
+ import { ClockSvc } from './clock-svc.ts'
7
10
  import { ColsSvc } from './cols-svc.ts'
11
+ import { canonicalSel, currentHash, hashFor, parseHash, selOf, type HashSel } from './hash-route.ts'
12
+ import { ComfySvc } from './comfy-svc.ts'
8
13
  import { HealthSvc } from './health-svc.ts'
14
+ import { LiveSvc } from './live-svc.ts'
9
15
  import { MediaSvc } from './media-svc.ts'
16
+ import { TimelineSvc } from './timeline-svc.ts'
17
+ import { TreeSvc } from './tree-svc.ts'
18
+ import { ChecksSvc } from './checks-svc.ts'
19
+ import { ProjectsSvc } from './projects-svc.ts'
20
+ import { TestsSvc } from './tests-svc.ts'
21
+ import type { Resource } from './hub-types.ts'
10
22
 
11
- export type Sel =
12
- | { t: 'asset'; id: string }
13
- | { t: 'cat'; cat: string }
14
- | { t: 'col'; id: string }
15
- | { t: 'item'; col: string; id: string }
16
- | { t: 'status' }
17
- | null
23
+ export type Sel = HashSel | null
18
24
 
19
25
  /** one cmd+j target — run() fires the SAME action clicking would */
20
26
  export type Jump = { id: string; label: string; section: string; run: () => void }
@@ -28,9 +34,23 @@ const parseFilter = (search: string): Filter => {
28
34
 
29
35
  export class HubSt {
30
36
  name = ''
37
+ /** where this repo sits on disk, from /hub.json: what makes a source
38
+ * path on a page a link that opens his editor */
39
+ root = ''
40
+ /** the one second tick every age on the page reads (clock-svc.ts) */
41
+ clock = new ClockSvc()
31
42
  media = new MediaSvc(this)
32
43
  cols = new ColsSvc(this)
44
+ tree = new TreeSvc(this)
33
45
  health = new HealthSvc(this)
46
+ checks = new ChecksSvc(this)
47
+ comfy = new ComfySvc(this)
48
+ timeline = new TimelineSvc(this)
49
+ /** the runs in flight, whoever started them (live-svc.ts) */
50
+ live = new LiveSvc(this)
51
+ projects = new ProjectsSvc(this)
52
+ /** the pages behind the test groups of the tree (tests-svc.ts) */
53
+ tests = new TestsSvc(this)
34
54
  sel: Sel = null
35
55
  filter: Filter = parseFilter(location.search)
36
56
  /** sidebar search — live substring filter over asset + collection rows */
@@ -41,21 +61,30 @@ export class HubSt {
41
61
  private booted = false
42
62
 
43
63
  constructor() {
44
- makeAutoObservable<this, 'booted'>(this, { media: false, cols: false, health: false, booted: false })
64
+ makeAutoObservable<this, 'booted'>(this, { media: false, cols: false, tree: false, health: false, checks: false, comfy: false, timeline: false, live: false, projects: false, tests: false, booted: false })
45
65
  }
46
66
 
47
67
  boot(): void {
48
68
  void fetch('/hub.json')
49
- .then((r) => r.json() as Promise<{ name: string }>)
50
- .then((h) => { this.setName(h.name) })
69
+ .then((r) => r.json() as Promise<{ name: string; root?: string }>)
70
+ .then((h) => { this.setHub(h.name, h.root ?? '') })
51
71
  .catch(() => {})
72
+ this.clock.start()
52
73
  this.media.start()
53
74
  this.cols.start()
75
+ this.live.start()
76
+ void this.checks.loadGate()
54
77
  void this.health.load()
78
+ // back, forward, a pasted link and a hand edited hash all arrive
79
+ // here; applyHash is idempotent, so the two events firing together
80
+ // for one move cost nothing
55
81
  window.addEventListener('hashchange', () => this.applyHash())
82
+ window.addEventListener('popstate', () => this.applyHash())
83
+ this.applyHash()
56
84
  }
57
- private setName(n: string): void {
85
+ private setHub(n: string, root: string): void {
58
86
  this.name = n
87
+ this.root = root
59
88
  document.title = `${n} · hub`
60
89
  }
61
90
 
@@ -102,6 +131,8 @@ export class HubSt {
102
131
  out.push({ id: `cat:${cat}`, label: cat, section: 'media', run: () => this.showCat(cat) })
103
132
  for (const a of this.media.assets)
104
133
  out.push({ id: `asset:${a.id}`, label: a.label, section: a.cat, run: () => this.selectAsset(a.id) })
134
+ // the runs page is a page, not a resource, so no collection lists it
135
+ out.push({ id: 'page:runs', label: 'runs', section: 'page', run: () => this.cols.select('runs', RUNS_ID) })
105
136
  for (const col of this.cols.sidebarCols) {
106
137
  const health = col.id === 'health'
107
138
  out.push({ id: `col:${col.id}`, label: col.title, section: 'section', run: () => (health ? this.showStatus() : this.showCol(col.id)) })
@@ -125,49 +156,91 @@ export class HubSt {
125
156
  }
126
157
 
127
158
  // ── selection + hash deep-links ───────────────────────────────────────
128
- selectAsset(id: string, writeHash = true): void {
129
- this.sel = { t: 'asset', id }
130
- if (writeHash) this.setHash(id)
159
+ selectAsset(id: string): void {
160
+ this.setSel({ t: 'asset', id })
131
161
  void this.media.select(id)
132
162
  }
133
- showCat(cat: string, writeHash = true): void {
134
- this.sel = { t: 'cat', cat }
163
+ showCat(cat: string): void {
164
+ this.setSel({ t: 'cat', cat })
135
165
  this.media.openCats.add(cat)
136
- if (writeHash) this.setHash(`cat-${cat}`)
137
166
  void this.media.fillCat()
138
167
  }
139
168
  showStatus(): void {
140
- this.sel = { t: 'status' }
169
+ this.setSel({ t: 'status' })
141
170
  void this.health.load()
142
171
  }
172
+ /** where the test checks' time goes: the `tests` group (null) or one test
173
+ * check's group. Fetched as the page opens, never on the poll */
174
+ showTests(check: string | null): void {
175
+ this.setSel({ t: 'tests', check })
176
+ void this.tests.load(check)
177
+ }
178
+
179
+ /** the ONE place a selection is written, so the url always describes what
180
+ * is on screen. A new page is a history entry, so back returns to the
181
+ * page before it. The same address is never pushed twice: applyHash
182
+ * lands here too, with the hash already naming this selection */
183
+ setSel(sel: Sel): void {
184
+ this.sel = sel
185
+ const h = hashFor(sel)
186
+ if (h == null) return
187
+ const cur = parseHash(currentHash())
188
+ const next = parseHash(h)
189
+ // the tool set rides after the asset id (media-svc syncHash), so an
190
+ // address is the same page whatever tools it carries
191
+ if (cur.kind === next.kind && cur.col === next.col && cur.id === next.id) return
192
+ history.pushState(null, '', `#${h}`)
193
+ }
194
+ /** one tree row, opened: an asset goes to the atelier, everything else to
195
+ * its own item card. A gate check has a page of its own now, so nothing
196
+ * lands on the raw wall of text unless it is asked for by name */
197
+ openResource(r: Resource): void {
198
+ const assetId = r.id.replace(/^media:/, '')
199
+ if (r.collection === 'media' && this.media.assets.some((a) => a.id === assetId)) {
200
+ this.selectAsset(assetId)
201
+ return
202
+ }
203
+ this.cols.select(r.collection, r.id)
204
+ }
205
+
143
206
  /** collection view — one card per instance (health has StatusCard instead) */
144
207
  showCol(id: string): void {
145
- this.sel = { t: 'col', id }
146
- }
147
- private setHash(h: string): void {
148
- if (location.hash.slice(1) !== h) location.hash = h
208
+ this.setSel({ t: 'col', id })
209
+ }
210
+ /** open whatever a selection names, through the same calls a click makes */
211
+ go(sel: HashSel): void {
212
+ switch (sel.t) {
213
+ case 'asset': return this.selectAsset(sel.id)
214
+ case 'cat': return this.showCat(sel.cat)
215
+ case 'col': return this.showCol(sel.id)
216
+ case 'item': return this.cols.select(sel.col, sel.id)
217
+ case 'status': return this.showStatus()
218
+ case 'tests': return this.showTests(sel.check)
219
+ }
149
220
  }
221
+ /** the url decides the page: a reload, back, forward and a pasted link
222
+ * all land here, and only a hash naming something else moves the view */
150
223
  applyHash(): void {
151
- const raw = decodeURIComponent(location.hash.slice(1))
152
- if (raw === '') return
153
- // #<assetId>?tools=a,b — the query half is the media tool state
154
- const qi = raw.indexOf('?')
155
- const h = qi === -1 ? raw : raw.slice(0, qi)
156
- if (h.startsWith('cat-')) {
157
- const c = h.slice(4)
158
- if (this.media.assets.some((a) => a.cat === c) && this.media.catMode !== c) this.showCat(c, false)
159
- return
160
- }
161
- const tools = qi === -1 ? null : new URLSearchParams(raw.slice(qi + 1)).get('tools')
162
- const list = tools == null ? null : tools === '' ? [] : tools.split(',')
163
- if (this.media.assets.some((a) => a.id === h) && this.media.curId !== h) {
164
- this.media.pendingTools = list
165
- this.selectAsset(h, false)
166
- } else if (this.media.curId === h && list != null) {
167
- // same asset, new tool set (a pasted share link) — apply NOW; a
224
+ const p = parseHash(currentHash())
225
+ const named = selOf(p)
226
+ if (named == null) return
227
+ // an address naming what this build opens under another address is
228
+ // rewritten in place: a replace, so back never lands on the stale one
229
+ const sel = canonicalSel(named)
230
+ if (sel !== named) history.replaceState(null, '', `#${hashFor(sel)}`)
231
+ // media addresses resolve only once the first heartbeat listed the
232
+ // assets (afterFirstBeat calls back)
233
+ if (sel.t === 'asset' && !this.media.assets.some((a) => a.id === sel.id)) return
234
+ if (sel.t === 'cat' && !this.media.assets.some((a) => a.cat === sel.cat)) return
235
+ if (sel.t === 'asset' && this.media.curId === sel.id) {
236
+ // same asset, new tool set (a pasted share link): apply NOW, a
168
237
  // parked pendingTools would hijack the next selection instead
169
- this.media.applyTab(list)
238
+ if (p.tools != null) this.media.applyTab(p.tools)
239
+ return
170
240
  }
241
+ if (hashFor(sel) === hashFor(this.sel)) return
242
+ if (sel.t === 'asset') this.media.pendingTools = p.tools
243
+ this.go(sel)
171
244
  }
172
245
  /** first heartbeat has the asset list — only then can a hash resolve */
173
246
  afterFirstBeat(): void {
@@ -1,6 +1,7 @@
1
1
  // Wire types the hub app reads — mirrors of the server shapes
2
2
  // (studio-server.ts, collection-types.ts). Kept structural on purpose: the
3
3
  // page renders what the API sends, the file is the truth.
4
+ import type { ResourceType, SuiteFacts, TestFacts, TestRun } from '../../collections/collection-types.ts'
4
5
 
5
6
  export type SideAsset = {
6
7
  id: string
@@ -57,9 +58,118 @@ export type QueueState = {
57
58
 
58
59
  export type Candidate = { id: string; label: string; hint?: string }
59
60
  export type Artifact = { copy?: string; url?: string; steps?: string[] }
60
- export type ColInstance = {
61
+ /** the resource kind, never the collection: one folder holds several kinds.
62
+ * The list lives where the providers that emit it live, so a kind can never
63
+ * be known on one side of the wire only. Type only, so nothing server side
64
+ * reaches the bundle */
65
+ export type { ResourceType, SuiteFacts, TestFacts, TestRun }
66
+
67
+ export type CheckStatus = 'pass' | 'warn' | 'fail' | 'skip'
68
+
69
+ /** one outcome of one check, as the page draws it */
70
+ export type RecentRun = { at: number; ms: number; status: CheckStatus; replayed: boolean }
71
+
72
+ // the page reads the SERVER's words for these, type-only so nothing of the
73
+ // engine reaches the bundle: two declarations of one vocabulary is how a
74
+ // switch ends up writing a word the other half does not know
75
+ import type { WhenAt, WhenSource } from '../../core/when.ts'
76
+ import type { CheckFrom } from '../../check.ts'
77
+ export type { CheckFrom, WhenAt, WhenSource }
78
+
79
+ /** a gate check's declaration and its numbers (collections/collection-types.ts
80
+ * CheckFacts is the source; this is the wire mirror the page reads) */
81
+ export type CheckFacts = {
82
+ checkId: string
83
+ /** the check's one line description (the shape: check.ts
84
+ * descriptionProblem). Every inventory row carries one */
85
+ description: string
86
+ /** the wire lines of its last run, rendered as inline markdown */
87
+ lastLines: string[]
88
+ gate: boolean
89
+ /** a build step: the gate runs it, the health pass does not */
90
+ build: boolean
91
+ blocking: boolean | null
92
+ advisory: boolean
93
+ optIn: boolean
94
+ when: WhenAt
95
+ whenReason: string | null
96
+ whenSource: WhenSource
97
+ file: string | null
98
+ doc: string | null
99
+ cmd: string[] | null
100
+ script?: { name: string; body: string } | null
101
+ deps: string[] | null
102
+ from: CheckFrom
103
+ group: string | null
104
+ inputs?: string[]
105
+ ttl?: string
106
+ never?: string
107
+ timeoutMs: number
108
+ status: CheckStatus | null
109
+ replayed: boolean
110
+ lastMs: number | null
111
+ lastAt: number | null
112
+ avgMs: number | null
113
+ samples: number
114
+ slowestMs: number | null
115
+ failures: number
116
+ /** when it was last measured rather than replayed, and what that said */
117
+ measuredAt?: number | null
118
+ measuredStatus?: CheckStatus | null
119
+ /** a world probe only: when its remembered answer runs out */
120
+ keepsUntil?: number | null
121
+ recent?: RecentRun[]
122
+ }
123
+
124
+ /** the hub's daily health pass timer, as the resources feed sends it
125
+ * (serve/health-schedule.ts). null = this hub runs no timer */
126
+ export type Schedule = { everyMs: number; lastPassAt: number | null; nextAt: number; running: boolean }
127
+
128
+ /** one declared slow suite, with the case test-speed doctrine demands */
129
+ export type SlowSuite = { file: string; catches: string; whyNotFast: string }
130
+
131
+ /** the managed block in repo.config.ts, as the page sees it. `hash` is what
132
+ * the next write hands back, so a write can never land on top of an edit the
133
+ * page never saw. `dirty` is the file waiting for a commit */
134
+ export type GateBlock = {
135
+ path: string
136
+ hash: string
137
+ state: 'missing-file' | 'missing-block' | 'stale' | 'current'
138
+ when: Record<string, string>
139
+ tests: { budgetMs?: number; slow?: SlowSuite[]; serial?: { file: string; why: string }[] }
140
+ declaredOutside: boolean
141
+ dirty: boolean
142
+ /** the prose words block inside `prose: {` (core/when-block.ts) */
143
+ wordsState?: 'missing-file' | 'missing-block' | 'stale' | 'current' | 'no-prose'
144
+ }
145
+
146
+ /** one check finishing, as the run stream sends it */
147
+ export type CheckEvent = {
148
+ id: string
149
+ name: string
150
+ status: CheckStatus
151
+ ms: number
152
+ replayed: boolean
153
+ blocking: boolean | null
154
+ advisory: boolean
155
+ lines: { kind: string; text: string }[]
156
+ }
157
+ /** the colour of the tree label: red broken · warn taste pending · ok
158
+ * passing on a check that ran · idle the neutral default · quiet archived or out of play */
159
+ export type ResourceState = 'red' | 'warn' | 'ok' | 'idle' | 'quiet'
160
+ export type Resource = {
61
161
  id: string
162
+ collection: string
163
+ type: ResourceType
164
+ /** a slash path: the truth file, or a virtual root named for the type */
165
+ path: string
166
+ virtual?: boolean
167
+ title: string
168
+ state: ResourceState
62
169
  text: string
170
+ /** the ONE short fact the tree shows in its note slot (an ask its kind, a
171
+ * secret present or missing, a post its venue, an asset its roll count) */
172
+ slot?: string
63
173
  severity?: 'red' | 'yellow'
64
174
  href?: string
65
175
  artifact?: Artifact
@@ -70,5 +180,10 @@ export type ColInstance = {
70
180
  /** the truth file's markdown BODY (frontmatter stripped) — clamped card
71
181
  * in the grid, full in the item view */
72
182
  md?: string
183
+ /** gate checks only: what it is, and what it last cost */
184
+ check?: CheckFacts
185
+ /** test files only: where it runs and what it last cost */
186
+ test?: TestFacts
73
187
  }
74
- export type Col = { id: string; title: string; media: string; instances: ColInstance[] }
188
+ export type ColMeta = { id: string; title: string; media: string }
189
+ export type Col = ColMeta & { instances: Resource[] }