rvlib-shipkit 1.2.0 → 1.4.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 +104 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/asks/asks-queue.ts +4 -2
- package/src/assets/kits/identity-kit.ts +15 -13
- package/src/assets/run-assets.ts +15 -10
- package/src/check.ts +26 -10
- package/src/checks/SK001-package-json-shape.check.ts +34 -5
- package/src/checks/SK002-contracts-fulfillness.check.ts +3 -3
- package/src/checks/SK004-gitignore-policy.check.ts +11 -3
- package/src/checks/SK005-rvlib-versions.check.ts +13 -1
- package/src/checks/SK006-npm-publish-safety.check.ts +5 -0
- package/src/checks/SK007-github-visibility.check.ts +9 -9
- package/src/checks/SK008-reviewer-agent.check.ts +14 -8
- package/src/checks/SK009-commit-ai-tells.check.ts +103 -61
- package/src/checks/SK013-one-way-in-policy.check.ts +10 -2
- package/src/checks/SK014-prose-ste.check.ts +16 -6
- package/src/checks/SK015-nested-repo.check.ts +5 -1
- package/src/checks/SK016-legacy-check-shapes.check.ts +11 -5
- package/src/checks/SK017-vscode-hub-task.check.ts +7 -0
- package/src/checks/SK022-machine-runs-the-pin.check.ts +6 -2
- package/src/checks/SK024-identity-colors.check.ts +5 -0
- package/src/checks/SK025-release-drift.check.ts +58 -0
- package/src/checks/SK026-asks-autoclose.check.ts +44 -0
- package/src/checks/SK027-agent-wiki-links.check.ts +50 -0
- package/src/checks/SK028-readme.check.ts +83 -0
- package/src/cli/cmd_Assets.ts +1 -1
- package/src/cli/cmd_Ci.ts +5 -0
- package/src/cli/cmd_CrossRepoActivity.ts +119 -0
- package/src/cli/cmd_GlobalProcesses.ts +155 -0
- package/src/cli/cmd_Keys.ts +17 -3
- package/src/cli/cmd_Overview.ts +98 -0
- package/src/cli/cmd_Planned.ts +117 -0
- package/src/cli.ts +9 -1
- package/src/collections/checks.ts +3 -2
- package/src/collections/collection-types.ts +15 -0
- package/src/collections/providers.ts +7 -3
- package/src/collections/test-time.ts +10 -1
- package/src/contracts/apply-contracts.ts +9 -8
- package/src/contracts/contract-types.ts +8 -2
- package/src/contracts/contracts-engine.ts +34 -13
- package/src/contracts/manifests/identity.contract.ts +6 -5
- package/src/contracts/manifests/social.contract.ts +4 -2
- package/src/contracts/manifests/steam.contract.ts +7 -3
- package/src/contracts/web-surface.ts +59 -0
- package/src/core/agent-spawn.ts +46 -0
- package/src/core/archived.ts +82 -0
- package/src/core/budget.ts +19 -9
- package/src/core/canonical-scripts.ts +12 -3
- package/src/core/check-runner.ts +66 -17
- package/src/core/discover-checks.ts +10 -5
- package/src/core/file-stamp.ts +20 -0
- package/src/{checks/SK021-github-login.check.ts → core/gh-login.ts} +45 -54
- package/src/core/identity-color.ts +1 -1
- package/src/core/layout.ts +2 -0
- package/src/core/motherhome.ts +88 -66
- package/src/core/repo-facts.ts +14 -0
- package/src/core/retention.ts +14 -5
- package/src/core/retired-checks.ts +7 -0
- package/src/core/run-check.ts +8 -7
- package/src/core/run-ci.ts +46 -15
- package/src/core/run-fix.ts +7 -2
- package/src/core/run-fixers.ts +10 -6
- package/src/core/wire.ts +49 -9
- package/src/fleet/scan-fleet.ts +2 -1
- package/src/home/activity-shape.ts +138 -0
- package/src/home/activity.ts +256 -0
- package/src/home/global-units.ts +3 -2
- package/src/home/gui-app/ShipkitGui.swift +144 -25
- package/src/home/gui-app.ts +93 -30
- package/src/home/home-launchd.ts +10 -2
- package/src/home/home-routes.ts +34 -6
- package/src/home/home-start.ts +33 -5
- package/src/home/machine-stores.ts +4 -2
- package/src/home/open-app.ts +23 -10
- package/src/home/pm-config.ts +15 -11
- package/src/home/pm-inputs.ts +76 -0
- package/src/home/pm.ts +40 -7
- package/src/home/process-rows.ts +222 -0
- package/src/home/resources.ts +99 -20
- package/src/home/run-daemon.ts +12 -7
- package/src/home/run-pm.ts +30 -5
- package/src/home/serve-web.ts +6 -3
- package/src/home/supervise.ts +16 -1
- package/src/home/widget-text.ts +119 -0
- package/src/home/widgets.ts +32 -3
- package/src/hooks/commit-message.ts +2 -3
- package/src/hooks/run-hooks.ts +32 -5
- package/src/ledger/db.ts +1 -1
- package/src/ledger/gate-checks.ts +20 -6
- package/src/ledger/house-runs.ts +43 -0
- package/src/ledger/live-timeline.ts +4 -19
- package/src/ledger/run-log.ts +39 -10
- package/src/ledger/store.ts +30 -2
- package/src/ledger/world-cache.ts +63 -14
- package/src/plan/cadence.ts +21 -5
- package/src/plan/catch-up.ts +37 -0
- package/src/plan/plan-timeline.ts +99 -0
- package/src/plan/planner.ts +138 -13
- package/src/plan/repo-source.ts +120 -8
- package/src/release/run-release.ts +9 -2
- package/src/release/unit-resolve.ts +8 -12
- package/src/repo-config.ts +17 -4
- package/src/serve/house-runs.ts +53 -0
- package/src/serve/hub-app/activity-st.ts +168 -0
- package/src/serve/hub-app/activity-view.tsx +218 -0
- package/src/serve/hub-app/activity.css +78 -0
- package/src/serve/hub-app/admin-svc.ts +41 -0
- package/src/serve/hub-app/admin-view.tsx +42 -0
- package/src/serve/hub-app/chart-view.ts +12 -5
- package/src/serve/hub-app/check-words.ts +2 -0
- package/src/serve/hub-app/checks-svc.ts +2 -1
- package/src/serve/hub-app/col-tables-view.tsx +124 -0
- package/src/serve/hub-app/col-tables.ts +115 -0
- package/src/serve/hub-app/cols-svc.ts +28 -0
- package/src/serve/hub-app/controls.css +32 -0
- package/src/serve/hub-app/cycle-button.tsx +36 -0
- package/src/serve/hub-app/dock-anim.ts +25 -0
- package/src/serve/hub-app/dock-st.ts +329 -0
- package/src/serve/hub-app/dock-view.tsx +68 -0
- package/src/serve/hub-app/dock.css +35 -0
- package/src/serve/hub-app/hash-route.ts +7 -1
- package/src/serve/hub-app/header-bar.tsx +79 -104
- package/src/serve/hub-app/home-st.ts +50 -13
- package/src/serve/hub-app/home-strip.css +81 -23
- package/src/serve/hub-app/home-strip.tsx +243 -102
- package/src/serve/hub-app/home.html +4 -0
- package/src/serve/hub-app/hub-app.tsx +103 -161
- package/src/serve/hub-app/hub-types.ts +6 -3
- package/src/serve/hub-app/hub.css +101 -39
- package/src/serve/hub-app/icons.tsx +27 -3
- package/src/serve/hub-app/index.html +4 -0
- package/src/serve/hub-app/keys.ts +44 -9
- package/src/serve/hub-app/live-svc.ts +24 -64
- package/src/serve/hub-app/machine-st.ts +65 -13
- package/src/serve/hub-app/md.ts +38 -5
- package/src/serve/hub-app/next-run.ts +1 -1
- package/src/serve/hub-app/palette-st.ts +13 -9
- package/src/serve/hub-app/palette-view.tsx +47 -0
- package/src/serve/hub-app/pill-row.tsx +65 -0
- package/src/serve/hub-app/planned-st.ts +21 -85
- package/src/serve/hub-app/planned-view.tsx +12 -7
- package/src/serve/hub-app/planned.css +5 -2
- package/src/serve/hub-app/project-list.css +43 -21
- package/src/serve/hub-app/project-list.tsx +122 -23
- package/src/serve/hub-app/projects-svc.ts +120 -12
- package/src/serve/hub-app/readme-svc.ts +38 -0
- package/src/serve/hub-app/readme-view.tsx +19 -0
- package/src/serve/hub-app/readme.css +11 -0
- package/src/serve/hub-app/repo-sections.tsx +61 -0
- package/src/serve/hub-app/repo-st.ts +26 -7
- package/src/serve/hub-app/runs-tabs.ts +1 -1
- package/src/serve/hub-app/sidebar.tsx +84 -0
- package/src/serve/hub-app/tests-view.tsx +31 -4
- package/src/serve/hub-app/timeline-chart.tsx +31 -27
- package/src/serve/hub-app/timeline-geometry.ts +40 -5
- package/src/serve/hub-app/timeline-svc.ts +2 -2
- package/src/serve/hub-app/timeline-view.tsx +29 -9
- package/src/serve/hub-runtime.ts +17 -9
- package/src/serve/other-repos.ts +8 -45
- package/src/serve/project-gutter-route.ts +100 -20
- package/src/serve/project-gutter.ts +34 -13
- package/src/serve/readme-route.ts +57 -0
- package/src/serve/serve-hub.ts +13 -8
- package/src/serve/tracked-watch.ts +33 -1
- package/src/serve/warm-cell.ts +49 -16
- package/src/shipkit-files.ts +20 -4
- package/src/suite/judge-suites.ts +42 -17
- package/src/suite/suite-cli.ts +5 -8
- package/src/wiki/lint-md.ts +2 -0
- package/src/wiki/scan-md.ts +37 -10
- package/src/checks/SK023-stores-under-cap.check.ts +0 -49
- package/src/checks/contracts.check.ts +0 -20
|
@@ -1,40 +1,28 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// (
|
|
6
|
-
//
|
|
7
|
-
//
|
|
1
|
+
// A repo's pieces of the top bar, beside its icon and its name: what runs now
|
|
2
|
+
// and the two runs that are not one check (RepoRun), then on the right its
|
|
3
|
+
// processes, the hub's own trouble and modernize while it is behind
|
|
4
|
+
// (RepoTools), and at the far right end the editor icons (RepoOpeners). On
|
|
5
|
+
// the home they ride in the home strip (home-strip.tsx); a hub of its own
|
|
6
|
+
// draws them in its own header, with its name. The repo's sections are the
|
|
7
|
+
// left pane's (repo-sections.tsx). What runs, drawn, is the dock at the
|
|
8
|
+
// bottom of the page (dock-view.tsx).
|
|
8
9
|
import { observer } from 'mobx-react-lite'
|
|
9
|
-
import {
|
|
10
|
-
import { Help } from './help.tsx'
|
|
11
|
-
import { openWikis, WIKIS } from './wikis-view.tsx'
|
|
10
|
+
import { Icon, ICON } from './icons.tsx'
|
|
12
11
|
import type { RepoSt } from './repo-st.ts'
|
|
13
|
-
import { byWords, TimelineChart } from './timeline-chart.tsx'
|
|
14
|
-
import { RepoControls } from './home-strip.tsx'
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const out: CorePage[] = []
|
|
25
|
-
if (has('checks')) out.push({ id: 'checks', label: 'checks', on: onCol('checks'), go: () => app.showCol('checks') })
|
|
26
|
-
// a page, never a resource: every hub keeps a run record, so it is
|
|
27
|
-
// always here
|
|
28
|
-
out.push({ id: 'runs', label: 'runs', on: onCol('runs'), go: () => app.cols.select('runs', RUNS_ID) })
|
|
29
|
-
if (has('asks')) out.push({ id: 'asks', label: 'asks', on: onCol('asks'), go: () => app.showCol('asks') })
|
|
30
|
-
const firstCat = app.media.assets[0]?.cat ?? app.cols.cachedCat ?? undefined
|
|
31
|
-
if (firstCat != null) out.push({ id: 'media', label: 'media', on: sel?.t === 'asset' || sel?.t === 'cat', go: () => app.showCat(firstCat) })
|
|
32
|
-
if (has(WIKIS)) out.push({ id: WIKIS, label: 'wikis', on: onCol(WIKIS), go: () => openWikis(app) })
|
|
33
|
-
return out
|
|
13
|
+
/** the repo's hub has not answered on the home: its reason and its fix show
|
|
14
|
+
* instead of the page. A screen this browser kept shows meanwhile, until
|
|
15
|
+
* the machine says the hub is not up */
|
|
16
|
+
export const notUp = (app: RepoSt): boolean => {
|
|
17
|
+
const m = app.home.machine
|
|
18
|
+
if (m == null || app.started) return false
|
|
19
|
+
if (!app.cols.cached) return true
|
|
20
|
+
return m.fresh && m.worker(app.key)?.status !== 'up'
|
|
34
21
|
}
|
|
35
22
|
|
|
36
23
|
/** what is running, in one line: the hub's own run first, since it says the
|
|
37
|
-
* most, else the newest run the record has in flight
|
|
24
|
+
* most, else the newest run the record has in flight. Cut to its width, the
|
|
25
|
+
* whole line on hover */
|
|
38
26
|
const RunState = observer(({ app }: { app: RepoSt }) => {
|
|
39
27
|
const own = app.checks.strip
|
|
40
28
|
const run = app.live.view?.runs[0]
|
|
@@ -49,8 +37,28 @@ const RunState = observer(({ app }: { app: RepoSt }) => {
|
|
|
49
37
|
)
|
|
50
38
|
})
|
|
51
39
|
|
|
40
|
+
export const HEALTH_TITLE = 'run the health pass: every check but the build steps, as shipkit check runs them (creds, probes, rules, contracts)'
|
|
41
|
+
export const GATE_TITLE = 'run the gate: every check shipkit ci runs, the build steps included (lint, typecheck, tests)'
|
|
42
|
+
|
|
43
|
+
/** the run state, then the two runs, compact: below a width their labels
|
|
44
|
+
* give way to their icons, so the bar never wraps */
|
|
45
|
+
export const RepoRun = observer(({ app }: { app: RepoSt }) => {
|
|
46
|
+
const u = app.checks
|
|
47
|
+
return (
|
|
48
|
+
<span className="hrun">
|
|
49
|
+
<RunState app={app} />
|
|
50
|
+
<button className="primary hbtn" disabled={u.busy} title={HEALTH_TITLE} aria-label="health pass" onClick={() => void u.run()}>
|
|
51
|
+
<Icon d={ICON.health} /><span className="lbl">health pass</span>
|
|
52
|
+
</button>
|
|
53
|
+
<button className="hbtn" disabled={u.busy} title={GATE_TITLE} aria-label="gate" onClick={() => void u.run(undefined, { gate: true, label: 'the gate' })}>
|
|
54
|
+
<Icon d={ICON.gate} /><span className="lbl">gate</span>
|
|
55
|
+
</button>
|
|
56
|
+
</span>
|
|
57
|
+
)
|
|
58
|
+
})
|
|
59
|
+
|
|
52
60
|
/** what is wrong with the hub itself, one chip per problem. Nothing when all
|
|
53
|
-
* is well: the
|
|
61
|
+
* is well: the bar states trouble, never health */
|
|
54
62
|
const HubStatus = observer(({ app }: { app: RepoSt }) => (
|
|
55
63
|
<>
|
|
56
64
|
{app.cols.hubStatus.map((p) => (
|
|
@@ -59,30 +67,49 @@ const HubStatus = observer(({ app }: { app: RepoSt }) => (
|
|
|
59
67
|
</>
|
|
60
68
|
))
|
|
61
69
|
|
|
62
|
-
|
|
63
|
-
|
|
70
|
+
/** modernize, while the repo runs an older shipkit than the home */
|
|
71
|
+
const Modernize = observer(({ app }: { app: RepoSt }) => {
|
|
72
|
+
const m = app.home.machine
|
|
73
|
+
const behind = m?.data?.behind[app.key]
|
|
74
|
+
if (m == null || behind == null) return null
|
|
75
|
+
return <button className="primary hbtn" onClick={() => void m.modernize(app.key)} title={`this repo runs shipkit ${behind}, the home runs ${m.data?.version}`}>modernize {behind} → {m.data?.version}</button>
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
/** the repo in its editor and in its git client, one icon each, at the far
|
|
79
|
+
* right end of the bar. Only on the home: the machine opens them */
|
|
80
|
+
export const RepoOpeners = observer(({ app }: { app: RepoSt }) => {
|
|
81
|
+
const m = app.home.machine
|
|
82
|
+
if (m == null) return null
|
|
64
83
|
return (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
{/* the spinner's slot stays once the repo is read, so the links never move */}
|
|
70
|
-
<span className={`spin${app.cols.loaded ? ' idle' : ''}`} title={app.cols.loaded ? undefined : app.cols.cached ? 'showing what this browser kept, reading the repo' : 'reading the repo'} />
|
|
71
|
-
{corePages(app).map((p) => (
|
|
72
|
-
<button key={p.id} className={p.on ? 'on' : ''} onClick={p.go}>{p.label}</button>
|
|
73
|
-
))}
|
|
74
|
-
</nav>
|
|
75
|
-
<span className="sp" />
|
|
76
|
-
<RepoControls app={app.home} name={app.key} />
|
|
77
|
-
<ProcessChips app={app} />
|
|
78
|
-
<HubStatus app={app} />
|
|
79
|
-
<RunState app={app} />
|
|
80
|
-
<button className="primary" disabled={u.busy} onClick={() => void u.run()}>run the health pass</button>
|
|
81
|
-
<button disabled={u.busy} onClick={() => void u.run(undefined, { gate: true, label: 'the gate' })}>run the gate</button>
|
|
82
|
-
</header>
|
|
84
|
+
<>
|
|
85
|
+
<button className="hicon" title="open in VS Code" aria-label={`open ${app.key} in VS Code`} onClick={() => m.openInCode(app.key)}><Icon d={ICON.vscode} /></button>
|
|
86
|
+
<button className="hicon" title="open in Fork" aria-label={`open ${app.key} in Fork`} onClick={() => void app.home.activity.openFork(app.key)}><Icon d={ICON.fork} /></button>
|
|
87
|
+
</>
|
|
83
88
|
)
|
|
84
89
|
})
|
|
85
90
|
|
|
91
|
+
/** the right side of the bar for a repo, before the machine's trouble and
|
|
92
|
+
* the openers, which close it */
|
|
93
|
+
export const RepoTools = observer(({ app }: { app: RepoSt }) => (
|
|
94
|
+
<>
|
|
95
|
+
<ProcessChips app={app} />
|
|
96
|
+
<HubStatus app={app} />
|
|
97
|
+
<Modernize app={app} />
|
|
98
|
+
</>
|
|
99
|
+
))
|
|
100
|
+
|
|
101
|
+
/** the bar of a hub of its own: its name, then the same pieces the home
|
|
102
|
+
* strip carries for a repo */
|
|
103
|
+
export const HeaderBar = observer(({ app }: { app: RepoSt }) => (
|
|
104
|
+
<header className="hdr">
|
|
105
|
+
<span className="hname" style={app.name === '' ? undefined : { color: app.home.projects.identityOf(app.name).colorDark }}>{app.name !== '' ? app.name : 'hub'}</span>
|
|
106
|
+
<RepoRun app={app} />
|
|
107
|
+
<span className="sp" />
|
|
108
|
+
<RepoTools app={app} />
|
|
109
|
+
<RepoOpeners app={app} />
|
|
110
|
+
</header>
|
|
111
|
+
))
|
|
112
|
+
|
|
86
113
|
/** one switch per process the repo declares (serve.processes): its dot, its
|
|
87
114
|
* id, a click starts or stops it, and a link once it serves */
|
|
88
115
|
export const ProcessChips = observer(({ app }: { app: RepoSt }) => {
|
|
@@ -106,55 +133,3 @@ export const ProcessChips = observer(({ app }: { app: RepoSt }) => {
|
|
|
106
133
|
</span>
|
|
107
134
|
)
|
|
108
135
|
})
|
|
109
|
-
|
|
110
|
-
/** the key that folds and unfolds the strip, printed on its chevron */
|
|
111
|
-
export const LIVE_KEY = 'l'
|
|
112
|
-
|
|
113
|
-
/** the strip's own row, in px: the one height the folded strip has */
|
|
114
|
-
export const LIVE_ROW_H = 30
|
|
115
|
-
|
|
116
|
-
/** what the strip is, in the (?) beside its chevron */
|
|
117
|
-
export const LIVE_HELP = "every check shipkit runs, whoever started it: this hub, an agent in a terminal, the plan, here and in the other repos. Folded, the last two minutes: a bar grows at the right while its check runs and slides left once it ends, in its verdict colour. Unfolded, the whole record, opened on the last ten minutes: the run bands, the time axis, and over the lanes how many checks ran at once, with the cap dashed and the stretches past it in orange. It follows now: the right edge is the present and the view slides with it. The wheel zooms and a drag pans; leaving the right edge stops the view where it is so a bar can be read, and ● live, the k key or panning back to the right edge follows now again. While a tooltip is open the view stands still. fit shows every run on record. A lane at the bottom is another repo with something running; a click opens its hub when it serves. The count is the checks in flight against the runner's cap"
|
|
118
|
-
|
|
119
|
-
const Chevron = ({ open }: { open: boolean }) => (
|
|
120
|
-
<svg className={open ? 'cvt open' : 'cvt'} viewBox="0 0 24 24" width="11" height="11" aria-hidden="true">
|
|
121
|
-
<path d="M9 5l7 7-7 7" fill="none" stroke="currentColor" strokeWidth="3.4" strokeLinecap="round" strokeLinejoin="round" />
|
|
122
|
-
</svg>
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
export const LiveStrip = observer(({ app }: { app: RepoSt }) => {
|
|
126
|
-
const l = app.live
|
|
127
|
-
const v = l.view
|
|
128
|
-
const run = v?.runs[0]
|
|
129
|
-
const hidden = l.chart.layout?.hidden ?? 0
|
|
130
|
-
const waits = v == null ? '' : [
|
|
131
|
-
v.queued > 0 ? `${v.queued} queued for a free slot` : '',
|
|
132
|
-
...v.blocked.map((w) => `${w.id} waits on ${w.deps.join(', ')}`),
|
|
133
|
-
].filter((x) => x !== '').join('\n')
|
|
134
|
-
const who = run == null ? 'nothing running' : `${run.label}`
|
|
135
|
-
const chart = (
|
|
136
|
-
<TimelineChart view={l.chart} compact={!l.open} repos={v?.repos ?? []} follow zoomable={l.open}
|
|
137
|
-
onBar={(b) => app.cols.select('checks', `check:${b.id}`)}
|
|
138
|
-
onRepo={(r) => void l.openRepo(r.repo)} />
|
|
139
|
-
)
|
|
140
|
-
return (
|
|
141
|
-
<div className={`live${l.open ? ' open' : ''}${run == null ? ' idle' : ''}`} style={l.open ? { height: LIVE_ROW_H + l.held.h } : undefined}>
|
|
142
|
-
<div className="lrow">
|
|
143
|
-
<button className="lfold" onClick={() => l.toggle()} title={`${l.open ? 'fold' : 'unfold'} the live timeline (${LIVE_KEY})`}>
|
|
144
|
-
<Chevron open={l.open} /><span className="sk">{LIVE_KEY}</span>
|
|
145
|
-
</button>
|
|
146
|
-
<Help text={LIVE_HELP} />
|
|
147
|
-
<span className="lwho" title={run == null ? '' : `${run.label}, started from the ${byWords(run.by)}${v != null && v.runs.length > 1 ? `\n${v.runs.length - 1} more in flight here` : ''}`}>
|
|
148
|
-
{who}{run == null ? null : <i>{byWords(run.by)}</i>}{v != null && v.runs.length > 1 ? <b>+{v.runs.length - 1}</b> : null}
|
|
149
|
-
</span>
|
|
150
|
-
{l.open ? <span className="sp" /> : <div className="lchart">{chart}</div>}
|
|
151
|
-
{l.note !== '' ? <span className="lnote">{l.note}</span> : null}
|
|
152
|
-
{hidden > 0 ? <span className="lmore" title="check runs that did not fit in the folded strip: unfold it to see them">+{hidden}</span> : null}
|
|
153
|
-
<span className="lbusy" title={waits === '' ? 'checks in flight in this repo, against the runner cap' : waits}>
|
|
154
|
-
in flight <span className="dnum">{v?.busy ?? 0}</span> / cap <span className="dnum">{v?.cap ?? 0}</span>
|
|
155
|
-
</span>
|
|
156
|
-
</div>
|
|
157
|
-
{l.open ? <div className="lopen">{chart}</div> : null}
|
|
158
|
-
</div>
|
|
159
|
-
)
|
|
160
|
-
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// HomeSt: THE root of the page (build/app-state-tree), exposed as window.app.
|
|
2
|
-
// One tree for every page the home serves, '/', '/planned'
|
|
3
|
-
// the machine, the project list, the plan, and one RepoSt per repo visited, built on
|
|
2
|
+
// One tree for every page the home serves, '/', '/planned', '/activity' and
|
|
3
|
+
// '/r/<repo>/': the machine, the project list, the plan, the activity, and one RepoSt per repo visited, built on
|
|
4
4
|
// the first visit and kept for the life of the page (same name, same
|
|
5
5
|
// instance), so a repo you come back to shows what it showed. Only the shown
|
|
6
6
|
// repo polls. Every other repo whose hub is up is read ahead in the
|
|
@@ -11,30 +11,41 @@
|
|
|
11
11
|
// together, and only `show` moves the page. A list click, a link, back,
|
|
12
12
|
// forward and a hand edited address all land there.
|
|
13
13
|
import { makeAutoObservable, observableShallow, reaction, type IReactionDisposer } from 'mobx'
|
|
14
|
-
import { currentHash } from './hash-route.ts'
|
|
14
|
+
import { currentHash, hashFor } from './hash-route.ts'
|
|
15
15
|
import { MachineSt, type MachineOpts } from './machine-st.ts'
|
|
16
16
|
import { LayoutSt } from './layout-st.ts'
|
|
17
|
+
import { DockSt } from './dock-st.ts'
|
|
17
18
|
import { PaletteSt } from './palette-st.ts'
|
|
18
19
|
import { PlannedSt } from './planned-st.ts'
|
|
20
|
+
import { ActivitySt } from './activity-st.ts'
|
|
19
21
|
import { ProjectsSvc } from './projects-svc.ts'
|
|
20
22
|
import { parseFilter, RepoSt } from './repo-st.ts'
|
|
21
23
|
|
|
22
24
|
/** the longest one repo's read ahead holds the next one back */
|
|
23
25
|
export const PREFETCH_WAIT_MS = 10_000
|
|
24
26
|
|
|
25
|
-
/** what the page shows: the machine overview, the plan, or one repo */
|
|
26
|
-
export type Page = 'overview' | 'planned' | 'repo'
|
|
27
|
+
/** what the page shows: the machine overview, the plan, the activity, or one repo */
|
|
28
|
+
export type Page = 'overview' | 'planned' | 'activity' | 'archived' | 'repo'
|
|
29
|
+
|
|
30
|
+
/** the address of each page of the home that is no repo's */
|
|
31
|
+
export const PLACE_PATH = { overview: '/', planned: '/planned', activity: '/activity', archived: '/archived' } as const
|
|
27
32
|
|
|
28
33
|
/** where a repo's hub answers on the home */
|
|
29
34
|
export const repoBase = (name: string): string => `/r/${encodeURIComponent(name)}`
|
|
30
35
|
/** the page of a repo on the home */
|
|
31
36
|
export const repoPage = (name: string): string => `${repoBase(name)}/`
|
|
32
37
|
|
|
38
|
+
/** where a check's own page answers on the home */
|
|
39
|
+
export const checkAddress = (repo: string, check: string): string =>
|
|
40
|
+
`${repoPage(repo)}#${hashFor({ t: 'item', col: 'checks', id: `check:${check}` })}`
|
|
41
|
+
|
|
33
42
|
/** the page an address on the home names */
|
|
34
43
|
export function routeOf(pathname: string): { page: Page; repo: string | null } {
|
|
35
44
|
const m = /^\/r\/([^/]+)/.exec(pathname)
|
|
36
45
|
if (m != null) return { page: 'repo', repo: decodeURIComponent(m[1]!) }
|
|
37
46
|
if (/^\/planned\/?$/.test(pathname)) return { page: 'planned', repo: null }
|
|
47
|
+
if (/^\/activity\/?$/.test(pathname)) return { page: 'activity', repo: null }
|
|
48
|
+
if (/^\/archived\/?$/.test(pathname)) return { page: 'archived', repo: null }
|
|
38
49
|
return { page: 'overview', repo: null }
|
|
39
50
|
}
|
|
40
51
|
|
|
@@ -55,13 +66,18 @@ export class HomeSt {
|
|
|
55
66
|
projects: ProjectsSvc
|
|
56
67
|
/** the plan, polled while it is the page */
|
|
57
68
|
planned: PlannedSt
|
|
58
|
-
/**
|
|
69
|
+
/** what every repo committed and holds, polled while it is the page */
|
|
70
|
+
activity: ActivitySt
|
|
71
|
+
/** the one picker, cmd+j */
|
|
59
72
|
palette: PaletteSt
|
|
60
73
|
/** the widths of the project list and a repo's sidebar */
|
|
61
74
|
layout: LayoutSt
|
|
75
|
+
/** the run chart docked under the whole page */
|
|
76
|
+
dock: DockSt
|
|
62
77
|
page: Page
|
|
63
|
-
/** the key of the
|
|
64
|
-
* of a hub of its own.
|
|
78
|
+
/** the key of the repo picked last: its name on the home, '' for the one
|
|
79
|
+
* repo of a hub of its own. A global page keeps it (only `shown` says
|
|
80
|
+
* whether it is on screen); null until a repo is picked */
|
|
65
81
|
selected: string | null = null
|
|
66
82
|
/** every repo visited, by key, kept for the life of the page */
|
|
67
83
|
repos = new Map<string, RepoSt>()
|
|
@@ -73,15 +89,20 @@ export class HomeSt {
|
|
|
73
89
|
constructor(o: HomeOpts) {
|
|
74
90
|
this.projects = new ProjectsSvc()
|
|
75
91
|
this.planned = new PlannedSt(this)
|
|
92
|
+
this.activity = new ActivitySt(this)
|
|
76
93
|
this.palette = new PaletteSt(this)
|
|
77
94
|
this.layout = new LayoutSt()
|
|
95
|
+
this.dock = new DockSt(this)
|
|
78
96
|
this.machine = o.home ? new MachineSt(this, o) : null
|
|
79
97
|
this.page = o.home ? 'overview' : 'repo'
|
|
80
|
-
makeAutoObservable<this, 'disposers' | 'onUrl' | 'prefetching'>(this, { machine: false, projects: false, planned: false, palette: false, layout: false, repos: observableShallow, disposers: false, onUrl: false, prefetching: false })
|
|
98
|
+
makeAutoObservable<this, 'disposers' | 'onUrl' | 'prefetching'>(this, { machine: false, projects: false, planned: false, activity: false, palette: false, layout: false, dock: false, repos: observableShallow, disposers: false, onUrl: false, prefetching: false })
|
|
81
99
|
const m = this.machine
|
|
82
100
|
if (m != null) {
|
|
83
101
|
this.projects.onSelect = (name) => this.select(name)
|
|
84
|
-
this.projects.onPlace = (p) => this.navigate(p
|
|
102
|
+
this.projects.onPlace = (p) => this.navigate(PLACE_PATH[p])
|
|
103
|
+
this.projects.onPicker = () => this.palette.toggle()
|
|
104
|
+
this.projects.pickerOpen = () => this.palette.open
|
|
105
|
+
this.projects.onArchived = () => this.navigate(PLACE_PATH.archived)
|
|
85
106
|
this.projects.dotOf = (name) => m.dotOf(name)
|
|
86
107
|
this.projects.warnOf = (name) => m.warnOf(name)
|
|
87
108
|
} else {
|
|
@@ -106,6 +127,8 @@ export class HomeSt {
|
|
|
106
127
|
get title(): string {
|
|
107
128
|
if (this.page === 'overview') return 'home'
|
|
108
129
|
if (this.page === 'planned') return 'planned'
|
|
130
|
+
if (this.page === 'activity') return 'activity'
|
|
131
|
+
if (this.page === 'archived') return 'archived'
|
|
109
132
|
const r = this.shown
|
|
110
133
|
return r == null || r.name === '' ? 'hub' : `${r.name} · hub`
|
|
111
134
|
}
|
|
@@ -124,6 +147,7 @@ export class HomeSt {
|
|
|
124
147
|
|
|
125
148
|
boot(): void {
|
|
126
149
|
this.machine?.boot()
|
|
150
|
+
this.dock.boot()
|
|
127
151
|
this.disposers.push(reaction(() => this.title, (t) => { document.title = t }, { fireImmediately: true }))
|
|
128
152
|
// back, forward, a pasted link and a hand edited hash all arrive
|
|
129
153
|
// here; fromUrl is idempotent, so the two events firing together
|
|
@@ -140,6 +164,8 @@ export class HomeSt {
|
|
|
140
164
|
window.removeEventListener('hashchange', this.onUrl)
|
|
141
165
|
this.machine?.stop()
|
|
142
166
|
this.planned.stop()
|
|
167
|
+
this.activity.stop()
|
|
168
|
+
this.dock.dispose()
|
|
143
169
|
for (const r of this.repos.values()) r.dispose()
|
|
144
170
|
}
|
|
145
171
|
|
|
@@ -148,6 +174,13 @@ export class HomeSt {
|
|
|
148
174
|
this.show('repo', name, 'push')
|
|
149
175
|
}
|
|
150
176
|
|
|
177
|
+
/** a check: its own page in its repo, in place. The selection is written
|
|
178
|
+
* before the repo is shown, so the one history entry already names it */
|
|
179
|
+
openCheck(repo: string, check: string): void {
|
|
180
|
+
this.repo(repo).cols.select('checks', `check:${check}`)
|
|
181
|
+
this.select(repo)
|
|
182
|
+
}
|
|
183
|
+
|
|
151
184
|
/** a link to a page of the home: the same page, in place */
|
|
152
185
|
navigate(path: string): void {
|
|
153
186
|
const u = new URL(path, 'http://home.invalid')
|
|
@@ -183,16 +216,20 @@ export class HomeSt {
|
|
|
183
216
|
if (prev != null && prev !== next) prev.pause()
|
|
184
217
|
next?.markSeen()
|
|
185
218
|
this.page = page
|
|
186
|
-
|
|
219
|
+
// a global page is no repo's: the repo picked last stays selected
|
|
220
|
+
if (next != null) this.selected = next.key
|
|
187
221
|
this.projects.selected = this.selected
|
|
188
|
-
this.projects.
|
|
222
|
+
this.projects.away = next == null
|
|
223
|
+
this.projects.place = page === 'repo' || page === 'archived' ? null : page
|
|
189
224
|
if (how !== 'none') {
|
|
190
|
-
const to = next != null ? `${repoPage(next.key)}${next.search}${next.hash}` : page === 'planned' ? this.planned.address : '/'
|
|
225
|
+
const to = next != null ? `${repoPage(next.key)}${next.search}${next.hash}` : page === 'planned' ? this.planned.address : page === 'activity' || page === 'archived' ? PLACE_PATH[page] : '/'
|
|
191
226
|
if (how === 'push') history.pushState(null, '', to)
|
|
192
227
|
else history.replaceState(null, '', to)
|
|
193
228
|
}
|
|
194
229
|
if (page === 'planned') this.planned.start()
|
|
195
230
|
else this.planned.stop()
|
|
231
|
+
if (page === 'activity') this.activity.start()
|
|
232
|
+
else this.activity.stop()
|
|
196
233
|
if (next != null && next !== prev && this.machine?.data != null) void this.machine.hub(next.key, 'ensure')
|
|
197
234
|
this.wake()
|
|
198
235
|
}
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
/* the home's strip over a hub page, and the overview. hub.css owns every
|
|
2
2
|
token and the base controls: no colour is written here. */
|
|
3
|
-
.hstrip{display:flex;align-items:center;flex-wrap:nowrap;gap:var(--s2);height:32px;flex:none;padding:0 var(--s3);border-bottom:1px solid var(--line);overflow:hidden;white-space:nowrap}
|
|
3
|
+
.hstrip{display:flex;align-items:center;flex-wrap:nowrap;gap:var(--s2);height:32px;flex:none;padding:0 var(--s3);border-bottom:1px solid var(--line);background:var(--bg-top);overflow:hidden;white-space:nowrap}
|
|
4
4
|
.hstrip .sp{flex:1}
|
|
5
5
|
.hplace{font-size:12px;font-weight:650;letter-spacing:.08em;text-transform:uppercase;color:var(--dim)}
|
|
6
|
-
.hrepo{display:inline-flex;align-items:center;gap:var(--s2);min-width:0}
|
|
6
|
+
.hrepo{display:inline-flex;align-items:center;gap:var(--s2);min-width:0;flex:0 1 auto}
|
|
7
7
|
.hrepo img,.hrico{flex:none;width:18px;height:18px;border-radius:4px;object-fit:contain}
|
|
8
|
-
.hrname{font-size:13px;font-weight:650;letter-spacing:.04em}
|
|
8
|
+
.hrname{font-size:13px;font-weight:650;letter-spacing:.04em;min-width:0;overflow:hidden;text-overflow:ellipsis}
|
|
9
9
|
.hswatch{display:inline-flex;gap:2px}
|
|
10
10
|
.hswatch input{width:16px;height:16px;padding:0;border:1px solid var(--line);border-radius:3px;background:none;cursor:pointer}
|
|
11
11
|
.hswatch input::-webkit-color-swatch-wrapper{padding:0}
|
|
12
12
|
.hswatch input::-webkit-color-swatch{border:none;border-radius:2px}
|
|
13
13
|
.halert{display:inline-flex;align-items:center;font-size:12px;color:var(--errink)}
|
|
14
|
-
.hctl{display:inline-flex;align-items:center;gap:var(--s2)}
|
|
15
|
-
.hstat,.hflag{display:inline-flex;align-items:center;gap:var(--s1);font-size:12px;color:var(--dim)}
|
|
16
14
|
.herr{font-size:12px;color:var(--errink);max-width:40ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
17
|
-
|
|
18
|
-
.
|
|
15
|
+
/* the daemon or pm down: toasts over the bottom right corner of the page
|
|
16
|
+
column (hub.css .appcol is their box), just above the dock, never taking
|
|
17
|
+
room, so one coming or going moves nothing */
|
|
18
|
+
.htoasts{position:absolute;right:var(--s3);bottom:var(--s3);z-index:25;display:flex;flex-direction:column;align-items:flex-end;gap:var(--s1);max-width:min(60ch,calc(100% - 2 * var(--s3)));pointer-events:none}
|
|
19
|
+
.htoast{max-width:100%;padding:var(--s1) var(--s2);background:var(--panel);border:1px solid var(--red);border-radius:4px;box-shadow:0 6px 20px var(--shadow);font-size:12px;color:var(--errink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;pointer-events:auto}
|
|
20
|
+
.htoast b{color:var(--red);margin-right:var(--s2)}
|
|
19
21
|
.hdot{display:inline-block;width:8px;height:8px;border-radius:4px;margin-right:var(--s1);background:var(--line)}
|
|
20
22
|
.hdot.up{background:var(--ok)}.hdot.starting,.hdot.stopping{background:var(--warn)}.hdot.crashed{background:var(--red)}
|
|
21
23
|
.hres{display:inline-flex;align-items:center;padding:var(--s0) var(--s2);font-size:12px}
|
|
@@ -25,29 +27,86 @@
|
|
|
25
27
|
can move into it to scroll */
|
|
26
28
|
.hpop{z-index:20;margin:0;padding:var(--s2);width:min(80ch,80vw);max-height:40vh;overflow:auto;
|
|
27
29
|
background:var(--panel);border:1px solid var(--line);border-radius:3px;box-shadow:0 6px 20px var(--shadow);font:11px/1.45 ui-monospace,Menlo,monospace;color:var(--dim);white-space:pre-wrap}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
.
|
|
30
|
+
/* each widget card exactly as wide as what it holds; a widget's leading
|
|
31
|
+
<header> joins the title line, dim */
|
|
32
|
+
.hmon{display:flex;flex-wrap:wrap;align-items:flex-start;gap:var(--s2)}
|
|
33
|
+
.hwidget{flex:0 1 auto;max-width:100%;padding:var(--s0) var(--s2);border:1px solid var(--line);border-radius:3px;font-size:12px;line-height:1.4;overflow:auto}
|
|
34
|
+
.hwidget h3{margin:0;font-size:12px;font-weight:650;white-space:nowrap}
|
|
35
|
+
.hwidget h3 .hdim{font-weight:400}
|
|
36
|
+
/* meters in two columns: <table class="wmeters">, label · bar · value, twice a row */
|
|
37
|
+
.wmeters td{padding-right:var(--s1)}
|
|
38
|
+
.wmeters td:nth-child(3n){padding-right:var(--s3)}
|
|
39
|
+
.wmeters td:last-child{padding-right:0}
|
|
40
|
+
.wmeters .wbar{margin-right:0}
|
|
41
|
+
/* a meter's value: small, its digits one width so a column never jitters */
|
|
42
|
+
.wmeters .wv{font-size:11px;font-variant-numeric:tabular-nums}
|
|
31
43
|
.hwidget table{border-collapse:collapse}
|
|
32
44
|
.hwidget td{padding:0 var(--s3) 0 0;white-space:nowrap;vertical-align:middle}
|
|
45
|
+
/* a widget error: in its card where the html goes, or one mark after the cards */
|
|
46
|
+
.hwerr{font-size:12px;color:var(--warn);white-space:nowrap;cursor:help}
|
|
47
|
+
.hwerr .gpwarn{margin:0 var(--s1) 0 0}
|
|
33
48
|
.hbad{color:var(--errink)}
|
|
34
49
|
.hwait{flex:1;min-height:0;padding:var(--s4);color:var(--dim);font-size:13px;overflow:auto}
|
|
35
50
|
.hwait p{margin:0 0 var(--s2)}
|
|
36
51
|
.hlog{margin:var(--s2) 0 0;padding:var(--s2);background:var(--panel);border:1px solid var(--line);border-radius:3px;
|
|
37
52
|
font:11px/1.45 ui-monospace,Menlo,monospace;color:var(--dim);white-space:pre-wrap;max-height:60vh;overflow:auto}
|
|
38
53
|
.hall{flex:1;min-height:0;overflow:auto;padding:var(--s4)}
|
|
39
|
-
/* the sections one under the other:
|
|
54
|
+
/* the sections one under the other: the monitor, then the processes */
|
|
40
55
|
.hstack{display:flex;flex-direction:column;gap:var(--s4)}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
.
|
|
49
|
-
.
|
|
50
|
-
.
|
|
56
|
+
.hpico{flex:none;display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:3px;font-size:13px;line-height:1;font-style:normal;object-fit:contain}
|
|
57
|
+
/* the processes: the activity page's table (activity.css actable, acgrp,
|
|
58
|
+
acname, acrepo), a line per process tinted by its state, the group's name
|
|
59
|
+
at the top of its group, the label cut to its narrow column, then one
|
|
60
|
+
column per kind of button so they align across lines. The container query
|
|
61
|
+
drops the url, the version and the pid first, so a phone never scrolls
|
|
62
|
+
sideways */
|
|
63
|
+
.gpwrap{container-type:inline-size;font-size:13px}
|
|
64
|
+
.c-gpname{width:180px}.c-gpver{width:56px}.c-gppid{width:64px}.c-gpstate{width:110px}
|
|
65
|
+
.c-gpb.take{width:78px}.c-gpb.stop,.c-gpb.restart{width:24px}
|
|
66
|
+
.gptable td{vertical-align:middle}
|
|
67
|
+
.gptable td.acname{vertical-align:top}
|
|
68
|
+
.gpcore{color:var(--ink)}
|
|
69
|
+
.gptag{flex:none;padding:0 var(--s1);border:1px solid var(--line);border-radius:3px;font-size:9px;font-weight:600;line-height:13px;letter-spacing:.06em;text-transform:uppercase;color:var(--dim)}
|
|
70
|
+
.gpver{color:var(--dim);font-size:12px;font-variant-numeric:tabular-nums}
|
|
71
|
+
.gppid{color:var(--dim);font-size:12px;font-variant-numeric:tabular-nums}
|
|
72
|
+
.gpcopy{padding:0;border:0;background:none;color:inherit;font:inherit;cursor:pointer}
|
|
73
|
+
.gpcopy:hover{color:var(--ink);text-decoration:underline dotted}
|
|
74
|
+
.gpcopy.on{color:var(--ok)}
|
|
75
|
+
.gpline.up td:not(.acname){background:var(--okbg)}
|
|
76
|
+
.gpline.crashed td:not(.acname){background:var(--errbg)}
|
|
77
|
+
.gplabel{display:flex;align-items:center;gap:var(--s1);min-width:0;cursor:default}
|
|
78
|
+
.gplabel .hdot{flex:none;margin-right:0}
|
|
79
|
+
.gplabel span{overflow:hidden;text-overflow:ellipsis}
|
|
80
|
+
.hdot.unknown{background:var(--line)}
|
|
81
|
+
.gphost a{color:var(--accent);text-decoration:underline;text-underline-offset:2px;font-size:12px}
|
|
82
|
+
.gphost span{color:var(--dim);font-size:12px}
|
|
83
|
+
.gpstate{color:var(--dim);font-size:12px;font-variant-numeric:tabular-nums}
|
|
84
|
+
.gpstate.up{color:var(--ok)}.gpstate.crashed{color:var(--red)}.gpstate.starting{color:var(--warn)}
|
|
85
|
+
.gptable td.gpb{padding-left:0;padding-right:var(--s1);text-align:right;overflow:visible}
|
|
86
|
+
.gptable td.gpb.stop,.gptable td.gpb.restart{padding:0;text-align:center}
|
|
87
|
+
/* a small icon button: a process's start or stop, its restart, the open before its url */
|
|
88
|
+
.gpi{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:0;border-radius:3px;background:none;color:var(--dim);cursor:pointer;vertical-align:middle}
|
|
89
|
+
.gpi .hico{width:14px;height:14px;color:inherit}
|
|
90
|
+
.gpi:hover:not(:disabled){color:var(--ink);background:var(--panel2)}
|
|
91
|
+
.gpi.go{color:var(--ok)}
|
|
92
|
+
.gpi:disabled{opacity:.35;cursor:default}
|
|
93
|
+
.gphost .gpi{margin-right:var(--s1)}
|
|
94
|
+
/* the app button while its app builds: a spinner, never faded */
|
|
95
|
+
.gpi[aria-busy]:disabled{opacity:1;cursor:progress}
|
|
96
|
+
.gpb button.hsm{padding:0 var(--s1);font-size:11px}
|
|
97
|
+
.gpb.act{white-space:nowrap}
|
|
98
|
+
.gpb.act button + button{margin-left:var(--s1)}
|
|
99
|
+
.gphead div{display:flex;align-items:center;gap:var(--s1)}
|
|
100
|
+
.gphead .sp{flex:1}
|
|
101
|
+
.gphead button.hsm{padding:0 var(--s1);font-size:11px}
|
|
102
|
+
/* what a process has to say (its note, a refused take over): a mark after
|
|
103
|
+
its state words, the words on hover, never a line of its own */
|
|
104
|
+
.gpwarn{display:inline-block;vertical-align:middle;width:6px;height:6px;margin-left:var(--s1);border-radius:1px;background:var(--warn)}
|
|
105
|
+
.gpstate span[title]{cursor:help}
|
|
106
|
+
@container (max-width:620px){.c-gphost,.gphost,.c-gpver,.gpver,.c-gppid,.gppid{display:none}.gptable .c-name{width:96px}.c-gpname{width:110px}.c-gpstate{width:88px}}
|
|
107
|
+
/* a phone: the state words go too, the dot and the tint still say it, and
|
|
108
|
+
only start (stop) stays, the label taking what is left */
|
|
109
|
+
@container (max-width:440px){.c-gpstate,.gpstate,.c-gpb.act,.gpb.act,.c-gpb.take,.gpb.take,.c-gpb.restart,.gpb.restart{display:none}.gptable .c-name{width:64px}.c-gpname{width:auto}}
|
|
51
110
|
.hsec{min-width:0}
|
|
52
111
|
.hsec h2{display:flex;align-items:center;gap:var(--s2);height:28px;margin:0 0 var(--s2);padding-bottom:var(--s1);border-bottom:1px solid var(--line);
|
|
53
112
|
font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--dim);font-weight:600}
|
|
@@ -63,8 +122,7 @@
|
|
|
63
122
|
.hnote{font-size:12px;color:var(--warn)}
|
|
64
123
|
/* a filled bar any widget can draw: <span class="wbar" style="--p:51%"><i></i></span>,
|
|
65
124
|
green below 70%, `mid` from 70%, `hi` from 90% (the widget picks the class) */
|
|
66
|
-
.wbar{display:inline-block;vertical-align:middle;width:
|
|
125
|
+
.wbar{display:inline-block;vertical-align:middle;width:40px;height:7px;margin-right:var(--s2);border-radius:4px;background:var(--line);overflow:hidden}
|
|
67
126
|
.wbar > i{display:block;height:100%;width:var(--p,0%);background:var(--ok)}
|
|
68
127
|
.wbar.mid > i{background:var(--warn)}
|
|
69
128
|
.wbar.hi > i{background:var(--red)}
|
|
70
|
-
.hcard .hrow{border-bottom:0;padding:var(--s0) 0}
|