harlay 0.1.0 → 0.1.1
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/app/renderer/popover.css +4 -0
- package/app/renderer/popover.html +1 -1
- package/app/renderer/popover.js +32 -8
- package/dist/harlay.mjs +1 -1
- package/package.json +1 -1
package/app/renderer/popover.css
CHANGED
|
@@ -88,6 +88,10 @@ button { font: inherit; color: inherit; background: none; border: 0; text-align:
|
|
|
88
88
|
.label::before { content: ""; width: 5px; height: 5px; background: var(--accent); }
|
|
89
89
|
|
|
90
90
|
/* sessions */
|
|
91
|
+
.label.group { padding: 10px 8px 4px; }
|
|
92
|
+
.label.group:first-child { padding-top: 2px; }
|
|
93
|
+
.label.attn::before { background: var(--err); }
|
|
94
|
+
.label .count { margin-left: auto; letter-spacing: 0; color: var(--fg3); opacity: .8; }
|
|
91
95
|
.scroll { max-height: 372px; overflow-y: auto; padding: 0 8px 8px; }
|
|
92
96
|
.scroll::-webkit-scrollbar { width: 8px; }
|
|
93
97
|
.scroll::-webkit-scrollbar-thumb { background: rgba(var(--ink-rgb), .15); border-radius: 8px; border: 2px solid var(--bg); }
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2.5 20 10 4h4.3L6.8 20z" fill="currentColor" opacity=".45"/><path class="mk-b" d="M10.2 20 17.7 4H22l-7.5 16z"/></svg>
|
|
15
15
|
harlay
|
|
16
16
|
</div>
|
|
17
|
-
<span class="pill" id="pill"><i></i><span id="pill-text">
|
|
17
|
+
<span class="pill" id="pill"><i></i><span id="pill-text">Standing by</span></span>
|
|
18
18
|
<button class="icon-btn" id="toggle-theme" aria-label="Dark mode" title="Dark mode"></button>
|
|
19
19
|
<button class="icon-btn" id="toggle-settings" aria-label="Settings" title="Settings"></button>
|
|
20
20
|
</header>
|
package/app/renderer/popover.js
CHANGED
|
@@ -34,7 +34,7 @@ function h(tag, props, ...kids) {
|
|
|
34
34
|
else if (k.startsWith('on')) el.addEventListener(k.slice(2), v);
|
|
35
35
|
else el.setAttribute(k, v === true ? '' : String(v));
|
|
36
36
|
}
|
|
37
|
-
for (const kid of kids.flat()) if (kid != null && kid !== false) el.append(kid);
|
|
37
|
+
for (const kid of kids.flat(Infinity)) if (kid != null && kid !== false) el.append(kid);
|
|
38
38
|
return el;
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -67,9 +67,9 @@ function renderHero() {
|
|
|
67
67
|
h('p', {}, `${plural(sessions.length, 'recent session')} · ${plural(installed, 'agent')} ready`));
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
function renderSession(s) {
|
|
70
|
+
function renderSession(s, showRepo) {
|
|
71
71
|
const open = s.key === openKey;
|
|
72
|
-
const meta = [s.toolLabel, s.repo, s.ago].filter(Boolean).join(' · ');
|
|
72
|
+
const meta = [s.toolLabel, showRepo ? s.repo : null, s.ago].filter(Boolean).join(' · ');
|
|
73
73
|
const tool = (t) => model.tools.find((x) => x.tool === t.tool);
|
|
74
74
|
const actions = s.busy
|
|
75
75
|
? h('div', { class: 'busy' }, h('span', { class: 'spin' }), 'Handing off…')
|
|
@@ -106,14 +106,37 @@ function previewBrief(s) {
|
|
|
106
106
|
api.act({ type: 'previewBrief', key: s.key });
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Stopped and near-limit sessions first, then everything else grouped by project (most recent project first,
|
|
111
|
+
* as the sessions arrive newest first).
|
|
112
|
+
*/
|
|
113
|
+
function groupSessions(sessions) {
|
|
114
|
+
const attention = sessions.filter((s) => s.status === 'limit' || s.status === 'warning');
|
|
115
|
+
const projects = new Map();
|
|
116
|
+
for (const s of sessions) {
|
|
117
|
+
if (attention.includes(s)) continue;
|
|
118
|
+
const key = s.cwd ?? '';
|
|
119
|
+
if (!projects.has(key)) projects.set(key, { title: s.repo ?? 'No project folder', path: s.cwd, sessions: [] });
|
|
120
|
+
projects.get(key).sessions.push(s);
|
|
121
|
+
}
|
|
122
|
+
return { attention, projects: [...projects.values()] };
|
|
123
|
+
}
|
|
124
|
+
|
|
109
125
|
function renderHome() {
|
|
110
126
|
const { sessions } = model;
|
|
127
|
+
if (!sessions.length) {
|
|
128
|
+
$('#home').replaceChildren(renderHero(), h('p', { class: 'empty' }, 'Harlay reads session logs from ~/.claude, ~/.codex and ~/.cursor on this Mac.'));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const { attention, projects } = groupSessions(sessions);
|
|
111
132
|
$('#home').replaceChildren(
|
|
112
133
|
renderHero(),
|
|
113
|
-
h('div', { class: '
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
134
|
+
h('div', { class: 'scroll' },
|
|
135
|
+
attention.length ? [h('div', { class: 'label group attn' }, 'Needs attention'), attention.map((s) => renderSession(s, true))] : null,
|
|
136
|
+
projects.map((p) => [
|
|
137
|
+
h('div', { class: 'label group', title: p.path }, p.title, h('span', { class: 'count' }, String(p.sessions.length))),
|
|
138
|
+
p.sessions.map((s) => renderSession(s, false)),
|
|
139
|
+
])));
|
|
117
140
|
}
|
|
118
141
|
|
|
119
142
|
function seg(options, current, onPick) {
|
|
@@ -260,7 +283,7 @@ function render() {
|
|
|
260
283
|
theme.title = theme.getAttribute('aria-label');
|
|
261
284
|
const pill = $('#pill');
|
|
262
285
|
pill.classList.toggle('auto', model.settings.autoContinue);
|
|
263
|
-
$('#pill-text').textContent = model.settings.autoContinue ? 'Auto-continue' : '
|
|
286
|
+
$('#pill-text').textContent = model.settings.autoContinue ? 'Auto-continue' : 'Standing by';
|
|
264
287
|
$('#hotkey').textContent = model.hotkey;
|
|
265
288
|
if (view === 'home') renderHome();
|
|
266
289
|
else if (view === 'settings') renderSettings();
|
|
@@ -342,6 +365,7 @@ function demoApi() {
|
|
|
342
365
|
{ key: 'claude-code:1', from: 'claude-code', title: 'Migrate auth to JWT', toolLabel: 'Claude Code', status: 'limit', detail: 'Limit hit · resets 6:00 PM', cwd: '/Users/you/acme-api', repo: 'acme-api', ago: 'now', busy: false },
|
|
343
366
|
{ key: 'codex:2', from: 'codex', title: 'Fix flaky checkout tests', toolLabel: 'Codex', status: 'warning', detail: '92% of limit used', cwd: '/Users/you/shop', repo: 'shop', ago: '4m ago', busy: false },
|
|
344
367
|
{ key: 'claude-code:3', from: 'claude-code', title: 'Harlay landing page', toolLabel: 'Claude Code', status: 'idle', detail: 'Idle', cwd: '/Users/you/harlay', repo: 'harlay', ago: '1h ago', busy: false },
|
|
368
|
+
{ key: 'codex:5', from: 'codex', title: 'Rate limiter for /login', toolLabel: 'Codex', status: 'idle', detail: 'Idle', cwd: '/Users/you/acme-api', repo: 'acme-api', ago: '2h ago', busy: false },
|
|
345
369
|
{ key: 'cursor:4', from: 'cursor', title: 'Onboarding copy pass', toolLabel: 'Cursor', status: 'handed_off', detail: 'Handed off to Codex', cwd: '/Users/you/web', repo: 'web', ago: '3h ago', busy: false },
|
|
346
370
|
];
|
|
347
371
|
let onModel = () => {};
|
package/dist/harlay.mjs
CHANGED
package/package.json
CHANGED