@vimoxshah/tokenflow 1.1.1 → 1.2.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 +228 -0
- package/Dockerfile.team +20 -0
- package/README.md +30 -11
- package/bin/tokenflow.js +147 -12
- package/design/tokens.yaml +330 -0
- package/docs/architecture.md +5 -4
- package/docs/cli.md +204 -0
- package/docs/configuration.md +117 -2
- package/docs/design-system.md +187 -0
- package/docs/exports-and-budgets.md +85 -0
- package/docs/guard-codex.md +132 -0
- package/docs/ledger.md +144 -0
- package/docs/live-mode.md +40 -0
- package/docs/media/overview-aurora-dark.png +0 -0
- package/docs/media/receipts-aurora-dark.png +0 -0
- package/docs/providers-otel.md +179 -0
- package/docs/providers.md +54 -1
- package/docs/receipt-schema.md +74 -0
- package/docs/roadmap.md +182 -0
- package/docs/team-server.md +170 -0
- package/docs/ui-views.md +322 -0
- package/package.json +7 -2
- package/schemas/receipt.v0.json +160 -0
- package/scripts/build-dmg.sh +11 -2
- package/scripts/build-menubar-app.sh +58 -7
- package/scripts/design-build.js +475 -0
- package/src/analytics/anatomy.js +467 -0
- package/src/analytics/branch-compare.js +159 -0
- package/src/analytics/cache-health.js +141 -0
- package/src/analytics/live-view.js +266 -0
- package/src/analytics/receipt-schema.js +214 -0
- package/src/analytics/receipt.js +709 -0
- package/src/analytics/rhythm.js +184 -0
- package/src/analytics/whatif.js +263 -0
- package/src/commands/budget-scopes.js +133 -0
- package/src/commands/doctor-checks.js +400 -0
- package/src/commands/guard.js +531 -0
- package/src/commands/hooks.js +238 -0
- package/src/commands/pricing-diff.js +316 -0
- package/src/commands/receipt.js +226 -0
- package/src/commands/team-serve.js +407 -0
- package/src/commands/week.js +86 -0
- package/src/core/annotations.js +97 -0
- package/src/core/budget.js +33 -0
- package/src/core/bundle.js +45 -2
- package/src/core/ingest.js +33 -0
- package/src/core/live-status.js +227 -2
- package/src/core/policy.js +103 -0
- package/src/core/receipt-note.js +123 -0
- package/src/core/repo.js +64 -0
- package/src/core/sync.js +163 -26
- package/src/core/team.js +0 -0
- package/src/export/html-snapshot.js +28 -1
- package/src/export/menubar.js +21 -0
- package/src/export/receipt-card.js +210 -0
- package/src/export/week-card.js +185 -0
- package/src/providers/mock/index.js +383 -52
- package/src/providers/openai/index.js +31 -1
- package/src/providers/otel/index.js +656 -0
- package/src/server/routes/annotations.js +42 -0
- package/src/server/routes/cache-health.js +95 -0
- package/src/server/routes/index.js +54 -0
- package/src/server/routes/session.js +157 -0
- package/src/server/server.js +47 -1
- package/src/ui/app.js +541 -308
- package/src/ui/charts.js +95 -0
- package/src/ui/first-run.js +144 -0
- package/src/ui/index.html +4 -1
- package/src/ui/palette.js +335 -0
- package/src/ui/styles/anatomy.css +117 -0
- package/src/ui/styles/annotations.css +40 -0
- package/src/ui/styles/branches.css +99 -0
- package/src/ui/styles/cache.css +6 -0
- package/src/ui/styles/first-run.css +31 -0
- package/src/ui/styles/live.css +100 -0
- package/src/ui/styles/palette.css +85 -0
- package/src/ui/styles/rhythm.css +8 -0
- package/src/ui/styles/whatif.css +55 -0
- package/src/ui/styles.css +303 -196
- package/src/ui/views/anatomy.js +567 -0
- package/src/ui/views/annotations.js +121 -0
- package/src/ui/views/branches.js +304 -0
- package/src/ui/views/cache.js +232 -0
- package/src/ui/views/index.js +85 -0
- package/src/ui/views/live.js +683 -0
- package/src/ui/views/rhythm.js +206 -0
- package/src/ui/views/whatif.js +196 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/* Session anatomy — the picker list, the subagent tree and the step line.
|
|
2
|
+
Charts here are SVG from charts.js and carry no styles of their own. */
|
|
3
|
+
|
|
4
|
+
.anatomy-picker { padding: 6px 14px 14px; }
|
|
5
|
+
|
|
6
|
+
.anatomy-search {
|
|
7
|
+
width: 100%;
|
|
8
|
+
max-width: 460px;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.anatomy-count { margin: 8px 0 2px; }
|
|
12
|
+
|
|
13
|
+
.anatomy-rows {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
max-height: 330px;
|
|
17
|
+
overflow: auto;
|
|
18
|
+
border-bottom: 1px solid var(--hairline);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.anatomy-row {
|
|
22
|
+
display: grid;
|
|
23
|
+
grid-template-columns: minmax(130px, 1.7fr) minmax(90px, 1fr) minmax(90px, 1.1fr) 88px minmax(110px, 1.2fr) 64px 88px;
|
|
24
|
+
gap: 10px;
|
|
25
|
+
align-items: center;
|
|
26
|
+
width: 100%;
|
|
27
|
+
padding: 7px 6px;
|
|
28
|
+
border: 0;
|
|
29
|
+
border-top: 1px solid var(--hairline);
|
|
30
|
+
background: transparent;
|
|
31
|
+
color: var(--text-primary);
|
|
32
|
+
font: inherit;
|
|
33
|
+
text-align: left;
|
|
34
|
+
transition: background 140ms ease-out;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.anatomy-row > span {
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
text-overflow: ellipsis;
|
|
40
|
+
white-space: nowrap;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
button.anatomy-row { cursor: pointer; }
|
|
44
|
+
button.anatomy-row:hover { background: var(--surface-2); }
|
|
45
|
+
button.anatomy-row:focus-visible { outline: 2px solid var(--series-1); outline-offset: -2px; }
|
|
46
|
+
|
|
47
|
+
.anatomy-row.is-picked {
|
|
48
|
+
background: var(--surface-2);
|
|
49
|
+
box-shadow: inset 2px 0 0 var(--series-1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.anatomy-row-head {
|
|
53
|
+
position: sticky;
|
|
54
|
+
top: 0;
|
|
55
|
+
z-index: 1;
|
|
56
|
+
border-top: 0;
|
|
57
|
+
background: var(--surface-1);
|
|
58
|
+
color: var(--text-muted);
|
|
59
|
+
font-size: 11px;
|
|
60
|
+
letter-spacing: 0.04em;
|
|
61
|
+
text-transform: uppercase;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.anatomy-row .num {
|
|
65
|
+
text-align: right;
|
|
66
|
+
font-variant-numeric: tabular-nums;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.anatomy-id { font-size: 11.5px; }
|
|
70
|
+
.anatomy-model { color: var(--text-muted); }
|
|
71
|
+
|
|
72
|
+
.anatomy-loading {
|
|
73
|
+
display: flex;
|
|
74
|
+
gap: 10px;
|
|
75
|
+
align-items: center;
|
|
76
|
+
padding: 14px;
|
|
77
|
+
color: var(--text-muted);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.anatomy-dot {
|
|
81
|
+
width: 9px;
|
|
82
|
+
height: 9px;
|
|
83
|
+
border-radius: 50%;
|
|
84
|
+
background: var(--series-1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.anatomy-error {
|
|
88
|
+
padding: 12px 14px;
|
|
89
|
+
color: var(--critical);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.anatomy-tree { padding: 4px 0 6px; }
|
|
93
|
+
|
|
94
|
+
.anatomy-branch {
|
|
95
|
+
display: flex;
|
|
96
|
+
gap: 8px;
|
|
97
|
+
align-items: baseline;
|
|
98
|
+
padding: 6px 0;
|
|
99
|
+
border-top: 1px solid var(--hairline);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.anatomy-swatch {
|
|
103
|
+
width: 9px;
|
|
104
|
+
height: 9px;
|
|
105
|
+
flex: 0 0 auto;
|
|
106
|
+
border-radius: 2px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.anatomy-branch-name { font-weight: 600; }
|
|
110
|
+
|
|
111
|
+
.anatomy-step { padding: 4px 14px 14px; }
|
|
112
|
+
|
|
113
|
+
.anatomy-step-line {
|
|
114
|
+
margin: 6px 0 4px;
|
|
115
|
+
font-size: 15px;
|
|
116
|
+
font-weight: 600;
|
|
117
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/* Annotations tab: the list of marked days and the add form. */
|
|
2
|
+
|
|
3
|
+
.annotations-row {
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
gap: 12px;
|
|
7
|
+
padding: 8px 0;
|
|
8
|
+
border-top: 1px solid var(--hairline);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.annotations-row:first-child {
|
|
12
|
+
border-top: none;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.annotations-date {
|
|
16
|
+
min-width: 96px;
|
|
17
|
+
color: var(--text-muted);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.annotations-text {
|
|
21
|
+
flex: 1;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.annotations-form {
|
|
25
|
+
display: flex;
|
|
26
|
+
gap: 8px;
|
|
27
|
+
flex-wrap: wrap;
|
|
28
|
+
align-items: flex-end;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.annotations-note-fld {
|
|
32
|
+
flex: 1;
|
|
33
|
+
min-width: 220px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.annotations-error {
|
|
37
|
+
color: var(--critical);
|
|
38
|
+
width: 100%;
|
|
39
|
+
margin: 0;
|
|
40
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/* Compare branches — a diverging bar per metric, A on the left, B on the
|
|
2
|
+
right, coloured from the --div-1..5 ramp so "A costs more" and "B costs
|
|
3
|
+
more" read as mirror images around a neutral parity centre.
|
|
4
|
+
|
|
5
|
+
Note: docs/ui-views.md lists --hairline as a surface token and src/ui/views
|
|
6
|
+
/live.js uses it, but no --hairline custom property is ever defined in the
|
|
7
|
+
generated block at the top of src/ui/styles.css (checked design/tokens.yaml
|
|
8
|
+
and scripts/design-build.js too — it isn't produced anywhere). This view
|
|
9
|
+
uses --border for the same subtle-divider role instead of depending on the
|
|
10
|
+
undefined variable; see the handoff report for the flagged discrepancy. */
|
|
11
|
+
|
|
12
|
+
.branches-select-row {
|
|
13
|
+
display: flex; gap: 18px; flex-wrap: wrap; padding: 2px 14px 14px;
|
|
14
|
+
}
|
|
15
|
+
.branches-side {
|
|
16
|
+
display: flex; flex-direction: column; gap: 6px; min-width: 230px; flex: 1;
|
|
17
|
+
}
|
|
18
|
+
.branches-side-label {
|
|
19
|
+
font-size: var(--fs-label); letter-spacing: var(--tr-label); color: var(--text-muted);
|
|
20
|
+
text-transform: uppercase;
|
|
21
|
+
}
|
|
22
|
+
.branches-fields { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
23
|
+
.branches-fields select { flex: 1; min-width: 140px; }
|
|
24
|
+
|
|
25
|
+
.branches-head {
|
|
26
|
+
display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 0 14px 12px;
|
|
27
|
+
}
|
|
28
|
+
.branches-head-name {
|
|
29
|
+
font-family: var(--mono); font-size: var(--fs-title); color: var(--text-primary);
|
|
30
|
+
}
|
|
31
|
+
.branches-head-vs { color: var(--text-muted); font-size: var(--fs-caption); }
|
|
32
|
+
|
|
33
|
+
.branches-axis {
|
|
34
|
+
display: flex; align-items: center; gap: 10px; padding: 2px 14px 8px;
|
|
35
|
+
}
|
|
36
|
+
.branches-axis-spacer { width: 168px; flex: none; }
|
|
37
|
+
.branches-axis-ends { width: 64px; flex: none; }
|
|
38
|
+
.branches-axis-track { position: relative; flex: 1; height: 14px; }
|
|
39
|
+
.branches-axis-tick {
|
|
40
|
+
position: absolute; top: 0; transform: translateX(-50%);
|
|
41
|
+
font-size: var(--fs-micro); color: var(--text-muted); white-space: nowrap;
|
|
42
|
+
}
|
|
43
|
+
.branches-axis-tick:first-child { transform: translateX(0); }
|
|
44
|
+
.branches-axis-tick:last-child { transform: translateX(-100%); }
|
|
45
|
+
|
|
46
|
+
.branches-row {
|
|
47
|
+
display: flex; align-items: center; gap: 10px; padding: 7px 14px;
|
|
48
|
+
border-top: 1px solid var(--border);
|
|
49
|
+
}
|
|
50
|
+
.branches-row:first-child { border-top: none; }
|
|
51
|
+
.branches-row-label {
|
|
52
|
+
width: 168px; flex: none; font-size: var(--fs-caption); color: var(--text-secondary);
|
|
53
|
+
}
|
|
54
|
+
.branches-bar { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0; }
|
|
55
|
+
.branches-end {
|
|
56
|
+
width: 64px; flex: none; font-size: var(--fs-caption); color: var(--text-primary);
|
|
57
|
+
font-variant-numeric: tabular-nums;
|
|
58
|
+
}
|
|
59
|
+
.branches-end-a { text-align: right; }
|
|
60
|
+
.branches-end-b { text-align: left; }
|
|
61
|
+
.branches-track {
|
|
62
|
+
position: relative; flex: 1; display: flex; height: 14px;
|
|
63
|
+
background: var(--surface-2); border-radius: var(--radius-xs); overflow: hidden;
|
|
64
|
+
}
|
|
65
|
+
.branches-half { position: relative; width: 50%; height: 100%; display: flex; }
|
|
66
|
+
.branches-half-a { justify-content: flex-end; }
|
|
67
|
+
.branches-half-b { justify-content: flex-start; }
|
|
68
|
+
.branches-fill { height: 100%; transition: width var(--dur-slow) var(--ease-out); }
|
|
69
|
+
.branches-centre {
|
|
70
|
+
position: absolute; left: 50%; top: 0; bottom: 0; width: 1px;
|
|
71
|
+
background: var(--border-strong); pointer-events: none;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.branches-legend {
|
|
75
|
+
display: flex; gap: 16px; align-items: center; flex-wrap: wrap;
|
|
76
|
+
padding: 8px 14px 2px; font-size: var(--fs-caption); color: var(--text-secondary);
|
|
77
|
+
}
|
|
78
|
+
.branches-swatch {
|
|
79
|
+
display: inline-block; width: 10px; height: 10px; border-radius: 3px;
|
|
80
|
+
vertical-align: middle; margin-right: 6px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.branches-side-cards {
|
|
84
|
+
display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px;
|
|
85
|
+
}
|
|
86
|
+
.branches-side-card {
|
|
87
|
+
background: var(--surface-2); border: 1px solid var(--border);
|
|
88
|
+
border-radius: var(--radius-sm); padding: 12px 14px;
|
|
89
|
+
}
|
|
90
|
+
.branches-side-card h4 {
|
|
91
|
+
margin: 0 0 6px; font-size: var(--fs-caption); font-weight: 600; color: var(--text-primary);
|
|
92
|
+
font-family: var(--mono);
|
|
93
|
+
}
|
|
94
|
+
.branches-side-card p {
|
|
95
|
+
margin: 0 0 6px; font-size: var(--fs-body); line-height: var(--lh-body); color: var(--text-secondary);
|
|
96
|
+
}
|
|
97
|
+
.branches-side-card p:last-child { margin-bottom: 0; }
|
|
98
|
+
|
|
99
|
+
.branches-foot { padding: 2px 14px 0; }
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/* Cache health view. Everything else here reuses the shared card, table and
|
|
2
|
+
kpi styles; this tab only adds a status line for the churn card while it
|
|
3
|
+
loads or fails, so that state reads as a status message and not as a data
|
|
4
|
+
row. */
|
|
5
|
+
|
|
6
|
+
.cache-status { color: var(--text-muted); font-style: italic; }
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/* First-run screen (src/ui/first-run.js). A one-time explanation, not a
|
|
2
|
+
keyboard shortcut, so the ordinary 220ms dialog motion in styles.css is
|
|
3
|
+
fine here — only the command palette needs zero motion. */
|
|
4
|
+
|
|
5
|
+
.first-run-list {
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
gap: 8px;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.first-run-row {
|
|
12
|
+
margin: 0;
|
|
13
|
+
font-size: var(--fs-body);
|
|
14
|
+
color: var(--text-secondary);
|
|
15
|
+
line-height: var(--lh-body);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.first-run-missing {
|
|
19
|
+
margin-top: var(--sp-16);
|
|
20
|
+
}
|
|
21
|
+
.first-run-missing .sec-title {
|
|
22
|
+
margin-top: 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.first-run-closing {
|
|
26
|
+
margin: var(--sp-20) 0 0;
|
|
27
|
+
padding-top: var(--sp-12);
|
|
28
|
+
border-top: 1px solid var(--hairline);
|
|
29
|
+
color: var(--text-muted);
|
|
30
|
+
font-size: var(--fs-caption);
|
|
31
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/* Live view — severity chips for the anomaly list.
|
|
2
|
+
Moved verbatim from src/ui/styles.css so the tab carries its own styles.
|
|
3
|
+
The #fff is the existing on-critical text colour, the same literal
|
|
4
|
+
.badge.demo uses; there is no token for it in design/tokens.yaml. */
|
|
5
|
+
|
|
6
|
+
.sev-high { color: #fff; background: var(--critical); border-color: transparent; font-weight: 640; }
|
|
7
|
+
.sev-warn { color: var(--warning); border-color: color-mix(in srgb, var(--warning) 45%, transparent); }
|
|
8
|
+
.sev-info { color: var(--text-muted); }
|
|
9
|
+
|
|
10
|
+
/* ---------------------------------------------------------- right now ----
|
|
11
|
+
The four sections fed by data/status.json. Every class is prefixed
|
|
12
|
+
`live-` because all view stylesheets load together in every mode.
|
|
13
|
+
Colours come from the token variables only: --accent for the context
|
|
14
|
+
gauge, and the three status colours for the guard level and nothing
|
|
15
|
+
else. */
|
|
16
|
+
|
|
17
|
+
.live-sessions {
|
|
18
|
+
display: grid; gap: calc(var(--gap-grid) - 4px);
|
|
19
|
+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.live-session { display: flex; flex-direction: column; gap: 8px; }
|
|
23
|
+
.live-session-head { display: flex; align-items: flex-start; gap: 10px; }
|
|
24
|
+
.live-session-where { min-width: 0; flex: 1; }
|
|
25
|
+
.live-session-project {
|
|
26
|
+
font-size: var(--fs-title); letter-spacing: var(--tr-title); line-height: var(--lh-title);
|
|
27
|
+
font-weight: 600; overflow-wrap: anywhere;
|
|
28
|
+
}
|
|
29
|
+
.live-session-branch, .live-session-meta, .live-session-note { margin: 0; overflow-wrap: anywhere; }
|
|
30
|
+
.live-session-meta { font-family: var(--mono); }
|
|
31
|
+
|
|
32
|
+
.live-stat { display: flex; align-items: baseline; gap: 10px; }
|
|
33
|
+
.live-k {
|
|
34
|
+
color: var(--text-muted); font-size: var(--fs-label); letter-spacing: var(--tr-label);
|
|
35
|
+
min-width: 54px;
|
|
36
|
+
}
|
|
37
|
+
.live-v { font-variant-numeric: tabular-nums; }
|
|
38
|
+
.live-est { margin-left: 6px; }
|
|
39
|
+
|
|
40
|
+
/* The guard chip. Status colours mark status, so they live here and on
|
|
41
|
+
nothing else in this tab. */
|
|
42
|
+
.live-guard { white-space: nowrap; }
|
|
43
|
+
.live-guard-ok { color: var(--good); border-color: color-mix(in srgb, var(--good) 45%, transparent); }
|
|
44
|
+
.live-guard-warn { color: var(--warning); border-color: color-mix(in srgb, var(--warning) 45%, transparent); }
|
|
45
|
+
.live-guard-block { color: var(--critical); border-color: color-mix(in srgb, var(--critical) 45%, transparent); font-weight: 640; }
|
|
46
|
+
|
|
47
|
+
/* The context gauge. The fill is --accent at every level: how full a context
|
|
48
|
+
window is is a quantity, not a verdict, and the guard chip already says
|
|
49
|
+
whether it is a problem. No transition on the width — the fill may only
|
|
50
|
+
show a value the status file gave it, never a sweep toward one. */
|
|
51
|
+
.live-gauge { display: flex; flex-direction: column; gap: 4px; margin-top: 2px; }
|
|
52
|
+
.live-gauge-top { display: flex; align-items: baseline; gap: 10px; }
|
|
53
|
+
.live-gauge-value { font-variant-numeric: tabular-nums; margin-left: auto; }
|
|
54
|
+
.live-gauge-track {
|
|
55
|
+
height: 6px; border-radius: var(--radius-pill); background: var(--surface-2);
|
|
56
|
+
border: 1px solid var(--border); overflow: hidden;
|
|
57
|
+
}
|
|
58
|
+
.live-gauge-fill { height: 100%; background: var(--accent); border-radius: var(--radius-pill); }
|
|
59
|
+
.live-gauge-foot { display: flex; align-items: baseline; gap: 8px; margin: 0; }
|
|
60
|
+
.live-gauge-scale { margin-left: auto; }
|
|
61
|
+
|
|
62
|
+
/* Today's receipts. One grid so the money column lines up with the total. */
|
|
63
|
+
.live-receipts { display: flex; flex-direction: column; }
|
|
64
|
+
.live-receipt {
|
|
65
|
+
display: grid; gap: 10px; align-items: baseline; padding: 8px 0;
|
|
66
|
+
grid-template-columns: minmax(0, 1fr) auto 84px 92px;
|
|
67
|
+
border-top: 1px solid var(--border);
|
|
68
|
+
}
|
|
69
|
+
.live-receipt:first-child { border-top: 0; }
|
|
70
|
+
.live-receipt-repo { font-weight: 600; overflow-wrap: anywhere; }
|
|
71
|
+
.live-receipt-where p, .live-receipt-where .hint { margin: 0; }
|
|
72
|
+
.live-receipt-cost { font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }
|
|
73
|
+
.live-receipt-n { font-variant-numeric: tabular-nums; text-align: right; margin: 0; }
|
|
74
|
+
.live-receipt-total { border-top: 1px solid var(--border-strong); font-weight: 600; }
|
|
75
|
+
|
|
76
|
+
/* Guard card. */
|
|
77
|
+
.live-guard-body { display: flex; flex-direction: column; gap: 6px; }
|
|
78
|
+
.live-sub {
|
|
79
|
+
color: var(--text-muted); font-size: var(--fs-label); letter-spacing: var(--tr-label);
|
|
80
|
+
text-transform: uppercase; margin-top: 8px;
|
|
81
|
+
}
|
|
82
|
+
.live-guard-body .live-sub:first-child { margin-top: 0; }
|
|
83
|
+
.live-caps { display: flex; flex-direction: column; gap: 2px; }
|
|
84
|
+
.live-cap { display: flex; align-items: baseline; gap: 10px; }
|
|
85
|
+
.live-cap .live-k { min-width: 190px; }
|
|
86
|
+
.live-verdict { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
|
|
87
|
+
.live-verdict-id { overflow-wrap: anywhere; }
|
|
88
|
+
.live-none, .live-reason, .live-source { margin: 0; }
|
|
89
|
+
.live-cmd {
|
|
90
|
+
background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
|
|
91
|
+
padding: 8px 10px; margin: 0; overflow: auto; font-size: 11.5px; line-height: 1.55;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* Sparklines: one small chart per source, side by side. */
|
|
95
|
+
.live-sparks {
|
|
96
|
+
display: grid; gap: calc(var(--gap-grid) - 4px);
|
|
97
|
+
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
|
98
|
+
}
|
|
99
|
+
.live-spark { display: flex; flex-direction: column; gap: 6px; }
|
|
100
|
+
.live-spark-foot { margin: 0; font-variant-numeric: tabular-nums; }
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/* Command palette (src/ui/palette.js). Keyboard-initiated UI: opened dozens
|
|
2
|
+
of times a day, so it never animates open or closed, and nothing in the
|
|
3
|
+
list transitions — the active row must jump under the arrow keys, not ease
|
|
4
|
+
into place. Token variables only, same as every other stylesheet here. */
|
|
5
|
+
|
|
6
|
+
/* The base `dialog {}` / `dialog::backdrop {}` rules in styles.css carry a
|
|
7
|
+
220ms open/close transition meant for the mouse-driven export/pricing
|
|
8
|
+
modal. `transition: none` here has no `@starting-style` to interpolate
|
|
9
|
+
from, so this dialog and its backdrop simply appear and disappear. */
|
|
10
|
+
dialog.palette-dialog,
|
|
11
|
+
dialog.palette-dialog::backdrop {
|
|
12
|
+
transition: none;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
dialog.palette-dialog {
|
|
16
|
+
margin-top: 12vh;
|
|
17
|
+
max-width: 640px;
|
|
18
|
+
padding: 0;
|
|
19
|
+
overflow: hidden;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.palette-input {
|
|
23
|
+
width: 100%;
|
|
24
|
+
border: 0;
|
|
25
|
+
border-bottom: 1px solid var(--border);
|
|
26
|
+
border-radius: 0;
|
|
27
|
+
padding: 14px 16px;
|
|
28
|
+
font-size: var(--fs-title);
|
|
29
|
+
background: var(--surface-1);
|
|
30
|
+
color: var(--text-primary);
|
|
31
|
+
}
|
|
32
|
+
.palette-input:focus,
|
|
33
|
+
.palette-input:focus-visible {
|
|
34
|
+
outline: none;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.palette-list {
|
|
38
|
+
max-height: 50vh;
|
|
39
|
+
overflow: auto;
|
|
40
|
+
padding: 6px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.palette-row {
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
justify-content: space-between;
|
|
47
|
+
gap: 12px;
|
|
48
|
+
padding: 8px 10px;
|
|
49
|
+
border-radius: var(--radius-sm);
|
|
50
|
+
cursor: pointer;
|
|
51
|
+
}
|
|
52
|
+
.palette-row.active {
|
|
53
|
+
background: var(--surface-2);
|
|
54
|
+
}
|
|
55
|
+
.palette-row-label {
|
|
56
|
+
color: var(--text-primary);
|
|
57
|
+
font-size: var(--fs-body);
|
|
58
|
+
overflow: hidden;
|
|
59
|
+
text-overflow: ellipsis;
|
|
60
|
+
white-space: nowrap;
|
|
61
|
+
}
|
|
62
|
+
.palette-row-group {
|
|
63
|
+
flex: none;
|
|
64
|
+
color: var(--text-muted);
|
|
65
|
+
font-size: var(--fs-micro);
|
|
66
|
+
letter-spacing: var(--tr-micro);
|
|
67
|
+
text-transform: uppercase;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.palette-empty {
|
|
71
|
+
padding: 20px 16px;
|
|
72
|
+
color: var(--text-muted);
|
|
73
|
+
font-size: var(--fs-caption);
|
|
74
|
+
text-align: center;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* The "⌘K" chip at the end of the tab bar. Placement (flex, and pushing to
|
|
78
|
+
the row's end) lives in styles.css's .tabbar .palette-chip rule; this only
|
|
79
|
+
adds the mono glyph styling. It reuses `.chip`'s own hover and press
|
|
80
|
+
feedback — that motion belongs to the chip being clicked, not to the
|
|
81
|
+
palette it opens, so it stays inside the general 300ms ceiling. */
|
|
82
|
+
.palette-chip {
|
|
83
|
+
font-family: var(--mono);
|
|
84
|
+
letter-spacing: 0;
|
|
85
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/* Rhythm view.
|
|
2
|
+
Every element here reuses shared classes already styled elsewhere: `story`
|
|
3
|
+
/ `story-line` / `story-text` (styles.css, the Overview's story strip),
|
|
4
|
+
`cards` / `card` / `kpi` (styles.css), and the chart + table twin
|
|
5
|
+
(charts.js). None of it needs a rule of its own, so this file is
|
|
6
|
+
intentionally empty rather than adding a look-alike class that would only
|
|
7
|
+
drift from the shared one. Kept in place (instead of dropping `css` from
|
|
8
|
+
the view module) so the file exists at the path docs/ui-views.md expects. */
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/* What-if pricing view. Every view stylesheet loads together in every skin
|
|
2
|
+
and mode, so class names are prefixed `whatif-` to stay out of another
|
|
3
|
+
view's way. Delta colouring uses the diverging ramp (--div-1 .. --div-5):
|
|
4
|
+
--div-1 for extra cost, --div-5 for savings, --div-3 for a flat delta. That
|
|
5
|
+
ramp is reserved for polarity, so it appears here and nowhere a chart series
|
|
6
|
+
or a status marker already claims a colour. */
|
|
7
|
+
|
|
8
|
+
.whatif-footnote {
|
|
9
|
+
margin: 0 0 var(--sp-4);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.whatif-pickers {
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
gap: var(--sp-8);
|
|
16
|
+
padding: var(--sp-4) var(--sp-14) var(--sp-14);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.whatif-picker-row {
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
gap: var(--sp-10);
|
|
23
|
+
flex-wrap: wrap;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.whatif-picker-label {
|
|
27
|
+
min-width: 200px;
|
|
28
|
+
font-family: var(--mono);
|
|
29
|
+
font-size: var(--fs-caption);
|
|
30
|
+
color: var(--text-primary);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.whatif-picker-arrow {
|
|
34
|
+
color: var(--text-muted);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.whatif-target {
|
|
38
|
+
background: var(--surface-2);
|
|
39
|
+
color: var(--text-primary);
|
|
40
|
+
border: 1px solid var(--border);
|
|
41
|
+
border-radius: var(--radius-sm);
|
|
42
|
+
padding: var(--sp-6) var(--sp-10);
|
|
43
|
+
font-size: var(--fs-caption);
|
|
44
|
+
font-family: var(--sans);
|
|
45
|
+
transition: border-color var(--dur-fast) var(--ease-out);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.whatif-target:focus-visible {
|
|
49
|
+
border-color: var(--series-1);
|
|
50
|
+
outline: none;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.whatif-delta-kpi.whatif-save .k-value { color: var(--div-5); }
|
|
54
|
+
.whatif-delta-kpi.whatif-extra .k-value { color: var(--div-1); }
|
|
55
|
+
.whatif-delta-kpi.whatif-flat .k-value { color: var(--div-3); }
|