@loupekit/sdk 0.10.14 → 0.10.15
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/README.md +23 -0
- package/dist/index.global.js +226 -4
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +226 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -207,7 +207,12 @@ var STYLES = (
|
|
|
207
207
|
|
|
208
208
|
.item { border: 1px solid var(--line); border-radius: 10px; padding: 10px; cursor: pointer; background: var(--bg-2); }
|
|
209
209
|
.item:hover { border-color: var(--accent); }
|
|
210
|
-
|
|
210
|
+
/* The header row now carries the number, a detached badge, a lifecycle chip, a PR
|
|
211
|
+
chip, a checks meter and the caret \u2014 more than fits on one line in a narrow
|
|
212
|
+
panel, so it wraps rather than clipping the last chip. The caret keeps its place
|
|
213
|
+
at the end of the row. */
|
|
214
|
+
.item .top { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 8px; margin-bottom: 6px; }
|
|
215
|
+
.item .top .caret { margin-left: auto; }
|
|
211
216
|
.item .num { background: var(--pin); color: #fff; width: 20px; height: 20px; border-radius: 50%; font-size: 11px; font-weight: 700; display: grid; place-items: center; flex: none; }
|
|
212
217
|
.item .num.detached { background: #9aa0af; }
|
|
213
218
|
.item .num.done { background: #10935a; }
|
|
@@ -581,6 +586,74 @@ var STYLES = (
|
|
|
581
586
|
.menu-ver b { color: var(--ink); font-family: ui-monospace, Menlo, monospace; font-weight: 600; }
|
|
582
587
|
.menu-mode { text-transform: uppercase; letter-spacing: .06em; font-size: 9.5px; }
|
|
583
588
|
|
|
589
|
+
/* ------------------------------------------------ lifecycle chips + review flow */
|
|
590
|
+
.lifechip {
|
|
591
|
+
padding: 1px 6px; border-radius: 999px; white-space: nowrap;
|
|
592
|
+
border: 1px solid var(--line); background: var(--bg); color: var(--muted);
|
|
593
|
+
font-size: 9.5px; font-weight: 700; letter-spacing: .02em; text-transform: uppercase;
|
|
594
|
+
}
|
|
595
|
+
.lifechip.st-sent { border-color: var(--accent); color: var(--accent); }
|
|
596
|
+
.lifechip.st-in_pr { border-color: #3f8ae0; color: #3f8ae0; }
|
|
597
|
+
.lifechip.st-preview { border-color: var(--accent); background: var(--accent); color: #fff; }
|
|
598
|
+
.lifechip.st-reviewed { border-color: #2f9e6a; color: #2f9e6a; }
|
|
599
|
+
.prchip {
|
|
600
|
+
padding: 1px 6px; border-radius: 6px; text-decoration: none; cursor: pointer;
|
|
601
|
+
border: 1px solid #3f8ae0; background: var(--bg); color: #3f8ae0;
|
|
602
|
+
font-family: ui-monospace, Menlo, monospace; font-size: 10px; font-weight: 700;
|
|
603
|
+
}
|
|
604
|
+
.prchip:hover { background: #3f8ae0; color: #fff; }
|
|
605
|
+
.prchip.st-merged { border-color: #8250df; color: #8250df; }
|
|
606
|
+
.prchip.st-merged:hover { background: #8250df; color: #fff; }
|
|
607
|
+
.prchip.st-closed { border-color: var(--line); color: var(--muted); }
|
|
608
|
+
/* checks meter \u2014 a numerator over a thin bar */
|
|
609
|
+
.checks { display: inline-flex; align-items: center; gap: 4px; }
|
|
610
|
+
.checks-n { font-family: ui-monospace, Menlo, monospace; font-size: 9.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
|
|
611
|
+
.checks-bar { display: block; width: 26px; height: 3px; border-radius: 2px; background: var(--line); overflow: hidden; }
|
|
612
|
+
.checks-bar i { display: block; height: 100%; background: #2f9e6a; }
|
|
613
|
+
|
|
614
|
+
/* the "N waiting on your review" strip above the list */
|
|
615
|
+
.reviewbar {
|
|
616
|
+
display: flex; align-items: center; gap: 7px; margin: 8px 12px 0; padding: 7px 10px;
|
|
617
|
+
border: 1px solid var(--accent); border-radius: 9px; background: var(--bg-2);
|
|
618
|
+
}
|
|
619
|
+
.rb-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); flex: none; animation: loupe-pulse 1.6s ease-in-out infinite; }
|
|
620
|
+
@media (prefers-reduced-motion: reduce) { .rb-dot { animation: none; } }
|
|
621
|
+
.rb-t { flex: 1; font-size: 11.5px; color: var(--ink); }
|
|
622
|
+
.rb-t b { font-variant-numeric: tabular-nums; }
|
|
623
|
+
.rb-b {
|
|
624
|
+
padding: 3px 9px; border: 1px solid var(--accent); border-radius: 7px;
|
|
625
|
+
background: var(--accent); color: #fff; font-size: 11px; font-weight: 600; cursor: pointer;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/* the review banner inside a thread */
|
|
629
|
+
.revbanner {
|
|
630
|
+
display: flex; align-items: center; gap: 6px; margin: 8px 0; padding: 7px 8px;
|
|
631
|
+
border: 1px solid var(--accent); border-radius: 9px; background: var(--bg-3);
|
|
632
|
+
}
|
|
633
|
+
.rev-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); flex: none; }
|
|
634
|
+
.rev-t { flex: 1; font-size: 11.5px; font-weight: 600; color: var(--ink); }
|
|
635
|
+
.rev-approve {
|
|
636
|
+
padding: 4px 10px; border: 1px solid var(--accent); border-radius: 7px;
|
|
637
|
+
background: var(--accent); color: #fff; font-size: 11px; font-weight: 600; cursor: pointer;
|
|
638
|
+
}
|
|
639
|
+
.rev-approve:disabled { opacity: .6; cursor: default; }
|
|
640
|
+
.rev-comment, .rev-origin {
|
|
641
|
+
padding: 4px 8px; border: 1px solid var(--line); border-radius: 7px;
|
|
642
|
+
background: var(--bg); color: var(--ink); font-size: 11px; cursor: pointer;
|
|
643
|
+
}
|
|
644
|
+
.rev-comment:hover, .rev-origin:hover { border-color: var(--accent); }
|
|
645
|
+
|
|
646
|
+
/* original request beside the proposed change */
|
|
647
|
+
.origin { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 8px; margin: 8px 0; }
|
|
648
|
+
.or-col { min-width: 0; padding: 7px 8px; border: 1px solid var(--line); border-radius: 8px; background: var(--bg); }
|
|
649
|
+
.or-h { margin-bottom: 4px; font-size: 9.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
|
|
650
|
+
.or-b { font-size: 11px; line-height: 1.45; color: var(--ink); white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
651
|
+
.or-code {
|
|
652
|
+
margin: 6px 0 0; padding: 6px; max-height: 130px; overflow: auto; border-radius: 6px;
|
|
653
|
+
background: var(--bg-2); color: var(--muted); font-family: ui-monospace, Menlo, monospace;
|
|
654
|
+
font-size: 10px; white-space: pre-wrap; overflow-wrap: anywhere;
|
|
655
|
+
}
|
|
656
|
+
|
|
584
657
|
/* ------------------------------------------------------- hint card (once per view) */
|
|
585
658
|
.hint {
|
|
586
659
|
position: relative; margin: 8px 12px 0; padding: 10px 28px 10px 11px;
|
|
@@ -2845,6 +2918,44 @@ function summarizeActivity(events, status = "idle", now = Date.now()) {
|
|
|
2845
2918
|
};
|
|
2846
2919
|
}
|
|
2847
2920
|
|
|
2921
|
+
// ../shared/dist/lifecycle.js
|
|
2922
|
+
var LIFECYCLE_LABELS = {
|
|
2923
|
+
sent: "Sent to agent",
|
|
2924
|
+
in_pr: "In PR",
|
|
2925
|
+
preview: "Review preview",
|
|
2926
|
+
reviewed: "Reviewed"
|
|
2927
|
+
};
|
|
2928
|
+
function lifecycle(c) {
|
|
2929
|
+
const hasProposal = !!c.proposal;
|
|
2930
|
+
const pr = c.pr ?? void 0;
|
|
2931
|
+
const withPr = (stage) => {
|
|
2932
|
+
const out = { stage, label: LIFECYCLE_LABELS[stage] };
|
|
2933
|
+
if (pr) {
|
|
2934
|
+
out.pr = pr;
|
|
2935
|
+
if (typeof pr.checksTotal === "number" && pr.checksTotal > 0) {
|
|
2936
|
+
const passed = Math.max(0, Math.min(pr.checksTotal, pr.checksPassed ?? 0));
|
|
2937
|
+
out.checks = {
|
|
2938
|
+
text: `${passed}/${pr.checksTotal}`,
|
|
2939
|
+
ratio: passed / pr.checksTotal
|
|
2940
|
+
};
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
return out;
|
|
2944
|
+
};
|
|
2945
|
+
if (c.status === "resolved")
|
|
2946
|
+
return hasProposal || pr ? withPr("reviewed") : null;
|
|
2947
|
+
if (pr)
|
|
2948
|
+
return withPr("in_pr");
|
|
2949
|
+
if (c.status === "in_review")
|
|
2950
|
+
return { stage: "preview", label: LIFECYCLE_LABELS.preview };
|
|
2951
|
+
if (hasProposal)
|
|
2952
|
+
return { stage: "sent", label: LIFECYCLE_LABELS.sent };
|
|
2953
|
+
return null;
|
|
2954
|
+
}
|
|
2955
|
+
function awaitingReview(comments) {
|
|
2956
|
+
return comments.filter((c) => c.status === "in_review");
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2848
2959
|
// ../shared/dist/index.js
|
|
2849
2960
|
var COMMENT_STAGES = ["queue", "todo", "in_progress", "in_review", "resolved"];
|
|
2850
2961
|
var STAGE_LABELS = {
|
|
@@ -2892,7 +3003,7 @@ function normalizePriority(value) {
|
|
|
2892
3003
|
}
|
|
2893
3004
|
|
|
2894
3005
|
// src/app.ts
|
|
2895
|
-
var SDK_VERSION = true ? "0.10.
|
|
3006
|
+
var SDK_VERSION = true ? "0.10.15" : "dev";
|
|
2896
3007
|
var ACCENTS = [
|
|
2897
3008
|
{ id: "indigo", dark: "#6b73e6", light: "#4a55d6", soft: "rgba(107,115,230,0.12)" },
|
|
2898
3009
|
{ id: "violet", dark: "#a06be6", light: "#7c3fd4", soft: "rgba(160,107,230,0.14)" },
|
|
@@ -3504,7 +3615,9 @@ var LoupeApp = class {
|
|
|
3504
3615
|
this.homeEl = homeView;
|
|
3505
3616
|
const commentsView = el("div", "view comments-view");
|
|
3506
3617
|
this.commentsHint = el("div", "hint-slot");
|
|
3507
|
-
|
|
3618
|
+
this.reviewBar = el("div", "reviewbar");
|
|
3619
|
+
this.reviewBar.style.display = "none";
|
|
3620
|
+
commentsView.append(this.commentsHint, tools, listHead, this.reviewBar, this.listEl, this.buildIntegrations());
|
|
3508
3621
|
const activityView = el("div", "view activity-view");
|
|
3509
3622
|
this.activityEl = activityView;
|
|
3510
3623
|
const customViews = (this.cfg.tabs ?? []).map((t) => {
|
|
@@ -4858,6 +4971,7 @@ var LoupeApp = class {
|
|
|
4858
4971
|
});
|
|
4859
4972
|
if (this.scope === "all") items = [...items].sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
4860
4973
|
this.renderRepoFilter();
|
|
4974
|
+
this.renderReviewBar();
|
|
4861
4975
|
if (!items.length) {
|
|
4862
4976
|
this.listEl.appendChild(el(
|
|
4863
4977
|
"div",
|
|
@@ -4902,7 +5016,32 @@ var LoupeApp = class {
|
|
|
4902
5016
|
top.appendChild(el("span", "device", `${icon} ${kind}`));
|
|
4903
5017
|
}
|
|
4904
5018
|
if (isResolved(c)) top.appendChild(el("span", "badge done", "resolved"));
|
|
4905
|
-
else if (detached)
|
|
5019
|
+
else if (detached) {
|
|
5020
|
+
const b = el("span", "badge detached", "moved");
|
|
5021
|
+
b.title = "element moved or removed";
|
|
5022
|
+
top.appendChild(b);
|
|
5023
|
+
}
|
|
5024
|
+
const lc = lifecycle(c);
|
|
5025
|
+
if (lc) {
|
|
5026
|
+
if (lc.stage !== "reviewed") top.appendChild(el("span", `lifechip st-${lc.stage}`, lc.label));
|
|
5027
|
+
if (lc.pr) {
|
|
5028
|
+
const pr = el(lc.pr.url ? "a" : "span", `prchip${lc.pr.state ? ` st-${lc.pr.state}` : ""}`, `#${lc.pr.number}`);
|
|
5029
|
+
pr.title = `Pull request #${lc.pr.number}${lc.pr.state ? ` \u2014 ${lc.pr.state}` : ""}`;
|
|
5030
|
+
if (lc.pr.url) {
|
|
5031
|
+
pr.href = lc.pr.url;
|
|
5032
|
+
pr.setAttribute("target", "_blank");
|
|
5033
|
+
pr.setAttribute("rel", "noreferrer");
|
|
5034
|
+
pr.addEventListener("click", (e) => e.stopPropagation());
|
|
5035
|
+
}
|
|
5036
|
+
top.appendChild(pr);
|
|
5037
|
+
}
|
|
5038
|
+
if (lc.checks) {
|
|
5039
|
+
const meter = el("span", "checks");
|
|
5040
|
+
meter.title = `Checks \u2014 ${lc.checks.text} passed`;
|
|
5041
|
+
meter.innerHTML = `<span class="checks-n">${escapeHtml(lc.checks.text)}</span><span class="checks-bar"><i style="width:${Math.round(lc.checks.ratio * 100)}%"></i></span>`;
|
|
5042
|
+
top.appendChild(meter);
|
|
5043
|
+
}
|
|
5044
|
+
}
|
|
4906
5045
|
if (this.showPaths && this.scope === "all" && c.url) {
|
|
4907
5046
|
top.appendChild(el("span", "pathtag", shortPath(c.url)));
|
|
4908
5047
|
}
|
|
@@ -4911,6 +5050,8 @@ var LoupeApp = class {
|
|
|
4911
5050
|
const summary = c.title || (c.body.split("\n")[0] ?? "").slice(0, 140) || "(no description)";
|
|
4912
5051
|
item.appendChild(el("div", "summary", summary));
|
|
4913
5052
|
const detail = el("div", "detail");
|
|
5053
|
+
if (c.status === "in_review") detail.appendChild(this.reviewBanner(c));
|
|
5054
|
+
if (c.proposal) detail.appendChild(this.proposalView(c));
|
|
4914
5055
|
if (c.title || c.body.includes("\n")) detail.appendChild(el("div", "body", c.body));
|
|
4915
5056
|
detail.appendChild(el("div", "meta", describeAnchor(c)));
|
|
4916
5057
|
if (c.recording) {
|
|
@@ -4977,6 +5118,87 @@ var LoupeApp = class {
|
|
|
4977
5118
|
};
|
|
4978
5119
|
return item;
|
|
4979
5120
|
}
|
|
5121
|
+
/**
|
|
5122
|
+
* The review banner. This is the one place the panel asks a human to decide
|
|
5123
|
+
* something, so it sits above everything else in the detail.
|
|
5124
|
+
*
|
|
5125
|
+
* Approving resolves the thread. That asymmetry is the rule the whole flow rests
|
|
5126
|
+
* on: an agent moves work to In Review, only a person closes it.
|
|
5127
|
+
*/
|
|
5128
|
+
reviewBanner(c) {
|
|
5129
|
+
const label = c.title || c.body.split("\n")[0] || "this thread";
|
|
5130
|
+
const banner = el("div", "revbanner");
|
|
5131
|
+
banner.innerHTML = `<span class="rev-dot"></span><span class="rev-t">Waiting on your review</span><span class="rev-spacer"></span>`;
|
|
5132
|
+
const approve = el("button", "rev-approve", "Approve");
|
|
5133
|
+
approve.onclick = async (e) => {
|
|
5134
|
+
e.stopPropagation();
|
|
5135
|
+
approve.disabled = true;
|
|
5136
|
+
approve.textContent = "Approving\u2026";
|
|
5137
|
+
await this.store.update(c.id, { status: "resolved" });
|
|
5138
|
+
c.status = "resolved";
|
|
5139
|
+
this.renderPins();
|
|
5140
|
+
this.renderList();
|
|
5141
|
+
this.renderHome();
|
|
5142
|
+
this.addActivity({ kind: "review.approve", label: `Approved \u201C${label}\u201D` });
|
|
5143
|
+
};
|
|
5144
|
+
const discuss = el("button", "rev-comment", "Add comment");
|
|
5145
|
+
discuss.onclick = (e) => {
|
|
5146
|
+
e.stopPropagation();
|
|
5147
|
+
const target = this.resolved.get(c.id);
|
|
5148
|
+
if (target && target.isConnected) {
|
|
5149
|
+
const r = target.getBoundingClientRect();
|
|
5150
|
+
this.openComposer({ kind: "element", element: target }, r.left + r.width / 2, r.top + r.height);
|
|
5151
|
+
} else {
|
|
5152
|
+
this.setMode("free");
|
|
5153
|
+
}
|
|
5154
|
+
};
|
|
5155
|
+
banner.append(approve, discuss);
|
|
5156
|
+
if (c.proposal) {
|
|
5157
|
+
const origin = el("button", "rev-origin", "Show original");
|
|
5158
|
+
origin.onclick = (e) => {
|
|
5159
|
+
e.stopPropagation();
|
|
5160
|
+
const view = banner.parentElement?.querySelector(".origin");
|
|
5161
|
+
if (!view) return;
|
|
5162
|
+
const shown = view.classList.toggle("show");
|
|
5163
|
+
view.style.display = shown ? "" : "none";
|
|
5164
|
+
origin.textContent = shown ? "Hide original" : "Show original";
|
|
5165
|
+
};
|
|
5166
|
+
banner.appendChild(origin);
|
|
5167
|
+
}
|
|
5168
|
+
return banner;
|
|
5169
|
+
}
|
|
5170
|
+
/**
|
|
5171
|
+
* The original request beside Claude's proposed change. Hidden until the review
|
|
5172
|
+
* banner's toggle asks for it — a reviewer who does not care should not pay for
|
|
5173
|
+
* the markup.
|
|
5174
|
+
*/
|
|
5175
|
+
proposalView(c) {
|
|
5176
|
+
const p = c.proposal;
|
|
5177
|
+
const wrap = el("div", "origin");
|
|
5178
|
+
wrap.style.display = "none";
|
|
5179
|
+
wrap.innerHTML = `<div class="or-col"><div class="or-h">Original request</div><div class="or-b">${escapeHtml(c.title ? `${c.title}
|
|
5180
|
+
|
|
5181
|
+
${c.body}` : c.body)}</div>` + (c.context?.html ? `<pre class="or-code">${escapeHtml(c.context.html)}</pre>` : "") + `</div><div class="or-col"><div class="or-h">Proposed change${p.author ? ` \xB7 ${escapeHtml(p.author)}` : ""}</div>` + (p.notes ? `<div class="or-b">${escapeHtml(p.notes)}</div>` : "") + (p.html ? `<pre class="or-code">${escapeHtml(p.html)}</pre>` : "") + (p.css ? `<pre class="or-code">${escapeHtml(p.css)}</pre>` : "") + `</div>`;
|
|
5182
|
+
return wrap;
|
|
5183
|
+
}
|
|
5184
|
+
/** The strips above the list: how many threads are waiting on a human. */
|
|
5185
|
+
renderReviewBar() {
|
|
5186
|
+
if (!this.reviewBar) return;
|
|
5187
|
+
const waiting = awaitingReview(this.visibleComments);
|
|
5188
|
+
if (!waiting.length) {
|
|
5189
|
+
this.reviewBar.innerHTML = "";
|
|
5190
|
+
this.reviewBar.style.display = "none";
|
|
5191
|
+
return;
|
|
5192
|
+
}
|
|
5193
|
+
const on = this.statFilter === "needs_you";
|
|
5194
|
+
this.reviewBar.style.display = "";
|
|
5195
|
+
this.reviewBar.innerHTML = `<span class="rb-dot"></span><span class="rb-t"><b>${waiting.length}</b> waiting on your review</span><button class="rb-b" data-role="rb-toggle">${on ? "Show all" : "Review"}</button>`;
|
|
5196
|
+
this.reviewBar.querySelector('[data-role="rb-toggle"]').onclick = () => {
|
|
5197
|
+
this.statFilter = on ? "" : "needs_you";
|
|
5198
|
+
this.renderList();
|
|
5199
|
+
this.renderHome();
|
|
5200
|
+
};
|
|
5201
|
+
}
|
|
4980
5202
|
flash(id) {
|
|
4981
5203
|
const c = this.comments.find((x) => x.id === id);
|
|
4982
5204
|
const pin = this.pins.get(id);
|