anentrypoint-design 0.0.357 → 0.0.359
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/dist/247420.css +122 -0
- package/dist/247420.js +28 -26
- package/package.json +4 -1
- package/src/components/data-density.js +14 -0
- package/src/components/freddie.js +83 -1
- package/src/components/git-status.js +153 -0
- package/src/components/worktree-switcher.js +57 -0
- package/src/components.js +5 -0
- package/src/css/app-shell/data-density.css +10 -0
- package/src/css/app-shell/git-status.css +112 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anentrypoint-design",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.359",
|
|
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",
|
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
"./components/shell.js": "./src/components/shell.js",
|
|
24
24
|
"./components/files.js": "./src/components/files.js",
|
|
25
25
|
"./components/files-modals.js": "./src/components/files-modals.js",
|
|
26
|
+
"./components/overlay-primitives.js": "./src/components/overlay-primitives.js",
|
|
27
|
+
"./components/git-status.js": "./src/components/git-status.js",
|
|
28
|
+
"./components/worktree-switcher.js": "./src/components/worktree-switcher.js",
|
|
26
29
|
"./web-components/ds-chat.js": "./src/web-components/ds-chat.js",
|
|
27
30
|
"./web-components/freddie-chat.js": "./src/web-components/freddie-chat.js",
|
|
28
31
|
"./lint": {
|
|
@@ -70,6 +70,20 @@ export function BarRow({ label, value, pct = 0, tone } = {}) {
|
|
|
70
70
|
h('span', { class: 'ds-bar-row-value', 'aria-hidden': 'true' }, value));
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// RateCell — a tone-colored numeric cell for dense admin/observability tables
|
|
75
|
+
// (percentile latency columns, success-rate columns). Ported from docstudio's
|
|
76
|
+
// admin-observability-views.js endpointsView() success-rate coloring, which
|
|
77
|
+
// had no kit equivalent: a plain Table cell has no notion of a value implying
|
|
78
|
+
// good/warn/bad. Host computes the tone (this component has no opinion on
|
|
79
|
+
// thresholds, matching Table's onSort host-owns-logic convention) and passes
|
|
80
|
+
// it plus the display text; renders inline so it drops into any Table row.
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
export function RateCell({ value, tone = 'neutral' } = {}) {
|
|
83
|
+
const cls = 'ds-rate-cell ds-rate-cell-' + tone;
|
|
84
|
+
return h('span', { class: cls }, value == null ? '–' : String(value));
|
|
85
|
+
}
|
|
86
|
+
|
|
73
87
|
// ---------------------------------------------------------------------------
|
|
74
88
|
// StatTile / StatsGrid — compact KPI tiles, denser than the existing .kpi.
|
|
75
89
|
// cls on StatTile selects an accent variant: '' | 'rate-big' | 'err-rate'.
|
|
@@ -15,6 +15,8 @@ import { queueMessage, watchReconnect, isOnline } from '../idb-outbox.js';
|
|
|
15
15
|
import { ChatMessage, ChatComposer } from './chat.js';
|
|
16
16
|
import { fmtTime, fmtAgo } from './sessions.js';
|
|
17
17
|
import { createVirtualizer, measureRef } from '../virtual-scroll.js';
|
|
18
|
+
import { GitStatusPanel, GitDiffView } from './git-status.js';
|
|
19
|
+
import { WorktreeSwitcher } from './worktree-switcher.js';
|
|
18
20
|
|
|
19
21
|
const h = webjsx.createElement;
|
|
20
22
|
|
|
@@ -868,12 +870,92 @@ export const debug = makePage((ctx) => {
|
|
|
868
870
|
};
|
|
869
871
|
});
|
|
870
872
|
|
|
873
|
+
// ---- git ---------------------------------------------------------------
|
|
874
|
+
|
|
875
|
+
export const git = makePage((ctx) => {
|
|
876
|
+
Object.assign(ctx.state, { cwd: null, status: null, log: null, worktrees: null, diff: null, activeFile: null, diffLoading: false, note: null });
|
|
877
|
+
async function load() {
|
|
878
|
+
try {
|
|
879
|
+
const proj = await api('/api/projects').catch(() => null);
|
|
880
|
+
const active = proj && proj.active;
|
|
881
|
+
const cwd = (active && typeof active === 'object' ? active.path : null) || ctx.state.cwd || '';
|
|
882
|
+
const qs = '?cwd=' + encodeURIComponent(cwd);
|
|
883
|
+
const [status, log, worktrees] = await Promise.all([
|
|
884
|
+
api('/api/git/status' + qs).catch((e) => ({ _err: e })),
|
|
885
|
+
api('/api/git/log' + qs + '&limit=20').catch((e) => ({ _err: e })),
|
|
886
|
+
api('/api/worktree' + qs).catch((e) => ({ _err: e })),
|
|
887
|
+
]);
|
|
888
|
+
ctx.set({ loading: false, cwd, status, log, worktrees, error: null });
|
|
889
|
+
} catch (e) { ctx.set({ loading: false, error: e }); }
|
|
890
|
+
}
|
|
891
|
+
async function openDiff(file) {
|
|
892
|
+
ctx.set({ activeFile: file.path, diffLoading: true, diff: null });
|
|
893
|
+
try {
|
|
894
|
+
const qs = '?cwd=' + encodeURIComponent(ctx.state.cwd || '') + '&file=' + encodeURIComponent(file.path);
|
|
895
|
+
const res = await api('/api/git/diff' + qs);
|
|
896
|
+
ctx.set({ diff: res, diffLoading: false });
|
|
897
|
+
} catch (e) { ctx.set({ note: { kind: 'error', msg: String(e.message || e) }, diffLoading: false }); }
|
|
898
|
+
}
|
|
899
|
+
async function createWorktree() {
|
|
900
|
+
const path = (ctx.state.newWtPath || '').trim();
|
|
901
|
+
const branch = (ctx.state.newWtBranch || '').trim();
|
|
902
|
+
if (!path) { ctx.set({ note: { kind: 'warn', msg: 'path required' } }); return; }
|
|
903
|
+
ctx.set({ busy: true, note: null });
|
|
904
|
+
try {
|
|
905
|
+
await api('/api/worktree', { method: 'POST', body: { cwd: ctx.state.cwd || '', path, branch: branch || undefined } });
|
|
906
|
+
ctx.state.newWtPath = ''; ctx.state.newWtBranch = '';
|
|
907
|
+
await load();
|
|
908
|
+
} catch (e) { ctx.set({ note: { kind: 'error', msg: String(e.message || e) } }); }
|
|
909
|
+
ctx.set({ busy: false });
|
|
910
|
+
}
|
|
911
|
+
load();
|
|
912
|
+
return () => {
|
|
913
|
+
const s = ctx.state;
|
|
914
|
+
if (s.loading) return loadingState('loading git status…');
|
|
915
|
+
if (s.error && !s.status) return errorState(s.error, load);
|
|
916
|
+
const statusFailed = s.status && s.status._err;
|
|
917
|
+
const logFailed = s.log && s.log._err;
|
|
918
|
+
const wtFailed = s.worktrees && s.worktrees._err;
|
|
919
|
+
const files = statusFailed ? [] : (s.status && s.status.files) || [];
|
|
920
|
+
const commits = logFailed ? [] : (s.log && s.log.commits) || s.log || [];
|
|
921
|
+
const worktrees = wtFailed ? [] : (s.worktrees && s.worktrees.worktrees) || s.worktrees || [];
|
|
922
|
+
const current = Array.isArray(worktrees) ? (worktrees.find(w => w.path === s.cwd) || {}).path : undefined;
|
|
923
|
+
return [
|
|
924
|
+
PageHeader({ eyebrow: 'freddie', title: 'git', lede: s.cwd || 'active project' }),
|
|
925
|
+
noteAlert(s.note),
|
|
926
|
+
statusFailed ? refreshError(statusFailed) : null,
|
|
927
|
+
section('worktrees',
|
|
928
|
+
WorktreeSwitcher({
|
|
929
|
+
worktrees: Array.isArray(worktrees) ? worktrees : [],
|
|
930
|
+
current,
|
|
931
|
+
onSwitch: () => {},
|
|
932
|
+
onCreate: () => ctx.set({ showWtForm: !s.showWtForm }),
|
|
933
|
+
}),
|
|
934
|
+
s.showWtForm ? h('div', { class: 'fd-row-actions' },
|
|
935
|
+
TextField({ label: 'path', value: s.newWtPath, onInput: (v) => { s.newWtPath = v; }, placeholder: '/path/to/worktree' }),
|
|
936
|
+
TextField({ label: 'branch (optional)', value: s.newWtBranch, onInput: (v) => { s.newWtBranch = v; }, placeholder: 'feature/x' }),
|
|
937
|
+
Btn({ variant: 'primary', disabled: s.busy, children: s.busy ? 'working…' : 'create', onClick: createWorktree })) : null),
|
|
938
|
+
section('changed files',
|
|
939
|
+
statusFailed ? errorState(statusFailed) : GitStatusPanel({ files, onFileClick: openDiff, active: s.activeFile })),
|
|
940
|
+
section('diff' + (s.activeFile ? ' · ' + s.activeFile : ''),
|
|
941
|
+
s.diffLoading ? loadingState('loading diff…')
|
|
942
|
+
: s.diff ? GitDiffView({ diff: s.diff.diff || s.diff, filename: s.activeFile })
|
|
943
|
+
: emptyState('select a file to view its diff')),
|
|
944
|
+
section('log',
|
|
945
|
+
logFailed ? errorState(logFailed)
|
|
946
|
+
: commits.length
|
|
947
|
+
? Table({ headers: ['sha', 'message', 'author', 'date'], rows: commits.slice(0, 20).map(c => [String(c.sha || c.hash || '').slice(0, 8), truncSpan(c.message || c.subject, TRUNC_SUB), c.author || '', c.date ? fmtAgo(c.date) : ''])})
|
|
948
|
+
: emptyState('no commits')),
|
|
949
|
+
].filter(Boolean);
|
|
950
|
+
};
|
|
951
|
+
});
|
|
952
|
+
|
|
871
953
|
// ---- registry --------------------------------------------------------------
|
|
872
954
|
|
|
873
955
|
export const FREDDIE_PAGES = {
|
|
874
956
|
home, chat, voice, sessions, projects, agents, analytics,
|
|
875
957
|
models, cron, skills, config, env, tools, batch, gateway, chains,
|
|
876
|
-
machines, health, debug, logs,
|
|
958
|
+
machines, health, debug, logs, git,
|
|
877
959
|
};
|
|
878
960
|
|
|
879
961
|
export { skillLabel, getRecentPaths, saveRecentPath, renderChatMessages };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// Git status + diff primitives — changed-file list and unified-diff renderer.
|
|
2
|
+
// Ported from pi-web's BranchNavigator concept into this design system's
|
|
3
|
+
// pure-webjsx idiom (see files.js for the sibling file-list pattern this
|
|
4
|
+
// mirrors: FileIcon-style type glyph, ds-file-row-style clickable row).
|
|
5
|
+
|
|
6
|
+
import * as webjsx from '../../vendor/webjsx/index.js';
|
|
7
|
+
import { Icon } from './shell.js';
|
|
8
|
+
import { fileGlyph } from './files.js';
|
|
9
|
+
import { highlightAllUnder } from '../highlight.js';
|
|
10
|
+
const h = webjsx.createElement;
|
|
11
|
+
|
|
12
|
+
// git status letter -> { label, tone, icon } used for both the row's status
|
|
13
|
+
// chip and its left-edge tone. Mirrors the porcelain status codes pi-web's
|
|
14
|
+
// git API returns (A/M/D/R/? ...), collapsed to the ones a status list needs.
|
|
15
|
+
const STATUS_META = {
|
|
16
|
+
A: { label: 'added', tone: 'add', glyph: 'A' },
|
|
17
|
+
M: { label: 'modified', tone: 'modify', glyph: 'M' },
|
|
18
|
+
D: { label: 'deleted', tone: 'delete', glyph: 'D' },
|
|
19
|
+
R: { label: 'renamed', tone: 'modify', glyph: 'R' },
|
|
20
|
+
C: { label: 'copied', tone: 'modify', glyph: 'C' },
|
|
21
|
+
U: { label: 'conflicted', tone: 'delete', glyph: 'U' },
|
|
22
|
+
'?': { label: 'untracked', tone: 'add', glyph: '?' },
|
|
23
|
+
'!': { label: 'ignored', tone: 'neutral', glyph: '!' },
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function statusMeta(status) {
|
|
27
|
+
return STATUS_META[status] || { label: status || 'changed', tone: 'neutral', glyph: (status || '?').slice(0, 1) };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Guess a file "type" (for FileIcon-style glyph reuse) from its extension —
|
|
31
|
+
// git-status entries are paths, not the richer {type} shape files.js rows get
|
|
32
|
+
// from a directory listing.
|
|
33
|
+
const EXT_TYPE = {
|
|
34
|
+
js: 'code', mjs: 'code', cjs: 'code', ts: 'code', tsx: 'code', jsx: 'code',
|
|
35
|
+
py: 'code', rs: 'code', go: 'code', java: 'code', c: 'code', cpp: 'code', h: 'code',
|
|
36
|
+
css: 'code', scss: 'code', html: 'code', json: 'code', yml: 'code', yaml: 'code', toml: 'code', sh: 'code',
|
|
37
|
+
md: 'document', mdx: 'document', txt: 'text',
|
|
38
|
+
png: 'image', jpg: 'image', jpeg: 'image', gif: 'image', svg: 'image', webp: 'image',
|
|
39
|
+
mp4: 'video', mov: 'video', webm: 'video',
|
|
40
|
+
mp3: 'audio', wav: 'audio',
|
|
41
|
+
zip: 'archive', tar: 'archive', gz: 'archive',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function fileTypeFromPath(pathname = '') {
|
|
45
|
+
const base = pathname.split('/').pop() || pathname;
|
|
46
|
+
const dot = base.lastIndexOf('.');
|
|
47
|
+
if (dot <= 0) return 'other';
|
|
48
|
+
return EXT_TYPE[base.slice(dot + 1).toLowerCase()] || 'other';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// GitStatusPanel — a list of changed files with add/modify/delete indicators.
|
|
52
|
+
// files: [{ path, status, staged?, insertions?, deletions? }]
|
|
53
|
+
// onFileClick(file) opens the diff for a row.
|
|
54
|
+
export function GitStatusPanel({ files = [], onFileClick, emptyText = 'no changes', active } = {}) {
|
|
55
|
+
if (!files.length) {
|
|
56
|
+
return h('div', { class: 'ds-git-empty', role: 'status' },
|
|
57
|
+
h('span', { class: 'ds-git-empty-glyph', 'aria-hidden': 'true' }, Icon('circle-dot', { size: 22 })),
|
|
58
|
+
h('span', {}, emptyText));
|
|
59
|
+
}
|
|
60
|
+
return h('div', { class: 'ds-git-status-grid', role: 'group', 'aria-label': 'changed files' },
|
|
61
|
+
...files.map((f, i) => GitStatusRow({ key: f.path || i, file: f, onClick: onFileClick, active: active === f.path })));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function GitStatusRow({ key, file, onClick, active } = {}) {
|
|
65
|
+
const meta = statusMeta(file.status);
|
|
66
|
+
const type = fileTypeFromPath(file.path);
|
|
67
|
+
const stats = [
|
|
68
|
+
file.insertions != null ? h('span', { class: 'ds-git-stat ds-git-stat-add' }, '+' + file.insertions) : null,
|
|
69
|
+
file.deletions != null ? h('span', { class: 'ds-git-stat ds-git-stat-del' }, '-' + file.deletions) : null,
|
|
70
|
+
].filter(Boolean);
|
|
71
|
+
return h('button', {
|
|
72
|
+
key, type: 'button',
|
|
73
|
+
class: 'ds-git-row' + (active ? ' active' : ''),
|
|
74
|
+
onclick: onClick ? () => onClick(file) : null,
|
|
75
|
+
disabled: onClick ? null : true,
|
|
76
|
+
'aria-label': meta.label + ': ' + file.path,
|
|
77
|
+
'aria-pressed': active ? 'true' : 'false',
|
|
78
|
+
},
|
|
79
|
+
h('span', { class: 'ds-git-status-chip tone-' + meta.tone, 'aria-hidden': 'true' }, meta.glyph),
|
|
80
|
+
h('span', { class: 'ds-git-row-icon', 'aria-hidden': 'true' }, Icon(fileGlyph(type), { size: 15 })),
|
|
81
|
+
h('span', { class: 'ds-git-row-path' }, file.path),
|
|
82
|
+
file.staged ? h('span', { class: 'ds-git-row-tag' }, 'staged') : null,
|
|
83
|
+
stats.length ? h('span', { class: 'ds-git-row-stats' }, ...stats) : null,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Parse a unified diff into hunks of typed lines so we can color +/- context
|
|
88
|
+
// independently of the raw text (avoids relying on Prism's diff grammar for
|
|
89
|
+
// the leading marker column, which we style ourselves).
|
|
90
|
+
function parseUnifiedDiff(diff = '') {
|
|
91
|
+
const lines = diff.split('\n');
|
|
92
|
+
const hunks = [];
|
|
93
|
+
let current = null;
|
|
94
|
+
for (const line of lines) {
|
|
95
|
+
if (line.startsWith('@@')) {
|
|
96
|
+
current = { header: line, lines: [] };
|
|
97
|
+
hunks.push(current);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (line.startsWith('diff --git') || line.startsWith('index ') ||
|
|
101
|
+
line.startsWith('--- ') || line.startsWith('+++ ')) {
|
|
102
|
+
continue; // file-header noise; the host already knows the filename
|
|
103
|
+
}
|
|
104
|
+
if (!current) continue;
|
|
105
|
+
let kind = 'context';
|
|
106
|
+
if (line.startsWith('+')) kind = 'add';
|
|
107
|
+
else if (line.startsWith('-')) kind = 'del';
|
|
108
|
+
current.lines.push({ kind, text: line });
|
|
109
|
+
}
|
|
110
|
+
return hunks;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const EXT_LANG = {
|
|
114
|
+
js: 'javascript', mjs: 'javascript', cjs: 'javascript', jsx: 'jsx',
|
|
115
|
+
ts: 'typescript', tsx: 'tsx', py: 'python', rs: 'rust', go: 'go',
|
|
116
|
+
css: 'css', scss: 'css', html: 'markup', md: 'markdown', json: 'json',
|
|
117
|
+
yml: 'yaml', yaml: 'yaml', sh: 'bash', sql: 'sql', toml: 'toml',
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
function langFromFilename(filename = '') {
|
|
121
|
+
const base = filename.split('/').pop() || filename;
|
|
122
|
+
const dot = base.lastIndexOf('.');
|
|
123
|
+
if (dot <= 0) return null;
|
|
124
|
+
return EXT_LANG[base.slice(dot + 1).toLowerCase()] || null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// GitDiffView — unified-diff renderer with +/- line coloring. Uses
|
|
128
|
+
// highlight.js's Prism loader for language detection by file extension; the
|
|
129
|
+
// +/- marker column is drawn by CSS (tone classes), Prism only tokenizes the
|
|
130
|
+
// code content inside each line.
|
|
131
|
+
export function GitDiffView({ diff = '', filename } = {}) {
|
|
132
|
+
const hunks = parseUnifiedDiff(diff);
|
|
133
|
+
const lang = langFromFilename(filename);
|
|
134
|
+
const highlightRef = (el) => {
|
|
135
|
+
if (!el) return;
|
|
136
|
+
try { highlightAllUnder(el); } catch { /* progressive enhancement only */ }
|
|
137
|
+
};
|
|
138
|
+
if (!hunks.length) {
|
|
139
|
+
return h('div', { class: 'ds-git-diff-empty', role: 'status' }, 'no diff to show');
|
|
140
|
+
}
|
|
141
|
+
return h('div', { class: 'ds-git-diff', ref: highlightRef },
|
|
142
|
+
filename ? h('div', { class: 'ds-git-diff-head' }, h('span', { class: 'name' }, filename)) : null,
|
|
143
|
+
...hunks.map((hunk, hi) => h('div', { key: 'hunk' + hi, class: 'ds-git-hunk' },
|
|
144
|
+
h('div', { class: 'ds-git-hunk-header' }, hunk.header),
|
|
145
|
+
h('pre', { class: 'ds-git-hunk-body' + (lang ? ' lang-' + lang : '') },
|
|
146
|
+
...hunk.lines.map((l, li) => h('code', {
|
|
147
|
+
key: li,
|
|
148
|
+
class: 'ds-git-line ds-git-line-' + l.kind + (lang ? ' language-' + lang : ''),
|
|
149
|
+
}, l.text + '\n'))
|
|
150
|
+
)
|
|
151
|
+
))
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// WorktreeSwitcher — a dropdown for listing/switching git worktrees + branches,
|
|
2
|
+
// with a "new worktree" action. Built on overlay-primitives.js's Dropdown
|
|
3
|
+
// (itself the Popover/useFloating primitives + roving-focus menu keyboard
|
|
4
|
+
// handling already in this kit) rather than re-deriving popover positioning,
|
|
5
|
+
// outside-click, Escape, and focus-trap logic from scratch.
|
|
6
|
+
|
|
7
|
+
import * as webjsx from '../../vendor/webjsx/index.js';
|
|
8
|
+
import { Icon } from './shell.js';
|
|
9
|
+
import { Dropdown } from './overlay-primitives.js';
|
|
10
|
+
const h = webjsx.createElement;
|
|
11
|
+
|
|
12
|
+
const NEW_WORKTREE_ID = '__ds_new_worktree__';
|
|
13
|
+
|
|
14
|
+
// WorktreeSwitcher({ worktrees, current, onSwitch, onCreate })
|
|
15
|
+
// worktrees: [{ path, branch, current? }]
|
|
16
|
+
// current: path of the active worktree (falls back to a worktree's own `current` flag)
|
|
17
|
+
// onSwitch(worktree) — fired when an existing worktree is picked
|
|
18
|
+
// onCreate() — fired from the trailing "new worktree" row; the host owns the create flow (prompt/modal)
|
|
19
|
+
export function WorktreeSwitcher({ worktrees = [], current, onSwitch, onCreate, ariaLabel = 'switch worktree' } = {}) {
|
|
20
|
+
const isCurrent = (wt) => wt.current || (current != null && wt.path === current);
|
|
21
|
+
const activeWt = worktrees.find(isCurrent) || worktrees[0];
|
|
22
|
+
const byId = new Map(worktrees.map((wt, i) => [wt.path || String(i), wt]));
|
|
23
|
+
|
|
24
|
+
const items = [
|
|
25
|
+
...worktrees.map((wt, i) => ({
|
|
26
|
+
id: wt.path || String(i),
|
|
27
|
+
label: h('span', { class: 'ds-wts-item-body' },
|
|
28
|
+
h('span', { class: 'ds-wts-item-check', 'aria-hidden': 'true' },
|
|
29
|
+
isCurrent(wt) ? Icon('check', { size: 14 }) : null),
|
|
30
|
+
h('span', { class: 'ds-wts-item-text' },
|
|
31
|
+
h('span', { class: 'ds-wts-item-branch' }, wt.branch || '(detached HEAD)'),
|
|
32
|
+
h('span', { class: 'ds-wts-item-path' }, wt.path)
|
|
33
|
+
),
|
|
34
|
+
),
|
|
35
|
+
disabled: isCurrent(wt),
|
|
36
|
+
})),
|
|
37
|
+
worktrees.length && onCreate ? { separator: true } : null,
|
|
38
|
+
onCreate ? {
|
|
39
|
+
id: NEW_WORKTREE_ID,
|
|
40
|
+
glyph: '+',
|
|
41
|
+
label: 'new worktree',
|
|
42
|
+
} : null,
|
|
43
|
+
].filter(Boolean);
|
|
44
|
+
|
|
45
|
+
const onSelect = (id) => {
|
|
46
|
+
if (id === NEW_WORKTREE_ID) { onCreate && onCreate(); return; }
|
|
47
|
+
const wt = byId.get(id);
|
|
48
|
+
if (wt && onSwitch && !isCurrent(wt)) onSwitch(wt);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const trigger = h('button', { type: 'button', class: 'ds-wts-trigger' },
|
|
52
|
+
h('span', { class: 'ds-wts-trigger-branch' }, (activeWt && activeWt.branch) || 'select worktree'),
|
|
53
|
+
h('span', { class: 'ds-wts-trigger-caret', 'aria-hidden': 'true' }, Icon('chevron-down', { size: 13 }))
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
return h('span', { class: 'ds-wts' }, Dropdown({ trigger, items, onSelect, ariaLabel }));
|
|
57
|
+
}
|
package/src/components.js
CHANGED
|
@@ -35,6 +35,11 @@ export { ContextPane } from './components/context-pane.js';
|
|
|
35
35
|
|
|
36
36
|
export { SpreadsheetPreview } from './components/spreadsheet-preview.js';
|
|
37
37
|
|
|
38
|
+
export {
|
|
39
|
+
DEFAULT_PHASES, PhaseWalk, TreeNode, BarRow, RateCell,
|
|
40
|
+
StatTile, StatsGrid, SubGrid, SessionRow, DevRow, LiveLogEntry, LiveLog
|
|
41
|
+
} from './components/data-density.js';
|
|
42
|
+
|
|
38
43
|
export {
|
|
39
44
|
fileGlyph, fmtFileSize,
|
|
40
45
|
FileIcon, FileRow, FileGrid, FileSkeleton, sortFiles, FileToolbar, RootsPicker,
|
|
@@ -77,6 +77,16 @@
|
|
|
77
77
|
.ds-stat-val.err-rate { font-size: clamp(24px, 7cqi, 32px); color: var(--warn); }
|
|
78
78
|
.ds-stat-lbl { font-size: var(--fs-micro); color: var(--fg-3); margin-top: 2px; }
|
|
79
79
|
|
|
80
|
+
/* RateCell — tone-colored numeric table cell (success-rate / percentile-latency
|
|
81
|
+
columns), ported from docstudio's endpointsView() success-rate coloring.
|
|
82
|
+
Neutral at rest; color carries meaning only, matching Table's sortable-header
|
|
83
|
+
restraint pattern. */
|
|
84
|
+
.ds-rate-cell { font-variant-numeric: tabular-nums; font-weight: 600; }
|
|
85
|
+
.ds-rate-cell-neutral { color: var(--fg-2); font-weight: 400; }
|
|
86
|
+
.ds-rate-cell-good { color: var(--success); }
|
|
87
|
+
.ds-rate-cell-warn { color: var(--warn); }
|
|
88
|
+
.ds-rate-cell-bad { color: var(--danger); }
|
|
89
|
+
|
|
80
90
|
/* Inline row — flex row with centered items and a small gap, for demo/kit
|
|
81
91
|
markup that needs to lay siblings out horizontally without an inline style. */
|
|
82
92
|
.ds-inline-row { display: flex; align-items: center; gap: var(--space-2, 8px); }
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
Git status panel + diff view — changed-file list, unified diff.
|
|
3
|
+
============================================================ */
|
|
4
|
+
|
|
5
|
+
.ds-git-status-grid {
|
|
6
|
+
display: flex; flex-direction: column; gap: 2px;
|
|
7
|
+
}
|
|
8
|
+
.ds-git-row {
|
|
9
|
+
display: flex; align-items: center; gap: 10px;
|
|
10
|
+
width: 100%; padding: 7px 12px;
|
|
11
|
+
background: none; border: var(--bw-hair) solid transparent; border-radius: var(--r-2);
|
|
12
|
+
color: var(--fg); font: inherit; text-align: left; cursor: pointer;
|
|
13
|
+
transition: background var(--dur-snap) var(--ease), border-color var(--dur-snap) var(--ease);
|
|
14
|
+
}
|
|
15
|
+
.ds-git-row:hover { background: var(--bg-2); border-color: var(--rule); }
|
|
16
|
+
.ds-git-row.active { background: var(--accent-tint); border-color: var(--accent); }
|
|
17
|
+
.ds-git-row:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
|
|
18
|
+
.ds-git-row[disabled] { cursor: default; }
|
|
19
|
+
|
|
20
|
+
.ds-git-status-chip {
|
|
21
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
22
|
+
flex: 0 0 auto; width: 18px; height: 18px;
|
|
23
|
+
font-family: var(--ff-mono); font-size: var(--fs-micro); font-weight: 700;
|
|
24
|
+
border-radius: var(--r-1);
|
|
25
|
+
}
|
|
26
|
+
.ds-git-status-chip.tone-add { color: var(--green-2); background: color-mix(in oklab, var(--green-2) 16%, transparent); }
|
|
27
|
+
.ds-git-status-chip.tone-modify { color: var(--sun); background: color-mix(in oklab, var(--sun) 16%, transparent); }
|
|
28
|
+
.ds-git-status-chip.tone-delete { color: var(--flame); background: color-mix(in oklab, var(--flame) 16%, transparent); }
|
|
29
|
+
.ds-git-status-chip.tone-neutral { color: var(--fg-3); background: var(--bg-2); }
|
|
30
|
+
|
|
31
|
+
.ds-git-row-icon { display: inline-flex; flex: 0 0 auto; color: var(--fg-3); }
|
|
32
|
+
.ds-git-row-path {
|
|
33
|
+
flex: 1 1 auto; min-width: 0;
|
|
34
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
35
|
+
font-family: var(--ff-mono); font-size: var(--fs-sm);
|
|
36
|
+
}
|
|
37
|
+
.ds-git-row-tag {
|
|
38
|
+
flex: 0 0 auto; font-size: var(--fs-micro); color: var(--fg-3);
|
|
39
|
+
border: var(--bw-hair) solid var(--rule); border-radius: var(--r-1); padding: 1px 6px;
|
|
40
|
+
}
|
|
41
|
+
.ds-git-row-stats { flex: 0 0 auto; display: inline-flex; gap: 6px; font-family: var(--ff-mono); font-size: var(--fs-micro); }
|
|
42
|
+
.ds-git-stat-add { color: var(--green-2); }
|
|
43
|
+
.ds-git-stat-del { color: var(--flame); }
|
|
44
|
+
|
|
45
|
+
.ds-git-empty {
|
|
46
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
47
|
+
gap: var(--space-2); padding: var(--space-6) var(--space-3);
|
|
48
|
+
color: var(--fg-3); text-align: center;
|
|
49
|
+
}
|
|
50
|
+
.ds-git-empty-glyph { opacity: 0.55; }
|
|
51
|
+
|
|
52
|
+
/* Diff view */
|
|
53
|
+
.ds-git-diff {
|
|
54
|
+
display: flex; flex-direction: column; gap: var(--space-2);
|
|
55
|
+
background: var(--ink); color: var(--paper);
|
|
56
|
+
border-radius: var(--r-2); overflow: hidden;
|
|
57
|
+
}
|
|
58
|
+
.ds-git-diff-head {
|
|
59
|
+
padding: var(--space-2) var(--space-3);
|
|
60
|
+
border-bottom: var(--bw-hair) solid color-mix(in oklab, var(--paper) 18%, transparent);
|
|
61
|
+
font-family: var(--ff-mono); font-size: var(--fs-xs);
|
|
62
|
+
}
|
|
63
|
+
.ds-git-diff-empty { padding: var(--space-3); color: var(--fg-3); font-size: var(--fs-sm); }
|
|
64
|
+
.ds-git-hunk + .ds-git-hunk { border-top: var(--bw-hair) solid color-mix(in oklab, var(--paper) 12%, transparent); }
|
|
65
|
+
.ds-git-hunk-header {
|
|
66
|
+
padding: 4px var(--space-3);
|
|
67
|
+
color: var(--purple-2); font-family: var(--ff-mono); font-size: var(--fs-micro);
|
|
68
|
+
background: color-mix(in oklab, var(--paper) 6%, transparent);
|
|
69
|
+
}
|
|
70
|
+
.ds-git-hunk-body {
|
|
71
|
+
margin: 0; padding: 0 0 var(--space-2);
|
|
72
|
+
font-family: var(--ff-mono); font-size: var(--fs-xs); line-height: var(--lh-base);
|
|
73
|
+
white-space: pre; overflow-x: auto; tab-size: 2;
|
|
74
|
+
}
|
|
75
|
+
.ds-git-line {
|
|
76
|
+
display: block; padding: 0 var(--space-3);
|
|
77
|
+
}
|
|
78
|
+
.ds-git-line-add { background: color-mix(in oklab, var(--green-2) 16%, transparent); color: var(--paper); }
|
|
79
|
+
.ds-git-line-del { background: color-mix(in oklab, var(--flame) 16%, transparent); color: var(--paper); }
|
|
80
|
+
.ds-git-line-context { color: var(--paper-3); }
|
|
81
|
+
|
|
82
|
+
/* ============================================================
|
|
83
|
+
WorktreeSwitcher — trigger + dropdown menu of worktrees/branches.
|
|
84
|
+
============================================================ */
|
|
85
|
+
.ds-wts { display: inline-flex; }
|
|
86
|
+
.ds-wts-trigger {
|
|
87
|
+
display: inline-flex; align-items: center; gap: 8px;
|
|
88
|
+
padding: 5px 10px; min-height: 32px;
|
|
89
|
+
background: var(--bg); border: var(--bw-hair) solid var(--rule); border-radius: var(--r-1);
|
|
90
|
+
color: var(--fg); font: inherit; font-size: var(--fs-sm); cursor: pointer;
|
|
91
|
+
max-width: 220px;
|
|
92
|
+
}
|
|
93
|
+
.ds-wts-trigger:hover { background: var(--bg-2); }
|
|
94
|
+
.ds-wts-trigger:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
|
|
95
|
+
.ds-wts-trigger-branch {
|
|
96
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
97
|
+
font-family: var(--ff-mono);
|
|
98
|
+
}
|
|
99
|
+
.ds-wts-trigger-caret { display: inline-flex; flex: 0 0 auto; color: var(--fg-3); }
|
|
100
|
+
|
|
101
|
+
.ds-wts-item-body { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
102
|
+
.ds-wts-item-check { display: inline-flex; flex: 0 0 16px; color: var(--accent-ink); }
|
|
103
|
+
.ds-wts-item-text { display: flex; flex-direction: column; min-width: 0; gap: 1px; }
|
|
104
|
+
.ds-wts-item-branch {
|
|
105
|
+
font-family: var(--ff-mono); font-size: var(--fs-sm);
|
|
106
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
107
|
+
}
|
|
108
|
+
.ds-wts-item-path {
|
|
109
|
+
font-size: var(--fs-micro); color: var(--fg-3);
|
|
110
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
111
|
+
}
|
|
112
|
+
.ds-dropdown-item[aria-disabled="true"] .ds-wts-item-branch { color: var(--fg); font-weight: 600; }
|