rvlib-shipkit 1.3.0 → 1.5.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 +105 -0
- package/README.md +17 -17
- package/package.json +3 -2
- package/src/asks/asks-queue.ts +4 -2
- package/src/assets/kits/identity-kit.ts +15 -13
- package/src/assets/run-assets.ts +12 -2
- package/src/check.ts +73 -98
- package/src/checks/SK001-package-json-shape.check.ts +26 -4
- package/src/checks/SK002-contracts-fulfillness.check.ts +4 -0
- package/src/checks/SK003-claude-md-shape.check.ts +5 -1
- 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 +94 -74
- 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 +20 -6
- package/src/checks/SK015-nested-repo.check.ts +4 -2
- package/src/checks/SK016-legacy-check-shapes.check.ts +23 -16
- 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 +34 -9
- package/src/checks/SK028-readme.check.ts +10 -2
- package/src/checks/SK029-explicit-check-files.check.ts +95 -0
- package/src/cli/cmd_Asks.ts +5 -5
- package/src/cli/cmd_Assets.ts +7 -7
- package/src/cli/cmd_Bump.ts +12 -11
- 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 +10 -6
- package/src/cli/cmd_Fleet.ts +9 -7
- package/src/cli/cmd_GlobalProcesses.ts +116 -4
- package/src/cli/cmd_Home.ts +23 -17
- package/src/cli/cmd_Hooks.ts +6 -5
- package/src/cli/cmd_Init.ts +5 -4
- package/src/cli/cmd_Keys.ts +8 -7
- package/src/cli/cmd_Mv.ts +7 -6
- package/src/cli/cmd_Needs.ts +4 -3
- package/src/cli/cmd_Overview.ts +4 -3
- 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 +4 -3
- 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_Tui.ts +3 -2
- package/src/cli/cmd_When.ts +71 -0
- package/src/cli/cmd_Wiki.ts +4 -4
- package/src/cli.ts +7 -2
- package/src/collections/checks.ts +2 -0
- package/src/collections/collection-types.ts +24 -4
- package/src/collections/providers.ts +6 -2
- 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/budget.ts +19 -9
- package/src/core/canonical-scripts.ts +1 -1
- package/src/core/check-runner.ts +63 -25
- package/src/core/config-rewrite.ts +37 -24
- package/src/core/discover-checks.ts +31 -7
- package/src/core/layout.ts +2 -0
- package/src/core/legacy-shapes.ts +39 -10
- package/src/core/motherhome.ts +88 -66
- 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/retention.ts +0 -14
- package/src/core/run-check.ts +14 -14
- package/src/core/run-ci.ts +11 -7
- package/src/core/run-fix.ts +30 -23
- package/src/core/run-fixers.ts +42 -5
- package/src/core/runs-matrix.ts +179 -0
- package/src/core/runs.ts +242 -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 +34 -9
- package/src/fleet/scan-fleet.ts +20 -4
- package/src/fleet/update-fleet.ts +6 -6
- package/src/home/activity-shape.ts +4 -1
- package/src/home/activity.ts +22 -15
- package/src/home/gui-app/ShipkitGui.swift +144 -25
- package/src/home/gui-app.ts +93 -30
- package/src/home/home-launchd.ts +30 -6
- package/src/home/home-routes.ts +14 -2
- package/src/home/home-start.ts +33 -5
- package/src/home/hubs.ts +5 -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 +11 -3
- package/src/home/process-rows.ts +23 -6
- package/src/home/resources.ts +67 -15
- package/src/home/run-daemon.ts +6 -1
- package/src/home/run-pm.ts +30 -5
- package/src/home/serve-web.ts +2 -2
- package/src/home/supervise.ts +24 -1
- package/src/home/widgets.ts +32 -3
- package/src/hooks/commit-message.ts +2 -3
- package/src/hooks/run-hooks.ts +60 -6
- package/src/init-repo.ts +6 -6
- package/src/ledger/gate-checks.ts +115 -61
- package/src/ledger/git-key.ts +5 -1
- package/src/ledger/ledger.ts +5 -5
- package/src/ledger/store.ts +11 -0
- package/src/ledger/toolchain-identity.ts +45 -27
- package/src/ledger/world-cache.ts +17 -15
- package/src/plan/repo-source.ts +18 -39
- package/src/release/run-release.ts +5 -0
- package/src/repo-config.ts +36 -56
- 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 +4 -4
- package/src/serve/hub-app/admin-view.tsx +13 -11
- 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 +16 -36
- package/src/serve/hub-app/col-tables-view.tsx +131 -0
- package/src/serve/hub-app/col-tables.ts +115 -0
- package/src/serve/hub-app/cols-svc.ts +33 -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 +7 -1
- 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 +15 -3
- package/src/serve/hub-app/home-strip.css +69 -19
- package/src/serve/hub-app/home-strip.tsx +192 -118
- package/src/serve/hub-app/home.html +1 -0
- package/src/serve/hub-app/hub-app.tsx +21 -5
- package/src/serve/hub-app/hub-types.ts +11 -5
- package/src/serve/hub-app/hub.css +88 -44
- package/src/serve/hub-app/icons.tsx +15 -0
- package/src/serve/hub-app/index.html +1 -0
- package/src/serve/hub-app/keys.ts +11 -8
- package/src/serve/hub-app/live-svc.ts +3 -2
- package/src/serve/hub-app/machine-st.ts +34 -49
- package/src/serve/hub-app/palette-st.ts +5 -5
- package/src/serve/hub-app/palette-view.tsx +1 -1
- 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 +38 -0
- package/src/serve/hub-app/project-list.tsx +3 -3
- package/src/serve/hub-app/projects-svc.ts +15 -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 +117 -0
- package/src/serve/hub-app/repo-st.ts +47 -10
- package/src/serve/hub-app/runs-tabs.ts +1 -1
- package/src/serve/hub-app/sidebar.tsx +12 -37
- package/src/serve/hub-app/timeline-svc.ts +3 -2
- package/src/serve/hub-app/timeline-view.tsx +14 -11
- package/src/serve/hub-app/tree-build.ts +149 -155
- package/src/serve/hub-app/tree-row.tsx +39 -9
- package/src/serve/hub-app/tree-svc.ts +70 -49
- 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 +3 -16
- package/src/serve/project-gutter.ts +1 -2
- package/src/serve/run-serve.ts +1 -1
- package/src/serve/serve-hub.ts +4 -14
- package/src/serve/warm-cell.ts +49 -16
- 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/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,7 @@ 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
|
-
.appcol{display:flex;flex-direction:column}
|
|
41
|
+
.appcol{position:relative;display:flex;flex-direction:column}
|
|
42
42
|
/* one region per repo visited, the shown one alone displayed */
|
|
43
43
|
.repo{flex:1;min-height:0;display:flex;flex-direction:column}
|
|
44
44
|
.repo[hidden]{display:none}
|
|
@@ -49,10 +49,9 @@ a,a:visited{color:var(--accent)}
|
|
|
49
49
|
.pmain > main{flex:1}
|
|
50
50
|
|
|
51
51
|
/* ── a repo's bar: on the home the strip (home-strip.css .hstrip), on a hub
|
|
52
|
-
of its own the header (.hdr). Its name,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
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) ── */
|
|
56
55
|
/* the repo's processes (serve.processes): a dot, its id, a link once up */
|
|
57
56
|
.hprocs{display:inline-flex;gap:var(--s1);align-items:center}
|
|
58
57
|
.hproc{display:inline-flex;align-items:center;gap:var(--s0)}
|
|
@@ -63,7 +62,7 @@ a,a:visited{color:var(--accent)}
|
|
|
63
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)}
|
|
64
63
|
.hdr .sp{flex:1}
|
|
65
64
|
.hname{font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink);font-weight:600;margin-right:var(--s2);white-space:nowrap}
|
|
66
|
-
.hdr,.hstrip{
|
|
65
|
+
.hdr,.hstrip{white-space:nowrap}
|
|
67
66
|
.hrun{display:inline-flex;align-items:center;gap:var(--s1);flex:none;margin-left:var(--s2)}
|
|
68
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}
|
|
69
68
|
.hbtn .hico{width:13px;height:13px}
|
|
@@ -71,40 +70,34 @@ a,a:visited{color:var(--accent)}
|
|
|
71
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)}
|
|
72
71
|
.hicon:hover{color:var(--accent)}
|
|
73
72
|
.hicon .hico{width:15px;height:15px}
|
|
74
|
-
/* the run state holds its width whatever it says, so a run never moves the buttons beside it */
|
|
75
|
-
.hstatus{max-width:420px;flex:0 1 auto;font-size:11.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
76
|
-
.hstatus.red{color:var(--red)}
|
|
77
|
-
.hstatus.warn{color:var(--warn)}
|
|
78
|
-
/* its words sit against the run buttons, cut with an ellipsis */
|
|
79
|
-
.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}
|
|
80
|
-
.hstate.on{color:var(--ink)}
|
|
81
|
-
.hstate .htext{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
82
|
-
.hstate .spin.idle{visibility:hidden;animation:none}
|
|
83
|
-
/* after .hstate, so a narrow bar wins over its width */
|
|
84
|
-
@container (max-width:600px){.hbtn .lbl{display:none}.hbtn{padding:0 var(--s1)}}
|
|
85
|
-
@container (max-width:520px){.hstate{width:120px;min-width:120px}}
|
|
86
|
-
@container (max-width:420px){.hstate .htext{display:none}.hstate{width:16px;min-width:16px}}
|
|
87
73
|
|
|
88
74
|
/* a row of a (?) legend: one glyph or word, then its meaning */
|
|
89
75
|
.lrow{display:flex;gap:var(--s2);align-items:center;height:29px;flex:none;padding:0 var(--s3)}
|
|
90
76
|
|
|
91
|
-
/* ── the left pane: the
|
|
92
|
-
|
|
77
|
+
/* ── the left pane: the filter, then the tree, the one part that
|
|
78
|
+
scrolls ── */
|
|
93
79
|
aside{border-right:1px solid var(--line);background:var(--panel);min-height:0;display:flex;flex-direction:column;overflow:hidden}
|
|
94
80
|
aside > *{flex:none}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
.
|
|
99
|
-
.
|
|
100
|
-
.
|
|
101
|
-
|
|
102
|
-
.
|
|
103
|
-
|
|
104
|
-
.
|
|
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.on{box-shadow:inset 2px 0 0 var(--accent)}
|
|
93
|
+
.tsec.cur{background:var(--sel);color:var(--ink);box-shadow:inset 3px 0 0 var(--accent);font-weight:600}
|
|
94
|
+
.tsec .hico{width:16px;height:16px}
|
|
95
|
+
.tsec:hover .tw{color:var(--ink)}
|
|
96
|
+
.rsecl{min-width:0;overflow:hidden;text-overflow:ellipsis}
|
|
97
|
+
.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)}
|
|
105
98
|
.rsecn.nred{background:var(--red)}
|
|
106
99
|
.rsecn.nwarn{background:var(--warn)}
|
|
107
|
-
.
|
|
100
|
+
.tsec:hover .pjkey,.tsec.cur .pjkey{opacity:1}
|
|
108
101
|
/* segmented severity filter — all · needs · red. A thin border and an
|
|
109
102
|
underline on the live one: no fill, no pill */
|
|
110
103
|
.seg{display:inline-flex;border:1px solid var(--line);border-radius:3px;overflow:hidden;flex:none}
|
|
@@ -114,9 +107,8 @@ aside > .tscroll{flex:1;min-height:0;overflow-y:auto}
|
|
|
114
107
|
.seg button:hover{color:var(--ink)}
|
|
115
108
|
.seg button.on{color:var(--accent);border-bottom-color:var(--accent)}
|
|
116
109
|
.seg button .sk{margin-left:var(--s1);font-size:9px;opacity:.55}
|
|
117
|
-
.swhen{padding:0 var(--s3) var(--s2);font-size:11px;color:var(--dim)}
|
|
118
110
|
/* a narrow pane puts the severity filter under the input, never over it */
|
|
119
|
-
.ssearch{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2);padding:var(--s2) var(--s3)}
|
|
111
|
+
.ssearch{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2);padding:var(--s2) var(--s3);border-bottom:1px solid var(--line)}
|
|
120
112
|
.sfield{position:relative;flex:1 1 96px;min-width:0;display:flex}
|
|
121
113
|
.ssearch input{width:100%;background:var(--bg);border:1px solid var(--line);border-radius:4px;color:var(--ink);
|
|
122
114
|
padding:var(--s0) var(--s4) var(--s0) var(--s2);font-size:12px;outline:none}
|
|
@@ -132,8 +124,8 @@ aside > .tscroll{flex:1;min-height:0;overflow-y:auto}
|
|
|
132
124
|
no prose, no dash separators ── */
|
|
133
125
|
.tree{padding:var(--s1) 0 var(--s4);outline:none}
|
|
134
126
|
.tree:focus-visible{box-shadow:inset 2px 0 0 var(--accent)}
|
|
135
|
-
.trow{display:grid;grid-template-columns:calc(var(--d,0)*
|
|
136
|
-
align-items:center;gap:var(--s1);padding:0 var(--s2);height:20px;line-height:20px;cursor:pointer;white-space:nowrap}
|
|
127
|
+
.trow{display:grid;grid-template-columns:calc(var(--d,0)*10px + 14px) 14px minmax(0,1fr) 12px auto 14px;
|
|
128
|
+
align-items:center;gap:var(--s1);padding:0 var(--s1) 0 var(--s2);height:20px;line-height:20px;cursor:pointer;white-space:nowrap}
|
|
137
129
|
.trow:hover{background:var(--panel2)}
|
|
138
130
|
.trow.on{background:var(--sel);box-shadow:inset 2px 0 0 var(--accent)}
|
|
139
131
|
/* the fold marker: an 11px chevron with the whole first column as its hit
|
|
@@ -167,21 +159,20 @@ aside > .tscroll{flex:1;min-height:0;overflow-y:auto}
|
|
|
167
159
|
.tg.ok{color:var(--ok)}
|
|
168
160
|
.tg.adv{color:var(--warn);opacity:.75}
|
|
169
161
|
.tg.run{color:var(--accent);animation:pulse 1.1s ease-in-out infinite}
|
|
170
|
-
/*
|
|
171
|
-
|
|
172
|
-
.tdur{
|
|
162
|
+
/* the duration: a clock tinted by the tone table (hub-app/dur.ts), the
|
|
163
|
+
figure in its tooltip, so the label keeps the width */
|
|
164
|
+
.tdur{display:flex;align-items:center;justify-content:center;color:var(--dim)}
|
|
165
|
+
.tdur .hico{width:11px;height:11px;color:inherit}
|
|
173
166
|
.tdur.fast,.dnum.fast{color:var(--ok)}
|
|
174
167
|
.tdur.plain,.dnum.plain{color:var(--ink)}
|
|
175
168
|
.tdur.slow,.dnum.slow{color:var(--warn)}
|
|
176
169
|
.tdur.bad,.dnum.bad{color:var(--red)}
|
|
177
170
|
.tnote{font-size:10px;text-align:right;color:var(--dim);overflow:hidden;text-overflow:ellipsis}
|
|
178
171
|
.tnote b{font-weight:600;color:var(--red)}
|
|
179
|
-
.stabs{display:flex;gap:var(--s2);align-items:center;padding:var(--s1) var(--s3);border-bottom:1px solid var(--line)}
|
|
180
|
-
.stabs .sp{flex:1}
|
|
181
|
-
.stabs .cnt{min-width:4ch;text-align:right;font-size:11px;color:var(--dim);font-variant-numeric:tabular-nums}
|
|
182
172
|
.catgen{display:inline-flex;gap:var(--s1)}
|
|
183
173
|
.catgen button{padding:var(--s0) var(--s2);font-size:11px;font-weight:700}
|
|
184
174
|
.tempty{padding:var(--s2) var(--s3);font-size:12px;color:var(--dim);white-space:pre-wrap}
|
|
175
|
+
.thidden{cursor:pointer}.thidden:hover{color:var(--ink)}
|
|
185
176
|
.twait{display:flex;align-items:center;gap:var(--s2)}
|
|
186
177
|
.warnline{color:var(--red)}
|
|
187
178
|
.fold{cursor:pointer;color:var(--dim);font-size:15px;display:inline-block;width:22px;text-align:center;
|
|
@@ -368,7 +359,15 @@ kbd{background:var(--panel2);border:1px solid var(--line);border-radius:4px;padd
|
|
|
368
359
|
.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}
|
|
369
360
|
|
|
370
361
|
/* ── collection item card + status card ── */
|
|
371
|
-
|
|
362
|
+
/* a repo page sits on the page background, never in a box of its own: its
|
|
363
|
+
header and its sentence are unboxed, and each group it holds (the facts,
|
|
364
|
+
a titled section, the command, the admin rows, the runs list) is one
|
|
365
|
+
lighter box, so a box always means "these belong together" */
|
|
366
|
+
.colcard{max-width:760px;width:100%;padding:var(--s2) 0;text-align:left}
|
|
367
|
+
.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)}
|
|
368
|
+
.colcard>.cfacts,.colcard>.ccmd,.colcard>.md:not(.usays),.colcard>.adbox{margin-top:var(--s3)}
|
|
369
|
+
.ccmd>.cf:first-child,.cfacts>.cf:nth-child(-n+2){border-top:0}
|
|
370
|
+
@media (max-width:900px){.cfacts>.cf:nth-child(2){border-top:1px solid var(--line)}}
|
|
372
371
|
.colcard h3{margin:0 0 var(--s2);font-size:15px;display:flex;gap:var(--s2);align-items:baseline}
|
|
373
372
|
.colcard h3 .sp{flex:1}
|
|
374
373
|
.colcard .truth{margin-left:auto;font:11px ui-monospace,Menlo,monospace;color:var(--dim)}
|
|
@@ -559,6 +558,15 @@ a.tok{color:var(--accent)}
|
|
|
559
558
|
.checkcard .dim{color:var(--dim)}
|
|
560
559
|
/* what is not a fact: the output and the slow suites, titled, unboxed */
|
|
561
560
|
.csec{margin-top:var(--s4)}
|
|
561
|
+
/* where it runs: one line per context, the value then its meaning. A context
|
|
562
|
+
that never runs the check stays dim, so the lit lines read down the list */
|
|
563
|
+
.cruns{display:grid;grid-template-columns:auto auto minmax(0,1fr);column-gap:var(--s3);row-gap:var(--s0);align-items:baseline}
|
|
564
|
+
.crun{display:contents;color:var(--dim)}
|
|
565
|
+
.crv{font:11px ui-monospace,Menlo,monospace;white-space:nowrap}
|
|
566
|
+
.crun.on .crv{color:var(--ok)}
|
|
567
|
+
.crun.cache .crv{color:var(--ink)}
|
|
568
|
+
.crm{font-size:11.5px;color:var(--dim);opacity:.8}
|
|
569
|
+
.crun:not(.on) .crm{opacity:.5}
|
|
562
570
|
.cstitle{display:flex;gap:var(--s1);align-items:center;font-size:11px;text-transform:uppercase;letter-spacing:.08em;
|
|
563
571
|
color:var(--dim);margin-bottom:var(--s2)}
|
|
564
572
|
.wline{margin-bottom:var(--s1);word-break:break-word}
|
|
@@ -627,7 +635,29 @@ a.tok{color:var(--accent)}
|
|
|
627
635
|
.colgrid{flex:1;min-height:0;overflow-y:auto;padding:var(--s3) var(--s4)}
|
|
628
636
|
.cghead{font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--accent);
|
|
629
637
|
display:flex;gap:var(--s2);align-items:baseline;margin:0 0 var(--s3)}
|
|
638
|
+
.ctlead{font-size:11px;color:var(--dim);margin:calc(-1 * var(--s1)) 0 var(--s2)}
|
|
630
639
|
.cghead .cnt{font-size:11px;color:var(--dim);letter-spacing:0;text-transform:none}
|
|
640
|
+
/* the checks and asks pages: one dense table each (col-tables-view.tsx), on
|
|
641
|
+
the activity table (.actable), a group header per state that folds */
|
|
642
|
+
.ctpage{flex:1;min-height:0;overflow-y:auto;padding:var(--s3) var(--s4);font-size:12.5px;container-type:inline-size}
|
|
643
|
+
.cttable{font-variant-numeric:tabular-nums}
|
|
644
|
+
.cttable td{padding:var(--s0) var(--s2);vertical-align:baseline}
|
|
645
|
+
.ctgrp td{padding:var(--s2) var(--s2) var(--s1);color:var(--dim);font-size:11.5px;border-bottom:1px solid var(--line);cursor:pointer;user-select:none}
|
|
646
|
+
.ctgrp:hover td{color:var(--ink)}
|
|
647
|
+
.ctgrp.g-fail td{color:var(--red)}
|
|
648
|
+
.ctgrp.g-warn td{color:var(--warn)}
|
|
649
|
+
.ctcaret{display:inline-block;width:14px}
|
|
650
|
+
.ctrow{cursor:pointer}
|
|
651
|
+
.ctrow:hover{background:var(--panel2)}
|
|
652
|
+
.ctdot{font-size:10px}
|
|
653
|
+
.ctid,.ctkind{font:11.5px ui-monospace,Menlo,monospace}
|
|
654
|
+
.ctfind{font:11.5px ui-monospace,Menlo,monospace;color:var(--dim)}
|
|
655
|
+
.ctdur{text-align:right;font:11.5px ui-monospace,Menlo,monospace}
|
|
656
|
+
.ctbudget,.ctplan,.ctblocks{color:var(--dim)}
|
|
657
|
+
.ctprobe{font-size:11.5px}
|
|
658
|
+
.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}
|
|
659
|
+
.cttable .c-kind{width:92px}.cttable .c-blocks{width:32%}.cttable .c-probe{width:84px}
|
|
660
|
+
@container (max-width:760px){.cttable .c-title{width:0}.cttable .cttitle{display:none}.cttable .c-plan{width:0}.cttable .ctplan{display:none}}
|
|
631
661
|
.cgcards{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:var(--s3)}
|
|
632
662
|
.cgcard{background:var(--panel);border:1px solid var(--line);border-radius:5px;padding:var(--s2) var(--s3);cursor:pointer;min-width:0}
|
|
633
663
|
.cgcard:hover{border-color:var(--accent)}
|
|
@@ -694,7 +724,7 @@ a.mint{color:var(--accent);font-size:12px;text-decoration:none}
|
|
|
694
724
|
a.mint:hover{text-decoration:underline}
|
|
695
725
|
.colcard h3 .stchip,.colcard h3 a.mint{margin-left:var(--s2)}
|
|
696
726
|
|
|
697
|
-
/* ── the cmd+
|
|
727
|
+
/* ── the cmd+j picker ── */
|
|
698
728
|
.palov{position:fixed;inset:0;background:var(--scrim);z-index:100;display:flex;justify-content:center;align-items:flex-start;
|
|
699
729
|
padding-top:14vh}
|
|
700
730
|
.pal{width:min(560px,90vw);background:var(--panel);border:1px solid var(--line);border-radius:3px;
|
|
@@ -723,9 +753,23 @@ pre{background:var(--panel);border:1px solid var(--line);border-radius:3px;paddi
|
|
|
723
753
|
|
|
724
754
|
/* ── the admin page: one row per control, its sentence under its name ── */
|
|
725
755
|
.adrow{display:flex;align-items:center;gap:var(--s4);padding:var(--s3) 0;border-top:1px solid var(--line)}
|
|
756
|
+
.adrow:first-child{border-top:0}
|
|
726
757
|
.adtext{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--s0);font-size:13px}
|
|
727
758
|
.adtext .dim{font-size:12px}
|
|
728
759
|
.adctl{flex:none;display:flex;align-items:center}
|
|
729
760
|
.adflag{display:inline-flex;align-items:center;gap:var(--s1);font-size:12px;color:var(--dim)}
|
|
730
761
|
.adflag input{width:auto;margin:0}
|
|
731
762
|
.adconfirm{display:inline-flex;align-items:center;gap:var(--s2);font-size:12px}
|
|
763
|
+
/* a repo's plan page: one line per planned check */
|
|
764
|
+
.plntable{width:100%;border-collapse:collapse;font-size:12.5px}
|
|
765
|
+
.plntable td{padding:var(--s1) var(--s2);border-top:1px solid var(--line);vertical-align:baseline}
|
|
766
|
+
.plntable tr:first-child td{border-top:0}
|
|
767
|
+
.plnwhen{white-space:nowrap;font-variant-numeric:tabular-nums}
|
|
768
|
+
.plnrun{text-align:right;white-space:nowrap}
|
|
769
|
+
/* a repo's git page */
|
|
770
|
+
.gitfiles,.gitcommits{width:100%;border-collapse:collapse;font-size:12.5px}
|
|
771
|
+
.gitfiles td,.gitcommits td{padding:2px var(--s2);vertical-align:baseline}
|
|
772
|
+
.gitkind{width:1%;white-space:nowrap;color:var(--dim)}
|
|
773
|
+
.gitkind.staged{color:var(--ok)}
|
|
774
|
+
.gitkind.untracked{color:var(--warn)}
|
|
775
|
+
.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',
|
|
@@ -27,6 +36,12 @@ export const ICON = {
|
|
|
27
36
|
// the VS Code mark, its ribbon drawn as one outline
|
|
28
37
|
vscode: 'M14 2.5l3.5 1.7v11.6L14 17.5zM14 2.5 4.5 12.3 2.5 11V9l2-1.3L14 17.5M14 6.8 10.2 10l3.8 3.2',
|
|
29
38
|
sort: 'M6 3.5v13M3 13.5l3 3 3-3M14 16.5v-13M11 6.5l3-3 3 3',
|
|
39
|
+
// a process's own switches on the overview
|
|
40
|
+
play: 'M6.5 4.5v11l9-5.5z',
|
|
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',
|
|
44
|
+
restart: 'M15.5 10a5.5 5.5 0 1 1-1.6-3.9M14.2 3v3.3h-3.3',
|
|
30
45
|
} as const
|
|
31
46
|
|
|
32
47
|
export const Icon = ({ d }: { d: string }) => (
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
// the page's command keys, read the same on every layout: ⌘1 to ⌘9 select the
|
|
2
2
|
// nth repo of the project list, then the home row under the right hand: ⌘J
|
|
3
|
-
// the
|
|
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
|
|
@@ -17,17 +17,17 @@ export function pageKey(e: KeyLike): PageKey | null {
|
|
|
17
17
|
if (e.shiftKey) return null
|
|
18
18
|
if (e.code === 'Quote' || e.key === "'") return { t: 'planned' }
|
|
19
19
|
const k = e.key.toLowerCase()
|
|
20
|
-
if (k === 'j') return { t: '
|
|
21
|
-
if (k === 'k') return { t: '
|
|
20
|
+
if (k === 'j') return { t: 'picker' }
|
|
21
|
+
if (k === 'k') return { t: 'home' }
|
|
22
22
|
if (k === 'l') return { t: 'activity' }
|
|
23
23
|
if (k === 'b') return { t: 'dock' }
|
|
24
24
|
return null
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/** the shortcut each place of the home carries on its button */
|
|
28
|
-
export const PLACE_KEY = { overview: '⌘
|
|
29
|
-
/** the picker's shortcut, on the
|
|
30
|
-
export const PICKER_KEY = '⌘
|
|
28
|
+
export const PLACE_KEY = { overview: '⌘K', planned: "⌘'", activity: '⌘L' } as const
|
|
29
|
+
/** the picker's shortcut, on the first button, before home */
|
|
30
|
+
export const PICKER_KEY = '⌘J'
|
|
31
31
|
/** the dock's shortcut, on the dock's own header */
|
|
32
32
|
export const DOCK_KEY = '⌘B'
|
|
33
33
|
|
|
@@ -46,8 +46,11 @@ export function isSearchKey(e: Pick<KeyLike, 'key' | 'metaKey' | 'ctrlKey' | 'al
|
|
|
46
46
|
* plus a letter only types a character inside a text field, where the page
|
|
47
47
|
* leaves the key alone. Read from the physical key (`code`): on a Mac ⌥C
|
|
48
48
|
* types ç, and AZERTY moves the letters */
|
|
49
|
-
export type SectionId = '
|
|
50
|
-
export const SECTION_LETTER: Record<SectionId, string> = {
|
|
49
|
+
export type SectionId = 'readme' | 'contracts' | 'shipkit' | 'repo' | 'secrets' | 'when' | 'runs' | 'plan' | 'processes' | 'git' | 'asks' | 'reflections' | 'tests' | 'media' | 'wikis' | 'admin'
|
|
50
|
+
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' }
|
|
51
|
+
/** a section's words on its row, where the id alone would not say it */
|
|
52
|
+
export const SECTION_LABEL: Partial<Record<SectionId, string>> = { shipkit: 'shipkit checks', repo: 'repo checks' }
|
|
53
|
+
export const sectionLabel = (id: SectionId): string => SECTION_LABEL[id] ?? id
|
|
51
54
|
/** the badge at the end of a section's line */
|
|
52
55
|
export const sectionKeyLabel = (id: SectionId): string => `⌥${SECTION_LETTER[id]}`
|
|
53
56
|
|
|
@@ -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
|
}
|
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
import { makeAutoObservable, observableRef, runInAction } from 'mobx'
|
|
9
9
|
import type { HomeAnswer, WidgetsAnswer } from '../../home/home-routes.ts'
|
|
10
10
|
import type { WorkerView } from '../../home/hubs.ts'
|
|
11
|
-
import type { ResourceView } from '../../home/resources.ts'
|
|
11
|
+
import type { OpenHow, ResourceView } from '../../home/resources.ts'
|
|
12
12
|
import type { PmAnswer, Role } from '../../home/pm.ts'
|
|
13
13
|
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
|
|
@@ -27,18 +28,6 @@ export const MACHINE_KEY = 'hub:machine'
|
|
|
27
28
|
/** how long a copied pid says so */
|
|
28
29
|
export const COPIED_MS = 1_200
|
|
29
30
|
|
|
30
|
-
/** how open opens a global process's url, kept per browser */
|
|
31
|
-
export const OPEN_HOW_KEY = 'hub:open-how'
|
|
32
|
-
export type OpenHow = 'browser' | 'app'
|
|
33
|
-
|
|
34
|
-
function loadOpenHow(): OpenHow {
|
|
35
|
-
try {
|
|
36
|
-
return globalThis.localStorage?.getItem(OPEN_HOW_KEY) === 'app' ? 'app' : 'browser'
|
|
37
|
-
} catch {
|
|
38
|
-
return 'browser'
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
31
|
type Kept = { data: HomeAnswer | null; pm: PmAnswer | null; widgets: WidgetsAnswer | null }
|
|
43
32
|
|
|
44
33
|
function loadKept(): Kept | null {
|
|
@@ -50,8 +39,9 @@ function loadKept(): Kept | null {
|
|
|
50
39
|
}
|
|
51
40
|
}
|
|
52
41
|
|
|
53
|
-
/** the
|
|
54
|
-
|
|
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`
|
|
55
45
|
|
|
56
46
|
export type MachineOpts = { reload?: () => void; timeoutMs?: number }
|
|
57
47
|
|
|
@@ -67,9 +57,10 @@ export class MachineSt {
|
|
|
67
57
|
error = ''
|
|
68
58
|
/** where open sends a global process's url: the default browser, or a
|
|
69
59
|
* shipkit app window */
|
|
70
|
-
openHow: OpenHow = loadOpenHow()
|
|
71
60
|
/** why the daemon did not answer the last poll, or '' while it does */
|
|
72
61
|
down = ''
|
|
62
|
+
/** which one did not answer: the web itself, or the daemon behind it */
|
|
63
|
+
downWho: 'web' | 'daemon' = 'daemon'
|
|
73
64
|
/** the daemon answered once since the load: until then data, pm and
|
|
74
65
|
* widgets are what this browser kept, shown but never acted on */
|
|
75
66
|
fresh = false
|
|
@@ -115,7 +106,8 @@ export class MachineSt {
|
|
|
115
106
|
* the project list's order, one row per process (process-rows.ts, what
|
|
116
107
|
* `shipkit global-processes` prints) */
|
|
117
108
|
get processGroups(): ProcessGroup[] {
|
|
118
|
-
|
|
109
|
+
const silent = { ...(this.down === '' ? {} : { [this.downWho]: this.down }), ...(this.pmDown === '' ? {} : { pm: this.pmDown }) }
|
|
110
|
+
return processGroups(this.pm, this.st.projects.list.map((p) => p.name), { daemonVersion: this.data?.version ?? null, silent })
|
|
119
111
|
}
|
|
120
112
|
|
|
121
113
|
resource(id: string): ResourceView | null {
|
|
@@ -154,7 +146,7 @@ export class MachineSt {
|
|
|
154
146
|
/** pm is polled beside the daemon: either one down leaves the other shown */
|
|
155
147
|
private async loadPm(): Promise<void> {
|
|
156
148
|
try {
|
|
157
|
-
const r = await
|
|
149
|
+
const r = await reach('/api/pm/status.json', { signal: AbortSignal.timeout(this.o.timeoutMs ?? MACHINE_TIMEOUT_MS) })
|
|
158
150
|
if (!r.ok) throw new Error(await r.text())
|
|
159
151
|
const j = (await r.json()) as PmAnswer
|
|
160
152
|
runInAction(() => {
|
|
@@ -162,10 +154,8 @@ export class MachineSt {
|
|
|
162
154
|
this.pmDown = ''
|
|
163
155
|
})
|
|
164
156
|
} catch (e) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
this.pmDown = (e as Error).message
|
|
168
|
-
})
|
|
157
|
+
// the last answer stays, its rows unknown (processGroups): the table keeps its lines
|
|
158
|
+
runInAction(() => { this.pmDown = (e as Error).message })
|
|
169
159
|
}
|
|
170
160
|
}
|
|
171
161
|
|
|
@@ -187,19 +177,15 @@ export class MachineSt {
|
|
|
187
177
|
if (this.polling) return
|
|
188
178
|
this.polling = true
|
|
189
179
|
const pm = this.loadPm()
|
|
190
|
-
// never held on: a widget that answers late only shows late
|
|
191
|
-
|
|
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') {
|
|
192
184
|
this.widgetsPolling = true
|
|
193
185
|
void this.loadWidgets().finally(() => { this.widgetsPolling = false })
|
|
194
186
|
}
|
|
195
187
|
try {
|
|
196
|
-
|
|
197
|
-
try {
|
|
198
|
-
r = await fetch('/api/home.json', { signal: AbortSignal.timeout(this.o.timeoutMs ?? MACHINE_TIMEOUT_MS) })
|
|
199
|
-
} catch (e) {
|
|
200
|
-
throw new DownError(`the home does not answer: ${(e as Error).message}`)
|
|
201
|
-
}
|
|
202
|
-
if (r.headers.get('x-shipkit-daemon') === 'down') throw new DownError(await r.text())
|
|
188
|
+
const r = await reach('/api/home.json', { signal: AbortSignal.timeout(this.o.timeoutMs ?? MACHINE_TIMEOUT_MS) })
|
|
203
189
|
if (!r.ok) throw new Error(`/api/home.json answered ${r.status}: ${await r.text()}`)
|
|
204
190
|
const j = (await r.json()) as HomeAnswer
|
|
205
191
|
const back = this.wasDown
|
|
@@ -220,9 +206,13 @@ export class MachineSt {
|
|
|
220
206
|
if (back) this.reload()
|
|
221
207
|
} catch (e) {
|
|
222
208
|
const msg = (e as Error).message
|
|
223
|
-
if (e instanceof
|
|
209
|
+
if (e instanceof Unreachable) {
|
|
224
210
|
this.wasDown = true
|
|
225
|
-
|
|
211
|
+
const who = e.who === 'web' ? 'web' : 'daemon'
|
|
212
|
+
runInAction(() => {
|
|
213
|
+
this.down = who === 'web' ? webDownText(msg) : msg
|
|
214
|
+
this.downWho = who
|
|
215
|
+
})
|
|
226
216
|
} else {
|
|
227
217
|
this.pollError = true
|
|
228
218
|
runInAction(() => {
|
|
@@ -257,14 +247,16 @@ export class MachineSt {
|
|
|
257
247
|
|
|
258
248
|
private async post(path: string, body: unknown): Promise<boolean> {
|
|
259
249
|
try {
|
|
260
|
-
const r = await
|
|
250
|
+
const r = await reach(path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) })
|
|
261
251
|
if (!r.ok) throw new Error(await r.text())
|
|
262
252
|
this.pollError = false
|
|
263
253
|
runInAction(() => { this.error = '' })
|
|
264
254
|
void this.load()
|
|
265
255
|
return true
|
|
266
256
|
} catch (e) {
|
|
267
|
-
|
|
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()
|
|
268
260
|
return false
|
|
269
261
|
}
|
|
270
262
|
}
|
|
@@ -278,16 +270,16 @@ export class MachineSt {
|
|
|
278
270
|
}
|
|
279
271
|
|
|
280
272
|
/** a global process: start, stop, restart, take over the copy started
|
|
281
|
-
* elsewhere, open its url
|
|
282
|
-
*
|
|
283
|
-
processAct(id: string, act: 'start' | 'stop' | 'restart' | 'takeover' | 'open' | 'app' | 'action', action?: string): Promise<boolean> {
|
|
273
|
+
* elsewhere, open its url (started first when it is down), or run one
|
|
274
|
+
* of its actions */
|
|
275
|
+
processAct(id: string, act: 'start' | 'stop' | 'restart' | 'takeover' | 'open' | 'chrome' | 'app' | 'action', action?: string): Promise<boolean> {
|
|
284
276
|
return this.post('/api/pm/resource', { id, act, action })
|
|
285
277
|
}
|
|
286
278
|
|
|
287
|
-
/** the url of a global process
|
|
288
|
-
* first when it is down) */
|
|
289
|
-
openProcess(id: string): Promise<boolean> {
|
|
290
|
-
return this.processAct(id,
|
|
279
|
+
/** the url of a global process in the default browser, in Chrome, or as
|
|
280
|
+
* its own app (started first when it is down) */
|
|
281
|
+
openProcess(id: string, how: OpenHow): Promise<boolean> {
|
|
282
|
+
return this.processAct(id, how === 'browser' ? 'open' : how)
|
|
291
283
|
}
|
|
292
284
|
|
|
293
285
|
/** a process's pid to the clipboard, and the "copied" flash on it; a
|
|
@@ -305,13 +297,6 @@ export class MachineSt {
|
|
|
305
297
|
this.copiedTimer = setTimeout(() => runInAction(() => { this.copiedPid = null }), COPIED_MS)
|
|
306
298
|
}
|
|
307
299
|
|
|
308
|
-
setOpenHow(how: OpenHow): void {
|
|
309
|
-
this.openHow = how
|
|
310
|
-
try {
|
|
311
|
-
globalThis.localStorage?.setItem(OPEN_HOW_KEY, how)
|
|
312
|
-
} catch { /* private window or blocked site data: the pick lasts the tab */ }
|
|
313
|
-
}
|
|
314
|
-
|
|
315
300
|
/** restart the daemon or the web through pm */
|
|
316
301
|
restartChild(id: Role): Promise<boolean> {
|
|
317
302
|
return this.post('/api/pm/child', { id, act: 'restart' })
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// the one picker of the page (cmd+
|
|
2
|
-
// home's places (home, planned,
|
|
1
|
+
// the one picker of the page (cmd+j, keys.ts), a node of the root: the
|
|
2
|
+
// home's places (home, activity, planned, in the list's order) first, then the shown repo's own jump
|
|
3
3
|
// targets, then every other house repo as a place to go. Another repo's
|
|
4
4
|
// resources are never listed: a jump to a repo selects it in place, and its
|
|
5
|
-
// own targets are one more cmd+
|
|
5
|
+
// own targets are one more cmd+j away.
|
|
6
6
|
import { makeAutoObservable } from 'mobx'
|
|
7
7
|
import type { HomeSt } from './home-st.ts'
|
|
8
8
|
import type { Jump } from './repo-st.ts'
|
|
@@ -25,8 +25,8 @@ export class PaletteSt {
|
|
|
25
25
|
if (st.machine == null) return []
|
|
26
26
|
return [
|
|
27
27
|
{ id: 'place:home', label: 'home', section: 'place', face: { place: 'home' }, run: () => st.navigate('/') },
|
|
28
|
-
{ id: 'place:planned', label: 'planned', section: 'place', face: { place: 'planned' }, run: () => st.navigate('/planned') },
|
|
29
28
|
{ id: 'place:activity', label: 'activity', section: 'place', face: { place: 'activity' }, run: () => st.navigate('/activity') },
|
|
29
|
+
{ id: 'place:planned', label: 'planned', section: 'place', face: { place: 'planned' }, run: () => st.navigate('/planned') },
|
|
30
30
|
{ id: 'place:archived', label: 'archived', section: 'place', face: { place: 'archived' }, run: () => st.navigate('/archived') },
|
|
31
31
|
]
|
|
32
32
|
}
|
|
@@ -45,7 +45,7 @@ export class PaletteSt {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/** ranked: prefix matches first, then substring. With no query, home,
|
|
48
|
-
*
|
|
48
|
+
* activity and planned, then the shown repo's first targets, then every other repo,
|
|
49
49
|
* so a jump elsewhere is always in reach */
|
|
50
50
|
get results(): Jump[] {
|
|
51
51
|
const q = this.query.trim().toLowerCase()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// the one picker, cmd+
|
|
1
|
+
// the one picker, cmd+j (palette-st.ts): home, activity, planned and
|
|
2
2
|
// archived, the shown repo's targets, then every other repo. A place wears
|
|
3
3
|
// its button's icon, a repo its own icon, as the project list shows them.
|
|
4
4
|
import { observer } from 'mobx-react-lite'
|
|
@@ -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
|
+
})
|