rvlib-shipkit 1.4.0 → 1.6.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 +114 -0
- package/README.md +17 -17
- package/package.json +3 -2
- package/src/assets/gen/comfyts-gen.ts +12 -8
- package/src/assets/server/studio-server.ts +14 -2
- package/src/check.ts +73 -103
- package/src/checks/SK001-package-json-shape.check.ts +11 -6
- package/src/checks/SK002-contracts-fulfillness.check.ts +4 -0
- package/src/checks/SK003-claude-md-shape.check.ts +7 -3
- package/src/checks/SK004-gitignore-policy.check.ts +7 -4
- package/src/checks/SK005-rvlib-versions.check.ts +53 -21
- package/src/checks/SK006-npm-publish-safety.check.ts +4 -0
- package/src/checks/SK007-github-visibility.check.ts +4 -2
- package/src/checks/SK008-reviewer-agent.check.ts +6 -4
- package/src/checks/SK009-commit-ai-tells.check.ts +4 -2
- package/src/checks/SK010-undeclared-unit.check.ts +4 -0
- package/src/checks/SK011-sibling-unit-ranges.check.ts +4 -0
- package/src/checks/SK012-config-barrel-import.check.ts +5 -1
- package/src/checks/SK013-one-way-in-policy.check.ts +7 -3
- package/src/checks/SK014-prose-ste.check.ts +4 -0
- package/src/checks/SK015-nested-repo.check.ts +4 -2
- package/src/checks/SK016-legacy-check-shapes.check.ts +13 -12
- package/src/checks/SK017-vscode-hub-task.check.ts +22 -7
- package/src/checks/SK018-test-files-run.check.ts +6 -2
- package/src/checks/SK019-tests-run-in-units.check.ts +8 -4
- package/src/checks/SK020-self-spawning-check.check.ts +4 -0
- package/src/checks/SK022-machine-runs-the-pin.check.ts +6 -4
- package/src/checks/SK024-identity-colors.check.ts +5 -3
- package/src/checks/SK025-release-drift.check.ts +4 -5
- package/src/checks/SK026-asks-autoclose.check.ts +4 -4
- package/src/checks/SK027-agent-wiki-links.check.ts +4 -1
- package/src/checks/SK028-readme.check.ts +5 -1
- package/src/checks/SK029-explicit-check-files.check.ts +95 -0
- package/src/checks/SK030-cache-audit.check.ts +34 -0
- package/src/checks/SK031-trace-audit.check.ts +39 -0
- package/src/checks/SK032-ignored-inputs.check.ts +79 -0
- package/src/cli/cmd_Asks.ts +5 -5
- package/src/cli/cmd_Assets.ts +17 -13
- package/src/cli/cmd_Bump.ts +12 -11
- package/src/cli/cmd_CacheAudit.ts +43 -0
- package/src/cli/cmd_Check.ts +8 -7
- package/src/cli/cmd_Checks.ts +7 -6
- package/src/cli/cmd_Ci.ts +12 -11
- package/src/cli/cmd_Commands.ts +16 -13
- package/src/cli/cmd_ConfigKeys.ts +7 -6
- package/src/cli/cmd_Contracts.ts +3 -3
- package/src/cli/cmd_CrossRepoActivity.ts +8 -6
- package/src/cli/cmd_Fleet.ts +9 -7
- package/src/cli/cmd_GlobalProcesses.ts +120 -7
- package/src/cli/cmd_Home.ts +26 -19
- package/src/cli/cmd_Hooks.ts +6 -5
- package/src/cli/cmd_Init.ts +5 -4
- package/src/cli/cmd_Keys.ts +20 -8
- package/src/cli/cmd_Mv.ts +7 -6
- package/src/cli/cmd_Needs.ts +4 -3
- package/src/cli/cmd_Overview.ts +5 -4
- package/src/cli/cmd_Pb.ts +2 -2
- package/src/cli/cmd_Planned.ts +6 -5
- package/src/cli/cmd_Prose.ts +5 -5
- package/src/cli/cmd_Reflect.ts +7 -7
- package/src/cli/cmd_Release.ts +6 -5
- package/src/cli/cmd_Releases.ts +5 -4
- package/src/cli/cmd_Serve.ts +6 -4
- package/src/cli/cmd_Smoke.ts +5 -4
- package/src/cli/cmd_Start.ts +7 -6
- package/src/cli/cmd_Status.ts +4 -3
- package/src/cli/cmd_TraceAudit.ts +62 -0
- package/src/cli/cmd_Tui.ts +3 -2
- package/src/cli/cmd_When.ts +71 -0
- package/src/cli/cmd_Wiki.ts +4 -4
- package/src/cli.ts +11 -2
- package/src/collections/checks.ts +2 -0
- package/src/collections/collection-types.ts +12 -4
- package/src/collections/providers.ts +5 -3
- package/src/core/app-icon.ts +154 -0
- package/src/core/audit-report.ts +21 -0
- package/src/core/cache-audit.ts +72 -0
- package/src/core/canonical-scripts.ts +2 -2
- package/src/core/check-ctx.ts +14 -5
- package/src/core/check-runner.ts +43 -23
- package/src/core/config-rewrite.ts +37 -24
- package/src/core/discover-checks.ts +23 -2
- package/src/core/floor-resolve.ts +5 -7
- package/src/core/git-root.ts +13 -0
- package/src/core/ignored-block.ts +94 -0
- package/src/core/ignored-inputs.ts +47 -0
- package/src/core/jobs.ts +9 -2
- package/src/core/legacy-shapes.ts +39 -10
- package/src/core/one-lib.ts +59 -4
- package/src/core/one-way-in-policy.ts +2 -2
- package/src/core/outcome-words.ts +18 -2
- package/src/core/port-check-cli.ts +52 -0
- package/src/core/port-check-file.ts +295 -0
- package/src/core/port-check-files.ts +31 -0
- package/src/core/read-set.ts +97 -9
- package/src/core/retention.ts +0 -14
- package/src/core/run-check.ts +12 -9
- package/src/core/run-ci.ts +14 -5
- package/src/core/run-fix.ts +30 -23
- package/src/core/run-fixers.ts +48 -5
- package/src/core/runs-matrix.ts +179 -0
- package/src/core/runs.ts +242 -0
- package/src/core/trace-audit.ts +120 -0
- package/src/core/update-policy.ts +20 -0
- package/src/core/when-block.ts +29 -30
- package/src/core/when.ts +41 -103
- package/src/core/wire.ts +10 -14
- package/src/fleet/scan-fleet.ts +20 -4
- package/src/fleet/update-fleet.ts +6 -6
- package/src/home/gui-app.ts +24 -27
- package/src/home/home-launchd.ts +20 -4
- package/src/home/home-routes.ts +12 -1
- package/src/home/home-start.ts +11 -5
- package/src/home/hubs.ts +5 -5
- package/src/home/open-app.ts +2 -2
- package/src/home/pm.ts +8 -0
- package/src/home/run-pm.ts +1 -0
- package/src/home/serve-web.ts +2 -2
- package/src/home/supervise.ts +24 -1
- package/src/home/via-hub.ts +14 -0
- package/src/hooks/run-hooks.ts +48 -4
- package/src/init-repo.ts +6 -6
- package/src/ledger/db.ts +2 -13
- package/src/ledger/gate-checks.ts +133 -61
- package/src/ledger/git-key.ts +153 -31
- package/src/ledger/git-scan.ts +5 -2
- package/src/ledger/ledger.ts +136 -21
- package/src/ledger/store.ts +14 -0
- package/src/ledger/toolchain-identity.ts +248 -45
- package/src/plan/repo-source.ts +18 -39
- package/src/problems/extract.ts +3 -0
- package/src/release/run-release.ts +5 -0
- package/src/repo-config.ts +46 -54
- package/src/rules/rule.ts +2 -2
- package/src/scaffold/files/agent-coding.md +1 -1
- package/src/scaffold/files/agent-index.md +1 -1
- package/src/scaffold/files/verify-skill.md +2 -2
- package/src/serve/hub-app/activity-st.ts +5 -4
- package/src/serve/hub-app/activity-view.tsx +1 -1
- package/src/serve/hub-app/admin-svc.ts +36 -3
- package/src/serve/hub-app/admin-view.tsx +46 -12
- package/src/serve/hub-app/check-view.tsx +66 -46
- package/src/serve/hub-app/check-words.ts +1 -11
- package/src/serve/hub-app/checks-svc.ts +14 -35
- package/src/serve/hub-app/col-card.tsx +1 -44
- package/src/serve/hub-app/col-tables-view.tsx +84 -11
- package/src/serve/hub-app/col-tables.ts +27 -0
- package/src/serve/hub-app/cols-svc.ts +5 -4
- package/src/serve/hub-app/dock-st.ts +3 -2
- package/src/serve/hub-app/dock-view.tsx +36 -0
- package/src/serve/hub-app/dock.css +6 -0
- package/src/serve/hub-app/float-cards.tsx +30 -0
- package/src/serve/hub-app/floating.tsx +41 -4
- package/src/serve/hub-app/git-st.ts +42 -0
- package/src/serve/hub-app/git-view.tsx +61 -0
- package/src/serve/hub-app/hash-route.ts +45 -1
- package/src/serve/hub-app/header-bar.tsx +62 -72
- package/src/serve/hub-app/health-svc.ts +3 -2
- package/src/serve/hub-app/home-st.ts +14 -2
- package/src/serve/hub-app/home-strip.css +43 -12
- package/src/serve/hub-app/home-strip.tsx +115 -64
- package/src/serve/hub-app/home.html +1 -0
- package/src/serve/hub-app/hub-app.tsx +32 -8
- package/src/serve/hub-app/hub-types.ts +5 -2
- package/src/serve/hub-app/hub.css +89 -63
- package/src/serve/hub-app/icons.tsx +11 -0
- package/src/serve/hub-app/index.html +1 -0
- package/src/serve/hub-app/keys.ts +15 -4
- package/src/serve/hub-app/live-svc.ts +3 -2
- package/src/serve/hub-app/machine-st.ts +18 -22
- package/src/serve/hub-app/plan-view.tsx +51 -0
- package/src/serve/hub-app/planned-st.ts +9 -4
- package/src/serve/hub-app/processes-view.tsx +45 -0
- package/src/serve/hub-app/project-list.css +4 -1
- package/src/serve/hub-app/project-list.tsx +6 -3
- package/src/serve/hub-app/projects-svc.ts +25 -10
- package/src/serve/hub-app/prose-hits-st.ts +3 -2
- package/src/serve/hub-app/reach.ts +36 -0
- package/src/serve/hub-app/readme-svc.ts +3 -2
- package/src/serve/hub-app/repo-sections.ts +131 -0
- package/src/serve/hub-app/repo-st.ts +70 -14
- package/src/serve/hub-app/sidebar.tsx +17 -42
- package/src/serve/hub-app/timeline-svc.ts +3 -2
- package/src/serve/hub-app/tree-build.ts +151 -155
- package/src/serve/hub-app/tree-row.tsx +55 -12
- package/src/serve/hub-app/tree-svc.ts +97 -56
- package/src/serve/hub-app/when-view.tsx +89 -0
- package/src/serve/hub-app/when.css +17 -0
- package/src/serve/hub-runtime.ts +7 -18
- package/src/serve/project-gutter-route.ts +22 -1
- package/src/serve/run-serve.ts +1 -1
- package/src/serve/serve-hub.ts +4 -14
- package/src/suite/judge-suites.ts +6 -0
- package/src/trace/interpose.c +655 -0
- package/src/trace/read-trace.ts +485 -0
- package/src/trace/tracer.ts +282 -0
- package/src/serve/hub-app/processes-svc.ts +0 -63
- package/src/serve/hub-app/repo-sections.tsx +0 -61
- package/src/serve/repo-processes.ts +0 -89
|
@@ -38,7 +38,6 @@ a,a:visited{color:var(--accent)}
|
|
|
38
38
|
.pjpanel,.appcol,.pside,.pmain{height:100%;min-width:0}
|
|
39
39
|
.apptop,.pdock{height:100%;min-height:0}
|
|
40
40
|
.pjpanel{display:flex}
|
|
41
|
-
/* the box the machine toasts float in (home-strip.css .htoasts) */
|
|
42
41
|
.appcol{position:relative;display:flex;flex-direction:column}
|
|
43
42
|
/* one region per repo visited, the shown one alone displayed */
|
|
44
43
|
.repo{flex:1;min-height:0;display:flex;flex-direction:column}
|
|
@@ -50,10 +49,9 @@ a,a:visited{color:var(--accent)}
|
|
|
50
49
|
.pmain > main{flex:1}
|
|
51
50
|
|
|
52
51
|
/* ── a repo's bar: on the home the strip (home-strip.css .hstrip), on a hub
|
|
53
|
-
of its own the header (.hdr). Its name,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
run state to its spinner ── */
|
|
52
|
+
of its own the header (.hdr). Its name, its check… button and a spinner
|
|
53
|
+
while a run goes, then its processes and its editor icons, on one line.
|
|
54
|
+
Its trouble floats under it (home-strip.css .htoasts) ── */
|
|
57
55
|
/* the repo's processes (serve.processes): a dot, its id, a link once up */
|
|
58
56
|
.hprocs{display:inline-flex;gap:var(--s1);align-items:center}
|
|
59
57
|
.hproc{display:inline-flex;align-items:center;gap:var(--s0)}
|
|
@@ -64,7 +62,7 @@ a,a:visited{color:var(--accent)}
|
|
|
64
62
|
.hdr{display:flex;gap:var(--s2);align-items:center;height:36px;flex:none;padding:0 var(--s3);border-bottom:1px solid var(--line);background:var(--bg-top)}
|
|
65
63
|
.hdr .sp{flex:1}
|
|
66
64
|
.hname{font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink);font-weight:600;margin-right:var(--s2);white-space:nowrap}
|
|
67
|
-
.hdr,.hstrip{
|
|
65
|
+
.hdr,.hstrip{white-space:nowrap}
|
|
68
66
|
.hrun{display:inline-flex;align-items:center;gap:var(--s1);flex:none;margin-left:var(--s2)}
|
|
69
67
|
.hbtn{display:inline-flex;align-items:center;gap:var(--s1);height:22px;padding:0 var(--s2);font-size:11.5px;white-space:nowrap;flex:none}
|
|
70
68
|
.hbtn .hico{width:13px;height:13px}
|
|
@@ -72,40 +70,37 @@ a,a:visited{color:var(--accent)}
|
|
|
72
70
|
.hicon{display:inline-flex;align-items:center;justify-content:center;flex:none;width:24px;height:22px;padding:0;border:0;background:none;color:var(--dim)}
|
|
73
71
|
.hicon:hover{color:var(--accent)}
|
|
74
72
|
.hicon .hico{width:15px;height:15px}
|
|
75
|
-
/* the run state holds its width whatever it says, so a run never moves the buttons beside it */
|
|
76
|
-
.hstatus{max-width:420px;flex:0 1 auto;font-size:11.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
77
|
-
.hstatus.red{color:var(--red)}
|
|
78
|
-
.hstatus.warn{color:var(--warn)}
|
|
79
|
-
/* its words sit against the run buttons, cut with an ellipsis */
|
|
80
|
-
.hstate{display:inline-flex;gap:var(--s1);align-items:center;width:200px;min-width:200px;flex:none;justify-content:flex-end;font-size:11.5px;color:var(--dim);overflow:hidden}
|
|
81
|
-
.hstate.on{color:var(--ink)}
|
|
82
|
-
.hstate .htext{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
83
|
-
.hstate .spin.idle{visibility:hidden;animation:none}
|
|
84
|
-
/* after .hstate, so a narrow bar wins over its width */
|
|
85
|
-
@container (max-width:600px){.hbtn .lbl{display:none}.hbtn{padding:0 var(--s1)}}
|
|
86
|
-
@container (max-width:520px){.hstate{width:120px;min-width:120px}}
|
|
87
|
-
@container (max-width:420px){.hstate .htext{display:none}.hstate{width:16px;min-width:16px}}
|
|
88
73
|
|
|
89
74
|
/* a row of a (?) legend: one glyph or word, then its meaning */
|
|
90
75
|
.lrow{display:flex;gap:var(--s2);align-items:center;height:29px;flex:none;padding:0 var(--s3)}
|
|
91
76
|
|
|
92
|
-
/* ── the left pane: the
|
|
93
|
-
|
|
77
|
+
/* ── the left pane: the filter, then the tree, the one part that
|
|
78
|
+
scrolls ── */
|
|
94
79
|
aside{border-right:1px solid var(--line);background:var(--panel);min-height:0;display:flex;flex-direction:column;overflow:hidden}
|
|
95
80
|
aside > *{flex:none}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
.
|
|
100
|
-
.
|
|
101
|
-
.
|
|
102
|
-
|
|
103
|
-
.
|
|
104
|
-
|
|
105
|
-
.
|
|
81
|
+
/* the tree is long, so it keeps a scroll indicator, a thin one with no track:
|
|
82
|
+
WebKit (the macOS app) ignores scrollbar-width and draws a wide dark band */
|
|
83
|
+
aside > .tscroll{flex:1;min-height:0;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--line) transparent}
|
|
84
|
+
aside > .tscroll::-webkit-scrollbar{width:6px}
|
|
85
|
+
aside > .tscroll::-webkit-scrollbar-track{background:transparent}
|
|
86
|
+
aside > .tscroll::-webkit-scrollbar-thumb{background:var(--line);border-radius:3px}
|
|
87
|
+
/* a section, the tree's top row: the project list's lines (project-list.css
|
|
88
|
+
.pjpl), a little shorter, the current one lit and marked in the accent.
|
|
89
|
+
The chevron column is the tree's own, so a section's rows line up under it */
|
|
90
|
+
.tsec{display:grid;grid-template-columns:14px 16px minmax(0,1fr) auto auto;align-items:center;gap:var(--s2);height:26px;padding:0 var(--s1) 0 var(--s2);color:var(--dim);font-size:13px;cursor:pointer;white-space:nowrap}
|
|
91
|
+
.tsec:hover{background:var(--panel2);color:var(--ink)}
|
|
92
|
+
.tsec.empty{opacity:.4}
|
|
93
|
+
.tsec.noop{cursor:default}
|
|
94
|
+
.tsec.noop:hover{background:none;color:var(--dim)}
|
|
95
|
+
.tsec.on{box-shadow:inset 2px 0 0 var(--accent)}
|
|
96
|
+
.tsec.cur{background:var(--sel);color:var(--ink);box-shadow:inset 3px 0 0 var(--accent);font-weight:600}
|
|
97
|
+
.tsec .hico{width:16px;height:16px}
|
|
98
|
+
.tsec:hover .tw{color:var(--ink)}
|
|
99
|
+
.rsecl{min-width:0;overflow:hidden;text-overflow:ellipsis}
|
|
100
|
+
.rsecn{min-width:16px;height:16px;padding:0 var(--s1);border-radius:8px;font-size:10px;font-weight:700;line-height:16px;text-align:center;color:var(--bg)}
|
|
106
101
|
.rsecn.nred{background:var(--red)}
|
|
107
102
|
.rsecn.nwarn{background:var(--warn)}
|
|
108
|
-
.
|
|
103
|
+
.tsec:hover .pjkey,.tsec.cur .pjkey{opacity:1}
|
|
109
104
|
/* segmented severity filter — all · needs · red. A thin border and an
|
|
110
105
|
underline on the live one: no fill, no pill */
|
|
111
106
|
.seg{display:inline-flex;border:1px solid var(--line);border-radius:3px;overflow:hidden;flex:none}
|
|
@@ -115,9 +110,8 @@ aside > .tscroll{flex:1;min-height:0;overflow-y:auto}
|
|
|
115
110
|
.seg button:hover{color:var(--ink)}
|
|
116
111
|
.seg button.on{color:var(--accent);border-bottom-color:var(--accent)}
|
|
117
112
|
.seg button .sk{margin-left:var(--s1);font-size:9px;opacity:.55}
|
|
118
|
-
.swhen{padding:0 var(--s3) var(--s2);font-size:11px;color:var(--dim)}
|
|
119
113
|
/* a narrow pane puts the severity filter under the input, never over it */
|
|
120
|
-
.ssearch{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2);padding:var(--s2) var(--s3)}
|
|
114
|
+
.ssearch{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2);padding:var(--s2) var(--s3);border-bottom:1px solid var(--line)}
|
|
121
115
|
.sfield{position:relative;flex:1 1 96px;min-width:0;display:flex}
|
|
122
116
|
.ssearch input{width:100%;background:var(--bg);border:1px solid var(--line);border-radius:4px;color:var(--ink);
|
|
123
117
|
padding:var(--s0) var(--s4) var(--s0) var(--s2);font-size:12px;outline:none}
|
|
@@ -133,8 +127,8 @@ aside > .tscroll{flex:1;min-height:0;overflow-y:auto}
|
|
|
133
127
|
no prose, no dash separators ── */
|
|
134
128
|
.tree{padding:var(--s1) 0 var(--s4);outline:none}
|
|
135
129
|
.tree:focus-visible{box-shadow:inset 2px 0 0 var(--accent)}
|
|
136
|
-
.trow{display:grid;grid-template-columns:calc(var(--d,0)*
|
|
137
|
-
align-items:center;gap:var(--s1);padding:0 var(--s2);height:20px;line-height:20px;cursor:pointer;white-space:nowrap}
|
|
130
|
+
.trow{display:grid;grid-template-columns:calc(var(--d,0)*10px + 14px) 14px minmax(0,1fr) 12px auto 14px;
|
|
131
|
+
align-items:center;gap:var(--s1);padding:0 var(--s1) 0 var(--s2);height:20px;line-height:20px;cursor:pointer;white-space:nowrap}
|
|
138
132
|
.trow:hover{background:var(--panel2)}
|
|
139
133
|
.trow.on{background:var(--sel);box-shadow:inset 2px 0 0 var(--accent)}
|
|
140
134
|
/* the fold marker: an 11px chevron with the whole first column as its hit
|
|
@@ -168,21 +162,20 @@ aside > .tscroll{flex:1;min-height:0;overflow-y:auto}
|
|
|
168
162
|
.tg.ok{color:var(--ok)}
|
|
169
163
|
.tg.adv{color:var(--warn);opacity:.75}
|
|
170
164
|
.tg.run{color:var(--accent);animation:pulse 1.1s ease-in-out infinite}
|
|
171
|
-
/*
|
|
172
|
-
|
|
173
|
-
.tdur{
|
|
165
|
+
/* the duration: a clock tinted by the tone table (hub-app/dur.ts), the
|
|
166
|
+
figure in its tooltip, so the label keeps the width */
|
|
167
|
+
.tdur{display:flex;align-items:center;justify-content:center;color:var(--dim)}
|
|
168
|
+
.tdur .hico{width:11px;height:11px;color:inherit}
|
|
174
169
|
.tdur.fast,.dnum.fast{color:var(--ok)}
|
|
175
170
|
.tdur.plain,.dnum.plain{color:var(--ink)}
|
|
176
171
|
.tdur.slow,.dnum.slow{color:var(--warn)}
|
|
177
172
|
.tdur.bad,.dnum.bad{color:var(--red)}
|
|
178
173
|
.tnote{font-size:10px;text-align:right;color:var(--dim);overflow:hidden;text-overflow:ellipsis}
|
|
179
174
|
.tnote b{font-weight:600;color:var(--red)}
|
|
180
|
-
.stabs{display:flex;gap:var(--s2);align-items:center;padding:var(--s1) var(--s3);border-bottom:1px solid var(--line)}
|
|
181
|
-
.stabs .sp{flex:1}
|
|
182
|
-
.stabs .cnt{min-width:4ch;text-align:right;font-size:11px;color:var(--dim);font-variant-numeric:tabular-nums}
|
|
183
175
|
.catgen{display:inline-flex;gap:var(--s1)}
|
|
184
176
|
.catgen button{padding:var(--s0) var(--s2);font-size:11px;font-weight:700}
|
|
185
177
|
.tempty{padding:var(--s2) var(--s3);font-size:12px;color:var(--dim);white-space:pre-wrap}
|
|
178
|
+
.thidden{cursor:pointer}.thidden:hover{color:var(--ink)}
|
|
186
179
|
.twait{display:flex;align-items:center;gap:var(--s2)}
|
|
187
180
|
.warnline{color:var(--red)}
|
|
188
181
|
.fold{cursor:pointer;color:var(--dim);font-size:15px;display:inline-block;width:22px;text-align:center;
|
|
@@ -369,7 +362,15 @@ kbd{background:var(--panel2);border:1px solid var(--line);border-radius:4px;padd
|
|
|
369
362
|
.assetrem .rem{color:var(--ok);border-left:2px solid var(--ok);padding-left:var(--s2);font-size:12.5px;margin:0 0 var(--s2);white-space:pre-wrap}
|
|
370
363
|
|
|
371
364
|
/* ── collection item card + status card ── */
|
|
372
|
-
|
|
365
|
+
/* a repo page sits on the page background, never in a box of its own: its
|
|
366
|
+
header and its sentence are unboxed, and each group it holds (the facts,
|
|
367
|
+
a titled section, the command, the admin rows, the runs list) is one
|
|
368
|
+
lighter box, so a box always means "these belong together" */
|
|
369
|
+
.colcard{max-width:760px;width:100%;padding:var(--s2) 0;text-align:left}
|
|
370
|
+
.colcard>.cfacts,.colcard>.csec,.colcard>.ccmd,.colcard>.md:not(.usays),.colcard>.adbox,.tlcard>.rlist{background:var(--panel);border:1px solid var(--line);border-radius:4px;padding:var(--s2) var(--s3)}
|
|
371
|
+
.colcard>.cfacts,.colcard>.ccmd,.colcard>.md:not(.usays),.colcard>.adbox{margin-top:var(--s3)}
|
|
372
|
+
.ccmd>.cf:first-child,.cfacts>.cf:nth-child(-n+2){border-top:0}
|
|
373
|
+
@media (max-width:900px){.cfacts>.cf:nth-child(2){border-top:1px solid var(--line)}}
|
|
373
374
|
.colcard h3{margin:0 0 var(--s2);font-size:15px;display:flex;gap:var(--s2);align-items:baseline}
|
|
374
375
|
.colcard h3 .sp{flex:1}
|
|
375
376
|
.colcard .truth{margin-left:auto;font:11px ui-monospace,Menlo,monospace;color:var(--dim)}
|
|
@@ -560,6 +561,15 @@ a.tok{color:var(--accent)}
|
|
|
560
561
|
.checkcard .dim{color:var(--dim)}
|
|
561
562
|
/* what is not a fact: the output and the slow suites, titled, unboxed */
|
|
562
563
|
.csec{margin-top:var(--s4)}
|
|
564
|
+
/* where it runs: one line per context, the value then its meaning. A context
|
|
565
|
+
that never runs the check stays dim, so the lit lines read down the list */
|
|
566
|
+
.cruns{display:grid;grid-template-columns:auto auto minmax(0,1fr);column-gap:var(--s3);row-gap:var(--s0);align-items:baseline}
|
|
567
|
+
.crun{display:contents;color:var(--dim)}
|
|
568
|
+
.crv{font:11px ui-monospace,Menlo,monospace;white-space:nowrap}
|
|
569
|
+
.crun.on .crv{color:var(--ok)}
|
|
570
|
+
.crun.cache .crv{color:var(--ink)}
|
|
571
|
+
.crm{font-size:11.5px;color:var(--dim);opacity:.8}
|
|
572
|
+
.crun:not(.on) .crm{opacity:.5}
|
|
563
573
|
.cstitle{display:flex;gap:var(--s1);align-items:center;font-size:11px;text-transform:uppercase;letter-spacing:.08em;
|
|
564
574
|
color:var(--dim);margin-bottom:var(--s2)}
|
|
565
575
|
.wline{margin-bottom:var(--s1);word-break:break-word}
|
|
@@ -624,18 +634,21 @@ a.tok{color:var(--accent)}
|
|
|
624
634
|
padding:var(--s1) var(--s2);font-size:12.5px;outline:none}
|
|
625
635
|
.wform input:focus{border-color:var(--accent)}
|
|
626
636
|
|
|
627
|
-
/* ── collection
|
|
628
|
-
.
|
|
629
|
-
.cghead{font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--accent);
|
|
637
|
+
/* ── a collection's page: its title and count, then its table ── */
|
|
638
|
+
.cghead{font-size:15px;font-weight:600;color:var(--ink);
|
|
630
639
|
display:flex;gap:var(--s2);align-items:baseline;margin:0 0 var(--s3)}
|
|
631
|
-
.
|
|
632
|
-
|
|
640
|
+
.ctlead{font-size:11px;color:var(--dim);margin:calc(-1 * var(--s1)) 0 var(--s2)}
|
|
641
|
+
.cghead .cnt{font-size:12px;font-weight:400;color:var(--dim)}
|
|
642
|
+
/* the checks, asks and secrets pages: one dense table each (col-tables-view.tsx), on
|
|
633
643
|
the activity table (.actable), a group header per state that folds */
|
|
634
644
|
.ctpage{flex:1;min-height:0;overflow-y:auto;padding:var(--s3) var(--s4);font-size:12.5px;container-type:inline-size}
|
|
645
|
+
/* the table sits on the panel surface, the way every card section does, so
|
|
646
|
+
its lines read against something lighter than the page */
|
|
647
|
+
.ctbox{background:var(--panel);border:1px solid var(--line);border-radius:4px;padding:var(--s1) 0}
|
|
635
648
|
.cttable{font-variant-numeric:tabular-nums}
|
|
636
649
|
.cttable td{padding:var(--s0) var(--s2);vertical-align:baseline}
|
|
637
|
-
.ctgrp td{padding:var(--s2) var(--s2) var(--s1);color:var(--
|
|
638
|
-
.ctgrp:hover td{color:var(--
|
|
650
|
+
.ctgrp td{padding:var(--s2) var(--s2) var(--s1);color:var(--ink);font-size:12.5px;font-weight:600;border-bottom:1px solid var(--line);cursor:pointer;user-select:none}
|
|
651
|
+
.ctgrp:hover td{color:var(--accent)}
|
|
639
652
|
.ctgrp.g-fail td{color:var(--red)}
|
|
640
653
|
.ctgrp.g-warn td{color:var(--warn)}
|
|
641
654
|
.ctcaret{display:inline-block;width:14px}
|
|
@@ -648,17 +661,8 @@ a.tok{color:var(--accent)}
|
|
|
648
661
|
.ctbudget,.ctplan,.ctblocks{color:var(--dim)}
|
|
649
662
|
.ctprobe{font-size:11.5px}
|
|
650
663
|
.cttable .c-dot{width:22px}.cttable .c-id{width:140px}.cttable .c-title{width:16%}.cttable .c-age{width:72px}.cttable .c-dur{width:152px}.cttable .c-plan{width:88px}
|
|
651
|
-
.cttable .c-kind{width:92px}.cttable .c-blocks{width:32%}.cttable .c-probe{width:84px}
|
|
664
|
+
.cttable .c-name{width:30%}.cttable .c-file{width:34%}.cttable .c-kind{width:92px}.cttable .c-blocks{width:32%}.cttable .c-probe{width:84px}
|
|
652
665
|
@container (max-width:760px){.cttable .c-title{width:0}.cttable .cttitle{display:none}.cttable .c-plan{width:0}.cttable .ctplan{display:none}}
|
|
653
|
-
.cgcards{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:var(--s3)}
|
|
654
|
-
.cgcard{background:var(--panel);border:1px solid var(--line);border-radius:5px;padding:var(--s2) var(--s3);cursor:pointer;min-width:0}
|
|
655
|
-
.cgcard:hover{border-color:var(--accent)}
|
|
656
|
-
.cgcard .cgtop{display:flex;gap:var(--s2);align-items:center;min-width:0}
|
|
657
|
-
.cgcard .cgtop b{font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
658
|
-
.cgcard .truth{display:block;font:11px ui-monospace,Menlo,monospace;color:var(--dim);margin-top:var(--s1);
|
|
659
|
-
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
660
|
-
.cgcard .cgrem{color:var(--ok);border-left:2px solid var(--ok);padding-left:var(--s2);font-size:12px;margin-top:var(--s1);
|
|
661
|
-
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
662
666
|
|
|
663
667
|
/* ── graph item view (the graph itself fills the stage) ── */
|
|
664
668
|
.graphview{flex:1;min-height:0;display:flex;flex-direction:column;gap:var(--s2);padding:var(--s3) var(--s4) var(--s4)}
|
|
@@ -701,13 +705,10 @@ a.tok{color:var(--accent)}
|
|
|
701
705
|
overflow-x:auto;font:11px/1.5 ui-monospace,Menlo,monospace;white-space:pre}
|
|
702
706
|
.md pre code{background:none;border:0;padding:0}
|
|
703
707
|
.md a{color:var(--accent)}
|
|
704
|
-
.md.clamp{max-height:180px;overflow:hidden;margin-top:var(--s1);
|
|
705
|
-
-webkit-mask-image:linear-gradient(var(--solid) 65%,transparent);mask-image:linear-gradient(var(--solid) 65%,transparent)}
|
|
706
708
|
|
|
707
709
|
/* ── secret (cred) cards ── */
|
|
708
710
|
.credname{font:600 13px ui-monospace,Menlo,monospace}
|
|
709
711
|
.creddesc{font-size:12px;color:var(--dim);margin-top:var(--s1)}
|
|
710
|
-
.credrow{display:flex;gap:var(--s2);align-items:center;margin-top:var(--s2)}
|
|
711
712
|
.stchip{font-size:10px;border-radius:5px;padding:var(--s0) var(--s2);letter-spacing:.08em;text-transform:uppercase;
|
|
712
713
|
border:1px solid;flex:none;font-weight:600}
|
|
713
714
|
.stchip.ok{color:var(--ok);border-color:var(--ok)}
|
|
@@ -745,9 +746,34 @@ pre{background:var(--panel);border:1px solid var(--line);border-radius:3px;paddi
|
|
|
745
746
|
|
|
746
747
|
/* ── the admin page: one row per control, its sentence under its name ── */
|
|
747
748
|
.adrow{display:flex;align-items:center;gap:var(--s4);padding:var(--s3) 0;border-top:1px solid var(--line)}
|
|
749
|
+
.adrow:first-child{border-top:0}
|
|
748
750
|
.adtext{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--s0);font-size:13px}
|
|
749
751
|
.adtext .dim{font-size:12px}
|
|
750
752
|
.adctl{flex:none;display:flex;align-items:center}
|
|
751
753
|
.adflag{display:inline-flex;align-items:center;gap:var(--s1);font-size:12px;color:var(--dim)}
|
|
752
754
|
.adflag input{width:auto;margin:0}
|
|
755
|
+
.adid{display:flex;align-items:flex-start;gap:var(--s4)}
|
|
756
|
+
.adicon{flex:none;width:128px;display:flex;flex-direction:column;gap:var(--s1);font-size:12px}
|
|
757
|
+
.adicon img{display:block;width:128px;height:128px}
|
|
758
|
+
.adicon img[hidden]{display:none}
|
|
759
|
+
.adpick{display:flex;align-items:center;gap:var(--s2);margin-top:var(--s1);font-size:12px;cursor:pointer}
|
|
760
|
+
.colcard .adpick input{flex:none;min-width:0;width:28px;height:28px;padding:0;margin:0;border:1px solid var(--line);border-radius:4px;background:none;cursor:pointer}
|
|
761
|
+
.adpick input::-webkit-color-swatch-wrapper{padding:0}
|
|
762
|
+
.adpick input::-webkit-color-swatch{border:none;border-radius:3px}
|
|
763
|
+
.adsample{padding:var(--s0) var(--s2);border-radius:4px;font-weight:600;font-size:13px}
|
|
764
|
+
.adsample.dark{background:var(--bg)}
|
|
765
|
+
.adsample.light{background:var(--tag)}
|
|
753
766
|
.adconfirm{display:inline-flex;align-items:center;gap:var(--s2);font-size:12px}
|
|
767
|
+
/* a repo's plan page: one line per planned check */
|
|
768
|
+
.plntable{width:100%;border-collapse:collapse;font-size:12.5px}
|
|
769
|
+
.plntable td{padding:var(--s1) var(--s2);border-top:1px solid var(--line);vertical-align:baseline}
|
|
770
|
+
.plntable tr:first-child td{border-top:0}
|
|
771
|
+
.plnwhen{white-space:nowrap;font-variant-numeric:tabular-nums}
|
|
772
|
+
.plnrun{text-align:right;white-space:nowrap}
|
|
773
|
+
/* a repo's git page */
|
|
774
|
+
.gitfiles,.gitcommits{width:100%;border-collapse:collapse;font-size:12.5px}
|
|
775
|
+
.gitfiles td,.gitcommits td{padding:2px var(--s2);vertical-align:baseline}
|
|
776
|
+
.gitkind{width:1%;white-space:nowrap;color:var(--dim)}
|
|
777
|
+
.gitkind.staged{color:var(--ok)}
|
|
778
|
+
.gitkind.untracked{color:var(--warn)}
|
|
779
|
+
.gitpath{font:12px ui-monospace,Menlo,monospace;overflow-wrap:anywhere}
|
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
export const ICON = {
|
|
6
6
|
shipkit: 'M3 7.5 10 4l7 3.5v5L10 16l-7-3.5zM3 7.5 10 11l7-3.5M10 11v5',
|
|
7
7
|
process: 'M2 10h3.5l2-5 5 10 2-5H18',
|
|
8
|
+
processes: 'M2 10h3.5l2-5 5 10 2-5H18',
|
|
9
|
+
when: 'M10 3a7 7 0 1 0 .01 0M10 6v4l2.5 2',
|
|
10
|
+
plan: 'M5.5 3h9M5.5 17h9M6.5 3c0 4 7 4 7 7s-7 3-7 7M13.5 3c0 4-7 4-7 7s7 3 7 7',
|
|
11
|
+
git: 'M6 5v10M6 3a2 2 0 1 0 0 .01M6 17a2 2 0 1 0 0 .01M14 5a2 2 0 1 0 0 .01M14 7c0 4.5-8 3.5-8 7',
|
|
12
|
+
reflections: 'M4 3.5h12v9H9l-4 3.5v-3.5H4zM7.5 7h5M7.5 9.5h3',
|
|
8
13
|
monitor: 'M3 4h14v9H3zM7 16.5h6M10 13v3.5',
|
|
9
14
|
archived: 'M2.5 4h15v3.5h-15zM4 7.5V16h12V7.5M8 10.5h4',
|
|
10
15
|
home: 'M3 9.5 10 3.5l7 6M5 8v8.5h10V8M8.5 16.5v-4.5h3v4.5',
|
|
@@ -15,7 +20,11 @@ export const ICON = {
|
|
|
15
20
|
fork: 'M6 5v10M6 3a2 2 0 1 0 0 .01M6 17a2 2 0 1 0 0 .01M14 5a2 2 0 1 0 0 .01M14 7c0 4.5-8 3.5-8 7',
|
|
16
21
|
search: 'M8.5 3a5.5 5.5 0 1 0 0 11a5.5 5.5 0 1 0 0-11M12.5 12.5 17 17',
|
|
17
22
|
// a repo page's sections, then its two runs and its editor
|
|
23
|
+
readme: 'M5 2.5h7l3 3v12H5zM12 2.5v3h3M7.5 9h5M7.5 12h5M7.5 15h3',
|
|
18
24
|
checks: 'M4 10.5 7.5 14 16 5.5',
|
|
25
|
+
contracts: 'M5 2.5h10v15H5zM7.5 6h5M7.5 9h5M7.5 12h2.5',
|
|
26
|
+
repo: 'M2.5 5h5l1.5 1.5h8.5v9.5h-15z',
|
|
27
|
+
secrets: 'M7 7a3 3 0 1 0 0 6a3 3 0 1 0 0-6M10 10h7.5M15 10v2.5M17.5 10v2',
|
|
19
28
|
runs: 'M3 5h8M3 10h12M3 15h6',
|
|
20
29
|
asks: 'M3 4h14v9H8l-4 3.5V13H3z',
|
|
21
30
|
tests: 'M8 3h4M8.5 3v5L4 16.5h12L11.5 8V3M6.2 12.5h7.6',
|
|
@@ -30,6 +39,8 @@ export const ICON = {
|
|
|
30
39
|
// a process's own switches on the overview
|
|
31
40
|
play: 'M6.5 4.5v11l9-5.5z',
|
|
32
41
|
stop: 'M5.5 5.5h9v9h-9z',
|
|
42
|
+
link: 'M8.5 11.5a3.5 3.5 0 0 0 5 0l2.5-2.5a3.5 3.5 0 0 0-5-5l-1 1M11.5 8.5a3.5 3.5 0 0 0-5 0L4 11a3.5 3.5 0 0 0 5 5l1-1',
|
|
43
|
+
clock: 'M10 2.5a7.5 7.5 0 1 0 0 15a7.5 7.5 0 1 0 0-15M10 6v4l2.5 2',
|
|
33
44
|
restart: 'M15.5 10a5.5 5.5 0 1 1-1.6-3.9M14.2 3v3.3h-3.3',
|
|
34
45
|
} as const
|
|
35
46
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
<link rel="stylesheet" href="./prose-hits.css" />
|
|
10
10
|
<link rel="stylesheet" href="./home-strip.css" />
|
|
11
11
|
<link rel="stylesheet" href="./planned.css" />
|
|
12
|
+
<link rel="stylesheet" href="./when.css" />
|
|
12
13
|
<link rel="stylesheet" href="./activity.css" />
|
|
13
14
|
<link rel="stylesheet" href="./readme.css" />
|
|
14
15
|
<link rel="stylesheet" href="./dock.css" />
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// the page's command keys, read the same on every layout: ⌘1 to ⌘9 select the
|
|
2
|
-
// nth repo of the project list, then the home row under the right hand: ⌘J
|
|
2
|
+
// nth repo of the project list, ⌘↑ and ⌘↓ the repo above or below, then the home row under the right hand: ⌘J
|
|
3
3
|
// the picker, ⌘K the overview, ⌘L the activity, ⌘' the plan. ⌘B cycles the
|
|
4
4
|
// dock at the bottom of the page (dock-st.ts): the line, slim, open.
|
|
5
5
|
// A digit and the quote are read from the physical key (`code`), so an
|
|
6
6
|
// AZERTY keyboard, where the key under 1 types `&`, selects the same repo. Ctrl stands in for
|
|
7
7
|
// ⌘ off a Mac. The macOS app binds none of these to a menu item, so they
|
|
8
8
|
// reach the page (tests/home-strip.test.tsx reads ShipkitGui.swift).
|
|
9
|
-
export type PageKey = { t: 'repo'; n: number } | { t: 'home' } | { t: 'planned' } | { t: 'activity' } | { t: 'picker' } | { t: 'dock' }
|
|
9
|
+
export type PageKey = { t: 'repo'; n: number } | { t: 'step'; by: -1 | 1 } | { t: 'home' } | { t: 'planned' } | { t: 'activity' } | { t: 'picker' } | { t: 'dock' }
|
|
10
10
|
|
|
11
11
|
type KeyLike = { key: string; code: string; metaKey: boolean; ctrlKey: boolean; shiftKey: boolean; altKey: boolean }
|
|
12
12
|
|
|
@@ -15,6 +15,8 @@ export function pageKey(e: KeyLike): PageKey | null {
|
|
|
15
15
|
const digit = /^Digit([1-9])$/.exec(e.code)
|
|
16
16
|
if (digit != null) return { t: 'repo', n: Number(digit[1]) }
|
|
17
17
|
if (e.shiftKey) return null
|
|
18
|
+
if (e.key === 'ArrowUp') return { t: 'step', by: -1 }
|
|
19
|
+
if (e.key === 'ArrowDown') return { t: 'step', by: 1 }
|
|
18
20
|
if (e.code === 'Quote' || e.key === "'") return { t: 'planned' }
|
|
19
21
|
const k = e.key.toLowerCase()
|
|
20
22
|
if (k === 'j') return { t: 'picker' }
|
|
@@ -28,6 +30,8 @@ export function pageKey(e: KeyLike): PageKey | null {
|
|
|
28
30
|
export const PLACE_KEY = { overview: '⌘K', planned: "⌘'", activity: '⌘L' } as const
|
|
29
31
|
/** the picker's shortcut, on the first button, before home */
|
|
30
32
|
export const PICKER_KEY = '⌘J'
|
|
33
|
+
/** the repo above and below, hinted at the project list's foot */
|
|
34
|
+
export const STEP_KEYS = ['⌘↑', '⌘↓'] as const
|
|
31
35
|
/** the dock's shortcut, on the dock's own header */
|
|
32
36
|
export const DOCK_KEY = '⌘B'
|
|
33
37
|
|
|
@@ -35,6 +39,10 @@ export const DOCK_KEY = '⌘B'
|
|
|
35
39
|
* badge at the input's right prints this */
|
|
36
40
|
export const SEARCH_KEY = '/'
|
|
37
41
|
|
|
42
|
+
/** in the filter, up and down walk the tree rows the filter keeps; left
|
|
43
|
+
* and right stay the text caret's */
|
|
44
|
+
export const isTreeWalk = (key: string): boolean => key === 'ArrowUp' || key === 'ArrowDown'
|
|
45
|
+
|
|
38
46
|
export function isSearchKey(e: Pick<KeyLike, 'key' | 'metaKey' | 'ctrlKey' | 'altKey'>): boolean {
|
|
39
47
|
return e.key === SEARCH_KEY && !e.metaKey && !e.ctrlKey && !e.altKey
|
|
40
48
|
}
|
|
@@ -46,8 +54,11 @@ export function isSearchKey(e: Pick<KeyLike, 'key' | 'metaKey' | 'ctrlKey' | 'al
|
|
|
46
54
|
* plus a letter only types a character inside a text field, where the page
|
|
47
55
|
* leaves the key alone. Read from the physical key (`code`): on a Mac ⌥C
|
|
48
56
|
* types ç, and AZERTY moves the letters */
|
|
49
|
-
export type SectionId = '
|
|
50
|
-
export const SECTION_LETTER: Record<SectionId, string> = {
|
|
57
|
+
export type SectionId = 'readme' | 'contracts' | 'shipkit' | 'repo' | 'secrets' | 'when' | 'runs' | 'plan' | 'processes' | 'git' | 'asks' | 'reflections' | 'tests' | 'media' | 'wikis' | 'admin'
|
|
58
|
+
export const SECTION_LETTER: Record<SectionId, string> = { readme: 'E', contracts: 'K', shipkit: 'S', repo: 'C', secrets: 'X', when: 'O', runs: 'R', plan: 'N', processes: 'P', git: 'G', asks: 'A', reflections: 'F', tests: 'T', media: 'M', wikis: 'W', admin: 'D' }
|
|
59
|
+
/** a section's words on its row, where the id alone would not say it */
|
|
60
|
+
export const SECTION_LABEL: Partial<Record<SectionId, string>> = { shipkit: 'shipkit checks', repo: 'repo checks' }
|
|
61
|
+
export const sectionLabel = (id: SectionId): string => SECTION_LABEL[id] ?? id
|
|
51
62
|
/** the badge at the end of a section's line */
|
|
52
63
|
export const sectionKeyLabel = (id: SectionId): string => `⌥${SECTION_LETTER[id]}`
|
|
53
64
|
|
|
@@ -11,6 +11,7 @@ import type { TimelineInvocation } from '../../ledger/run-timeline.ts'
|
|
|
11
11
|
import { ChartView, type ChartSource } from './chart-view.ts'
|
|
12
12
|
import type { RepoSt } from './repo-st.ts'
|
|
13
13
|
import { chartHeight } from './timeline-geometry.ts'
|
|
14
|
+
import { ownTrouble, reach } from './reach.ts'
|
|
14
15
|
|
|
15
16
|
/** how often the page asks. A strip a second behind the runner is still live */
|
|
16
17
|
export const LIVE_POLL_MS = 1_000
|
|
@@ -107,7 +108,7 @@ export class LiveSvc {
|
|
|
107
108
|
if (this.inFlight) return
|
|
108
109
|
this.inFlight = true
|
|
109
110
|
try {
|
|
110
|
-
const r = await
|
|
111
|
+
const r = await reach(this.st.url('/api/live.json'))
|
|
111
112
|
if (!r.ok) throw new Error(await r.text())
|
|
112
113
|
const v = (await r.json()) as LiveView
|
|
113
114
|
const ended = this.running && v.runs.length === 0
|
|
@@ -125,7 +126,7 @@ export class LiveSvc {
|
|
|
125
126
|
if (this.st.timeline.data != null) void this.st.timeline.load(true)
|
|
126
127
|
}
|
|
127
128
|
} catch (e) {
|
|
128
|
-
runInAction(() => { this.error = `the live runs could not be read: ${
|
|
129
|
+
runInAction(() => { this.error = ownTrouble(e, this.st.home.machine, (m) => `the live runs could not be read: ${m}`) })
|
|
129
130
|
} finally {
|
|
130
131
|
this.inFlight = false
|
|
131
132
|
}
|
|
@@ -14,6 +14,7 @@ import { processGroups, type ProcessGroup } from '../../home/process-rows.ts'
|
|
|
14
14
|
import { ClockSvc } from './clock-svc.ts'
|
|
15
15
|
import type { HomeSt } from './home-st.ts'
|
|
16
16
|
import type { HubDot } from './projects-svc.ts'
|
|
17
|
+
import { ownTrouble, reach, Unreachable } from './reach.ts'
|
|
17
18
|
|
|
18
19
|
export const MACHINE_POLL_MS = 1_500
|
|
19
20
|
/** past the web's own wait on the daemon (serve-web.ts MACHINE_ANSWER_MS), so
|
|
@@ -38,13 +39,9 @@ function loadKept(): Kept | null {
|
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
/** the
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
constructor(msg: string, readonly who: 'web' | 'daemon') {
|
|
45
|
-
super(msg)
|
|
46
|
-
}
|
|
47
|
-
}
|
|
42
|
+
/** the web not answering, in the words of the daemon's (serve-web.ts daemonDownText) */
|
|
43
|
+
export const webDownText = (why: string): string =>
|
|
44
|
+
`the shipkit web does not answer on ${globalThis.location?.origin ?? 'this page\'s origin'} (${why}). \`shipkit pm\` runs it; this page comes back by itself once it answers`
|
|
48
45
|
|
|
49
46
|
export type MachineOpts = { reload?: () => void; timeoutMs?: number }
|
|
50
47
|
|
|
@@ -149,7 +146,7 @@ export class MachineSt {
|
|
|
149
146
|
/** pm is polled beside the daemon: either one down leaves the other shown */
|
|
150
147
|
private async loadPm(): Promise<void> {
|
|
151
148
|
try {
|
|
152
|
-
const r = await
|
|
149
|
+
const r = await reach('/api/pm/status.json', { signal: AbortSignal.timeout(this.o.timeoutMs ?? MACHINE_TIMEOUT_MS) })
|
|
153
150
|
if (!r.ok) throw new Error(await r.text())
|
|
154
151
|
const j = (await r.json()) as PmAnswer
|
|
155
152
|
runInAction(() => {
|
|
@@ -180,19 +177,15 @@ export class MachineSt {
|
|
|
180
177
|
if (this.polling) return
|
|
181
178
|
this.polling = true
|
|
182
179
|
const pm = this.loadPm()
|
|
183
|
-
// never held on: a widget that answers late only shows late
|
|
184
|
-
|
|
180
|
+
// never held on: a widget that answers late only shows late. Asked only
|
|
181
|
+
// while the overview shows: the daemon renders a widget for a reader,
|
|
182
|
+
// and a render can cost another machine an ssh session
|
|
183
|
+
if (!this.widgetsPolling && this.st.page === 'overview' && globalThis.document?.visibilityState !== 'hidden') {
|
|
185
184
|
this.widgetsPolling = true
|
|
186
185
|
void this.loadWidgets().finally(() => { this.widgetsPolling = false })
|
|
187
186
|
}
|
|
188
187
|
try {
|
|
189
|
-
|
|
190
|
-
try {
|
|
191
|
-
r = await fetch('/api/home.json', { signal: AbortSignal.timeout(this.o.timeoutMs ?? MACHINE_TIMEOUT_MS) })
|
|
192
|
-
} catch (e) {
|
|
193
|
-
throw new DownError(`the home does not answer: ${(e as Error).message}`, 'web')
|
|
194
|
-
}
|
|
195
|
-
if (r.headers.get('x-shipkit-daemon') === 'down') throw new DownError(await r.text(), 'daemon')
|
|
188
|
+
const r = await reach('/api/home.json', { signal: AbortSignal.timeout(this.o.timeoutMs ?? MACHINE_TIMEOUT_MS) })
|
|
196
189
|
if (!r.ok) throw new Error(`/api/home.json answered ${r.status}: ${await r.text()}`)
|
|
197
190
|
const j = (await r.json()) as HomeAnswer
|
|
198
191
|
const back = this.wasDown
|
|
@@ -213,11 +206,12 @@ export class MachineSt {
|
|
|
213
206
|
if (back) this.reload()
|
|
214
207
|
} catch (e) {
|
|
215
208
|
const msg = (e as Error).message
|
|
216
|
-
if (e instanceof
|
|
209
|
+
if (e instanceof Unreachable) {
|
|
217
210
|
this.wasDown = true
|
|
211
|
+
const who = e.who === 'web' ? 'web' : 'daemon'
|
|
218
212
|
runInAction(() => {
|
|
219
|
-
this.down = msg
|
|
220
|
-
this.downWho =
|
|
213
|
+
this.down = who === 'web' ? webDownText(msg) : msg
|
|
214
|
+
this.downWho = who
|
|
221
215
|
})
|
|
222
216
|
} else {
|
|
223
217
|
this.pollError = true
|
|
@@ -253,14 +247,16 @@ export class MachineSt {
|
|
|
253
247
|
|
|
254
248
|
private async post(path: string, body: unknown): Promise<boolean> {
|
|
255
249
|
try {
|
|
256
|
-
const r = await
|
|
250
|
+
const r = await reach(path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) })
|
|
257
251
|
if (!r.ok) throw new Error(await r.text())
|
|
258
252
|
this.pollError = false
|
|
259
253
|
runInAction(() => { this.error = '' })
|
|
260
254
|
void this.load()
|
|
261
255
|
return true
|
|
262
256
|
} catch (e) {
|
|
263
|
-
|
|
257
|
+
// the machine not answering is the down card's, which the next poll raises
|
|
258
|
+
runInAction(() => { this.error = ownTrouble(e, this) })
|
|
259
|
+
void this.load()
|
|
264
260
|
return false
|
|
265
261
|
}
|
|
266
262
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// A repo's plan page: what the machine runs here on its own time, soonest
|
|
2
|
+
// first, then the checks only a save runs. The rows are this repo's slice of
|
|
3
|
+
// the machine's plan, already carried by /api/resources.json, so the page
|
|
4
|
+
// reads nothing of its own. The home's planned page is the same plan for
|
|
5
|
+
// every repo at once.
|
|
6
|
+
import { observer } from 'mobx-react-lite'
|
|
7
|
+
import type { PlanRow } from '../../plan/planner.ts'
|
|
8
|
+
import { LAST_TONE, markOf, reasonWords, whenWords } from './planned-st.ts'
|
|
9
|
+
import type { RepoSt } from './repo-st.ts'
|
|
10
|
+
import { ageWords } from './time-ago.tsx'
|
|
11
|
+
|
|
12
|
+
const soonest = (a: PlanRow, b: PlanRow): number => (a.dueAt ?? Infinity) - (b.dueAt ?? Infinity) || a.check.localeCompare(b.check)
|
|
13
|
+
|
|
14
|
+
const PlanLine = observer(({ app, r }: { app: RepoSt; r: PlanRow }) => {
|
|
15
|
+
const now = app.clock.now
|
|
16
|
+
const m = markOf(r)
|
|
17
|
+
const running = r.state === 'running'
|
|
18
|
+
return (
|
|
19
|
+
<tr className={`pln m-${m.mark}`}>
|
|
20
|
+
<td className="plnwhen">{whenWords(r, now)}</td>
|
|
21
|
+
<td><a href="#" title={r.title ?? r.check} onClick={(e) => { e.preventDefault(); app.timeline.openCheck(r.check) }}>{r.check}</a>{r.title != null ? <span className="dim"> {r.title}</span> : null}</td>
|
|
22
|
+
<td className="dim" title={`rule: ${r.rule}\ninactive: ${r.inactive}`}>{r.error != null ? <span className="tl red">{r.error}</span> : reasonWords(r)}</td>
|
|
23
|
+
<td>{r.last == null ? <span className="dim">never ran</span> : <span className={`tl ${LAST_TONE[r.last.status]}`}>{r.last.status}, {ageWords(r.last.at, now)}</span>}</td>
|
|
24
|
+
<td className="plnrun"><button className="ghost" disabled={running} onClick={() => void app.home.planned.run(r)}>{running ? <><span className="spin" />running</> : 'run now'}</button></td>
|
|
25
|
+
</tr>
|
|
26
|
+
)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const PlanTable = observer(({ app, rows }: { app: RepoSt; rows: PlanRow[] }) => (
|
|
30
|
+
<table className="plntable">
|
|
31
|
+
<tbody>{rows.map((r) => <PlanLine key={r.check} app={app} r={r} />)}</tbody>
|
|
32
|
+
</table>
|
|
33
|
+
))
|
|
34
|
+
|
|
35
|
+
export const PlanPage = observer(({ app }: { app: RepoSt }) => {
|
|
36
|
+
const all = [...(app.cols.planned ?? [])].sort(soonest)
|
|
37
|
+
const timed = all.filter((r) => r.dueAt != null)
|
|
38
|
+
const onSave = all.filter((r) => r.dueAt == null)
|
|
39
|
+
return (
|
|
40
|
+
<div className="colcard checkcard">
|
|
41
|
+
<div className="chead">
|
|
42
|
+
<span className="tid">plan</span>
|
|
43
|
+
<h3 className="ctitle">{app.name}</h3>
|
|
44
|
+
</div>
|
|
45
|
+
<div className="usays">what the machine runs here on its own time, soonest first. Every repo at once: the planned page (⌘')</div>
|
|
46
|
+
{app.cols.planned == null ? <div className="dim">no plan covers this repo</div> : null}
|
|
47
|
+
{timed.length > 0 ? <section className="csec"><div className="cstitle">next</div><PlanTable app={app} rows={timed} /></section> : null}
|
|
48
|
+
{onSave.length > 0 ? <section className="csec"><div className="cstitle">on a save only</div><PlanTable app={app} rows={onSave} /></section> : null}
|
|
49
|
+
</div>
|
|
50
|
+
)
|
|
51
|
+
})
|
|
@@ -8,6 +8,7 @@ import type { PlannedAnswer } from '../../home/home-routes.ts'
|
|
|
8
8
|
import type { PlanRow } from '../../plan/planner.ts'
|
|
9
9
|
import { sectionOf, timeline, titleWords, waitingRows, waitingWords } from '../../plan/plan-timeline.ts'
|
|
10
10
|
import type { HomeSt } from './home-st.ts'
|
|
11
|
+
import { ownTrouble, reach } from './reach.ts'
|
|
11
12
|
|
|
12
13
|
export const PLANNED_POLL_MS = 1_000
|
|
13
14
|
|
|
@@ -78,18 +79,22 @@ export class PlannedSt {
|
|
|
78
79
|
|
|
79
80
|
async poll(): Promise<void> {
|
|
80
81
|
try {
|
|
81
|
-
const r = await
|
|
82
|
+
const r = await reach('/api/home/planned.json', { signal: AbortSignal.timeout(8_000) })
|
|
82
83
|
if (!r.ok) throw new Error(`${r.status} ${await r.text()}`)
|
|
83
84
|
const j = (await r.json()) as PlannedAnswer
|
|
84
85
|
runInAction(() => { this.data = j; this.error = '' })
|
|
85
86
|
} catch (e) {
|
|
86
|
-
runInAction(() => { this.error = (e
|
|
87
|
+
runInAction(() => { this.error = ownTrouble(e, this.st.machine) })
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
async run(row: PlanRow): Promise<void> {
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
try {
|
|
93
|
+
const r = await reach('/api/home/planned/run', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ repo: row.repo, check: row.check }) })
|
|
94
|
+
if (!r.ok) throw new Error(`${r.status} ${r.statusText}`)
|
|
95
|
+
} catch (e) {
|
|
96
|
+
runInAction(() => { this.error = ownTrouble(e, this.st.machine, (m) => `run ${row.check}: ${m}`) })
|
|
97
|
+
}
|
|
93
98
|
await this.poll()
|
|
94
99
|
}
|
|
95
100
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// The processes page of a repo: the overview's process table, this repo's
|
|
2
|
+
// lines alone, never a card per process. The process a tree row opened gets
|
|
3
|
+
// its description and its log under the table. pm runs them, so the page
|
|
4
|
+
// reads the machine, never the hub.
|
|
5
|
+
import { observer } from 'mobx-react-lite'
|
|
6
|
+
import { useEffect, useRef } from 'react'
|
|
7
|
+
import { Log, ProcessMenu, ProcessTable } from './home-strip.tsx'
|
|
8
|
+
import type { RepoSt } from './repo-st.ts'
|
|
9
|
+
import type { ProcessRow } from '../../home/process-rows.ts'
|
|
10
|
+
|
|
11
|
+
const noop = (): void => {}
|
|
12
|
+
|
|
13
|
+
/** the opened process: its menu (actions, then its description) and its log */
|
|
14
|
+
const ProcessDetail = observer(({ app, r }: { app: RepoSt; r: ProcessRow }) => {
|
|
15
|
+
const el = useRef<HTMLElement>(null)
|
|
16
|
+
useEffect(() => { el.current?.scrollIntoView({ block: 'nearest' }) }, [r.id])
|
|
17
|
+
const m = app.home.machine
|
|
18
|
+
if (m == null) return null
|
|
19
|
+
return (
|
|
20
|
+
<section ref={el} className="csec pdetail" id={`process-${r.id}`}>
|
|
21
|
+
<div className="cstitle">{r.label}</div>
|
|
22
|
+
<ProcessMenu m={m} r={r} close={noop} lastLog={r.tail.length === 0} />
|
|
23
|
+
{r.tail.length === 0 ? null : <><div className="cstitle">log</div><Log lines={r.tail} /></>}
|
|
24
|
+
</section>
|
|
25
|
+
)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export const ProcessesPage = observer(({ app, id }: { app: RepoSt; id: string | null }) => {
|
|
29
|
+
const rows = app.globalProcesses
|
|
30
|
+
const m = app.home.machine
|
|
31
|
+
const lit = rows.find((r) => r.id === id) ?? null
|
|
32
|
+
return (
|
|
33
|
+
<div className="colcard checkcard">
|
|
34
|
+
<div className="chead">
|
|
35
|
+
<span className="tid">processes</span>
|
|
36
|
+
<h3 className="ctitle">{app.name}</h3>
|
|
37
|
+
</div>
|
|
38
|
+
<div className="usays">the processes this repo declares in its <code>.shipkit.ts</code> files. pm runs them, so they run whether or not the hub answers</div>
|
|
39
|
+
{rows.length === 0 || m == null
|
|
40
|
+
? <div className="dim">this repo declares no global process</div>
|
|
41
|
+
: <section className="csec"><ProcessTable app={app.home} m={m} repo={app.key} /></section>}
|
|
42
|
+
{lit == null ? null : <ProcessDetail app={app} r={lit} />}
|
|
43
|
+
</div>
|
|
44
|
+
)
|
|
45
|
+
})
|