Julien Simon Claude Opus 4.5 commited on
Commit
faddc33
·
1 Parent(s): fb139a8

Remove Share on X button, add review quality metrics bar

Browse files

Replace the Twitter share button with a metrics bar that displays
after each review: issues found, concrete fixes, lines analyzed,
review time, and line coverage percentage.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Files changed (4) hide show
  1. public/app.js +62 -24
  2. public/index.html +1 -5
  3. public/style.css +20 -15
  4. server.js +2 -1
public/app.js CHANGED
@@ -13,8 +13,8 @@ const streamError = document.getElementById("stream-error");
13
  const tabButtons = document.querySelectorAll(".tab");
14
  const brutalityBtns = document.querySelectorAll(".brutality-btn");
15
  const issueBadge = document.getElementById("issue-badge");
 
16
  const copyBtn = document.getElementById("copy-btn");
17
- const shareXBtn = document.getElementById("share-x-btn");
18
  const toast = document.getElementById("toast");
19
 
20
  const GITHUB_BLOB_RE = /^https?:\/\/github\.com\/[^/]+\/[^/]+\/blob\/[^/]+\/.+$/;
@@ -240,30 +240,63 @@ function renderIssueBadge(counts) {
240
  issueBadge.hidden = false;
241
  }
242
 
243
- /* --- Share on X --- */
244
 
245
- function stripMarkdown(md) {
246
- return md.replace(/\*\*/g, "").replace(/^#+\s*/gm, "").replace(/\n{2,}/g, "\n").trim();
247
- }
248
 
249
- function shareOnX() {
250
- const url = urlInput.value.trim();
251
- const mode = brutalityLevel;
252
- const verdicts = stripMarkdown(currentSections.verdicts || "");
253
-
254
- const header = `I reviewed code with Arcee AI Trinity Large in ${mode} mode and here's what Linus, Knuth, and Bjarne had to say:\n\n`;
255
- const footer = `\n\nTry it yourself: https://huggingface.co/spaces/julien-c/trinity-large\n${url}`;
256
- const maxVerdicts = 280 - header.length - footer.length;
257
- const trimmedVerdicts = verdicts.length > maxVerdicts
258
- ? verdicts.slice(0, maxVerdicts - 1) + "\u2026"
259
- : verdicts;
260
-
261
- const text = header + trimmedVerdicts + footer;
262
- const shareUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}`;
263
- window.open(shareUrl, "_blank", "width=600,height=400");
 
 
 
 
264
  }
265
 
266
- shareXBtn.addEventListener("click", shareOnX);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
 
268
  /* --- Copy to clipboard --- */
269
 
@@ -344,8 +377,8 @@ function startReview() {
344
  btn.classList.remove("has-content", "streaming");
345
  });
346
  issueBadge.hidden = true;
 
347
  copyBtn.hidden = true;
348
- shareXBtn.hidden = true;
349
  fullMarkdown = "";
350
 
351
  if (!url) { showInputError("Please enter a GitHub file URL."); return; }
@@ -358,6 +391,8 @@ function startReview() {
358
  setLoading(true);
359
 
360
  let markdown = "";
 
 
361
  const apiUrl = `/api/review?url=${encodeURIComponent(url)}&brutality=${encodeURIComponent(brutalityLevel)}`;
362
  const source = new EventSource(apiUrl);
363
  currentSource = source;
@@ -367,6 +402,7 @@ function startReview() {
367
  metaRepo.textContent = `${data.owner}/${data.repo}`;
368
  metaPath.textContent = data.path;
369
  metaBranch.textContent = data.branch;
 
370
  metaEl.hidden = false;
371
  tabsEl.hidden = false;
372
  tabContent.classList.add("is-streaming");
@@ -386,11 +422,13 @@ function startReview() {
386
  // Final render with no streaming section
387
  const sections = parseSections(markdown);
388
  updateTabs(sections, null);
389
- // Show badge and copy button
390
  const counts = countIssuePriorities(markdown);
391
  renderIssueBadge(counts);
 
 
 
392
  copyBtn.hidden = false;
393
- shareXBtn.hidden = false;
394
  });
395
 
396
  source.addEventListener("error", (e) => {
 
13
  const tabButtons = document.querySelectorAll(".tab");
14
  const brutalityBtns = document.querySelectorAll(".brutality-btn");
15
  const issueBadge = document.getElementById("issue-badge");
16
+ const reviewMetrics = document.getElementById("review-metrics");
17
  const copyBtn = document.getElementById("copy-btn");
 
18
  const toast = document.getElementById("toast");
19
 
20
  const GITHUB_BLOB_RE = /^https?:\/\/github\.com\/[^/]+\/[^/]+\/blob\/[^/]+\/.+$/;
 
240
  issueBadge.hidden = false;
241
  }
242
 
243
+ /* --- Review metrics --- */
244
 
245
+ function computeMetrics(markdown, totalLines, elapsedMs) {
246
+ // Count ```diff blocks (concrete fixes)
247
+ const diffBlocks = (markdown.match(/```diff/g) || []).length;
248
 
249
+ // Count total issues by priority tag
250
+ const issueCount = (markdown.match(/\[CRITICAL\]|\[HIGH\]|\[MEDIUM\]|\[LOW\]/gi) || []).length;
251
+
252
+ // Extract unique line numbers referenced (e.g. "Line 42", "Lines 10-20")
253
+ const lineRefs = new Set();
254
+ for (const m of markdown.matchAll(/Lines?\s+(\d+)(?:\s*[-–]\s*(\d+))?/gi)) {
255
+ const start = Number(m[1]);
256
+ const end = m[2] ? Number(m[2]) : start;
257
+ for (let i = start; i <= end && i <= totalLines; i++) lineRefs.add(i);
258
+ }
259
+ const coverage = totalLines > 0 ? Math.round((lineRefs.size / totalLines) * 100) : 0;
260
+
261
+ return {
262
+ issues: issueCount,
263
+ fixes: diffBlocks,
264
+ totalLines,
265
+ elapsedSec: (elapsedMs / 1000).toFixed(1),
266
+ coverage,
267
+ };
268
  }
269
 
270
+ function renderMetrics(m) {
271
+ reviewMetrics.textContent = "";
272
+
273
+ const items = [
274
+ { value: m.issues, label: "issues found" },
275
+ { value: m.fixes, label: "concrete fixes" },
276
+ { value: m.totalLines.toLocaleString(), label: "lines analyzed" },
277
+ { value: `${m.elapsedSec}s`, label: "review time" },
278
+ { value: `${m.coverage}%`, label: "line coverage" },
279
+ ];
280
+
281
+ for (const item of items) {
282
+ const span = document.createElement("span");
283
+ span.className = "metric-item";
284
+
285
+ const val = document.createElement("span");
286
+ val.className = "metric-value";
287
+ val.textContent = item.value;
288
+
289
+ const lbl = document.createElement("span");
290
+ lbl.className = "metric-label";
291
+ lbl.textContent = item.label;
292
+
293
+ span.appendChild(val);
294
+ span.appendChild(lbl);
295
+ reviewMetrics.appendChild(span);
296
+ }
297
+
298
+ reviewMetrics.hidden = false;
299
+ }
300
 
301
  /* --- Copy to clipboard --- */
302
 
 
377
  btn.classList.remove("has-content", "streaming");
378
  });
379
  issueBadge.hidden = true;
380
+ reviewMetrics.hidden = true;
381
  copyBtn.hidden = true;
 
382
  fullMarkdown = "";
383
 
384
  if (!url) { showInputError("Please enter a GitHub file URL."); return; }
 
391
  setLoading(true);
392
 
393
  let markdown = "";
394
+ let reviewStartTime = performance.now();
395
+ let fileTotalLines = 0;
396
  const apiUrl = `/api/review?url=${encodeURIComponent(url)}&brutality=${encodeURIComponent(brutalityLevel)}`;
397
  const source = new EventSource(apiUrl);
398
  currentSource = source;
 
402
  metaRepo.textContent = `${data.owner}/${data.repo}`;
403
  metaPath.textContent = data.path;
404
  metaBranch.textContent = data.branch;
405
+ fileTotalLines = data.lines || 0;
406
  metaEl.hidden = false;
407
  tabsEl.hidden = false;
408
  tabContent.classList.add("is-streaming");
 
422
  // Final render with no streaming section
423
  const sections = parseSections(markdown);
424
  updateTabs(sections, null);
425
+ // Show badge, metrics, and copy button
426
  const counts = countIssuePriorities(markdown);
427
  renderIssueBadge(counts);
428
+ const elapsed = performance.now() - reviewStartTime;
429
+ const metrics = computeMetrics(markdown, fileTotalLines, elapsed);
430
+ renderMetrics(metrics);
431
  copyBtn.hidden = false;
 
432
  });
433
 
434
  source.addEventListener("error", (e) => {
public/index.html CHANGED
@@ -258,6 +258,7 @@
258
  </div>
259
 
260
  <div id="issue-badge" hidden></div>
 
261
 
262
  <div id="tabs" hidden>
263
  <nav class="tab-bar">
@@ -267,11 +268,6 @@
267
  <button class="tab" data-section="security">Security</button>
268
  <button class="tab" data-section="suggestions">Suggestions</button>
269
  <button class="tab" data-section="verdicts">Verdicts</button>
270
- <button type="button" id="share-x-btn" hidden title="Share on X">
271
- <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
272
- <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
273
- </svg>
274
- </button>
275
  <button type="button" id="copy-btn" hidden>Copy Review</button>
276
  </nav>
277
  <div id="tab-content" class="tab-content"></div>
 
258
  </div>
259
 
260
  <div id="issue-badge" hidden></div>
261
+ <div id="review-metrics" hidden></div>
262
 
263
  <div id="tabs" hidden>
264
  <nav class="tab-bar">
 
268
  <button class="tab" data-section="security">Security</button>
269
  <button class="tab" data-section="suggestions">Suggestions</button>
270
  <button class="tab" data-section="verdicts">Verdicts</button>
 
 
 
 
 
271
  <button type="button" id="copy-btn" hidden>Copy Review</button>
272
  </nav>
273
  <div id="tab-content" class="tab-content"></div>
public/style.css CHANGED
@@ -493,29 +493,34 @@ h1 {
493
  color: var(--text-muted);
494
  }
495
 
496
- /* ---------- Share on X Button ---------- */
497
 
498
- #share-x-btn {
499
- margin-left: auto;
500
- padding: 6px 10px;
501
- color: var(--text-muted);
 
 
502
  background: var(--surface);
503
  border: 1px solid var(--border);
504
  border-radius: var(--radius);
505
- cursor: pointer;
506
- transition: all 0.15s;
507
- display: inline-flex;
508
- align-items: center;
509
  }
510
 
511
- #share-x-btn:hover {
512
- color: var(--text);
513
- border-color: var(--accent);
 
514
  }
515
 
516
- #share-x-btn:active {
517
- background: var(--accent);
518
- color: #fff;
 
 
 
 
 
519
  }
520
 
521
  /* ---------- Copy Button ---------- */
 
493
  color: var(--text-muted);
494
  }
495
 
496
+ /* ---------- Review Metrics ---------- */
497
 
498
+ #review-metrics {
499
+ display: flex;
500
+ gap: 20px;
501
+ flex-wrap: wrap;
502
+ margin-top: 10px;
503
+ padding: 10px 14px;
504
  background: var(--surface);
505
  border: 1px solid var(--border);
506
  border-radius: var(--radius);
507
+ font-size: 0.85rem;
 
 
 
508
  }
509
 
510
+ .metric-item {
511
+ display: flex;
512
+ align-items: baseline;
513
+ gap: 5px;
514
  }
515
 
516
+ .metric-value {
517
+ font-weight: 700;
518
+ color: var(--accent);
519
+ font-variant-numeric: tabular-nums;
520
+ }
521
+
522
+ .metric-label {
523
+ color: var(--text-muted);
524
  }
525
 
526
  /* ---------- Copy Button ---------- */
server.js CHANGED
@@ -225,7 +225,8 @@ app.get("/api/review", async (req, res) => {
225
  }
226
 
227
  // Send file metadata to the client
228
- send("meta", { owner: meta.owner, repo: meta.repo, branch: meta.branch, path: meta.path });
 
229
 
230
  // Stream from OpenRouter
231
  console.log(`[review] Requesting review from OpenRouter (brutality: ${brutalityLevel})…`);
 
225
  }
226
 
227
  // Send file metadata to the client
228
+ const totalLines = code.split("\n").length;
229
+ send("meta", { owner: meta.owner, repo: meta.repo, branch: meta.branch, path: meta.path, lines: totalLines });
230
 
231
  // Stream from OpenRouter
232
  console.log(`[review] Requesting review from OpenRouter (brutality: ${brutalityLevel})…`);