anentrypoint-design 1.0.2 → 1.0.3
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/chat.css +99 -3
- package/colors_and_type.css +10 -0
- package/community.css +34 -34
- package/dist/247420.css +262 -41
- package/dist/247420.js +23 -23
- package/package.json +1 -1
- package/src/community-app.js +6 -1
- package/src/components/content/charts.js +5 -1
- package/src/components/content/table.js +15 -5
- package/src/components/freddie/pages-telemetry.js +1 -1
- package/src/components/shell/atoms.js +6 -2
- package/src/css/app-shell/hero-content.css +36 -0
- package/src/css/app-shell/primitives.css +39 -2
- package/src/kits/os/freddie/pages-tools.js +8 -8
- package/src/kits/os/freddie-dashboard.css +3 -3
- package/src/kits/os/freddie-dashboard.js +1 -1
- package/src/kits/os/theme.css +26 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anentrypoint-design",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "247420 design system SDK — webjsx + modified ripple-ui, single-file ESM bundle for reproducible use of the AnEntrypoint design.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/247420.js",
|
package/src/community-app.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// data and receives action callbacks.
|
|
7
7
|
//
|
|
8
8
|
// Adapter contract (all fields optional; the app degrades when one is absent):
|
|
9
|
+
// adapter.brandName // string shown in the topbar brand span; defaults to 'app'
|
|
9
10
|
// adapter.get() -> snapshot {
|
|
10
11
|
// channels, categories, servers, currentChannel, currentServerId, homeMode,
|
|
11
12
|
// messages, // each message may carry reactions: [{emoji, count, users?, you?}]
|
|
@@ -60,6 +61,10 @@ export function mountCommunityApp(root, adapter = {}) {
|
|
|
60
61
|
const get = typeof adapter.get === 'function' ? adapter.get : () => ({});
|
|
61
62
|
const A = adapter.actions || {};
|
|
62
63
|
const H = adapter.helpers || {};
|
|
64
|
+
// The topbar brand name was hardcoded 'zellous' -- a design-system
|
|
65
|
+
// component should not bake in one consumer's name. Any host can supply
|
|
66
|
+
// its own via adapter.brandName; 'app' is a neutral, non-branded fallback.
|
|
67
|
+
const brandName = adapter.brandName || 'app';
|
|
63
68
|
const avatarColor = H.avatarColor || (() => 'var(--accent)');
|
|
64
69
|
const initial = H.initial || ((n) => String(n || '?').slice(0, 1).toUpperCase());
|
|
65
70
|
const formatTime = H.formatTime || ((t) => new Date(t || Date.now()).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }));
|
|
@@ -235,7 +240,7 @@ export function mountCommunityApp(root, adapter = {}) {
|
|
|
235
240
|
h('a', { href: '#app-main', class: 'skip-link' }, 'skip to main content'),
|
|
236
241
|
// top bar (sole app chrome above the chat-head)
|
|
237
242
|
h('header', { class: 'app-topbar' },
|
|
238
|
-
h('span', { class: 'brand' },
|
|
243
|
+
h('span', { class: 'brand' }, brandName, h('span', { class: 'slash' }, ' / '), h('span', {}, ch.name || 'general')),
|
|
239
244
|
h('span', {}),
|
|
240
245
|
h('nav', {},
|
|
241
246
|
h('a', { href: '../', title: 'Home', onclick: (e) => { if (A.goHome) { e.preventDefault(); A.goHome(); } } }, 'home'),
|
|
@@ -6,7 +6,7 @@ import * as webjsx from '../../../vendor/webjsx/index.js';
|
|
|
6
6
|
import { Icon } from '../shell.js';
|
|
7
7
|
const h = webjsx.createElement;
|
|
8
8
|
|
|
9
|
-
// items: [n, label] or [n, label, {delta, tone: 'up'|'down', spark: number[], invert}]
|
|
9
|
+
// items: [n, label] or [n, label, {delta, tone: 'up'|'down', spark: number[], invert, glyph}]
|
|
10
10
|
// meta is optional and additive — every existing 2-tuple call site is untouched.
|
|
11
11
|
// `tone` always drives the arrow glyph (it mirrors the delta's own arithmetic
|
|
12
12
|
// sign, so the arrow never contradicts the figure beside it). `invert` is a
|
|
@@ -15,12 +15,16 @@ const h = webjsx.createElement;
|
|
|
15
15
|
// relative to tone while the arrow direction is left alone — a rising error
|
|
16
16
|
// rate still shows an up-arrow (the number went up) but in the bad/danger
|
|
17
17
|
// color, not the good/success color the raw arithmetic sign would imply.
|
|
18
|
+
// `meta.glyph` is an optional icon NAME (resolved via Icon()) giving each
|
|
19
|
+
// tile a small identifying glyph in its top-right corner — purely additive,
|
|
20
|
+
// omitted call sites render exactly as before (no icon slot in the DOM).
|
|
18
21
|
export function Kpi({ items = [], emptyText = 'no metrics yet' }) {
|
|
19
22
|
if (!items.length) return h('div', { class: 'empty' }, emptyText);
|
|
20
23
|
return h('div', { class: 'kpi' }, ...items.map(([n, l, meta], i) => {
|
|
21
24
|
const isUp = meta && meta.tone !== 'down';
|
|
22
25
|
const good = meta && meta.invert ? !isUp : isUp;
|
|
23
26
|
return h('div', { key: i, class: 'kpi-card' },
|
|
27
|
+
meta && meta.glyph ? h('div', { class: 'kpi-glyph' }, Icon(meta.glyph, { size: 16 })) : null,
|
|
24
28
|
h('div', { class: 'num' }, String(n)),
|
|
25
29
|
h('div', { class: 'lbl' }, l),
|
|
26
30
|
meta && (meta.delta != null || meta.spark)
|
|
@@ -29,11 +29,11 @@ export function Table({ headers = [], rows = [], onRowClick, emptyText = 'nothin
|
|
|
29
29
|
// component has no opinion on comparator/locale/type - it only renders the
|
|
30
30
|
// control and current state). A docstudio-style dense admin table needs
|
|
31
31
|
// sortable columns; Table previously had no way to express that at all.
|
|
32
|
-
const thFor = (hd, i) => {
|
|
33
|
-
if (!sortable || !onSort) return h('th', { key: i, scope: 'col' }, hd);
|
|
32
|
+
const thFor = (hd, i, isNum) => {
|
|
33
|
+
if (!sortable || !onSort) return h('th', { key: i, scope: 'col', class: isNum ? 'is-num' : null }, hd);
|
|
34
34
|
const isActive = sortKey === i;
|
|
35
35
|
const ariaSort = isActive ? (sortDir === 'desc' ? 'descending' : 'ascending') : 'none';
|
|
36
|
-
return h('th', { key: i, scope: 'col', 'aria-sort': ariaSort },
|
|
36
|
+
return h('th', { key: i, scope: 'col', 'aria-sort': ariaSort, class: isNum ? 'is-num' : null },
|
|
37
37
|
h('button', { type: 'button', class: 'ds-table-sort-btn' + (isActive ? ' is-active' : ''), onclick: () => onSort(i) },
|
|
38
38
|
h('span', { class: 'ds-table-sort-label' }, hd),
|
|
39
39
|
isActive ? Icon(sortDir === 'desc' ? 'chevron-down' : 'chevron-up', { size: 12 }) : null));
|
|
@@ -50,13 +50,23 @@ export function Table({ headers = [], rows = [], onRowClick, emptyText = 'nothin
|
|
|
50
50
|
// NOTE this fires only when the table actually overflows, which is
|
|
51
51
|
// viewport-dependent — it reproduces at 1024x768 but not at 1280x900,
|
|
52
52
|
// which is why it surfaced only in CI's viewport.
|
|
53
|
+
// A column is numeric when every row's value in it is a plain
|
|
54
|
+
// integer/decimal (optionally signed) — checked across the whole column,
|
|
55
|
+
// not per-cell, so a mixed column (e.g. one row's count rendered as a
|
|
56
|
+
// Chip vnode) never right-aligns only some of its cells.
|
|
57
|
+
const NUM_RE = /^-?\d+(\.\d+)?$/;
|
|
58
|
+
const isNumericCol = (j) => rows.every((row) => {
|
|
59
|
+
const c = row[j];
|
|
60
|
+
return c != null && typeof c !== 'object' && NUM_RE.test(String(c).trim());
|
|
61
|
+
});
|
|
62
|
+
const numericCols = headers.map((_, j) => isNumericCol(j));
|
|
53
63
|
return h('div', {
|
|
54
64
|
class: wrapClass,
|
|
55
65
|
tabindex: '0',
|
|
56
66
|
role: 'group',
|
|
57
67
|
'aria-label': 'table, scrollable',
|
|
58
68
|
}, h('table', {},
|
|
59
|
-
h('thead', {}, h('tr', {}, ...headers.map((hd, i) => thFor(hd, i)))),
|
|
69
|
+
h('thead', {}, h('tr', {}, ...headers.map((hd, i) => thFor(hd, i, numericCols[i])))),
|
|
60
70
|
h('tbody', {}, ...rows.map((row, i) => h('tr', {
|
|
61
71
|
key: i,
|
|
62
72
|
class: onRowClick ? 'clickable' : '',
|
|
@@ -64,7 +74,7 @@ export function Table({ headers = [], rows = [], onRowClick, emptyText = 'nothin
|
|
|
64
74
|
// Space scrolls by default — preventDefault on Space (and Enter) so
|
|
65
75
|
// keyboard activation matches click without page jump.
|
|
66
76
|
...(onRowClick ? { tabindex: '0', role: 'button', 'aria-label': 'open ' + labelFor(row, i), onkeydown: (e) => { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); onRowClick(i); } } } : {})
|
|
67
|
-
}, ...row.map((c, j) => h('td', { key: j }, c == null ? '' : (typeof c === 'object' ? c : String(c)))))))));
|
|
77
|
+
}, ...row.map((c, j) => h('td', { key: j, class: numericCols[j] ? 'is-num' : null }, c == null ? '' : (typeof c === 'object' ? c : String(c)))))))));
|
|
68
78
|
}
|
|
69
79
|
|
|
70
80
|
// HealthTable — generic health-check table: given an arbitrary
|
|
@@ -69,7 +69,7 @@ export const logs = makePage((ctx) => {
|
|
|
69
69
|
return [
|
|
70
70
|
PageHeader({
|
|
71
71
|
title: 'logs', lede: 'live JSONL log tail — /api/logs/stream',
|
|
72
|
-
right: s.connected ? Chip({ tone: '
|
|
72
|
+
right: s.connected ? Chip({ tone: 'live', children: 'live' }) : Chip({ tone: 'miss', children: 'reconnecting…' }),
|
|
73
73
|
}),
|
|
74
74
|
s.wsError ? refreshError(s.wsError) : null,
|
|
75
75
|
h('div', { class: 'ds-toolbar' },
|
|
@@ -154,8 +154,12 @@ export function Lede({ children }) {
|
|
|
154
154
|
|
|
155
155
|
export function Dot({ tone = 'on' }) {
|
|
156
156
|
const isOn = tone === 'on' || tone === 'live';
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
// 'live' gets its own visual modifier (ds-dot-live, sky hue) layered on
|
|
158
|
+
// top of ds-dot-on so a live-broadcast indicator is never visually
|
|
159
|
+
// identical to a plain "this thing is on" status dot — same split
|
|
160
|
+
// rationale as .chip.tone-live / .ds-badge.tone-live.
|
|
161
|
+
const cls = 'ds-dot ' + (isOn ? 'ds-dot-on' : 'ds-dot-off') + (tone === 'live' ? ' ds-dot-live' : '');
|
|
162
|
+
const statusLabel = tone === 'live' ? 'live status indicator' : (isOn ? 'on status indicator' : 'off status indicator');
|
|
159
163
|
// Drawn as a CSS circle (.ds-dot) — no decorative text glyph.
|
|
160
164
|
return h('span', { class: cls, role: 'img', 'aria-label': statusLabel });
|
|
161
165
|
}
|
|
@@ -348,6 +348,12 @@ table tr.clickable:focus-visible {
|
|
|
348
348
|
outline-offset: -2px;
|
|
349
349
|
}
|
|
350
350
|
table tr.clickable:focus-visible td { background: var(--bg-2); }
|
|
351
|
+
/* Non-clickable (static) rows still get a subtle hover cue — most dashboard
|
|
352
|
+
tables (freddie's sessions-by-platform/model, tool distribution, commands)
|
|
353
|
+
never pass onRowClick, so they had zero row-hover feedback at all. A dim
|
|
354
|
+
hover on ANY row (not just .clickable) reads as "you're scanning this
|
|
355
|
+
row" without implying it's actionable. */
|
|
356
|
+
table tr:not(.clickable):hover td { background: color-mix(in oklab, var(--bg-2) 60%, transparent); }
|
|
351
357
|
|
|
352
358
|
/* Table() striped/compact opt-in density modifiers (webgeist g-table parity).
|
|
353
359
|
Scoped under .ds-table-wrap so they never leak onto an unrelated bare
|
|
@@ -355,8 +361,15 @@ table tr.clickable:focus-visible td { background: var(--bg-2); }
|
|
|
355
361
|
.ds-table-wrap.is-striped table tr:nth-child(even) td { background: var(--bg-2); }
|
|
356
362
|
.ds-table-wrap.is-striped table tr.clickable:nth-child(even):hover td,
|
|
357
363
|
.ds-table-wrap.is-striped table tr.clickable:nth-child(even):focus-visible td { background: var(--bg-3, var(--bg-2)); }
|
|
364
|
+
.ds-table-wrap.is-striped table tr:not(.clickable):nth-child(even):hover td { background: color-mix(in oklab, var(--bg-3, var(--bg-2)) 60%, transparent); }
|
|
358
365
|
.ds-table-wrap.is-compact table th { padding: var(--space-1-5, 5px) var(--space-2, 8px); }
|
|
359
366
|
.ds-table-wrap.is-compact table td { padding: var(--space-1-5, 5px) var(--space-2, 8px); }
|
|
367
|
+
/* Numeric-column right-alignment: Table() (content/table.js) tags a cell
|
|
368
|
+
td.is-num when its raw content is a plain integer/decimal, so digit
|
|
369
|
+
columns (session counts, tool counts, etc.) line up on the ones place
|
|
370
|
+
instead of ragged-left like prose columns. th.is-num mirrors it on the
|
|
371
|
+
matching header so the label sits over its column. */
|
|
372
|
+
table td.is-num, table th.is-num { text-align: right; font-variant-numeric: tabular-nums; }
|
|
360
373
|
|
|
361
374
|
/* ============================================================
|
|
362
375
|
Changelog
|
|
@@ -419,6 +432,9 @@ table tr.clickable:focus-visible td { background: var(--bg-2); }
|
|
|
419
432
|
.ds-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: currentColor; flex-shrink: 0; vertical-align: middle; }
|
|
420
433
|
.ds-dot-on { color: var(--green-2); }
|
|
421
434
|
.ds-dot-off { color: var(--fg-3); }
|
|
435
|
+
/* live is a distinct hue from a plain "on" dot — see .chip.tone-live's
|
|
436
|
+
comment for the success-vs-live rationale. */
|
|
437
|
+
.ds-dot-live { color: var(--sky); }
|
|
422
438
|
.ds-dot-live { color: var(--green-2); }
|
|
423
439
|
.ds-dot-idle { color: var(--fg-3); }
|
|
424
440
|
|
|
@@ -436,6 +452,26 @@ table tr.clickable:focus-visible td { background: var(--bg-2); }
|
|
|
436
452
|
padding: var(--space-3);
|
|
437
453
|
background: var(--bg-2);
|
|
438
454
|
border-radius: var(--r-1);
|
|
455
|
+
/* A thin border + hover elevation gives the tile real edges and a "this is
|
|
456
|
+
a distinct object" affordance instead of a flat color patch that blends
|
|
457
|
+
into the surrounding panel — matches the "no border, no hover state"
|
|
458
|
+
gap named against the stat tiles. */
|
|
459
|
+
border: 1px solid var(--rule);
|
|
460
|
+
position: relative;
|
|
461
|
+
transition: border-color 120ms ease, transform 120ms ease, box-shadow 120ms ease;
|
|
462
|
+
}
|
|
463
|
+
.kpi-card:hover {
|
|
464
|
+
border-color: color-mix(in oklab, var(--accent) 40%, var(--rule));
|
|
465
|
+
transform: translateY(-1px);
|
|
466
|
+
box-shadow: var(--shadow-1, 0 2px 8px rgba(0,0,0,.12));
|
|
467
|
+
}
|
|
468
|
+
.kpi-glyph {
|
|
469
|
+
position: absolute;
|
|
470
|
+
top: var(--space-2);
|
|
471
|
+
right: var(--space-2);
|
|
472
|
+
color: var(--fg-3);
|
|
473
|
+
opacity: 0.6;
|
|
474
|
+
display: inline-flex;
|
|
439
475
|
}
|
|
440
476
|
.kpi-card .num {
|
|
441
477
|
font-family: var(--ff-body); font-weight: 600;
|
|
@@ -96,9 +96,19 @@
|
|
|
96
96
|
workspace rail item-count badge (atoms.js Badge size="sm"). */
|
|
97
97
|
.ds-badge.ds-badge--sm { min-width: var(--icon-xs); height: var(--icon-xs); padding: 0 var(--space-1); font-size: var(--fs-nano); }
|
|
98
98
|
.ds-badge.ds-badge--lg { min-width: 22px; height: 22px; padding: 0 var(--space-2); font-size: var(--fs-tiny); }
|
|
99
|
-
.ds-badge.tone-green, .ds-badge.tone-
|
|
99
|
+
.ds-badge.tone-green, .ds-badge.tone-success {
|
|
100
100
|
background: var(--green-tint); color: var(--green-deep);
|
|
101
101
|
}
|
|
102
|
+
/* tone-live is its own hue (sky), split out of the success/green group:
|
|
103
|
+
"live/broadcasting now" and "operation succeeded" are different facts and
|
|
104
|
+
read identically when both borrow the brand's accent green — a LIVE badge
|
|
105
|
+
next to an "ok" chip must not look like the same state. --sky is already
|
|
106
|
+
used for informational surfaces (ds-alert-info, audio-file icons) and is
|
|
107
|
+
never pinned by the thebird brand's `--accent:var(--green)!important`
|
|
108
|
+
override, so it stays visually distinct under every accent preset. */
|
|
109
|
+
.ds-badge.tone-live {
|
|
110
|
+
background: color-mix(in oklab, var(--sky) 22%, var(--bg)); color: var(--sky);
|
|
111
|
+
}
|
|
102
112
|
.ds-badge.tone-flame, .ds-badge.tone-error {
|
|
103
113
|
background: var(--flame); color: var(--on-color);
|
|
104
114
|
}
|
|
@@ -128,9 +138,36 @@
|
|
|
128
138
|
text-transform: none; letter-spacing: 0; font-weight: 500;
|
|
129
139
|
border-radius: var(--r-0); padding: var(--space-half) var(--space-2);
|
|
130
140
|
}
|
|
131
|
-
.chip.tone-green, .chip.tone-
|
|
141
|
+
.chip.tone-green, .chip.tone-success, .chip.tone-ok {
|
|
132
142
|
background: var(--green-tint); color: var(--green-deep);
|
|
133
143
|
}
|
|
144
|
+
/* tone-live: its own sky hue, split from tone-ok/tone-success — see the
|
|
145
|
+
.ds-badge.tone-live comment above for the rationale. A small pulsing dot
|
|
146
|
+
precedes the label so "live" reads as an active broadcast state, not a
|
|
147
|
+
static status chip. */
|
|
148
|
+
.chip.tone-live {
|
|
149
|
+
background: color-mix(in oklab, var(--sky) 16%, var(--bg));
|
|
150
|
+
color: var(--sky);
|
|
151
|
+
position: relative;
|
|
152
|
+
padding-left: 22px;
|
|
153
|
+
}
|
|
154
|
+
.chip.tone-live::before {
|
|
155
|
+
content: '';
|
|
156
|
+
position: absolute;
|
|
157
|
+
left: 10px; top: 50%;
|
|
158
|
+
width: 6px; height: 6px;
|
|
159
|
+
border-radius: 50%;
|
|
160
|
+
background: var(--sky);
|
|
161
|
+
transform: translateY(-50%);
|
|
162
|
+
animation: chip-live-pulse 1.6s ease-in-out infinite;
|
|
163
|
+
}
|
|
164
|
+
@keyframes chip-live-pulse {
|
|
165
|
+
0%, 100% { opacity: 1; box-shadow: 0 0 0 0 color-mix(in oklab, var(--sky) 50%, transparent); }
|
|
166
|
+
50% { opacity: 0.7; box-shadow: 0 0 0 4px transparent; }
|
|
167
|
+
}
|
|
168
|
+
@media (prefers-reduced-motion: reduce) {
|
|
169
|
+
.chip.tone-live::before { animation: none; }
|
|
170
|
+
}
|
|
134
171
|
.chip.tone-flame, .chip.tone-error, .chip.tone-miss {
|
|
135
172
|
background: var(--flame); color: var(--on-color);
|
|
136
173
|
}
|
|
@@ -12,18 +12,18 @@ export function makeToolsPages(ctx) {
|
|
|
12
12
|
async analytics(h0) {
|
|
13
13
|
const list = await h0.pi.sessions.list();
|
|
14
14
|
const tools = [...h0.pi.tools.values()];
|
|
15
|
-
const byPlatform = list.reduce((a, s) => { const k = s.platform || '
|
|
16
|
-
const byModel = list.reduce((a, s) => { const k = s.model || '
|
|
15
|
+
const byPlatform = list.reduce((a, s) => { const k = s.platform || 'unknown'; a[k] = (a[k] || 0) + 1; return a; }, {});
|
|
16
|
+
const byModel = list.reduce((a, s) => { const k = s.model || 'unknown'; a[k] = (a[k] || 0) + 1; return a; }, {});
|
|
17
17
|
const byToolset = tools.reduce((a, t) => { (a[t.toolset || 'core'] = a[t.toolset || 'core'] || []).push(t.name); return a; }, {});
|
|
18
18
|
return [
|
|
19
19
|
Kpi({ items: [[list.length, 'sessions'], [tools.length, 'tools']] }),
|
|
20
20
|
Panel({ title: 'sessions by platform', children: Object.keys(byPlatform).length === 0
|
|
21
21
|
? EmptyState({ text: 'no data', glyph: Icon('activity') })
|
|
22
|
-
: Table({ headers: ['platform', 'count'], rows: Object.entries(byPlatform).sort((a, b) => b[1] - a[1]) }) }),
|
|
22
|
+
: Table({ headers: ['platform', 'count'], striped: true, rows: Object.entries(byPlatform).sort((a, b) => b[1] - a[1]) }) }),
|
|
23
23
|
Panel({ title: 'sessions by model', children: Object.keys(byModel).length === 0
|
|
24
24
|
? EmptyState({ text: 'no data', glyph: Icon('circle-dot') })
|
|
25
|
-
: Table({ headers: ['model', 'count'], rows: Object.entries(byModel).sort((a, b) => b[1] - a[1]) }) }),
|
|
26
|
-
Panel({ title: 'tool distribution', children: Table({ headers: ['toolset', 'count', 'tools'],
|
|
25
|
+
: Table({ headers: ['model', 'count'], striped: true, rows: Object.entries(byModel).sort((a, b) => b[1] - a[1]) }) }),
|
|
26
|
+
Panel({ title: 'tool distribution', children: Table({ headers: ['toolset', 'count', 'tools'], striped: true,
|
|
27
27
|
rows: Object.entries(byToolset).map(([k, v]) => [k, v.length, v.slice(0, 4).join(', ') + (v.length > 4 ? '…' : '')]) }) }),
|
|
28
28
|
];
|
|
29
29
|
},
|
|
@@ -65,7 +65,7 @@ export function makeToolsPages(ctx) {
|
|
|
65
65
|
}) }),
|
|
66
66
|
Panel({ title: 'scheduled jobs', count: list.length, children: list.length === 0
|
|
67
67
|
? EmptyState({ text: 'no cron jobs — add one above', glyph: Icon('circle') })
|
|
68
|
-
: Table({ headers: ['id', 'cron', 'prompt', 'enabled'],
|
|
68
|
+
: Table({ headers: ['id', 'cron', 'prompt', 'enabled'], striped: true,
|
|
69
69
|
rows: list.map(j => [j.id, j.cron, (j.prompt || '').slice(0, 40), j.enabled ? 'yes' : 'no']) }) }),
|
|
70
70
|
];
|
|
71
71
|
},
|
|
@@ -77,7 +77,7 @@ export function makeToolsPages(ctx) {
|
|
|
77
77
|
list.length === 0 ? EmptyState({ text: 'no skills loaded — add SKILL.md files to ~/.freddie/skills/', glyph: Icon('square') }) : null,
|
|
78
78
|
...Object.entries(byCat).map(([cat, ss]) => Panel({ title: cat, count: ss.length,
|
|
79
79
|
children: ss.length === 0 ? EmptyState({ text: 'none', glyph: Icon('square') })
|
|
80
|
-
: Table({ headers: ['name', 'description'], rows: ss.map(s => [skillLabel(s), (s.description || '').slice(0, 120)]) }) })),
|
|
80
|
+
: Table({ headers: ['name', 'description'], striped: true, rows: ss.map(s => [skillLabel(s), (s.description || '').slice(0, 120)]) }) })),
|
|
81
81
|
].filter(Boolean);
|
|
82
82
|
},
|
|
83
83
|
async config(h0) {
|
|
@@ -97,7 +97,7 @@ export function makeToolsPages(ctx) {
|
|
|
97
97
|
},
|
|
98
98
|
}) }),
|
|
99
99
|
Panel({ title: 'commands', count: commands.length,
|
|
100
|
-
children: Table({ headers: ['name', 'category', 'description'], rows: commands.map(c => [c.name, c.category || '', c.description || '']) }) }),
|
|
100
|
+
children: Table({ headers: ['name', 'category', 'description'], striped: true, rows: commands.map(c => [c.name, c.category || '', c.description || '']) }) }),
|
|
101
101
|
Panel({ title: 'active config', children: pre(cfg) }),
|
|
102
102
|
];
|
|
103
103
|
},
|
|
@@ -57,9 +57,9 @@
|
|
|
57
57
|
--------------------------------------------------------------------- */
|
|
58
58
|
.fd-chat-config { display: flex; flex-direction: column; gap: var(--space-2, 8px); padding-bottom: var(--space-3, 12px); border-bottom: 1px solid color-mix(in oklab, var(--fg) 10%, transparent); margin-bottom: var(--space-3, 12px); }
|
|
59
59
|
.fd-chat-config .fd-chat-field { display: flex; flex-direction: column; gap: var(--space-1, 4px); min-width: 120px; }
|
|
60
|
-
.fd-chat-config .fd-chat-field > label { font-family: var(--os-mono, monospace); font-size:
|
|
60
|
+
.fd-chat-config .fd-chat-field > label { font-family: var(--os-mono, monospace); font-size: var(--fs-nano, 11px); opacity: 0.6; letter-spacing: 0.05em; text-transform: uppercase; }
|
|
61
61
|
.fd-chat-config .fd-chat-field > input,
|
|
62
|
-
.fd-chat-config .fd-chat-field > select { width: 100%; box-sizing: border-box; padding: var(--space-1-75) var(--space-2); background: var(--panel-1, transparent); color: var(--fg, inherit); border: 1px solid color-mix(in oklab, var(--fg) 14%, transparent); border-radius: var(--r-0); font: inherit; font-size: 12px; }
|
|
62
|
+
.fd-chat-config .fd-chat-field > select { width: 100%; box-sizing: border-box; padding: var(--space-1-75) var(--space-2); background: var(--panel-1, transparent); color: var(--fg, inherit); border: 1px solid color-mix(in oklab, var(--fg) 14%, transparent); border-radius: var(--r-0); font: inherit; font-size: var(--fs-tiny, 12px); }
|
|
63
63
|
.fd-chat-config .fd-chat-field > input:focus-visible,
|
|
64
64
|
.fd-chat-config .fd-chat-field > select:focus-visible { outline: 2px solid var(--os-accent, #247420); outline-offset: 0; border-color: color-mix(in oklab, var(--os-accent, #247420) 60%, transparent); }
|
|
65
65
|
.fd-chat-config .fd-chat-row { display: flex; gap: var(--space-2, 8px); flex-wrap: wrap; }
|
|
@@ -68,5 +68,5 @@
|
|
|
68
68
|
.app-fd ds-chat.fd-dashboard-chat { flex: 1 1 auto; min-height: 280px; display: flex; flex-direction: column; overflow: hidden; }
|
|
69
69
|
|
|
70
70
|
.fd-chat-actions { display: inline-flex; gap: var(--space-1, 4px); align-items: center; }
|
|
71
|
-
.fd-chat-actions .btn-secondary { background: transparent; color: var(--danger, #c0392b); border: 1px solid color-mix(in oklab, var(--danger, #c0392b) 40%, transparent); cursor: pointer; padding: var(--space-hair) var(--space-2); border-radius: var(--r-0); font: inherit; font-size: 12px; }
|
|
71
|
+
.fd-chat-actions .btn-secondary { background: transparent; color: var(--danger, #c0392b); border: 1px solid color-mix(in oklab, var(--danger, #c0392b) 40%, transparent); cursor: pointer; padding: var(--space-hair) var(--space-2); border-radius: var(--r-0); font: inherit; font-size: var(--fs-tiny, 12px); }
|
|
72
72
|
.fd-chat-actions .btn-secondary:hover { background: color-mix(in oklab, var(--danger, #c0392b) 10%, transparent); }
|
|
@@ -64,7 +64,7 @@ export function createFreddieDashboard({ instance, bootHost, osSurfaces, loading
|
|
|
64
64
|
const route = allRoutes.find(r => r.path === state.active) || ROUTES[1];
|
|
65
65
|
return AppShell({
|
|
66
66
|
topbar: Topbar({ brand: 'assistant', leaf: 'dashboard', items: [], active: '' }),
|
|
67
|
-
crumb: Crumb({ trail: ['assistant', instance.id], leaf: route.path, right: state.error ? Chip({ tone: 'miss', children: 'error' }) : Chip({ tone: '
|
|
67
|
+
crumb: Crumb({ trail: ['assistant', instance.id], leaf: route.path, right: state.error ? Chip({ tone: 'miss', children: 'error' }) : Chip({ tone: 'live', children: 'live' }) }),
|
|
68
68
|
side: buildSide(),
|
|
69
69
|
main: state.body || EmptyState({ text: loadingText || 'loading…', glyph: Icon('circle') }),
|
|
70
70
|
status: Status({ left: ['ds-247420 · webjsx · ' + allRoutes.length + ' routes', 'instance=' + instance.id], right: [state.ts] }),
|
package/src/kits/os/theme.css
CHANGED
|
@@ -99,6 +99,23 @@ html, body {
|
|
|
99
99
|
text-rendering: optimizeLegibility;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/* ---- Desktop wallpaper ----
|
|
103
|
+
* A flat --os-bg-0 fill behind the window layer reads as an empty/unfinished
|
|
104
|
+
* void once windows are moved aside. A very subtle two-stop radial wash
|
|
105
|
+
* (barely above the base tone, no imagery/texture asset to fetch or theme)
|
|
106
|
+
* gives the desktop depth without competing with window chrome — scoped to
|
|
107
|
+
* .ds-247420 .wm-root specifically (the actual desktop viewport BEHIND
|
|
108
|
+
* windows), not the page background used by bare/non-OS surfaces. */
|
|
109
|
+
.ds-247420 .wm-root {
|
|
110
|
+
background:
|
|
111
|
+
radial-gradient(ellipse 120% 80% at 20% -10%, color-mix(in oklab, var(--os-accent) 6%, transparent), transparent 60%),
|
|
112
|
+
radial-gradient(ellipse 100% 70% at 100% 100%, color-mix(in oklab, var(--os-accent) 4%, transparent), transparent 55%),
|
|
113
|
+
var(--os-bg-0);
|
|
114
|
+
}
|
|
115
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
116
|
+
.ds-247420 .wm-root { background: var(--os-bg-0); }
|
|
117
|
+
}
|
|
118
|
+
|
|
102
119
|
/* ---- Global focus-visible ring ----
|
|
103
120
|
Baseline accent-colored outline for every keyboard-focusable element
|
|
104
121
|
(inputs/textareas/buttons/links/contenteditable, including the terminal's
|
|
@@ -192,6 +209,13 @@ html, body {
|
|
|
192
209
|
.os-btn:focus-visible { background: var(--panel-hover, var(--os-bg-2)); }
|
|
193
210
|
.os-btn .ic { color: var(--os-accent); display: inline-flex; width: 16px; height: 16px; }
|
|
194
211
|
.os-btn .ic svg { width: 16px; height: 16px; display: block; fill: none; stroke: currentColor; }
|
|
212
|
+
/* Filled/duotone active-state icon treatment: an active nav icon otherwise
|
|
213
|
+
* looks visually IDENTICAL to its inactive sibling (only the container
|
|
214
|
+
* background changes) — a soft `fill:currentColor` wash behind the same
|
|
215
|
+
* stroke-only glyph gives active items real duotone weight, matching the
|
|
216
|
+
* "filled/duotone treatment for active items" ask. fill-opacity keeps the
|
|
217
|
+
* glyph legible over the wash rather than solid-filling the whole path. */
|
|
218
|
+
.os-btn.active .ic svg, .os-btn[aria-pressed="true"] .ic svg { fill: currentColor; fill-opacity: 0.18; }
|
|
195
219
|
|
|
196
220
|
.os-menu {
|
|
197
221
|
position: absolute;
|
|
@@ -451,6 +475,7 @@ html, body {
|
|
|
451
475
|
.os-rail-btn:active { background: var(--panel-select, var(--os-accent-soft)); }
|
|
452
476
|
.os-rail-btn .ic { color: var(--os-accent); display: inline-flex; }
|
|
453
477
|
.os-rail-btn .ic svg { width: 22px; height: 22px; fill: none; stroke: currentColor; }
|
|
478
|
+
.os-rail-btn:active .ic svg { fill: currentColor; fill-opacity: 0.18; }
|
|
454
479
|
|
|
455
480
|
.os-drawer {
|
|
456
481
|
position: fixed;
|
|
@@ -518,6 +543,7 @@ html, body {
|
|
|
518
543
|
.os-drawer-tile:active, .os-drawer-tile.active { background: var(--panel-select, var(--os-accent-soft)); }
|
|
519
544
|
.os-drawer-tile .ic { color: var(--os-accent); display: inline-flex; }
|
|
520
545
|
.os-drawer-tile .ic svg { width: 32px; height: 32px; fill: none; stroke: currentColor; }
|
|
546
|
+
.os-drawer-tile:active .ic svg, .os-drawer-tile.active .ic svg { fill: currentColor; fill-opacity: 0.18; }
|
|
521
547
|
.os-drawer-tile .lbl { color: var(--os-fg); font-weight: 600; }
|
|
522
548
|
|
|
523
549
|
.os-spacer { flex: 1 1 auto; }
|