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.
- package/CHANGELOG.md +371 -16
- package/README.md +26 -14
- package/package.json +2 -2
- package/src/assets/gen/comfyts-gen.ts +27 -10
- package/src/assets/gen/generate-roll.ts +1 -1
- package/src/assets/kit-config.ts +6 -6
- package/src/assets/run-assets.ts +14 -10
- package/src/assets/server/studio-server.ts +6 -5
- package/src/check.ts +343 -0
- package/src/{rules/SK001-package-json-shape.ts → checks/SK001-package-json-shape.check.ts} +44 -46
- package/src/checks/SK002-contracts-fulfillness.check.ts +24 -0
- package/src/checks/SK003-claude-md-shape.check.ts +66 -0
- package/src/checks/SK004-gitignore-policy.check.ts +309 -0
- package/src/{rules/SK005-rvlib-versions.ts → checks/SK005-rvlib-versions.check.ts} +46 -32
- package/src/{rules/SK006-npm-publish-safety.ts → checks/SK006-npm-publish-safety.check.ts} +25 -24
- package/src/{rules/SK007-github-visibility.ts → checks/SK007-github-visibility.check.ts} +20 -14
- package/src/{rules/SK008-reviewer-agent.ts → checks/SK008-reviewer-agent.check.ts} +29 -14
- package/src/{rules/SK009-commit-ai-tells.ts → checks/SK009-commit-ai-tells.check.ts} +24 -21
- package/src/checks/SK010-undeclared-unit.check.ts +111 -0
- package/src/{rules/SK011-sibling-unit-ranges.ts → checks/SK011-sibling-unit-ranges.check.ts} +21 -20
- package/src/{rules/SK012-config-barrel-import.ts → checks/SK012-config-barrel-import.check.ts} +18 -23
- package/src/{rules/SK013-one-way-in-policy.ts → checks/SK013-one-way-in-policy.check.ts} +58 -50
- package/src/{rules/SK014-prose-ste.ts → checks/SK014-prose-ste.check.ts} +27 -13
- package/src/{rules/SK015-nested-repo.ts → checks/SK015-nested-repo.check.ts} +15 -12
- package/src/checks/SK016-legacy-check-shapes.check.ts +182 -0
- package/src/checks/SK017-vscode-hub-task.check.ts +197 -0
- package/src/checks/SK018-test-files-run.check.ts +28 -0
- package/src/checks/SK019-tests-run-in-units.check.ts +76 -0
- package/src/checks/SK020-self-spawning-check.check.ts +147 -0
- package/src/checks/contracts.check.ts +20 -0
- package/src/cli/cmd_Asks.ts +3 -2
- package/src/cli/cmd_Assets.ts +2 -2
- package/src/cli/cmd_Bump.ts +1 -1
- package/src/cli/cmd_Check.ts +6 -6
- package/src/cli/cmd_Checks.ts +95 -0
- package/src/cli/cmd_Ci.ts +48 -13
- package/src/cli/cmd_Commands.ts +2 -6
- package/src/cli/cmd_ConfigKeys.ts +69 -0
- package/src/cli/cmd_Contracts.ts +1 -1
- package/src/cli/cmd_Fleet.ts +6 -2
- package/src/cli/cmd_Hooks.ts +1 -1
- package/src/cli/cmd_Keys.ts +43 -18
- package/src/cli/cmd_Mv.ts +2 -2
- package/src/cli/cmd_Prose.ts +10 -1
- package/src/cli/cmd_Releases.ts +2 -2
- package/src/cli/cmd_Serve.ts +1 -1
- package/src/cli/cmd_Start.ts +6 -6
- package/src/cli/cmd_Status.ts +25 -0
- package/src/cli/cmd_Tui.ts +2 -2
- package/src/cli.ts +7 -4
- package/src/collections/actions.ts +4 -4
- package/src/collections/checks.ts +258 -0
- package/src/collections/collection-types.ts +254 -1
- package/src/collections/comfy.ts +23 -0
- package/src/collections/md-body.ts +14 -0
- package/src/collections/pings.ts +38 -12
- package/src/collections/posts.ts +19 -3
- package/src/collections/providers.ts +198 -91
- package/src/collections/reflections.ts +41 -0
- package/src/collections/state-rules.ts +73 -0
- package/src/collections/test-time.ts +288 -0
- package/src/collections/tests.ts +181 -0
- package/src/collections/wikis.ts +66 -0
- package/src/comfy/comfy-panel.ts +292 -0
- package/src/contracts/apply-contracts.ts +17 -12
- package/src/contracts/contract-types.ts +16 -4
- package/src/contracts/contracts-engine.ts +78 -42
- package/src/contracts/manifests/discord.contract.ts +6 -6
- package/src/contracts/manifests/identity.contract.ts +3 -3
- package/src/contracts/manifests/itch.contract.ts +12 -15
- package/src/contracts/manifests/social.contract.ts +5 -5
- package/src/contracts/manifests/steam.contract.ts +16 -16
- package/src/contracts/run-contracts.ts +4 -3
- package/src/core/attach.ts +96 -0
- package/src/core/canonical-scripts.ts +25 -0
- package/src/core/check-ctx.ts +204 -14
- package/src/core/check-file-names.ts +112 -0
- package/src/core/check-pass.ts +84 -0
- package/src/core/check-runner.ts +796 -0
- package/src/core/config-keys.ts +256 -0
- package/src/core/config-rewrite.ts +77 -0
- package/src/core/discover-checks.ts +166 -90
- package/src/core/discover-legacy-checks.ts +26 -0
- package/src/core/doc-block.ts +13 -0
- package/src/core/floor-resolve.ts +264 -0
- package/src/core/floor.ts +20 -137
- package/src/core/git-bin.ts +24 -0
- package/src/core/git-root.ts +108 -0
- package/src/core/instance.ts +195 -0
- package/src/core/jobs.ts +118 -0
- package/src/core/layout.ts +52 -0
- package/src/core/legacy-shapes.ts +207 -0
- package/src/core/lib-root.ts +5 -0
- package/src/core/locked-write.ts +110 -39
- package/src/core/machine-commit.ts +5 -4
- package/src/core/migrate-layout.ts +557 -0
- package/src/core/motherhome.ts +2 -8
- package/src/core/nesting.ts +4 -12
- package/src/core/one-way-in-policy.ts +73 -17
- package/src/core/outcome-words.ts +45 -0
- package/src/core/package-manager.ts +86 -0
- package/src/core/parallel.ts +21 -0
- package/src/core/process-tree.ts +44 -0
- package/src/core/raw-access.ts +93 -0
- package/src/core/read-set.ts +152 -0
- package/src/core/run-check.ts +155 -213
- package/src/core/run-ci.ts +153 -189
- package/src/core/run-fix.ts +96 -0
- package/src/core/run-fixers.ts +53 -30
- package/src/core/self-update.ts +9 -3
- package/src/core/slots.ts +83 -0
- package/src/core/spawn-timeout.ts +34 -3
- package/src/core/when-block.ts +401 -0
- package/src/core/when.ts +135 -0
- package/src/core/write-status.ts +5 -2
- package/src/hooks/leak-guard.ts +10 -8
- package/src/hooks/one-way-in-guard.ts +13 -5
- package/src/hooks/root-identity.ts +2 -1
- package/src/hooks/run-hooks.ts +35 -23
- package/src/init-repo.ts +39 -23
- package/src/ledger/check-stats.ts +155 -0
- package/src/ledger/check-table.ts +179 -0
- package/src/ledger/db.ts +255 -0
- package/src/ledger/gate-checks.ts +433 -0
- package/src/ledger/git-key.ts +116 -77
- package/src/ledger/git-scan.ts +96 -35
- package/src/ledger/ledger.ts +116 -42
- package/src/ledger/live-runs.ts +111 -0
- package/src/ledger/live-timeline.ts +97 -0
- package/src/ledger/repo-view.ts +73 -0
- package/src/ledger/run-log.ts +448 -0
- package/src/ledger/run-timeline.ts +173 -0
- package/src/ledger/store.ts +23 -12
- package/src/ledger/suite-log.ts +161 -0
- package/src/ledger/toolchain-identity.ts +7 -1
- package/src/ledger/world-cache.ts +111 -0
- package/src/mv/mv-md.ts +16 -6
- package/src/problems/extract.ts +182 -0
- package/src/problems/problem-board.ts +37 -0
- package/src/problems/problem-line.ts +77 -0
- package/src/prose/prose-view.ts +90 -0
- package/src/prose/run-prose.ts +60 -19
- package/src/prose/ste.ts +140 -24
- package/src/reflections/reflections-queue.ts +81 -0
- package/src/release/git-facts.ts +15 -9
- package/src/release/run-bump.ts +6 -5
- package/src/release/run-release.ts +25 -11
- package/src/release/run-releases.ts +2 -2
- package/src/release/unit-gate.ts +7 -54
- package/src/release/unit-resolve.ts +282 -0
- package/src/release/units.ts +33 -217
- package/src/repo-config.ts +155 -85
- package/src/rules/rule.ts +36 -96
- package/src/rules/untrack-generated.ts +5 -4
- package/src/scaffold/files/vscode-tasks.json +10 -31
- package/src/scaffold/jsonc-spans.ts +113 -0
- package/src/scaffold/project-scaffold.ts +16 -43
- package/src/scan.ts +98 -0
- package/src/serve/gate-trigger.ts +148 -0
- package/src/serve/health-schedule.ts +102 -0
- package/src/serve/hub-app/chart-view.ts +179 -0
- package/src/serve/hub-app/check-view.tsx +345 -0
- package/src/serve/hub-app/check-words.ts +164 -0
- package/src/serve/hub-app/checks-svc.ts +280 -0
- package/src/serve/hub-app/clock-svc.ts +33 -0
- package/src/serve/hub-app/col-card.tsx +23 -39
- package/src/serve/hub-app/cols-svc.ts +44 -20
- package/src/serve/hub-app/comfy-svc.ts +171 -0
- package/src/serve/hub-app/comfy-view.tsx +81 -0
- package/src/serve/hub-app/dur.ts +41 -0
- package/src/serve/hub-app/hash-route.ts +121 -0
- package/src/serve/hub-app/header-bar.tsx +117 -0
- package/src/serve/hub-app/health-svc.ts +23 -28
- package/src/serve/hub-app/help.tsx +29 -0
- package/src/serve/hub-app/hub-app.tsx +122 -119
- package/src/serve/hub-app/hub-st.ts +115 -42
- package/src/serve/hub-app/hub-types.ts +117 -2
- package/src/serve/hub-app/hub.css +534 -192
- package/src/serve/hub-app/index.html +2 -0
- package/src/serve/hub-app/live-svc.ts +174 -0
- package/src/serve/hub-app/md.ts +15 -0
- package/src/serve/hub-app/media-svc.ts +4 -1
- package/src/serve/hub-app/media-view.tsx +15 -0
- package/src/serve/hub-app/next-run.ts +68 -0
- package/src/serve/hub-app/project-gutter.css +65 -0
- package/src/serve/hub-app/project-gutter.tsx +128 -0
- package/src/serve/hub-app/projects-svc.ts +226 -0
- package/src/serve/hub-app/prose-hits-st.ts +124 -0
- package/src/serve/hub-app/prose-hits-view.tsx +176 -0
- package/src/serve/hub-app/prose-hits.css +21 -0
- package/src/serve/hub-app/run-strip.ts +47 -0
- package/src/serve/hub-app/runs-tabs.ts +24 -0
- package/src/serve/hub-app/test-view.tsx +107 -0
- package/src/serve/hub-app/tests-svc.ts +62 -0
- package/src/serve/hub-app/tests-view.tsx +238 -0
- package/src/serve/hub-app/time-ago.tsx +34 -0
- package/src/serve/hub-app/timeline-chart.tsx +274 -0
- package/src/serve/hub-app/timeline-geometry.ts +314 -0
- package/src/serve/hub-app/timeline-svc.ts +62 -0
- package/src/serve/hub-app/timeline-view.tsx +245 -0
- package/src/serve/hub-app/tree-build.ts +410 -0
- package/src/serve/hub-app/tree-row.tsx +42 -0
- package/src/serve/hub-app/tree-svc.ts +174 -0
- package/src/serve/hub-app/wikis-view.tsx +53 -0
- package/src/serve/other-repos.ts +79 -0
- package/src/serve/project-gutter-route.ts +350 -0
- package/src/serve/project-gutter.ts +449 -0
- package/src/serve/run-serve.ts +245 -16
- package/src/serve/same-origin.ts +14 -0
- package/src/serve/serve-hub.ts +213 -18
- package/src/shipkit.ts +19 -12
- package/src/smoke/run-smoke.ts +3 -2
- package/src/start-repo.ts +75 -36
- package/src/suite/judge-suites.ts +216 -18
- package/src/suite/kept-timings.ts +58 -0
- package/src/suite/runner-shapes.ts +88 -0
- package/src/suite/suite-cli.ts +147 -24
- package/src/suite/suite-cpu-preload.ts +92 -0
- package/src/suite/test-units.ts +243 -0
- package/src/ts/config.ts +4 -69
- package/src/ts/jsonc.ts +69 -0
- package/src/ts/lex.ts +143 -0
- package/src/ts/rename.ts +73 -10
- package/src/ts/source-graph.ts +28 -8
- package/src/ts/tsconfig-projects.ts +6 -97
- package/src/ts/tsconfig-resolve.ts +108 -0
- package/src/tui/build-steps-panel.ts +61 -0
- package/src/tui/checks-panel.ts +24 -16
- package/src/tui/run-tui.ts +2 -2
- package/src/tui/tui-panel.ts +5 -4
- package/src/wiki/graph-colors.ts +68 -0
- package/src/wiki/graph-mount.ts +7 -9
- package/src/wiki/graph-page.ts +42 -25
- package/src/wiki/graph-positions.ts +105 -0
- package/src/wiki/run-wiki.ts +1 -1
- package/src/wiki/serve-graph.ts +33 -3
- package/src/cli/cmd_Rules.ts +0 -51
- package/src/cli/cmd_Units.ts +0 -82
- package/src/collections/graphs.ts +0 -32
- package/src/contracts/contracts-once.ts +0 -43
- package/src/core/check-suite.ts +0 -302
- package/src/core/repair-contract-dirs.ts +0 -136
- package/src/core/task-runner.ts +0 -339
- package/src/ledger/gate-units.ts +0 -138
- package/src/ledger/unit-table.ts +0 -105
- package/src/rules/SK002-contracts-fulfillness.ts +0 -24
- package/src/rules/SK003-claude-md-shape.ts +0 -70
- package/src/rules/SK004-gitignore-policy.ts +0 -238
- package/src/rules/SK010-undeclared-release-unit.ts +0 -127
- package/src/rules/rule-registry.ts +0 -64
- package/src/rules/run-fix.ts +0 -72
- package/src/tui/tasks-panel.ts +0 -60
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// The view state of THE run chart: its zoom, the width it has on screen and
|
|
2
|
+
// what is under the pointer, over the geometry (timeline-geometry.ts) and the
|
|
3
|
+
// drawing (timeline-chart.tsx). The live strip owns it, and nothing else in
|
|
4
|
+
// the hub draws a run. What differs is the source: folded, a rolling window
|
|
5
|
+
// that ends now; unfolded, the whole record opened on its last minutes.
|
|
6
|
+
import { makeAutoObservable, observableRef } from 'mobx'
|
|
7
|
+
import type { TimelineInvocation } from '../../ledger/run-timeline.ts'
|
|
8
|
+
import {
|
|
9
|
+
anchorScroll, buildAxis, fitScale, fitWindow, layoutTimeline, maxZoom, PAD_PX, windowAxis, ZOOM_MIN, zoomStep, type Axis, type Layout, type Span,
|
|
10
|
+
} from './timeline-geometry.ts'
|
|
11
|
+
|
|
12
|
+
/** what a chart draws. `windowMs` null = the whole record, with idle
|
|
13
|
+
* stretches folded; a number = the rolling window that ends at `now` */
|
|
14
|
+
export type ChartSource = {
|
|
15
|
+
invocations: TimelineInvocation[]
|
|
16
|
+
now: number
|
|
17
|
+
windowMs: number | null
|
|
18
|
+
laneCap?: number
|
|
19
|
+
/** false = drawn at zoom 1 whatever the stored zoom says */
|
|
20
|
+
zoomable?: boolean
|
|
21
|
+
/** the runner's cap, drawn over the concurrency band */
|
|
22
|
+
cap?: number
|
|
23
|
+
/** the span the view opens on: at its home zoom this much time, ending
|
|
24
|
+
* now, fills the width. Absent = home is zoom 1, the whole source */
|
|
25
|
+
homeSpanMs?: number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** what the tooltip says, and the viewport point it is fixed at */
|
|
29
|
+
export type ChartTip = { x: number; y: number; head: string; tone: string; lines: string[] }
|
|
30
|
+
|
|
31
|
+
export class ChartView {
|
|
32
|
+
/** 1 fits the whole source in the view. null = home: the zoom the source
|
|
33
|
+
* opens on, which moves with the width and the source */
|
|
34
|
+
zoom: number | null
|
|
35
|
+
/** the width the chart has on screen, 0 until it is measured */
|
|
36
|
+
viewW = 0
|
|
37
|
+
hover: ChartTip | null = null
|
|
38
|
+
/** FOLLOW: the right edge is now and the view advances with the clock.
|
|
39
|
+
* Off, the view stays put in absolute time until the reader comes back
|
|
40
|
+
* to the right edge or asks for live again */
|
|
41
|
+
follow = true
|
|
42
|
+
/** the scale the view keeps while it does not follow, so nothing on it
|
|
43
|
+
* moves as the record grows */
|
|
44
|
+
pinned: number | null = null
|
|
45
|
+
/** the moment the view stands still at while a tooltip is open, so the
|
|
46
|
+
* thing under the pointer can be read */
|
|
47
|
+
heldNow: number | null = null
|
|
48
|
+
|
|
49
|
+
/** `key` persists the zoom per browser; null keeps it for the tab */
|
|
50
|
+
constructor(private key: string | null, private source: () => ChartSource | null) {
|
|
51
|
+
this.zoom = this.readZoom()
|
|
52
|
+
makeAutoObservable<this, 'key' | 'source'>(this, { key: false, source: false, hover: observableRef })
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private readZoom(): number | null {
|
|
56
|
+
if (this.key == null) return null
|
|
57
|
+
try {
|
|
58
|
+
const z = Number(globalThis.localStorage?.getItem(this.key) ?? '')
|
|
59
|
+
return Number.isFinite(z) && z >= ZOOM_MIN ? z : ZOOM_MIN
|
|
60
|
+
} catch {
|
|
61
|
+
return ZOOM_MIN
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private get src(): ChartSource | null {
|
|
66
|
+
return this.source()
|
|
67
|
+
}
|
|
68
|
+
/** the moment the view is drawn at: the source's now, unless a tooltip
|
|
69
|
+
* holds it still */
|
|
70
|
+
get now(): number {
|
|
71
|
+
return this.heldNow ?? this.src?.now ?? 0
|
|
72
|
+
}
|
|
73
|
+
private get spans(): Span[] {
|
|
74
|
+
return (this.src?.invocations ?? []).map((i) => ({ start: i.started, end: i.running === true ? this.now : i.ended }))
|
|
75
|
+
}
|
|
76
|
+
/** where a rolling window starts, null over the whole record */
|
|
77
|
+
get from(): number | null {
|
|
78
|
+
const s = this.src
|
|
79
|
+
return s == null || s.windowMs == null ? null : this.now - s.windowMs
|
|
80
|
+
}
|
|
81
|
+
/** the scale at zoom 1 */
|
|
82
|
+
get fit(): number {
|
|
83
|
+
const s = this.src
|
|
84
|
+
const w = Math.max(200, this.viewW)
|
|
85
|
+
if (s == null) return 1
|
|
86
|
+
return this.from == null ? fitScale(this.spans, this.now, w) : fitWindow(this.from, this.now, w)
|
|
87
|
+
}
|
|
88
|
+
/** the runner's cap the source carries, drawn over the concurrency band */
|
|
89
|
+
get cap(): number | undefined {
|
|
90
|
+
return this.src?.cap
|
|
91
|
+
}
|
|
92
|
+
get maxZoom(): number {
|
|
93
|
+
return maxZoom(this.fit)
|
|
94
|
+
}
|
|
95
|
+
/** the zoom at which the source's home span of drawn time fills the
|
|
96
|
+
* width. Idle stretches are folded, so a record with less drawn time
|
|
97
|
+
* than the span opens fitted */
|
|
98
|
+
get homeZoom(): number {
|
|
99
|
+
const s = this.src
|
|
100
|
+
if (s?.homeSpanMs == null) return ZOOM_MIN
|
|
101
|
+
const px = Math.max(1, Math.max(200, this.viewW) - 2 * PAD_PX) / s.homeSpanMs
|
|
102
|
+
return Math.min(this.maxZoom, Math.max(ZOOM_MIN, px / this.fit))
|
|
103
|
+
}
|
|
104
|
+
/** the zoom in effect */
|
|
105
|
+
get level(): number {
|
|
106
|
+
if (this.src?.zoomable === false) return ZOOM_MIN
|
|
107
|
+
return Math.min(this.zoom ?? this.homeZoom, this.maxZoom)
|
|
108
|
+
}
|
|
109
|
+
/** the reader zoomed away from home */
|
|
110
|
+
get zoomed(): boolean {
|
|
111
|
+
return this.zoom != null
|
|
112
|
+
}
|
|
113
|
+
/** px per ms: the zoom's while following, the pinned one while not */
|
|
114
|
+
get scale(): number {
|
|
115
|
+
return this.follow || this.pinned == null ? this.fit * this.level : this.pinned
|
|
116
|
+
}
|
|
117
|
+
get layout(): Layout | null {
|
|
118
|
+
const s = this.src
|
|
119
|
+
if (s == null || this.viewW === 0) return null
|
|
120
|
+
return layoutTimeline(s.invocations, this.now, this.scale, { from: this.from ?? undefined, laneCap: s.laneCap, cap: s.cap })
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private axisAt(scale: number): Axis {
|
|
124
|
+
return this.from == null ? buildAxis(this.spans, this.now, scale) : windowAxis(this.from, this.now, scale)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** stop following: the view keeps its scale and its place in time */
|
|
128
|
+
detach(): void {
|
|
129
|
+
if (!this.follow) return
|
|
130
|
+
this.pinned = this.scale
|
|
131
|
+
this.follow = false
|
|
132
|
+
}
|
|
133
|
+
/** follow again: the right edge is now */
|
|
134
|
+
attach(): void {
|
|
135
|
+
this.follow = true
|
|
136
|
+
this.pinned = null
|
|
137
|
+
}
|
|
138
|
+
/** the reader moved the view: away from the right edge it stops
|
|
139
|
+
* following, back at the right edge it follows again */
|
|
140
|
+
scrolled(scrollLeft: number, clientWidth: number): void {
|
|
141
|
+
const w = this.layout?.axis.width
|
|
142
|
+
if (w == null) return
|
|
143
|
+
const atRight = scrollLeft + clientWidth >= w - 4
|
|
144
|
+
if (atRight && !this.follow) this.attach()
|
|
145
|
+
else if (!atRight && this.follow) this.detach()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
setViewW(w: number): void {
|
|
149
|
+
this.viewW = w
|
|
150
|
+
}
|
|
151
|
+
/** a tooltip opens or closes. While one is open the view stands still */
|
|
152
|
+
setHover(t: ChartTip | null): void {
|
|
153
|
+
this.hover = t
|
|
154
|
+
if (t == null) this.heldNow = null
|
|
155
|
+
else if (this.heldNow == null) this.heldNow = this.src?.now ?? null
|
|
156
|
+
}
|
|
157
|
+
/** back to the zoom the source opens on */
|
|
158
|
+
home(): void {
|
|
159
|
+
this.zoom = null
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** zoom to `next` around the point `cx` of the viewport, and say where to
|
|
163
|
+
* scroll so the moment under that point stays under it */
|
|
164
|
+
private zoomTo(next: number, cx: number, scrollLeft: number): number {
|
|
165
|
+
const before = this.axisAt(this.scale)
|
|
166
|
+
this.zoom = Math.min(this.maxZoom, Math.max(ZOOM_MIN, next))
|
|
167
|
+
if (!this.follow) this.pinned = this.fit * this.level
|
|
168
|
+
if (this.key != null) {
|
|
169
|
+
try { globalThis.localStorage?.setItem(this.key, String(this.zoom)) } catch { /* private window: the zoom lasts the tab */ }
|
|
170
|
+
}
|
|
171
|
+
return anchorScroll(before, this.axisAt(this.scale), scrollLeft, cx)
|
|
172
|
+
}
|
|
173
|
+
wheel(deltaY: number, cx: number, scrollLeft: number): number {
|
|
174
|
+
return this.zoomTo(zoomStep(this.level, deltaY, this.maxZoom), cx, scrollLeft)
|
|
175
|
+
}
|
|
176
|
+
zoomBy(factor: number, cx: number, scrollLeft: number): number {
|
|
177
|
+
return this.zoomTo(this.level * factor, cx, scrollLeft)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
// ONE GATE CHECK, on its own page, as ONE compact block: the id as a tag, the
|
|
2
|
+
// title, the sentence it wrote about itself, the exact command it runs, then
|
|
3
|
+
// one grid of facts, each a dim label and a short value whose explanation is
|
|
4
|
+
// the row's `(?)`. No box holds a single field. Navigation is the tree on the
|
|
5
|
+
// left, so there is no back link.
|
|
6
|
+
import { observer } from 'mobx-react-lite'
|
|
7
|
+
import { useEffect, useRef } from 'react'
|
|
8
|
+
import { CACHED } from '../../core/outcome-words.ts'
|
|
9
|
+
import { durationTone, fmtMs } from './dur.ts'
|
|
10
|
+
import { Help, TERMS } from './help.tsx'
|
|
11
|
+
import type { HubSt } from './hub-st.ts'
|
|
12
|
+
import type { SlowSuite, CheckFacts, RecentRun, WhenAt } from './hub-types.ts'
|
|
13
|
+
import { renderMd, renderMdInline } from './md.ts'
|
|
14
|
+
import { nextRun } from './next-run.ts'
|
|
15
|
+
import { ProseHits } from './prose-hits-view.tsx'
|
|
16
|
+
import { stampWords, TimeAgo, TimeAhead } from './time-ago.tsx'
|
|
17
|
+
import { checkName, commandOf, docPath, editorHref, lastRun, rememberedFact, repoPath, whatFacts, WHEN_OPTION, type ShortPath } from './check-words.ts'
|
|
18
|
+
|
|
19
|
+
/** which declaration is answering, so the switch never looks like it wrote
|
|
20
|
+
* something it did not. The CHIP is the short one, its help is the sentence */
|
|
21
|
+
const SOURCE_CHIP: Record<CheckFacts['whenSource'], string> = {
|
|
22
|
+
when: 'repo.config.ts',
|
|
23
|
+
'check-file': 'the check file',
|
|
24
|
+
ruleExempt: 'ruleExempt',
|
|
25
|
+
optIn: 'optIn',
|
|
26
|
+
'gate-export': 'gate = false',
|
|
27
|
+
defaults: 'defaults',
|
|
28
|
+
default: 'default',
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const SOURCE_WORD: Record<CheckFacts['whenSource'], string> = {
|
|
32
|
+
when: 'set in the `when` block of `repo.config.ts`, which this switch writes',
|
|
33
|
+
'check-file': 'declared by the check file itself, and the `when` block overrides it',
|
|
34
|
+
ruleExempt: 'from the older `ruleExempt`, which reads as off',
|
|
35
|
+
optIn: 'from the older `optIn` key, which reads as health',
|
|
36
|
+
'gate-export': 'from `export const gate = false` in its own file',
|
|
37
|
+
defaults: 'from the `defaults` block, which removes the floor slot entirely',
|
|
38
|
+
default: 'nobody declared where this check runs, so it takes the default',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const WHEN_VALUES: WhenAt[] = ['always', 'release', 'health', 'off']
|
|
42
|
+
|
|
43
|
+
const VERDICT_WORD: Record<'pass' | 'warn' | 'fail' | 'skip', string> = {
|
|
44
|
+
pass: 'passed', warn: 'warned', fail: 'failed', skip: 'skipped',
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ── the pieces ───────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
/** one duration, in the one unit, in the one tone */
|
|
50
|
+
const Dur = ({ ms, ceiling }: { ms: number | null | undefined; ceiling?: number }) =>
|
|
51
|
+
ms == null
|
|
52
|
+
? <span className="dim">not timed yet</span>
|
|
53
|
+
: <span className={`dnum ${durationTone(ms, ceiling) ?? ''}`}>{fmtMs(ms)}</span>
|
|
54
|
+
|
|
55
|
+
const MdHelp = ({ text }: { text: string }) => <Help text={<span className="md" dangerouslySetInnerHTML={{ __html: renderMd(text) }} />} />
|
|
56
|
+
|
|
57
|
+
/** one fact: the label dim and small on the left, the value on the right. On
|
|
58
|
+
* a wide screen the grid holds two of them per line */
|
|
59
|
+
export const Row = ({ k, help, wide, children }: { k: string; help?: string; wide?: boolean; children: React.ReactNode }) => (
|
|
60
|
+
<div className={wide === true ? 'cf wide' : 'cf'}>
|
|
61
|
+
<div className="ck">{k}{help == null ? null : <MdHelp text={help} />}</div>
|
|
62
|
+
<div className="cv">{children}</div>
|
|
63
|
+
</div>
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
/** a path, short, never split mid token, the full one in its tooltip */
|
|
67
|
+
export const PathCode = ({ p, href }: { p: ShortPath; href?: string | null }) => {
|
|
68
|
+
const code = <code className="cpath" title={p.full}>{p.short}</code>
|
|
69
|
+
return href == null ? code : <a href={href} title={p.full}>{code}</a>
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** the section title for what is not a fact: the output, the slow suites */
|
|
73
|
+
export const Section = ({ title, help, children }: { title: string; help?: string; children: React.ReactNode }) => (
|
|
74
|
+
<section className="csec">
|
|
75
|
+
<div className="cstitle">{title}{help != null ? <Help text={help} /> : null}</div>
|
|
76
|
+
{children}
|
|
77
|
+
</section>
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
/** the sentence the check states about itself, as markdown, under the title */
|
|
81
|
+
export const Says = ({ description }: { description: string }) =>
|
|
82
|
+
<div className="usays md" dangerouslySetInnerHTML={{ __html: renderMd(description) }} />
|
|
83
|
+
|
|
84
|
+
// ── what it runs ─────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
export const copy = (text: string): void => {
|
|
87
|
+
try { void navigator.clipboard?.writeText(text) } catch { /* a page served over plain http has no clipboard */ }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** THE COMMAND, exactly as the gate spawns it from the repo root, or the one
|
|
91
|
+
* line saying its body is a function inside the gate */
|
|
92
|
+
export const CommandLine = ({ app, u }: { app: HubSt; u: CheckFacts }) => {
|
|
93
|
+
const c = commandOf(u, app.root)
|
|
94
|
+
if (c.kind === 'in-process')
|
|
95
|
+
return (
|
|
96
|
+
<div className="ccmd">
|
|
97
|
+
<Row k="runs" help="its body is a function, so it runs inside the gate process and a throw of its own is its own failure rather than the gate crashing" wide>
|
|
98
|
+
in the gate process
|
|
99
|
+
{c.file == null
|
|
100
|
+
? <span className="dim">, its source ships inside the toolkit</span>
|
|
101
|
+
: <>, source <PathCode p={repoPath(c.file, app.root)} href={editorHref(app.root, c.file)} /></>}
|
|
102
|
+
</Row>
|
|
103
|
+
</div>
|
|
104
|
+
)
|
|
105
|
+
return (
|
|
106
|
+
<div className="ccmd">
|
|
107
|
+
<Row k="command" help="the exact argv the gate spawns from the repo root. Its exit code is truth: a non zero exit with no FAIL line still fails loud" wide>
|
|
108
|
+
<code className="cargv" title={u.cmd?.join(' ')}>
|
|
109
|
+
{c.tokens.map((t, n) => (
|
|
110
|
+
<span key={n}>
|
|
111
|
+
{n > 0 ? ' ' : null}
|
|
112
|
+
{t.short === u.file && editorHref(app.root, u.file) != null
|
|
113
|
+
? <a className="tok" href={editorHref(app.root, u.file)!} title={t.full}>{t.short}</a>
|
|
114
|
+
: <span className="tok" title={t.full}>{t.short}</span>}
|
|
115
|
+
</span>
|
|
116
|
+
))}
|
|
117
|
+
</code>
|
|
118
|
+
<button className="ccopy" title="copy the command" onClick={() => copy(c.copy)}>copy</button>
|
|
119
|
+
{c.script == null
|
|
120
|
+
? null
|
|
121
|
+
: <div className="cscript">
|
|
122
|
+
<span className="dim">script <code className="cpath">{c.script.name}</code> runs</span> <code className="cargv">{c.script.body}</code>
|
|
123
|
+
</div>}
|
|
124
|
+
</Row>
|
|
125
|
+
</div>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── the switch ───────────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
/** RUN WHEN, one select named by effect. It writes `repo.config.ts` itself,
|
|
132
|
+
* through the managed block, and leaves the file dirty: a change of place is
|
|
133
|
+
* a change its owner reviews in a git client, never a commit made on their
|
|
134
|
+
* behalf. Choosing never asks for the reason before it writes. */
|
|
135
|
+
const RunWhen = observer(({ app, u }: { app: HubSt; u: CheckFacts }) => {
|
|
136
|
+
const s = app.checks
|
|
137
|
+
const g = s.gate
|
|
138
|
+
const shown = s.whenShown(u)
|
|
139
|
+
const reason = s.reasonFor(u.checkId)
|
|
140
|
+
return (
|
|
141
|
+
<Row k="run when" help="where this check runs. Changing it writes repo.config.ts and leaves the file for you to commit">
|
|
142
|
+
<span className="cwhen">
|
|
143
|
+
<select className="wsel" value={shown} disabled={s.gateBusy} onChange={(e) => s.chooseWhen(u.checkId, e.target.value as WhenAt)}>
|
|
144
|
+
{WHEN_VALUES.map((v) => <option key={v} value={v}>{WHEN_OPTION[v]}</option>)}
|
|
145
|
+
</select>
|
|
146
|
+
<Help text={
|
|
147
|
+
<span className="legend">
|
|
148
|
+
{WHEN_VALUES.map((v) => <span key={v} className="lrow"><b>{WHEN_OPTION[v]}</b> ({v}): {TERMS[v]}</span>)}
|
|
149
|
+
</span>
|
|
150
|
+
} />
|
|
151
|
+
<span className="stchip">{SOURCE_CHIP[u.whenSource]}</span>
|
|
152
|
+
<MdHelp text={SOURCE_WORD[u.whenSource]} />
|
|
153
|
+
{u.whenSource === 'when'
|
|
154
|
+
? <button className="wclear" disabled={s.gateBusy} onClick={() => void s.clearWhen(u.checkId)}>drop the entry</button>
|
|
155
|
+
: null}
|
|
156
|
+
</span>
|
|
157
|
+
{shown === 'off'
|
|
158
|
+
? <span className="cblock">
|
|
159
|
+
<input className="wreason" placeholder="why it is off (required, and it rides in the config)"
|
|
160
|
+
value={reason} onChange={(e) => s.setReason(u.checkId, e.target.value)} />
|
|
161
|
+
{u.when === 'off' && u.whenReason != null ? <span className="note">reason today: {u.whenReason}</span> : null}
|
|
162
|
+
<button disabled={s.gateBusy || reason.trim() === ''} onClick={() => void s.setWhen(u.checkId, 'off')}>
|
|
163
|
+
{u.when === 'off' ? 'change the reason' : 'turn it off'}
|
|
164
|
+
</button>
|
|
165
|
+
</span>
|
|
166
|
+
: null}
|
|
167
|
+
{g != null && g.state === 'stale'
|
|
168
|
+
? <span className="cblock warnline">the managed block in {g.path} was hand edited: reconcile it, then the switch works again</span>
|
|
169
|
+
: null}
|
|
170
|
+
{g != null && g.dirty ? <span className="cblock note">{g.path} is edited and not committed, review it in your git client</span> : null}
|
|
171
|
+
{s.gateNote === '' ? null : <span className="cblock note">{s.gateNote}</span>}
|
|
172
|
+
</Row>
|
|
173
|
+
)
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
// ── the numbers ──────────────────────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
/** the last N outcomes as a bar row: an average hides the shape, and the
|
|
179
|
+
* reds it cannot even see */
|
|
180
|
+
const Bars = ({ runs, ceiling }: { runs: RecentRun[]; ceiling: number }) => {
|
|
181
|
+
if (runs.length === 0) return <div className="dim">no run on record yet</div>
|
|
182
|
+
const top = Math.max(...runs.map((r) => r.ms), 1)
|
|
183
|
+
return (
|
|
184
|
+
<div className="bars">
|
|
185
|
+
{runs.map((r, n) => (
|
|
186
|
+
<i key={`${r.at}-${n}`}
|
|
187
|
+
className={`bar ${r.replayed ? 'rep' : r.status === 'fail' ? 'red' : r.status === 'warn' ? 'warn' : r.status === 'skip' ? 'rep' : durationTone(r.ms, ceiling) ?? 'plain'}`}
|
|
188
|
+
style={{ height: `${r.replayed ? 8 : Math.max(10, Math.round((r.ms / top) * 100))}%` }}
|
|
189
|
+
title={`${stampWords(r.at)}: ${r.replayed ? CACHED : `${VERDICT_WORD[r.status]} in ${fmtMs(r.ms)}`}`} />
|
|
190
|
+
))}
|
|
191
|
+
</div>
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** how the last answer came: `ran 3m ago in 412ms`, or `cached 16m ago` */
|
|
196
|
+
export const LastRunValue = observer(({ app, u }: { app: HubSt; u: CheckFacts }) => {
|
|
197
|
+
const l = lastRun(u)
|
|
198
|
+
if (l == null) return <span className="dim">never here</span>
|
|
199
|
+
return (
|
|
200
|
+
<>
|
|
201
|
+
<span className={`tl ${l.tone}`}>{l.word}</span>
|
|
202
|
+
{u.lastAt == null ? null : <> <TimeAgo clock={app.clock} at={u.lastAt} /></>}
|
|
203
|
+
{u.lastMs == null || u.replayed ? null : <> in <Dur ms={u.lastMs} ceiling={u.timeoutMs} /></>}
|
|
204
|
+
{l.verdict == null ? null : <span className={`tl ${l.tone}`}>, {l.verdict}</span>}
|
|
205
|
+
</>
|
|
206
|
+
)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
export const Timings = ({ u }: { u: CheckFacts }) => (
|
|
210
|
+
<Row k="timings" wide help="what this check costs: its last run, the mean of the runs that really ran, the worst one on record, and the limit it is killed at">
|
|
211
|
+
<span className="ctimes">
|
|
212
|
+
<span><span className="ck">last</span> <Dur ms={u.lastMs} ceiling={u.timeoutMs} /></span>
|
|
213
|
+
<span><span className="ck">average</span> {u.samples === 0 ? <span className="dim">not timed yet</span> : <><Dur ms={u.avgMs} ceiling={u.timeoutMs} /> <span className="dim">over {u.samples}</span></>}</span>
|
|
214
|
+
<span><span className="ck">slowest</span> <Dur ms={u.slowestMs} ceiling={u.timeoutMs} />{u.failures > 0 ? <span className="dim">, {u.failures} red</span> : null}</span>
|
|
215
|
+
{/* a limit, never a measurement: it wears no tone of its own */}
|
|
216
|
+
<span><span className="ck">limit</span> <span className="dnum dim">{fmtMs(u.timeoutMs)}</span> <Help term="ceiling" /></span>
|
|
217
|
+
</span>
|
|
218
|
+
<Bars runs={u.recent ?? []} ceiling={u.timeoutMs} />
|
|
219
|
+
</Row>
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
const Remembered = ({ u }: { u: CheckFacts }) => {
|
|
223
|
+
const kept = rememberedFact(u)
|
|
224
|
+
return (
|
|
225
|
+
<Row k="remembered" help={kept.help}>
|
|
226
|
+
{kept.inputs.length === 0
|
|
227
|
+
? kept.v
|
|
228
|
+
: <details className="cinputs">
|
|
229
|
+
<summary>{kept.v} <span className="dim">({kept.inputs.length})</span></summary>
|
|
230
|
+
<span className="winputs">{kept.inputs.map((p) => <code key={p} className="cpath">{p}</code>)}</span>
|
|
231
|
+
</details>}
|
|
232
|
+
</Row>
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ── what it printed ──────────────────────────────────────────────────────
|
|
237
|
+
|
|
238
|
+
/** each wire line renders as INLINE markdown, never as a fenced block: a
|
|
239
|
+
* `fix:` pointer is made of backtick spans and urls, and a fence turns a
|
|
240
|
+
* pointer a reader could click into text they have to retype */
|
|
241
|
+
const LogLines = ({ lines }: { lines: string[] }) => (
|
|
242
|
+
<>{lines.map((l, n) => <div key={`${n}-${l}`} className="wlogline" dangerouslySetInnerHTML={{ __html: renderMdInline(l) }} />)}</>
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
/** the lines of the run in flight, then the lines of the last one. It
|
|
246
|
+
* scrolls itself while a run streams, so the newest line is on screen */
|
|
247
|
+
const LastOutput = observer(({ app, lines }: { app: HubSt; lines: string[] }) => {
|
|
248
|
+
const live = app.checks.liveLines
|
|
249
|
+
const box = useRef<HTMLPreElement>(null)
|
|
250
|
+
useEffect(() => { if (box.current != null) box.current.scrollTop = box.current.scrollHeight }, [live.length])
|
|
251
|
+
return (
|
|
252
|
+
<Section title="last output" help="what it printed, with the fix pointers it emitted. While a run streams, this is the run">
|
|
253
|
+
{live.length > 0
|
|
254
|
+
? <pre className="wlog" ref={box}><LogLines lines={live} /></pre>
|
|
255
|
+
: lines.length > 0
|
|
256
|
+
? <pre className="wlog"><LogLines lines={lines} /></pre>
|
|
257
|
+
: <div className="dim">no lines from its last run: run it to see what it says</div>}
|
|
258
|
+
</Section>
|
|
259
|
+
)
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
/** the slow suites declared under the test step, each with the case it
|
|
263
|
+
* argued. Moving a suite between the tiers is the SAME managed block write,
|
|
264
|
+
* and it demands both answers exactly as `defineRepo` does. */
|
|
265
|
+
const SlowSuites = observer(({ app }: { app: HubSt }) => {
|
|
266
|
+
const s = app.checks
|
|
267
|
+
const declared: SlowSuite[] = s.gate?.tests.slow ?? []
|
|
268
|
+
return (
|
|
269
|
+
<Section title="slow suites" help="the suites the quick tier leaves out. Each one declares what it catches and why it cannot be fast">
|
|
270
|
+
<div className="wline">
|
|
271
|
+
{declared.length === 0
|
|
272
|
+
? <span className="dim">none: every suite runs in the quick tier</span>
|
|
273
|
+
: <>the quick tier leaves {declared.length} out, `shipkit ci --full` runs them</>}
|
|
274
|
+
</div>
|
|
275
|
+
{declared.map((x) => (
|
|
276
|
+
<div key={x.file} className="wsuite">
|
|
277
|
+
<div className="wfile"><a href="#" title="its test file page" onClick={(e) => { e.preventDefault(); app.timeline.openTest(x.file) }}><code className="cpath">{x.file}</code></a>
|
|
278
|
+
<button disabled={s.gateBusy} onClick={() => void s.removeSlow(x.file)}>back to the quick tier</button>
|
|
279
|
+
</div>
|
|
280
|
+
<div className="note">catches: {x.catches}</div>
|
|
281
|
+
<div className="note">why not fast: {x.whyNotFast}</div>
|
|
282
|
+
</div>
|
|
283
|
+
))}
|
|
284
|
+
<div className="wform">
|
|
285
|
+
<input placeholder="tests/heavy.test.ts" value={s.suite.file} onChange={(e) => s.setSuite('file', e.target.value)} />
|
|
286
|
+
<input placeholder="catches: the regression no fast test can catch" value={s.suite.catches} onChange={(e) => s.setSuite('catches', e.target.value)} />
|
|
287
|
+
<input placeholder="why not fast: what the cost is, and what you tried" value={s.suite.whyNotFast} onChange={(e) => s.setSuite('whyNotFast', e.target.value)} />
|
|
288
|
+
<button disabled={s.gateBusy || s.suite.file.trim() === ''} onClick={() => void s.addSlow()}>declare it slow</button>
|
|
289
|
+
</div>
|
|
290
|
+
</Section>
|
|
291
|
+
)
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
// ── the pages ────────────────────────────────────────────────────────────
|
|
295
|
+
|
|
296
|
+
export const CheckCard = observer(({ app, id }: { app: HubSt; id: string }) => {
|
|
297
|
+
const i = app.cols.find('checks', id)
|
|
298
|
+
if (i == null) return <div className="ph">check vanished, the inventory moved on</div>
|
|
299
|
+
const u = i.check
|
|
300
|
+
if (u == null) return <div className="ph">this row is not a gate check</div>
|
|
301
|
+
const live = app.checks.isRunning(u.checkId)
|
|
302
|
+
const name = checkName(u.checkId, i.title)
|
|
303
|
+
const doc = u.doc == null ? null : docPath(u.doc, app.root)
|
|
304
|
+
return (
|
|
305
|
+
<div className="colcard checkcard">
|
|
306
|
+
{/* the run button sits beside the thing it runs, and holds its
|
|
307
|
+
width so a click moves nothing on the page */}
|
|
308
|
+
<div className="chead">
|
|
309
|
+
{name.tag == null ? null : <span className="tid">{name.tag}</span>}
|
|
310
|
+
<h3 className={`ctitle tl ${i.state}`}>{name.title}</h3>
|
|
311
|
+
{u.advisory ? <span className="stchip warn">fails, does not block</span> : null}
|
|
312
|
+
<span className="sp" />
|
|
313
|
+
<button className="primary urun" disabled={app.checks.busy} onClick={() => void app.checks.run([u.checkId])}>
|
|
314
|
+
{live ? <><span className="spin" />running</> : 'run it now'}
|
|
315
|
+
</button>
|
|
316
|
+
</div>
|
|
317
|
+
<Says description={u.description} />
|
|
318
|
+
<CommandLine app={app} u={u} />
|
|
319
|
+
<div className="cfacts">
|
|
320
|
+
{whatFacts(u).map((f) => (
|
|
321
|
+
<Row key={f.k} k={f.k} help={f.help}>{f.tone == null ? f.v : <span className={`tl ${f.tone}`}>{f.v}</span>}</Row>
|
|
322
|
+
))}
|
|
323
|
+
<RunWhen app={app} u={u} />
|
|
324
|
+
<Remembered u={u} />
|
|
325
|
+
{/* the help never depends on the second, so it reads the wall
|
|
326
|
+
clock and the row does not repaint on every tick */}
|
|
327
|
+
<Row k="next run" help={nextRun(u, app.cols.schedule, Date.now()).help}>
|
|
328
|
+
<TimeAhead clock={app.clock} words={(now) => nextRun(u, app.cols.schedule, now)} />
|
|
329
|
+
</Row>
|
|
330
|
+
<Row k="last run" help={u.status == null
|
|
331
|
+
? 'no run of it is on record in this checkout'
|
|
332
|
+
: u.replayed ? TERMS.cached! : TERMS.ran!}>
|
|
333
|
+
<LastRunValue app={app} u={u} />
|
|
334
|
+
</Row>
|
|
335
|
+
{doc == null ? null : <Row k="owner" help="the page that OWNS the invariant, where a `fix:` pointer lands"><PathCode p={doc} /></Row>}
|
|
336
|
+
{u.deps != null && u.deps.length > 0 ? <Row k="waits on" help="the checks that must pass before this one starts">{u.deps.join(', ')}</Row> : null}
|
|
337
|
+
{u.group == null ? null : <Row k="group" help="a label several checks share, so `--skip <group>` parks all of them at once">{u.group}</Row>}
|
|
338
|
+
<Timings u={u} />
|
|
339
|
+
</div>
|
|
340
|
+
{u.build && u.checkId === 'test' ? <SlowSuites app={app} /> : null}
|
|
341
|
+
{u.checkId === 'SK014' ? <ProseHits app={app} /> : null}
|
|
342
|
+
<LastOutput app={app} lines={u.lastLines} />
|
|
343
|
+
</div>
|
|
344
|
+
)
|
|
345
|
+
})
|