great-cto 3.2.0 → 3.3.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/README.md
CHANGED
|
@@ -20,6 +20,25 @@ That's it. The CLI detects your stack, picks the right archetype, clones the plu
|
|
|
20
20
|
|
|
21
21
|
After install, restart Claude Code and run `/inbox` or `/audit`.
|
|
22
22
|
|
|
23
|
+
## What the plugin gives you
|
|
24
|
+
|
|
25
|
+
The CLI installs it; these are what it installs.
|
|
26
|
+
|
|
27
|
+
- **A pipeline of specialist agents** — architect, product owner, senior dev, QA,
|
|
28
|
+
security officer, and archetype-specific reviewers — that run in sequence with
|
|
29
|
+
human approval gates at the points that are expensive to undo.
|
|
30
|
+
- **Spending caps that refuse.** Set a per-agent cap; when measured spend passes
|
|
31
|
+
it the dispatcher declines to run that agent instead of quietly continuing. An
|
|
32
|
+
agent with no measurement reads as `unmeasured`, never as `$0`.
|
|
33
|
+
- **A board at `localhost:3141`** — tasks, metrics, per-agent budgets, docs
|
|
34
|
+
search, and the decision journal, all served from your own machine. Nothing
|
|
35
|
+
leaves it.
|
|
36
|
+
- **A decision journal** that records what each dispatch decided and why, so a
|
|
37
|
+
pipeline that did not run looks different from one that ran and found nothing.
|
|
38
|
+
|
|
39
|
+
Telemetry is opt-in and off by default — see
|
|
40
|
+
[docs/PRIVACY.md](https://github.com/avelikiy/great_cto/blob/main/docs/PRIVACY.md).
|
|
41
|
+
|
|
23
42
|
## Examples
|
|
24
43
|
|
|
25
44
|
### Commerce detection (Stripe + Next.js)
|
|
@@ -82,8 +101,10 @@ npx great-cto init [options]
|
|
|
82
101
|
## Trust signals
|
|
83
102
|
|
|
84
103
|
- Zero runtime dependencies — only Node built-ins (`node:fs`, `node:path`, etc.)
|
|
85
|
-
-
|
|
86
|
-
-
|
|
104
|
+
- 305 unit tests covering stack detection, archetype scoring, and settings merge
|
|
105
|
+
- Tests and the release gate run locally (`scripts/ci-local.sh`) — the hosted CI
|
|
106
|
+
workflows are present in the repo but have not run since June 2026, so treat the
|
|
107
|
+
badge-shaped claims in this file as claims about the local gate, not a cloud matrix
|
|
87
108
|
- Source: [`packages/cli/src/`](https://github.com/avelikiy/great_cto/tree/main/packages/cli/src) — ~1.2k LOC TypeScript
|
|
88
109
|
|
|
89
110
|
## License
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "great_cto",
|
|
3
3
|
"id": "great_cto",
|
|
4
4
|
"description": "Engineering process for solo founders and teams up to 50 engineers. Agents do architecture, code review, QA, and security. You make two decisions per feature.",
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.3.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Great CTO",
|
|
8
8
|
"url": "https://github.com/avelikiy/great_cto"
|
|
@@ -1174,9 +1174,24 @@ async function dispatch(req, res, url, cwd) {
|
|
|
1174
1174
|
return (x[0] - y[0]) || (x[1] - y[1]) || (x[2] - y[2]);
|
|
1175
1175
|
}).pop() || null;
|
|
1176
1176
|
} catch { installed = null; }
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1177
|
+
// Direction, not inequality. `installed !== BUILD_VERSION` is true in both
|
|
1178
|
+
// directions, and the banner it fed said "restart to pick up the new
|
|
1179
|
+
// version" either way — so a board running 3.2.0 against an installed 3.1.0
|
|
1180
|
+
// was told to restart into the OLDER build. A prompt to downgrade that
|
|
1181
|
+
// looks exactly like a prompt to upgrade is the same defect this endpoint
|
|
1182
|
+
// was written to fix, pointed the other way.
|
|
1183
|
+
//
|
|
1184
|
+
// Four states: `behind` (an install is waiting), `ahead` (the running code
|
|
1185
|
+
// is newer — restarting would lose it), `no`, and `unknown`.
|
|
1186
|
+
const cmp = (a, b) => {
|
|
1187
|
+
const x = a.split('.').map(Number), y = b.split('.').map(Number);
|
|
1188
|
+
return (x[0] - y[0]) || (x[1] - y[1]) || (x[2] - y[2]);
|
|
1189
|
+
};
|
|
1190
|
+
let stale = 'unknown';
|
|
1191
|
+
if (installed && BUILD_VERSION !== 'unknown' && /^\d+\.\d+\.\d+$/.test(BUILD_VERSION)) {
|
|
1192
|
+
const d = cmp(installed, BUILD_VERSION);
|
|
1193
|
+
stale = d > 0 ? 'behind' : d < 0 ? 'ahead' : 'no';
|
|
1194
|
+
}
|
|
1180
1195
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
|
|
1181
1196
|
res.end(JSON.stringify({
|
|
1182
1197
|
version: BUILD_VERSION, installed, stale,
|
|
@@ -123,6 +123,38 @@
|
|
|
123
123
|
--shadow-card-hover: 0 1px 3px rgba(0, 0, 0, 0.12), 0 8px 24px rgba(0, 168, 98, 0.08);
|
|
124
124
|
--drawer-shadow: 0 16px 32px rgba(0, 0, 0, 0.18);
|
|
125
125
|
}
|
|
126
|
+
/* ── Type scale ──────────────────────────────────────────────────────────────
|
|
127
|
+
The board had 22 raw font sizes (9px…52px, including 11.5/12.5/13.5) and no
|
|
128
|
+
rule for h1/h2/h3 at all — so Budgets, Docs, Sessions and Activity rendered
|
|
129
|
+
their titles in the browser's default bold 700, a weight used nowhere else in
|
|
130
|
+
the design. One role, six renderings.
|
|
131
|
+
|
|
132
|
+
Two ramps, because the board has two kinds of text. Text steps by +1px in the
|
|
133
|
+
dense band (this is a single-operator console; density is the point) and by
|
|
134
|
+
~1.2x in the title band. Live numbers get their own ramp: they are read as
|
|
135
|
+
quantities, not prose, so they take --serif at 500 with tabular-nums.
|
|
136
|
+
|
|
137
|
+
--serif is NOT a serif face — it resolves to the same Geist as --sans. The
|
|
138
|
+
name is legacy; the role is "display numerals". Reach for it only at >=24px.
|
|
139
|
+
|
|
140
|
+
Legal 10px: --fs-eyebrow, and only for mono uppercase labels with tracking.
|
|
141
|
+
10px in sans body text is not a size, it is a mistake. */
|
|
142
|
+
:root {
|
|
143
|
+
--fs-eyebrow: 10px; /* mono uppercase label, tracking 0.08–0.1em */
|
|
144
|
+
--fs-caption: 11px; /* metadata, counts, timestamps */
|
|
145
|
+
--fs-small: 12px; /* table cells, secondary meta, chips */
|
|
146
|
+
--fs-body: 13px; /* dense body: cards, list rows, form fields */
|
|
147
|
+
--fs-base: 14px; /* document base / longform prose */
|
|
148
|
+
--fs-title-s: 15px; /* card or section heading (h3 role) */
|
|
149
|
+
--fs-title-m: 18px; /* region heading (h2 role) */
|
|
150
|
+
--fs-title-l: 22px; /* page heading (h1 role) */
|
|
151
|
+
|
|
152
|
+
--fs-num-s: 24px; /* inline stat */
|
|
153
|
+
--fs-num-m: 30px; /* secondary metric card */
|
|
154
|
+
--fs-num-l: 36px; /* second-tier hero KPI */
|
|
155
|
+
--fs-num-xl: 52px; /* primary hero KPI */
|
|
156
|
+
}
|
|
157
|
+
|
|
126
158
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
127
159
|
html, body { height: 100%; }
|
|
128
160
|
body {
|
|
@@ -135,6 +167,51 @@ body {
|
|
|
135
167
|
}
|
|
136
168
|
button { font-family: inherit; cursor: pointer; }
|
|
137
169
|
|
|
170
|
+
/* Headings had no base rule, so every unstyled h1/h2/h3 fell to the browser's
|
|
171
|
+
bold 700. Three weights exist in this design — 400 body, 500 value, 600 title
|
|
172
|
+
— and 700 is not one of them. This removes it everywhere at once. Bare-element
|
|
173
|
+
specificity (0,0,1), so every component rule below still wins. */
|
|
174
|
+
h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
|
|
175
|
+
|
|
176
|
+
/* The page's own title. Present on every panel, one size, one weight. Before
|
|
177
|
+
this, two panels had no title element at all, four rendered the browser
|
|
178
|
+
default, and one rendered a section heading at title size. */
|
|
179
|
+
.page-title {
|
|
180
|
+
font-size: var(--fs-title-l);
|
|
181
|
+
font-weight: 600;
|
|
182
|
+
line-height: 1.25;
|
|
183
|
+
letter-spacing: -0.02em;
|
|
184
|
+
color: var(--text);
|
|
185
|
+
margin: 0 0 14px;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* A heading INSIDE a page. Notifications rendered its three sections through
|
|
189
|
+
.share-card h2 — the showcase hero — so "Email alerts" was set at 28px, the
|
|
190
|
+
size of a page title, three times on one screen. */
|
|
191
|
+
.section-title {
|
|
192
|
+
font-size: var(--fs-title-m);
|
|
193
|
+
font-weight: 600;
|
|
194
|
+
line-height: 1.3;
|
|
195
|
+
letter-spacing: -0.01em;
|
|
196
|
+
color: var(--text);
|
|
197
|
+
margin: 0 0 6px;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* Sessions had a .panel-head element with no rule of any kind: its h2 fell to
|
|
201
|
+
the browser default of 1.5em bold-700 and the search box beside it did not
|
|
202
|
+
align to anything. */
|
|
203
|
+
.panel-head {
|
|
204
|
+
display: flex; align-items: center; gap: 12px;
|
|
205
|
+
margin: 0 0 14px; flex-wrap: wrap;
|
|
206
|
+
}
|
|
207
|
+
.panel-head h2 {
|
|
208
|
+
font-size: var(--fs-title-l);
|
|
209
|
+
letter-spacing: -0.02em;
|
|
210
|
+
line-height: 1.25;
|
|
211
|
+
margin: 0; flex: 1 1 auto;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
|
|
138
215
|
.board {
|
|
139
216
|
position: relative;
|
|
140
217
|
width: 100%;
|
|
@@ -554,7 +631,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
554
631
|
flex-shrink: 0;
|
|
555
632
|
}
|
|
556
633
|
.card .title {
|
|
557
|
-
font-weight: 600; font-size:
|
|
634
|
+
font-weight: 600; font-size: var(--fs-body);
|
|
558
635
|
line-height: 1.35;
|
|
559
636
|
color: var(--text);
|
|
560
637
|
display: -webkit-box;
|
|
@@ -628,13 +705,13 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
628
705
|
#agent-panel.open { display: flex; }
|
|
629
706
|
#agent-panel header { display: flex; align-items: center; gap: 8px; padding: 10px 14px; border-bottom: 1px solid var(--border, #1f2a26); background: transparent; }
|
|
630
707
|
#agent-panel header b { font-size: 13px; }
|
|
631
|
-
#agent-panel .ap-x { margin-left: auto; cursor: pointer; border: 0; background: none; color: var(--
|
|
708
|
+
#agent-panel .ap-x { margin-left: auto; cursor: pointer; border: 0; background: none; color: var(--text3); font-size: 16px; }
|
|
632
709
|
#agent-log { flex: 1; overflow: auto; padding: 10px 14px; font: 12px/1.55 ui-monospace, Menlo, monospace; }
|
|
633
710
|
#agent-log .al { margin: 2px 0; white-space: pre-wrap; word-break: break-word; }
|
|
634
|
-
#agent-log .al.tool { color: var(--status-review); } #agent-log .al.system { color: var(--
|
|
711
|
+
#agent-log .al.tool { color: var(--status-review); } #agent-log .al.system { color: var(--text3); } #agent-log .al.error { color: var(--status-blocked, #ff5a5a); } #agent-log .al.done { color: var(--text3); font-weight: 600; }
|
|
635
712
|
#agent-panel .ap-foot { display: flex; gap: 8px; padding: 10px 14px; border-top: 1px solid var(--border, #1f2a26); }
|
|
636
|
-
#agent-panel input { flex: 1; background: var(--bg
|
|
637
|
-
#agent-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--
|
|
713
|
+
#agent-panel input { flex: 1; background: var(--bg-muted); color: var(--text, #ecf2ee); border: 1px solid var(--border, #1f2a26); border-radius: 8px; padding: 7px 10px; font: inherit; font-size: 13px; }
|
|
714
|
+
#agent-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--text3); }
|
|
638
715
|
#agent-dot.live { background: var(--status-review); animation: apulse 1.2s infinite; }
|
|
639
716
|
@keyframes apulse { 0%,100%{opacity:1} 50%{opacity:.4} }
|
|
640
717
|
.gate-reject { color: var(--status-blocked); border-color: rgba(220, 38, 38, 0.30); }
|
|
@@ -677,7 +754,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
677
754
|
font-size: 12px;
|
|
678
755
|
line-height: 1.45;
|
|
679
756
|
}
|
|
680
|
-
.degraded-banner strong { color: var(--
|
|
757
|
+
.degraded-banner strong { color: var(--text); font-weight: 600; }
|
|
681
758
|
.degraded-banner span { color: var(--text2); font-family: var(--mono, ui-monospace, monospace); word-break: break-word; }
|
|
682
759
|
|
|
683
760
|
/* ── Side panel ───────────────────────────────────────────────────────────── */
|
|
@@ -733,9 +810,9 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
733
810
|
.side-close:hover { background: var(--bg-muted); color: var(--text); }
|
|
734
811
|
.side-body { flex: 1; overflow: auto; padding: 18px; }
|
|
735
812
|
.side-title {
|
|
736
|
-
font-family: var(--
|
|
737
|
-
font-size:
|
|
738
|
-
letter-spacing: -0.
|
|
813
|
+
font-family: var(--sans); font-weight: 600;
|
|
814
|
+
font-size: var(--fs-title-m);
|
|
815
|
+
letter-spacing: -0.01em;
|
|
739
816
|
margin-bottom: 12px;
|
|
740
817
|
color: var(--text);
|
|
741
818
|
}
|
|
@@ -840,7 +917,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
840
917
|
text-transform: uppercase; color: var(--text3);
|
|
841
918
|
min-width: 34px; flex-shrink: 0;
|
|
842
919
|
}
|
|
843
|
-
.cmdk-label { font-size:
|
|
920
|
+
.cmdk-label { font-size: var(--fs-body); color: var(--text); flex: 1; min-width: 0;
|
|
844
921
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
845
922
|
.cmdk-hint { font-family: var(--mono); font-size: 11px; color: var(--text3); flex-shrink: 0; }
|
|
846
923
|
.cmdk-empty { padding: 18px 14px; font-size: 13px; color: var(--text2); }
|
|
@@ -853,7 +930,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
853
930
|
position: absolute; inset: 0; background: rgba(0,0,0,0.5);
|
|
854
931
|
}
|
|
855
932
|
.hk-card {
|
|
856
|
-
position: relative; background: var(--bg
|
|
933
|
+
position: relative; background: var(--bg-elevated);
|
|
857
934
|
color: var(--text); border: 1px solid var(--border);
|
|
858
935
|
border-radius: 8px; padding: 20px 24px; min-width: 360px;
|
|
859
936
|
box-shadow: 0 12px 32px rgba(0,0,0,0.5);
|
|
@@ -876,7 +953,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
876
953
|
}
|
|
877
954
|
.rdm-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,0.5); }
|
|
878
955
|
.rdm-card {
|
|
879
|
-
position: relative; background: var(--bg
|
|
956
|
+
position: relative; background: var(--bg-elevated);
|
|
880
957
|
color: var(--text); border: 1px solid var(--border);
|
|
881
958
|
border-radius: 8px; padding: 18px 22px; min-width: 480px; max-width: 720px;
|
|
882
959
|
box-shadow: 0 12px 32px rgba(0,0,0,0.5);
|
|
@@ -903,10 +980,10 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
903
980
|
text-decoration: none; color: var(--text);
|
|
904
981
|
font-family: ui-monospace, monospace;
|
|
905
982
|
}
|
|
906
|
-
.rdm-artefact:hover { background: var(--bg-
|
|
983
|
+
.rdm-artefact:hover { background: var(--bg-muted); }
|
|
907
984
|
.rdm-artefact .rdm-art-kind {
|
|
908
985
|
font-size: 10px; padding: 1px 4px; border-radius: 3px;
|
|
909
|
-
background: var(--accent-
|
|
986
|
+
background: var(--accent-soft); color: var(--accent-text);
|
|
910
987
|
}
|
|
911
988
|
.rdm-foot { margin-top: 12px; font-size: 11px; color: var(--text3, #9ca3af); }
|
|
912
989
|
.resume-item { cursor: pointer; }
|
|
@@ -937,9 +1014,9 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
937
1014
|
display: flex; align-items: center; justify-content: space-between;
|
|
938
1015
|
padding: 14px 18px; border-bottom: 1px solid var(--border);
|
|
939
1016
|
}
|
|
940
|
-
.ni-header h3 { font-size:
|
|
1017
|
+
.ni-header h3 { font-size: var(--fs-title-s); font-weight: 600; margin: 0; }
|
|
941
1018
|
.ni-close {
|
|
942
|
-
background: transparent; border: 0; color: var(--
|
|
1019
|
+
background: transparent; border: 0; color: var(--text3);
|
|
943
1020
|
cursor: pointer; padding: 4px; display: flex; border-radius: 4px;
|
|
944
1021
|
}
|
|
945
1022
|
.ni-close:hover { background: var(--bg-muted, rgba(255,255,255,0.06)); color: var(--text); }
|
|
@@ -952,8 +1029,8 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
952
1029
|
flex: 1;
|
|
953
1030
|
}
|
|
954
1031
|
.ni-label {
|
|
955
|
-
font-size:
|
|
956
|
-
text-transform: uppercase; letter-spacing: 0.
|
|
1032
|
+
font-family: var(--mono); font-size: var(--fs-eyebrow); color: var(--text3);
|
|
1033
|
+
text-transform: uppercase; letter-spacing: 0.08em; font-weight: 500;
|
|
957
1034
|
}
|
|
958
1035
|
.ni-field input[type="text"],
|
|
959
1036
|
.ni-field textarea,
|
|
@@ -981,7 +1058,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
981
1058
|
font-size: 13px; cursor: pointer;
|
|
982
1059
|
}
|
|
983
1060
|
.ni-checkbox input { margin: 0; cursor: pointer; }
|
|
984
|
-
.ni-checkbox span { color: var(--
|
|
1061
|
+
.ni-checkbox span { color: var(--text2); }
|
|
985
1062
|
.ni-actions {
|
|
986
1063
|
display: flex; justify-content: flex-end; gap: 8px;
|
|
987
1064
|
margin-top: 4px;
|
|
@@ -992,7 +1069,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
992
1069
|
cursor: pointer; transition: all 120ms;
|
|
993
1070
|
}
|
|
994
1071
|
.ni-btn-ghost {
|
|
995
|
-
background: transparent; color: var(--
|
|
1072
|
+
background: transparent; color: var(--text2);
|
|
996
1073
|
border-color: var(--border);
|
|
997
1074
|
}
|
|
998
1075
|
.ni-btn-ghost:hover { background: var(--bg-muted, rgba(255,255,255,0.05)); color: var(--text); }
|
|
@@ -1024,7 +1101,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
1024
1101
|
color: var(--text3); margin-bottom: 6px;
|
|
1025
1102
|
}
|
|
1026
1103
|
.side-section-body {
|
|
1027
|
-
font-size:
|
|
1104
|
+
font-size: var(--fs-body); line-height: 1.5;
|
|
1028
1105
|
color: var(--text2);
|
|
1029
1106
|
white-space: pre-wrap;
|
|
1030
1107
|
word-break: break-word;
|
|
@@ -1101,7 +1178,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
1101
1178
|
color: var(--text);
|
|
1102
1179
|
}
|
|
1103
1180
|
.mem-item .desc {
|
|
1104
|
-
font-size:
|
|
1181
|
+
font-size: var(--fs-caption); color: var(--text2);
|
|
1105
1182
|
margin-top: 2px;
|
|
1106
1183
|
}
|
|
1107
1184
|
.mem-item .meta {
|
|
@@ -1338,11 +1415,11 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
1338
1415
|
.cost-cell .v {
|
|
1339
1416
|
font-family: var(--serif); font-weight: 500;
|
|
1340
1417
|
font-variant-numeric: tabular-nums;
|
|
1341
|
-
font-size:
|
|
1418
|
+
font-size: var(--fs-num-s); letter-spacing: -0.02em;
|
|
1342
1419
|
color: var(--text); line-height: 1.1;
|
|
1343
1420
|
}
|
|
1344
1421
|
.cost-cell .v small { font-size: 14px; color: var(--text2); margin-left: 2px; font-weight: 400; }
|
|
1345
|
-
.cost-cell .sub { font-size:
|
|
1422
|
+
.cost-cell .sub { font-size: var(--fs-caption); color: var(--text2); }
|
|
1346
1423
|
.cost-cell.warn .v { color: var(--status-blocked); }
|
|
1347
1424
|
.cost-cell.good .v { color: var(--status-review); }
|
|
1348
1425
|
|
|
@@ -1443,12 +1520,12 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
1443
1520
|
font-size: 12px;
|
|
1444
1521
|
}
|
|
1445
1522
|
.feat-name {
|
|
1446
|
-
font-family: var(--mono); color: var(--
|
|
1523
|
+
font-family: var(--mono); color: var(--text);
|
|
1447
1524
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
1448
1525
|
}
|
|
1449
|
-
.feat-bar-wrap { background: var(--bg); border-radius: 3px; height: 6px; }
|
|
1526
|
+
.feat-bar-wrap { background: var(--bg-strong); border-radius: 3px; height: 6px; }
|
|
1450
1527
|
.feat-bar { height: 6px; border-radius: 3px; background: var(--accent); transition: width .3s; }
|
|
1451
|
-
.feat-cost { font-family: var(--mono); color: var(--
|
|
1528
|
+
.feat-cost { font-family: var(--mono); color: var(--text); text-align: right; }
|
|
1452
1529
|
.feat-runs { font-size: 11px; color: var(--text3); text-align: right; }
|
|
1453
1530
|
|
|
1454
1531
|
/* ── Inbox page ───────────────────────────────────────────────────────────── */
|
|
@@ -1476,8 +1553,10 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
1476
1553
|
display: flex; align-items: center; gap: 10px;
|
|
1477
1554
|
}
|
|
1478
1555
|
.inbox-summary .pill-stat .num {
|
|
1479
|
-
font-family: var(--
|
|
1480
|
-
font-
|
|
1556
|
+
font-family: var(--serif); font-weight: 500;
|
|
1557
|
+
font-variant-numeric: tabular-nums;
|
|
1558
|
+
font-size: var(--fs-num-s); letter-spacing: -0.02em;
|
|
1559
|
+
color: var(--text);
|
|
1481
1560
|
}
|
|
1482
1561
|
.inbox-summary .pill-stat .lbl {
|
|
1483
1562
|
font-family: var(--mono); font-size: 10px;
|
|
@@ -1542,7 +1621,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
1542
1621
|
.resume-list .resume-item:hover { color: var(--text); }
|
|
1543
1622
|
.resume-list .resume-item .ri-tag {
|
|
1544
1623
|
flex-shrink: 0;
|
|
1545
|
-
font-family: var(--mono); font-size:
|
|
1624
|
+
font-family: var(--mono); font-size: var(--fs-eyebrow);
|
|
1546
1625
|
padding: 2px 5px; border-radius: 3px;
|
|
1547
1626
|
letter-spacing: 0.04em;
|
|
1548
1627
|
background: rgba(255,255,255,0.05);
|
|
@@ -1550,7 +1629,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
1550
1629
|
margin-top: 1px;
|
|
1551
1630
|
}
|
|
1552
1631
|
.resume-list .resume-item .ri-tag.ok { color: var(--accent); background: rgba(0,217,126,0.08); }
|
|
1553
|
-
.resume-list .resume-item .ri-tag.fail { color: var(--p1); background: rgba(255,80,80,0.08); }
|
|
1632
|
+
.resume-list .resume-item .ri-tag.fail { color: var(--p1-fg); background: rgba(255,80,80,0.08); }
|
|
1554
1633
|
.resume-list .resume-item .ri-text {
|
|
1555
1634
|
flex: 1; min-width: 0;
|
|
1556
1635
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
@@ -1572,7 +1651,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
1572
1651
|
}
|
|
1573
1652
|
.inbox-section-head h3 {
|
|
1574
1653
|
font-family: var(--sans); font-weight: 600;
|
|
1575
|
-
font-size:
|
|
1654
|
+
font-size: var(--fs-title-s); margin: 0; color: var(--text);
|
|
1576
1655
|
}
|
|
1577
1656
|
.inbox-section-head .inbox-count {
|
|
1578
1657
|
font-family: var(--mono); font-size: 11px;
|
|
@@ -1607,7 +1686,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
1607
1686
|
min-width: 0; overflow-wrap: anywhere; word-break: break-word;
|
|
1608
1687
|
}
|
|
1609
1688
|
.inbox-row .ttl {
|
|
1610
|
-
font-size:
|
|
1689
|
+
font-size: var(--fs-body); color: var(--text);
|
|
1611
1690
|
font-weight: 500;
|
|
1612
1691
|
display: flex;
|
|
1613
1692
|
flex-direction: column;
|
|
@@ -1623,7 +1702,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
1623
1702
|
}
|
|
1624
1703
|
.inbox-row .ttl .ttl-desc {
|
|
1625
1704
|
font-weight: 400;
|
|
1626
|
-
font-size:
|
|
1705
|
+
font-size: var(--fs-small);
|
|
1627
1706
|
color: var(--text2);
|
|
1628
1707
|
display: -webkit-box;
|
|
1629
1708
|
-webkit-line-clamp: 2;
|
|
@@ -1649,7 +1728,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
1649
1728
|
.budgets-table td { padding: 10px 12px 10px 0; border-bottom: 1px solid var(--border); vertical-align: middle; }
|
|
1650
1729
|
/* A row has to answer three questions: what is this agent, what does it cost,
|
|
1651
1730
|
over what period. The slug alone answered none of them. */
|
|
1652
|
-
.budgets-table .bt-slug { font-family: var(--mono); font-size:
|
|
1731
|
+
.budgets-table .bt-slug { font-family: var(--mono); font-size: var(--fs-small); }
|
|
1653
1732
|
.budgets-table .bt-desc {
|
|
1654
1733
|
font-size: 12px; color: var(--text2); margin-top: 2px;
|
|
1655
1734
|
max-width: 52ch; text-wrap: pretty;
|
|
@@ -1753,7 +1832,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
1753
1832
|
so a figure that ticks over re-flows its own width and the number appears to
|
|
1754
1833
|
breathe. Tabular figures cost nothing here and stop it. */
|
|
1755
1834
|
font-variant-numeric: tabular-nums;
|
|
1756
|
-
font-size:
|
|
1835
|
+
font-size: var(--fs-num-xl); line-height: 1;
|
|
1757
1836
|
letter-spacing: -0.02em;
|
|
1758
1837
|
}
|
|
1759
1838
|
/* An absence is rendered quietly and never silently — the glyph stays out of the
|
|
@@ -1790,7 +1869,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
1790
1869
|
gap: 12px; margin-bottom: 28px;
|
|
1791
1870
|
}
|
|
1792
1871
|
.mp-secondary .metric-card { padding: 18px; }
|
|
1793
|
-
.mp-secondary .metric-num { font-size:
|
|
1872
|
+
.mp-secondary .metric-num { font-size: var(--fs-num-m); }
|
|
1794
1873
|
|
|
1795
1874
|
.mp-row {
|
|
1796
1875
|
display: grid; grid-template-columns: 1fr 1fr; gap: 16px;
|
|
@@ -1801,7 +1880,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
1801
1880
|
}
|
|
1802
1881
|
.mp-card h3 {
|
|
1803
1882
|
font-family: var(--sans); font-weight: 600;
|
|
1804
|
-
font-size:
|
|
1883
|
+
font-size: var(--fs-title-s); margin: 0 0 16px;
|
|
1805
1884
|
}
|
|
1806
1885
|
.mp-card-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 16px; }
|
|
1807
1886
|
.mp-card-head h3 { margin: 0; }
|
|
@@ -1821,7 +1900,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
1821
1900
|
.bar-row {
|
|
1822
1901
|
display: grid; grid-template-columns: 130px 1fr 50px;
|
|
1823
1902
|
align-items: center; gap: 12px;
|
|
1824
|
-
margin-bottom: 10px; font-size:
|
|
1903
|
+
margin-bottom: 10px; font-size: var(--fs-small);
|
|
1825
1904
|
}
|
|
1826
1905
|
.bar-row .name { font-family: var(--mono); color: var(--text); }
|
|
1827
1906
|
.bar-row .rail {
|
|
@@ -1863,7 +1942,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
1863
1942
|
/* ── Logs tab ───────────────────────────────────────────── */
|
|
1864
1943
|
.logs-page { display: flex; flex-direction: column; height: 100%; }
|
|
1865
1944
|
.logs-header { padding: 20px 24px 12px; border-bottom: 1px solid var(--border); display: flex; align-items: baseline; gap: 16px; }
|
|
1866
|
-
.logs-header h2 { margin: 0; font-size:
|
|
1945
|
+
.logs-header h2 { margin: 0; font-size: var(--fs-title-l); letter-spacing: -0.02em; }
|
|
1867
1946
|
.logs-hint { font-size: 12px; color: var(--text3); }
|
|
1868
1947
|
.logs-hint code { font-family: var(--mono); background: var(--bg-strong); padding: 1px 5px; border-radius: 3px; }
|
|
1869
1948
|
.logs-layout { display: flex; flex: 1; overflow: hidden; }
|
|
@@ -1890,7 +1969,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
1890
1969
|
.log-item .log-title { font-size: 13px; font-weight: 500; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1891
1970
|
.log-item .log-meta { font-size: 11px; color: var(--text3); margin-top: 2px; }
|
|
1892
1971
|
.log-detail-head { margin-bottom: 20px; padding-bottom: 14px; border-bottom: 1px solid var(--border); }
|
|
1893
|
-
.log-detail-head h3 { margin: 0 0 4px; font-size:
|
|
1972
|
+
.log-detail-head h3 { margin: 0 0 4px; font-size: var(--fs-title-m); letter-spacing: -0.01em; }
|
|
1894
1973
|
.log-detail-head .log-detail-meta { font-size: 12px; color: var(--text3); }
|
|
1895
1974
|
.log-section { margin-bottom: 18px; }
|
|
1896
1975
|
.log-section-label { font-size: 11px; color: var(--text3); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 6px; }
|
|
@@ -2086,7 +2165,8 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
2086
2165
|
padding: 16px 20px; border-bottom: 1px solid var(--border);
|
|
2087
2166
|
}
|
|
2088
2167
|
.agent-drawer-header h3 {
|
|
2089
|
-
font-family: var(--
|
|
2168
|
+
font-family: var(--sans); font-weight: 600;
|
|
2169
|
+
font-size: var(--fs-title-s);
|
|
2090
2170
|
color: var(--text); flex: 1; margin: 0;
|
|
2091
2171
|
}
|
|
2092
2172
|
.agent-drawer-close {
|
|
@@ -2169,7 +2249,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
2169
2249
|
border-radius: 16px;
|
|
2170
2250
|
padding: 32px;
|
|
2171
2251
|
}
|
|
2172
|
-
.share-card h2 {
|
|
2252
|
+
.share-card h2:not(.section-title) {
|
|
2173
2253
|
font-family: var(--serif); font-weight: 500;
|
|
2174
2254
|
font-size: 28px; letter-spacing: -0.01em;
|
|
2175
2255
|
margin: 0 0 6px;
|
|
@@ -2187,10 +2267,10 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
2187
2267
|
.share-toggle-row .label-grp .l { font-weight: 600; font-size: 14px; }
|
|
2188
2268
|
.share-toggle-row .label-grp .h { color: var(--text2); font-size: 12px; }
|
|
2189
2269
|
|
|
2190
|
-
.notif-row { display: flex; align-items: center; gap: 10px; padding: 8px 12px; background: var(--
|
|
2270
|
+
.notif-row { display: flex; align-items: center; gap: 10px; padding: 8px 12px; background: var(--bg-muted); border: 1px solid var(--border); border-radius: 8px; cursor: pointer; transition: border-color 0.15s; }
|
|
2191
2271
|
.notif-row:hover { border-color: var(--accent); }
|
|
2192
2272
|
.notif-row .emoji { font-size: 16px; flex-shrink: 0; }
|
|
2193
|
-
.notif-row strong { color: var(--
|
|
2273
|
+
.notif-row strong { color: var(--text); margin-right: 4px; }
|
|
2194
2274
|
.notif-row input[type="checkbox"] { width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; }
|
|
2195
2275
|
|
|
2196
2276
|
.toggle {
|
|
@@ -2224,7 +2304,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
2224
2304
|
}
|
|
2225
2305
|
.url-block .url-text {
|
|
2226
2306
|
flex: 1; font-family: var(--mono);
|
|
2227
|
-
font-size:
|
|
2307
|
+
font-size: var(--fs-small); color: var(--text);
|
|
2228
2308
|
white-space: nowrap; overflow: hidden;
|
|
2229
2309
|
text-overflow: ellipsis;
|
|
2230
2310
|
}
|
|
@@ -2357,7 +2437,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
2357
2437
|
display: grid;
|
|
2358
2438
|
grid-template-columns: 130px 1fr 70px;
|
|
2359
2439
|
align-items: center; gap: 12px;
|
|
2360
|
-
margin-bottom: 10px; font-size:
|
|
2440
|
+
margin-bottom: 10px; font-size: var(--fs-small);
|
|
2361
2441
|
}
|
|
2362
2442
|
.agent-cost-row .name { font-family: var(--mono); color: var(--text); }
|
|
2363
2443
|
.agent-cost-row .rail { display: block; height: 6px; background: var(--bg-strong); border-radius: 999px; overflow: hidden; }
|
|
@@ -2390,7 +2470,11 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
2390
2470
|
.side-section-head { font-family: var(--mono); font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text3); margin-bottom: 8px; margin-top: 16px; }
|
|
2391
2471
|
|
|
2392
2472
|
/* ── Security tab: hero KPI tile + activity feed ──────────────────────────── */
|
|
2393
|
-
.kpi-hero .kpi-value-hero {
|
|
2473
|
+
.kpi-hero .kpi-value-hero {
|
|
2474
|
+
font-family: var(--serif); font-weight: 500;
|
|
2475
|
+
font-variant-numeric: tabular-nums;
|
|
2476
|
+
font-size: var(--fs-num-l); line-height: 1.1; letter-spacing: -0.02em;
|
|
2477
|
+
}
|
|
2394
2478
|
.kpi-hero .kpi-label { font-size: 13px; font-weight: 500; }
|
|
2395
2479
|
.kpi-empty .kpi-value { color: var(--accent); font-size: 22px; }
|
|
2396
2480
|
.kpi-empty .kpi-value::after {
|
|
@@ -2745,6 +2829,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
2745
2829
|
|
|
2746
2830
|
<!-- Kanban -->
|
|
2747
2831
|
<div class="panel" id="panel-kanban">
|
|
2832
|
+
<h1 class="page-title">Tasks</h1>
|
|
2748
2833
|
<div id="filter-bar" class="filter-bar"></div>
|
|
2749
2834
|
<div class="kanban" id="kanban-board"></div>
|
|
2750
2835
|
</div>
|
|
@@ -2753,6 +2838,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
2753
2838
|
<!-- Metrics -->
|
|
2754
2839
|
<div class="panel" id="panel-dashboard">
|
|
2755
2840
|
<div class="metrics-page">
|
|
2841
|
+
<h1 class="page-title">Metrics</h1>
|
|
2756
2842
|
<div class="period-toolbar">
|
|
2757
2843
|
<span class="period-label">PERIOD</span>
|
|
2758
2844
|
<div class="period-chips" id="period-chips">
|
|
@@ -2898,7 +2984,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
2898
2984
|
|
|
2899
2985
|
<div class="panel" id="panel-budgets" role="tabpanel">
|
|
2900
2986
|
<div class="metrics-page">
|
|
2901
|
-
<h1>Agent budgets</h1>
|
|
2987
|
+
<h1 class="page-title">Agent budgets</h1>
|
|
2902
2988
|
<p class="muted" style="max-width:70ch;margin:6px 0 20px">
|
|
2903
2989
|
A cap on what one agent may spend. When measured spend passes it, the pipeline
|
|
2904
2990
|
refuses to dispatch that agent and says so — it does not fail quietly.
|
|
@@ -2910,7 +2996,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
2910
2996
|
</div>
|
|
2911
2997
|
|
|
2912
2998
|
<div class="panel" id="panel-docs">
|
|
2913
|
-
<h1>Docs</h1>
|
|
2999
|
+
<h1 class="page-title">Docs</h1>
|
|
2914
3000
|
<div class="docs-search">
|
|
2915
3001
|
<input type="search" id="docs-q" placeholder="Search inside documents…"
|
|
2916
3002
|
aria-label="Search inside documents"
|
|
@@ -2979,8 +3065,9 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
2979
3065
|
<!-- Notifications -->
|
|
2980
3066
|
<div class="panel" id="panel-notifications">
|
|
2981
3067
|
<div class="share-page">
|
|
3068
|
+
<h1 class="page-title">Notifications</h1>
|
|
2982
3069
|
<div class="share-card">
|
|
2983
|
-
<h2>Email alerts</h2>
|
|
3070
|
+
<h2 class="section-title">Email alerts</h2>
|
|
2984
3071
|
<p class="sub">Get notified when something needs your attention — even when you're away from the board. Verify your email once, pick what to receive, done. No API keys, no extra signup.</p>
|
|
2985
3072
|
|
|
2986
3073
|
<div class="share-toggle-row">
|
|
@@ -2997,7 +3084,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
2997
3084
|
<div id="notif-step-email">
|
|
2998
3085
|
<div style="font-size:11px;color:var(--text3);margin-bottom:4px;font-family:var(--mono);letter-spacing:.05em">RECIPIENT EMAIL</div>
|
|
2999
3086
|
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
|
3000
|
-
<input id="notif-to" type="email" placeholder="you@example.com" style="flex:1 1 280px;min-width:240px;padding:8px 10px;background:var(--
|
|
3087
|
+
<input id="notif-to" type="email" placeholder="you@example.com" style="flex:1 1 280px;min-width:240px;padding:8px 10px;background:var(--bg-muted);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:14px" />
|
|
3001
3088
|
<button class="btn-black" onclick="sendVerifyCode()">Send code</button>
|
|
3002
3089
|
</div>
|
|
3003
3090
|
</div>
|
|
@@ -3006,23 +3093,23 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
3006
3093
|
<div id="notif-step-code" style="display:none;margin-top:14px">
|
|
3007
3094
|
<div style="font-size:11px;color:var(--text3);margin-bottom:4px;font-family:var(--mono);letter-spacing:.05em">VERIFICATION CODE (CHECK <span id="notif-code-target">YOUR INBOX</span>)</div>
|
|
3008
3095
|
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
|
3009
|
-
<input id="notif-code" type="text" inputmode="numeric" pattern="\d{6}" maxlength="6" placeholder="123456" style="flex:1 1 200px;min-width:140px;padding:8px 10px;background:var(--
|
|
3096
|
+
<input id="notif-code" type="text" inputmode="numeric" pattern="\d{6}" maxlength="6" placeholder="123456" style="flex:1 1 200px;min-width:140px;padding:8px 10px;background:var(--bg-muted);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:18px;font-family:var(--mono);letter-spacing:.2em;text-align:center" />
|
|
3010
3097
|
<button class="btn-black" onclick="confirmVerifyCode()">Verify</button>
|
|
3011
3098
|
<button class="btn-ghost-sm" onclick="sendVerifyCode()">Resend code</button>
|
|
3012
3099
|
</div>
|
|
3013
3100
|
</div>
|
|
3014
3101
|
|
|
3015
3102
|
<!-- Step 3: verified -->
|
|
3016
|
-
<div id="notif-step-verified" style="display:none;margin-top:14px;padding:10px 14px;background:rgba(0,217,126,0.08);border:1px solid rgba(0,217,126,0.30);border-radius:8px;display:none;align-items:center;gap:10px;font-size:
|
|
3103
|
+
<div id="notif-step-verified" style="display:none;margin-top:14px;padding:10px 14px;background:rgba(0,217,126,0.08);border:1px solid rgba(0,217,126,0.30);border-radius:8px;display:none;align-items:center;gap:10px;font-size: var(--fs-body)">
|
|
3017
3104
|
<span style="color:var(--accent);font-size:18px">✓</span>
|
|
3018
3105
|
<span><strong id="notif-verified-email">—</strong> verified · alerts delivered through <code>greatcto.systems/notify</code></span>
|
|
3019
3106
|
<button class="btn-ghost-sm" style="margin-left:auto" onclick="changeEmail()">Change</button>
|
|
3020
3107
|
</div>
|
|
3021
3108
|
|
|
3022
3109
|
<div class="share-note" style="margin-top:24px">
|
|
3023
|
-
<strong style="color:var(--
|
|
3110
|
+
<strong style="color:var(--text)">Which alerts to send</strong>
|
|
3024
3111
|
</div>
|
|
3025
|
-
<div id="notif-triggers" style="display:flex;flex-direction:column;gap:10px;margin-top:10px;font-size:
|
|
3112
|
+
<div id="notif-triggers" style="display:flex;flex-direction:column;gap:10px;margin-top:10px;font-size: var(--fs-body)">
|
|
3026
3113
|
<label class="notif-row"><input type="checkbox" data-trigger="incident.p0"> <span class="emoji">🚨</span><strong>P0 incident</strong> — production failure, ack-grade</label>
|
|
3027
3114
|
<label class="notif-row"><input type="checkbox" data-trigger="gate.stale"> <span class="emoji">⏸️</span><strong>Gate stale > 2h</strong> — ship gate waiting for your approval</label>
|
|
3028
3115
|
<label class="notif-row"><input type="checkbox" data-trigger="gate.blocked"> <span class="emoji">🛡️</span><strong>Security BLOCKED</strong> — security-officer rejected a merge</label>
|
|
@@ -3044,7 +3131,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
3044
3131
|
|
|
3045
3132
|
<!-- ── Browser push card ───────────────────────────────────────── -->
|
|
3046
3133
|
<div class="share-card" style="margin-top:16px" id="push-card">
|
|
3047
|
-
<h2>Browser push</h2>
|
|
3134
|
+
<h2 class="section-title">Browser push</h2>
|
|
3048
3135
|
<p class="sub">Native desktop notifications — works even when the board tab is in the background. No email required. Uses Web Push (VAPID) — keys stay local.</p>
|
|
3049
3136
|
<div class="share-toggle-row">
|
|
3050
3137
|
<div class="label-grp">
|
|
@@ -3055,7 +3142,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
3055
3142
|
<input type="checkbox" id="push-toggle" onclick="event.stopPropagation()">
|
|
3056
3143
|
</div>
|
|
3057
3144
|
</div>
|
|
3058
|
-
<div id="push-status" style="margin-top:10px;font-size:
|
|
3145
|
+
<div id="push-status" style="margin-top:10px;font-size: var(--fs-small);font-family:var(--mono);color:var(--text3)"></div>
|
|
3059
3146
|
<div class="share-note" style="margin-top:16px">
|
|
3060
3147
|
Push subscriptions are stored locally in <code>~/.great_cto/push-subscriptions.json</code>. Notifications fire for the same 5 triggers as email alerts.
|
|
3061
3148
|
</div>
|
|
@@ -3063,7 +3150,7 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
3063
3150
|
|
|
3064
3151
|
<!-- ── In-app notification history ─────────────────────────────── -->
|
|
3065
3152
|
<div class="share-card" style="margin-top:16px">
|
|
3066
|
-
<h2>Recent notifications</h2>
|
|
3153
|
+
<h2 class="section-title">Recent notifications</h2>
|
|
3067
3154
|
<p class="sub">Last 100 alerts fired this session. Also accessible via the notification drawer (bell icon).</p>
|
|
3068
3155
|
<div id="notif-history-inline" style="display:flex;flex-direction:column;gap:0;margin-top:12px;
|
|
3069
3156
|
border:1px solid var(--border);border-radius:8px;overflow:hidden;font-size:13px">
|
|
@@ -6197,15 +6284,22 @@ document.addEventListener('keydown', e => { if (e.key === 'Escape') closeSide();
|
|
|
6197
6284
|
*/
|
|
6198
6285
|
async function checkBoardFreshness() {
|
|
6199
6286
|
const v = await api('/api/version');
|
|
6200
|
-
|
|
6287
|
+
// `behind` and `ahead` are different problems with different fixes; `no` and
|
|
6288
|
+
// `unknown` are both silent, because a comparison we could not make must not
|
|
6289
|
+
// render as a mismatch.
|
|
6290
|
+
if (!v || (v.stale !== 'behind' && v.stale !== 'ahead')) return;
|
|
6201
6291
|
const host = document.querySelector('.workspace');
|
|
6202
6292
|
if (!host || document.getElementById('stale-board')) return;
|
|
6203
6293
|
const el = document.createElement('div');
|
|
6204
6294
|
el.id = 'stale-board';
|
|
6205
6295
|
el.className = 'stale-board';
|
|
6206
6296
|
el.setAttribute('role', 'status');
|
|
6207
|
-
el.innerHTML =
|
|
6208
|
-
|
|
6297
|
+
el.innerHTML = v.stale === 'behind'
|
|
6298
|
+
? `<span>This board is running <code>v${esc(v.version)}</code> — `
|
|
6299
|
+
+ `<code>v${esc(v.installed)}</code> is installed. Restart it to pick up the new version.</span>`
|
|
6300
|
+
: `<span>This board is running <code>v${esc(v.version)}</code>, which is newer than the `
|
|
6301
|
+
+ `installed <code>v${esc(v.installed)}</code>. Restarting would go back to the older build — `
|
|
6302
|
+
+ `install this version first.</span>`;
|
|
6209
6303
|
host.insertBefore(el, host.firstChild);
|
|
6210
6304
|
}
|
|
6211
6305
|
|
|
@@ -7236,7 +7330,7 @@ function renderNotifDrawer() {
|
|
|
7236
7330
|
<div style="display:flex;align-items:flex-start;gap:10px">
|
|
7237
7331
|
<span style="font-size:15px;flex-shrink:0;margin-top:1px">${_notifLevelIcon(n.level)}</span>
|
|
7238
7332
|
<div style="flex:1;min-width:0">
|
|
7239
|
-
<div style="font-size:13px;font-weight:${n.read ? '400' : '600'};color:var(--
|
|
7333
|
+
<div style="font-size:13px;font-weight:${n.read ? '400' : '600'};color:var(--text);
|
|
7240
7334
|
white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${esc(n.title)}</div>
|
|
7241
7335
|
<div style="font-size:12px;color:var(--text3);margin-top:2px;line-height:1.4">${esc(n.body)}</div>
|
|
7242
7336
|
<div style="font-size:11px;color:var(--text3);margin-top:4px;font-family:var(--mono)">
|
|
@@ -7416,7 +7510,7 @@ if (_costLbl) _costLbl.textContent = `Cost · last ${periodLabel(currentPeriod)}
|
|
|
7416
7510
|
|
|
7417
7511
|
<!-- ── Notification drawer ──────────────────────────────────────────────── -->
|
|
7418
7512
|
<div id="notif-drawer" style="position:fixed;top:0;right:-400px;width:380px;height:100vh;
|
|
7419
|
-
background:var(--
|
|
7513
|
+
background:var(--bg-elevated);border-left:1px solid var(--border);z-index:9999;
|
|
7420
7514
|
transition:right .25s cubic-bezier(.4,0,.2,1);display:flex;flex-direction:column;
|
|
7421
7515
|
box-shadow:-4px 0 32px rgba(0,0,0,.5)">
|
|
7422
7516
|
<div style="padding:14px 16px;border-bottom:1px solid var(--border);
|
|
@@ -7425,7 +7519,7 @@ if (_costLbl) _costLbl.textContent = `Cost · last ${periodLabel(currentPeriod)}
|
|
|
7425
7519
|
style="width:16px;height:16px;color:var(--text2)">
|
|
7426
7520
|
<path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9 M10.3 21a1.94 1.94 0 0 0 3.4 0"/>
|
|
7427
7521
|
</svg>
|
|
7428
|
-
<span style="font-weight:600;font-size:14px;flex:1;color:var(--
|
|
7522
|
+
<span style="font-weight:600;font-size:14px;flex:1;color:var(--text)">Notifications</span>
|
|
7429
7523
|
<button class="btn-ghost-sm" onclick="markAllNotifsRead()"
|
|
7430
7524
|
style="font-size:12px">Mark all read</button>
|
|
7431
7525
|
<button onclick="closeNotifDrawer()" style="background:none;border:none;cursor:pointer;
|
package/package.json
CHANGED