@leftoff/cli 0.9.8-beta.16 → 0.9.8-beta.17
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 +2 -1
- package/mod/.claude-plugin/plugin.json +1 -1
- package/mod/hooks/register.js +1 -1
- package/package.json +1 -1
- package/ui/app.css +4 -3
- package/ui/app.js +8 -3
- package/ui/prs.js +10 -5
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,8 @@ What changed in Leftoff (called Checkpoints until 0.4.5), newest first. The boar
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
-
- Session page: a non-Live session's Live box shows the Live resume command with
|
|
7
|
+
- Session page: a non-Live session's Live box shows the Live resume command with a copy icon in it; the Lists ? opens “How lists work” right there.
|
|
8
|
+
- Session page: tap a PR for its details in place (as on the PRs page); GitHub ↗ sits beside it.
|
|
8
9
|
|
|
9
10
|
- Session page: Copy Live resume, for a session that wasn't Live (exit it, run the command, it comes back Live).
|
|
10
11
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leftoff",
|
|
3
|
-
"version": "0.9.8-beta.
|
|
3
|
+
"version": "0.9.8-beta.17",
|
|
4
4
|
"description": "Leftoff (formerly Checkpoints): WTF was I working on? Tracks every Claude Code session on this machine, writes a 3-line checkpoint when one goes quiet, and adds /cp (checkpoint now), /cp-name and a /leftoff pane. Talks to the local Leftoff daemon on 127.0.0.1:7429 and falls back to writing markdown in ~/checkpoints.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Joel"
|
package/mod/hooks/register.js
CHANGED
|
@@ -28,7 +28,7 @@ const END_TIMEOUT_MS = 1000
|
|
|
28
28
|
const MODEL_TIMEOUT_MS = 20000
|
|
29
29
|
|
|
30
30
|
// Module state. $.state is not used: the pane redraws with $.ui.invalidate.
|
|
31
|
-
const MOD_VERSION = '0.9.8-beta.
|
|
31
|
+
const MOD_VERSION = '0.9.8-beta.17' // keep in step with .claude-plugin/plugin.json
|
|
32
32
|
|
|
33
33
|
const st = {
|
|
34
34
|
internal: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leftoff/cli",
|
|
3
|
-
"version": "0.9.8-beta.
|
|
3
|
+
"version": "0.9.8-beta.17",
|
|
4
4
|
"description": "Leftoff: WTF was I working on? Tracks every Claude Code session, writes a 3-line checkpoint when one goes quiet, and gives you a daily digest and a board, on your Mac and (optionally) your phone.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
package/ui/app.css
CHANGED
|
@@ -2612,6 +2612,7 @@ html.pv-open, html.pv-open body { overflow: hidden; }
|
|
|
2612
2612
|
@media (prefers-reduced-motion: reduce) { .updatebar { transition: none; } }
|
|
2613
2613
|
|
|
2614
2614
|
/* 0.9.8: a non-Live session's Live box offers the Live resume command */
|
|
2615
|
-
.live-box.off .live-resume-row {
|
|
2616
|
-
.live-box.off .live-resume-row code {
|
|
2617
|
-
.live-box.off .live-resume-row .
|
|
2615
|
+
.live-box.off .live-resume-row { position: relative; margin-top: 6px; }
|
|
2616
|
+
.live-box.off .live-resume-row code { display: block; min-width: 0; overflow-wrap: anywhere; font-size: 12.5px; line-height: 1.5; padding: 12px 52px 12px 12px; min-height: 44px; box-sizing: border-box; border-radius: 8px; background: var(--card2); border: 1px solid var(--line); }
|
|
2617
|
+
.live-box.off .live-resume-row .copy-float { position: absolute; top: 0; right: 0; width: 44px; height: 44px; display: grid; place-items: center; border: 0; background: transparent; color: var(--muted); border-radius: 8px; cursor: pointer; }
|
|
2618
|
+
.live-box.off .live-resume-row .copy-float:hover, .live-box.off .live-resume-row .copy-float:focus-visible { color: var(--text); background: var(--card); }
|
package/ui/app.js
CHANGED
|
@@ -1411,7 +1411,7 @@ function sessionPageHtml(s, { spv = null, live: liveV = null, lists = null } = {
|
|
|
1411
1411
|
if (isLive(s)) html += (s.needsYou ? sessionNeedsStrip(s) : '') + liveBoxHtml(s, liveV)
|
|
1412
1412
|
html += parkRow(s)
|
|
1413
1413
|
html += spacePicker(s, spv, 'session', s.id)
|
|
1414
|
-
html += '<section class="sprs" id="prs">' + sessionPrsSection(s, { mayEdit: !s.agent }) + '</section>'
|
|
1414
|
+
html += '<section class="sprs" id="prs">' + sessionPrsSection(s, { mayEdit: !s.agent, open: prsUi.open, details: prsUi.details }) + '</section>'
|
|
1415
1415
|
// 0.7.0: steps handed back to you, and the docs, plans and artifacts it made
|
|
1416
1416
|
html += '<section class="stodos" id="todos">' + sessionTodosSection(s, { agoFn: ago, mayEdit: state.mayAnswer !== false }) + '</section>'
|
|
1417
1417
|
html += '<section class="sfiles" id="files">' + sessionFilesSection(s, { agoFn: ago }) + '</section>'
|
|
@@ -1485,7 +1485,7 @@ function liveBoxHtml(s, v) {
|
|
|
1485
1485
|
return '<section class="live-box off" aria-label="Live link"><div class="live-head"><h2>Live <span class="pill soon">Beta</span></h2></div>' +
|
|
1486
1486
|
'<p class="faint">To send this session messages from here or your phone, and answer its permission prompts, start it with <code>leftoff live</code>' +
|
|
1487
1487
|
(how && how.line ? ' (or <code>' + esc(how.line) + '</code>)' : '') + '. Claude Code asks once to load the channel; channels are a research preview.</p>' +
|
|
1488
|
-
(s.liveResumeCommand ? '<p class="faint">Already running? Exit it in its terminal, then paste this to bring it back Live:</p><div class="live-resume-row"><code class="mono">' + esc(s.liveResumeCommand) + '</code><button type="button" class="
|
|
1488
|
+
(s.liveResumeCommand ? '<p class="faint">Already running? Exit it in its terminal, then paste this to bring it back Live:</p><div class="live-resume-row"><code class="mono">' + esc(s.liveResumeCommand) + '</code><button type="button" class="copy-float" data-action="copy-cmd" data-cmd="' + esc(s.liveResumeCommand) + '" aria-label="Copy Live resume" title="Copy">' + icon('copy') + '</button></div>' : '') + '</section>'
|
|
1489
1489
|
}
|
|
1490
1490
|
const msgs = (v && v.messages) || []
|
|
1491
1491
|
const may = state.mayAnswer !== false
|
|
@@ -2196,6 +2196,11 @@ async function togglePr(url, { retry = false } = {}) {
|
|
|
2196
2196
|
const was = prsUi.open.has(url)
|
|
2197
2197
|
if (was && !retry) prsUi.open.delete(url); else prsUi.open.add(url)
|
|
2198
2198
|
const repaint = () => {
|
|
2199
|
+
if (state.route && state.route.name === 's') { // 0.9.8: the session page's PRs open in place too
|
|
2200
|
+
const s = state.sessions.find((x) => x.id === state.route.arg), sec = document.getElementById('prs')
|
|
2201
|
+
if (s && sec) sec.innerHTML = sessionPrsSection(s, { mayEdit: !s.agent, open: prsUi.open, details: prsUi.details })
|
|
2202
|
+
return
|
|
2203
|
+
}
|
|
2199
2204
|
const d = prsUi.data[prsUi.view]
|
|
2200
2205
|
const r = prRows(d).find((x) => x.pr.url === url)
|
|
2201
2206
|
const el = [...document.querySelectorAll('article.prrow[data-pr]')].find((a) => a.dataset.pr === url)
|
|
@@ -2209,7 +2214,7 @@ async function togglePr(url, { retry = false } = {}) {
|
|
|
2209
2214
|
try { res = await api('GET', '/v1/prs/detail?url=' + encodeURIComponent(url) + (retry ? '&fresh=1' : ''), undefined, { timeoutMs: 30000 }) }
|
|
2210
2215
|
catch (e) { if (e.code === 'login_required') throw e; res = { ok: false, code: 'failed' } }
|
|
2211
2216
|
prsUi.details[url] = { ...res, at: Date.now() }
|
|
2212
|
-
if (state.route && state.route.name === 'prs') repaint()
|
|
2217
|
+
if (state.route && (state.route.name === 'prs' || state.route.name === 's')) repaint()
|
|
2213
2218
|
}
|
|
2214
2219
|
function setPrsFilter(f) {
|
|
2215
2220
|
const h = prsHash(prsUi.view, f)
|
package/ui/prs.js
CHANGED
|
@@ -47,17 +47,22 @@ export function cardPrsHtml(prs) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// The session page: its PRs with Unlink, and "Link a PR" (a text box: a URL or #number).
|
|
50
|
-
export function sessionPrsSection(s, { mayEdit = true } = {}) {
|
|
50
|
+
export function sessionPrsSection(s, { mayEdit = true, open = null, details = {} } = {}) {
|
|
51
51
|
const list = (s.prs || []).filter((p) => p && p.number)
|
|
52
52
|
let h = '<h2>Pull requests' + (list.length ? ' <span class="count">' + list.length + '</span>' : '') + '</h2>'
|
|
53
53
|
if (list.length) {
|
|
54
54
|
h += '<div class="prlist">' + list.map((p) => {
|
|
55
55
|
const url = safeUrl(p.url)
|
|
56
|
-
|
|
56
|
+
// 0.9.8: tap the title (or Details) for the same details as the PRs page, in place; GitHub ↗ still goes out
|
|
57
|
+
const isOpen = !!(open && open.has && open.has(p.url))
|
|
58
|
+
return '<article class="prrow slim' + (isOpen ? ' open' : '') + '" data-pr="' + esc(p.url) + '"><div class="pr-main">' +
|
|
57
59
|
'<div class="pr-line1"><span class="pr-num">#' + esc(p.number) + '</span>' + stateHtml(p.state) + checksHtml(p.checks) + '</div>' +
|
|
58
|
-
'<div class="pr-title"
|
|
59
|
-
'<div class="pr-meta">' + esc(p.repo || '') + (whyText(p) ? ' · <span class="pr-why">' + esc(whyText(p)) + '</span>' : '') + '</div
|
|
60
|
-
(
|
|
60
|
+
'<div class="pr-title"><button type="button" class="linkish pr-title-btn" data-action="pr-expand" data-url="' + esc(p.url) + '" aria-expanded="' + isOpen + '">' + esc(p.title || 'Pull request #' + p.number) + '</button></div>' +
|
|
61
|
+
'<div class="pr-meta">' + esc(p.repo || '') + (whyText(p) ? ' · <span class="pr-why">' + esc(whyText(p)) + '</span>' : '') + '</div>' +
|
|
62
|
+
(isOpen ? prDetailHtml(p, details && details[p.url], {}) : '') + '</div>' +
|
|
63
|
+
'<div class="pr-actions"><button type="button" class="btn sm pr-toggle" data-action="pr-expand" data-url="' + esc(p.url) + '" aria-expanded="' + isOpen + '">' + (isOpen ? 'Less ▴' : 'Details ▾') + '</button>' +
|
|
64
|
+
(url ? '<a class="btn sm" href="' + esc(url) + '" target="_blank" rel="noopener noreferrer">GitHub ↗</a>' : '') +
|
|
65
|
+
(mayEdit ? '<button type="button" class="btn sm" data-action="pr-unlink" data-id="' + esc(s.id) + '" data-url="' + esc(p.url) + '">Unlink</button>' : '') + '</div></article>'
|
|
61
66
|
}).join('') + '</div>'
|
|
62
67
|
} else h += '<div class="nocp">No pull requests linked yet. They link on their own from gh pr create, PR links in the chat, or the branch.</div>'
|
|
63
68
|
if (mayEdit) h += '<form class="prlink addform sess-add" data-pr-link="' + esc(s.id) + '" autocomplete="off"><input name="ref" type="text" inputmode="url" placeholder="Link a PR: paste its link or #number" aria-label="Link a PR: paste its link or #number" autocapitalize="off" spellcheck="false" enterkeyhint="done"><button type="submit" class="btn sm">Link</button></form>'
|