glassbox 0.4.2 → 0.4.4

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/cli.js CHANGED
@@ -1403,9 +1403,9 @@ function createNewFileDiff(filePath, repoRoot) {
1403
1403
  isBinary: false
1404
1404
  };
1405
1405
  }
1406
- function parseDiff(raw2) {
1406
+ function parseDiff(raw) {
1407
1407
  const files = [];
1408
- const fileChunks = raw2.split(/^diff --git /m).filter(Boolean);
1408
+ const fileChunks = raw.split(/^diff --git /m).filter(Boolean);
1409
1409
  for (const chunk of fileChunks) {
1410
1410
  const headerEnd = chunk.indexOf("@@");
1411
1411
  const header = headerEnd === -1 ? chunk : chunk.slice(0, headerEnd);
@@ -1441,12 +1441,12 @@ function parseDiff(raw2) {
1441
1441
  }
1442
1442
  return files;
1443
1443
  }
1444
- function parseHunks(raw2) {
1444
+ function parseHunks(raw) {
1445
1445
  const hunks = [];
1446
1446
  const hunkRegex = /^@@\s+-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s+@@(.*)/gm;
1447
1447
  let match;
1448
1448
  const hunkStarts = [];
1449
- while ((match = hunkRegex.exec(raw2)) !== null) {
1449
+ while ((match = hunkRegex.exec(raw)) !== null) {
1450
1450
  const groups = match;
1451
1451
  hunkStarts.push({
1452
1452
  index: match.index + match[0].length,
@@ -1458,8 +1458,8 @@ function parseHunks(raw2) {
1458
1458
  }
1459
1459
  for (let i = 0; i < hunkStarts.length; i++) {
1460
1460
  const start = hunkStarts[i];
1461
- const end = i + 1 < hunkStarts.length ? raw2.lastIndexOf("\n@@", hunkStarts[i + 1].index) : raw2.length;
1462
- const body = raw2.slice(start.index, end);
1461
+ const end = i + 1 < hunkStarts.length ? raw.lastIndexOf("\n@@", hunkStarts[i + 1].index) : raw.length;
1462
+ const body = raw.slice(start.index, end);
1463
1463
  const lines = [];
1464
1464
  let oldNum = start.oldStart;
1465
1465
  let newNum = start.newStart;
@@ -4175,24 +4175,7 @@ var SafeHtml = class {
4175
4175
  return this.__html;
4176
4176
  }
4177
4177
  };
4178
- function raw(html) {
4179
- return new SafeHtml(html);
4180
- }
4181
- var VOID_TAGS = /* @__PURE__ */ new Set([
4182
- "area",
4183
- "base",
4184
- "br",
4185
- "col",
4186
- "embed",
4187
- "hr",
4188
- "img",
4189
- "input",
4190
- "link",
4191
- "meta",
4192
- "source",
4193
- "track",
4194
- "wbr"
4195
- ]);
4178
+ var VOID_TAGS = /* @__PURE__ */ new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "source", "track", "wbr"]);
4196
4179
  function renderChildren(children) {
4197
4180
  if (children == null || typeof children === "boolean") return "";
4198
4181
  if (children instanceof SafeHtml) return children.__html;
@@ -4201,10 +4184,134 @@ function renderChildren(children) {
4201
4184
  if (Array.isArray(children)) return children.map(renderChildren).join("");
4202
4185
  return "";
4203
4186
  }
4187
+ var ATTR_ALIASES = {
4188
+ // HTML attributes
4189
+ className: "class",
4190
+ htmlFor: "for",
4191
+ httpEquiv: "http-equiv",
4192
+ acceptCharset: "accept-charset",
4193
+ accessKey: "accesskey",
4194
+ autoCapitalize: "autocapitalize",
4195
+ autoComplete: "autocomplete",
4196
+ autoFocus: "autofocus",
4197
+ autoPlay: "autoplay",
4198
+ colSpan: "colspan",
4199
+ contentEditable: "contenteditable",
4200
+ crossOrigin: "crossorigin",
4201
+ dateTime: "datetime",
4202
+ defaultChecked: "checked",
4203
+ defaultValue: "value",
4204
+ encType: "enctype",
4205
+ formAction: "formaction",
4206
+ formEncType: "formenctype",
4207
+ formMethod: "formmethod",
4208
+ formNoValidate: "formnovalidate",
4209
+ formTarget: "formtarget",
4210
+ hrefLang: "hreflang",
4211
+ inputMode: "inputmode",
4212
+ maxLength: "maxlength",
4213
+ minLength: "minlength",
4214
+ noModule: "nomodule",
4215
+ noValidate: "novalidate",
4216
+ readOnly: "readonly",
4217
+ referrerPolicy: "referrerpolicy",
4218
+ rowSpan: "rowspan",
4219
+ spellCheck: "spellcheck",
4220
+ srcDoc: "srcdoc",
4221
+ srcLang: "srclang",
4222
+ srcSet: "srcset",
4223
+ tabIndex: "tabindex",
4224
+ useMap: "usemap",
4225
+ // SVG presentation attributes (camelCase → kebab-case)
4226
+ strokeWidth: "stroke-width",
4227
+ strokeLinecap: "stroke-linecap",
4228
+ strokeLinejoin: "stroke-linejoin",
4229
+ strokeDasharray: "stroke-dasharray",
4230
+ strokeDashoffset: "stroke-dashoffset",
4231
+ strokeMiterlimit: "stroke-miterlimit",
4232
+ strokeOpacity: "stroke-opacity",
4233
+ fillOpacity: "fill-opacity",
4234
+ fillRule: "fill-rule",
4235
+ clipPath: "clip-path",
4236
+ clipRule: "clip-rule",
4237
+ colorInterpolation: "color-interpolation",
4238
+ colorInterpolationFilters: "color-interpolation-filters",
4239
+ floodColor: "flood-color",
4240
+ floodOpacity: "flood-opacity",
4241
+ lightingColor: "lighting-color",
4242
+ stopColor: "stop-color",
4243
+ stopOpacity: "stop-opacity",
4244
+ shapeRendering: "shape-rendering",
4245
+ imageRendering: "image-rendering",
4246
+ textRendering: "text-rendering",
4247
+ pointerEvents: "pointer-events",
4248
+ vectorEffect: "vector-effect",
4249
+ paintOrder: "paint-order",
4250
+ // SVG text/font attributes
4251
+ fontFamily: "font-family",
4252
+ fontSize: "font-size",
4253
+ fontStyle: "font-style",
4254
+ fontVariant: "font-variant",
4255
+ fontWeight: "font-weight",
4256
+ fontStretch: "font-stretch",
4257
+ textAnchor: "text-anchor",
4258
+ textDecoration: "text-decoration",
4259
+ dominantBaseline: "dominant-baseline",
4260
+ alignmentBaseline: "alignment-baseline",
4261
+ baselineShift: "baseline-shift",
4262
+ letterSpacing: "letter-spacing",
4263
+ wordSpacing: "word-spacing",
4264
+ writingMode: "writing-mode",
4265
+ glyphOrientationHorizontal: "glyph-orientation-horizontal",
4266
+ glyphOrientationVertical: "glyph-orientation-vertical",
4267
+ // SVG marker/gradient/filter attributes
4268
+ markerStart: "marker-start",
4269
+ markerMid: "marker-mid",
4270
+ markerEnd: "marker-end",
4271
+ gradientUnits: "gradientUnits",
4272
+ gradientTransform: "gradientTransform",
4273
+ spreadMethod: "spreadMethod",
4274
+ patternUnits: "patternUnits",
4275
+ patternContentUnits: "patternContentUnits",
4276
+ patternTransform: "patternTransform",
4277
+ maskUnits: "maskUnits",
4278
+ maskContentUnits: "maskContentUnits",
4279
+ filterUnits: "filterUnits",
4280
+ primitiveUnits: "primitiveUnits",
4281
+ clipPathUnits: "clipPathUnits",
4282
+ // SVG xlink (legacy but still used)
4283
+ xlinkHref: "xlink:href",
4284
+ xlinkShow: "xlink:show",
4285
+ xlinkActuate: "xlink:actuate",
4286
+ xlinkType: "xlink:type",
4287
+ xlinkRole: "xlink:role",
4288
+ xlinkTitle: "xlink:title",
4289
+ xlinkArcrole: "xlink:arcrole",
4290
+ xmlBase: "xml:base",
4291
+ xmlLang: "xml:lang",
4292
+ xmlSpace: "xml:space",
4293
+ xmlns: "xmlns",
4294
+ xmlnsXlink: "xmlns:xlink",
4295
+ // SVG filter primitive attributes
4296
+ stdDeviation: "stdDeviation",
4297
+ baseFrequency: "baseFrequency",
4298
+ numOctaves: "numOctaves",
4299
+ kernelMatrix: "kernelMatrix",
4300
+ surfaceScale: "surfaceScale",
4301
+ specularConstant: "specularConstant",
4302
+ specularExponent: "specularExponent",
4303
+ diffuseConstant: "diffuseConstant",
4304
+ pointsAtX: "pointsAtX",
4305
+ pointsAtY: "pointsAtY",
4306
+ pointsAtZ: "pointsAtZ",
4307
+ limitingConeAngle: "limitingConeAngle",
4308
+ tableValues: "tableValues"
4309
+ // viewBox, preserveAspectRatio stay as-is (already correct casing)
4310
+ };
4204
4311
  function renderAttr(key, value) {
4312
+ const name = ATTR_ALIASES[key] ?? key;
4205
4313
  if (value == null || value === false) return "";
4206
- if (value === true) return ` ${key}`;
4207
- const name = key === "className" ? "class" : key === "htmlFor" ? "for" : key;
4314
+ if (value === true) return ` ${name}`;
4208
4315
  let strValue;
4209
4316
  if (value instanceof SafeHtml) {
4210
4317
  strValue = value.__html;
@@ -4225,6 +4332,126 @@ function jsx(tag, props) {
4225
4332
  const childStr = children != null ? renderChildren(children) : "";
4226
4333
  return new SafeHtml(`<${tag}${attrStr}>${childStr}</${tag}>`);
4227
4334
  }
4335
+ function Fragment({ children }) {
4336
+ return new SafeHtml(children != null ? renderChildren(children) : "");
4337
+ }
4338
+
4339
+ // src/icons.tsx
4340
+ var S14 = { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" };
4341
+ var S12 = { ...S14, width: "12", height: "12" };
4342
+ var S16 = { ...S14, width: "16", height: "16" };
4343
+ function IconEdit() {
4344
+ return /* @__PURE__ */ jsx("svg", { ...S14, children: [
4345
+ /* @__PURE__ */ jsx("path", { d: "M17 3a2.85 2.85 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" }),
4346
+ /* @__PURE__ */ jsx("path", { d: "m15 5 4 4" })
4347
+ ] });
4348
+ }
4349
+ function IconTrash() {
4350
+ return /* @__PURE__ */ jsx("svg", { ...S14, children: [
4351
+ /* @__PURE__ */ jsx("path", { d: "M3 6h18" }),
4352
+ /* @__PURE__ */ jsx("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }),
4353
+ /* @__PURE__ */ jsx("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" }),
4354
+ /* @__PURE__ */ jsx("line", { x1: "10", y1: "11", x2: "10", y2: "17" }),
4355
+ /* @__PURE__ */ jsx("line", { x1: "14", y1: "11", x2: "14", y2: "17" })
4356
+ ] });
4357
+ }
4358
+ function IconTrash16() {
4359
+ return /* @__PURE__ */ jsx("svg", { ...S16, children: [
4360
+ /* @__PURE__ */ jsx("path", { d: "M3 6h18" }),
4361
+ /* @__PURE__ */ jsx("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }),
4362
+ /* @__PURE__ */ jsx("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" }),
4363
+ /* @__PURE__ */ jsx("line", { x1: "10", y1: "11", x2: "10", y2: "17" }),
4364
+ /* @__PURE__ */ jsx("line", { x1: "14", y1: "11", x2: "14", y2: "17" })
4365
+ ] });
4366
+ }
4367
+ function IconReveal() {
4368
+ return /* @__PURE__ */ jsx("svg", { ...S12, children: [
4369
+ /* @__PURE__ */ jsx("path", { d: "M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" }),
4370
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "11", x2: "12", y2: "17" }),
4371
+ /* @__PURE__ */ jsx("polyline", { points: "9 14 12 11 15 14" })
4372
+ ] });
4373
+ }
4374
+ function IconZoomOut() {
4375
+ return /* @__PURE__ */ jsx("svg", { ...S14, children: /* @__PURE__ */ jsx("line", { x1: "5", y1: "12", x2: "19", y2: "12" }) });
4376
+ }
4377
+ function IconZoomIn() {
4378
+ return /* @__PURE__ */ jsx("svg", { ...S14, children: [
4379
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
4380
+ /* @__PURE__ */ jsx("line", { x1: "5", y1: "12", x2: "19", y2: "12" })
4381
+ ] });
4382
+ }
4383
+ function IconFit() {
4384
+ return /* @__PURE__ */ jsx("svg", { ...S14, children: [
4385
+ /* @__PURE__ */ jsx("path", { d: "M15 3h6v6" }),
4386
+ /* @__PURE__ */ jsx("path", { d: "M9 21H3v-6" }),
4387
+ /* @__PURE__ */ jsx("path", { d: "M21 3l-7 7" }),
4388
+ /* @__PURE__ */ jsx("path", { d: "M3 21l7-7" })
4389
+ ] });
4390
+ }
4391
+ function IconActualSize() {
4392
+ return /* @__PURE__ */ jsx("svg", { ...S14, children: [
4393
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
4394
+ /* @__PURE__ */ jsx("text", { x: "12", y: "15.5", textAnchor: "middle", fontSize: "9", fontWeight: "bold", fill: "currentColor", stroke: "none", children: "1:1" })
4395
+ ] });
4396
+ }
4397
+
4398
+ // src/utils/charDiff.ts
4399
+ function charDiff(oldStr, newStr) {
4400
+ if (!oldStr && !newStr) return null;
4401
+ if (oldStr === newStr) return null;
4402
+ const lcs = lcsTable(oldStr, newStr);
4403
+ const lcsLen = lcs[oldStr.length][newStr.length];
4404
+ const maxLen = Math.max(oldStr.length, newStr.length);
4405
+ if (maxLen === 0) return null;
4406
+ if (lcsLen / maxLen < 0.2) return null;
4407
+ const oldCommon = /* @__PURE__ */ new Set();
4408
+ const newCommon = /* @__PURE__ */ new Set();
4409
+ let i = oldStr.length, j = newStr.length;
4410
+ while (i > 0 && j > 0) {
4411
+ if (oldStr[i - 1] === newStr[j - 1]) {
4412
+ oldCommon.add(i - 1);
4413
+ newCommon.add(j - 1);
4414
+ i--;
4415
+ j--;
4416
+ } else if (lcs[i - 1][j] > lcs[i][j - 1]) {
4417
+ i--;
4418
+ } else {
4419
+ j--;
4420
+ }
4421
+ }
4422
+ return {
4423
+ oldSegments: buildSegments(oldStr, oldCommon),
4424
+ newSegments: buildSegments(newStr, newCommon)
4425
+ };
4426
+ }
4427
+ function lcsTable(a, b) {
4428
+ const m = a.length, n = b.length;
4429
+ const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
4430
+ for (let i = 1; i <= m; i++) {
4431
+ for (let j = 1; j <= n; j++) {
4432
+ dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] + 1 : Math.max(dp[i - 1][j], dp[i][j - 1]);
4433
+ }
4434
+ }
4435
+ return dp;
4436
+ }
4437
+ function buildSegments(str, commonPositions) {
4438
+ const segments = [];
4439
+ let current = "";
4440
+ let currentChanged = false;
4441
+ for (let i = 0; i < str.length; i++) {
4442
+ const changed = !commonPositions.has(i);
4443
+ if (changed !== currentChanged && current.length > 0) {
4444
+ segments.push({ text: current, changed: currentChanged });
4445
+ current = "";
4446
+ }
4447
+ currentChanged = changed;
4448
+ current += str[i];
4449
+ }
4450
+ if (current.length > 0) {
4451
+ segments.push({ text: current, changed: currentChanged });
4452
+ }
4453
+ return segments;
4454
+ }
4228
4455
 
4229
4456
  // src/components/imageDiff.tsx
4230
4457
  function ImageDiff({ file, diff, fontWarning, baseWidth, baseHeight }) {
@@ -4274,7 +4501,6 @@ function ImageDiff({ file, diff, fontWarning, baseWidth, baseHeight }) {
4274
4501
  }
4275
4502
 
4276
4503
  // src/components/diffView.tsx
4277
- var revealSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/><line x1="12" y1="11" x2="12" y2="17"/><polyline points="9 14 12 11 15 14"/></svg>';
4278
4504
  function DiffView({ file, diff, annotations, mode }) {
4279
4505
  const annotationsByLine = {};
4280
4506
  for (const a of annotations) {
@@ -4293,7 +4519,7 @@ function DiffView({ file, diff, annotations, mode }) {
4293
4519
  /* @__PURE__ */ jsx("div", { className: "diff-header", children: [
4294
4520
  /* @__PURE__ */ jsx("div", { className: "diff-header-file", children: [
4295
4521
  /* @__PURE__ */ jsx("span", { className: "file-path", children: diff.filePath }),
4296
- /* @__PURE__ */ jsx("button", { className: "reveal-btn", "data-file-id": file.id, title: "Reveal in file manager", children: raw(revealSvg) })
4522
+ /* @__PURE__ */ jsx("button", { className: "reveal-btn", "data-file-id": file.id, title: "Reveal in file manager", children: /* @__PURE__ */ jsx(IconReveal, {}) })
4297
4523
  ] }),
4298
4524
  /* @__PURE__ */ jsx("div", { className: "diff-header-actions", children: /* @__PURE__ */ jsx("span", { className: `file-status ${diff.status}`, children: diff.status }) })
4299
4525
  ] }),
@@ -4354,7 +4580,7 @@ function SplitDiff({ hunks, annotationsByLine }) {
4354
4580
  "data-new-line": group.pair.left?.newNum ?? group.pair.right?.newNum ?? "",
4355
4581
  children: [
4356
4582
  /* @__PURE__ */ jsx("span", { className: "gutter", "data-line-number": group.pair.left?.oldNum ?? "" }),
4357
- /* @__PURE__ */ jsx("span", { className: "code", children: group.pair.left ? raw(escapeHtml(group.pair.left.content)) : "" })
4583
+ /* @__PURE__ */ jsx("span", { className: "code", children: renderPairContent(group.pair, "left") })
4358
4584
  ]
4359
4585
  }
4360
4586
  ),
@@ -4366,7 +4592,7 @@ function SplitDiff({ hunks, annotationsByLine }) {
4366
4592
  "data-side": "new",
4367
4593
  children: [
4368
4594
  /* @__PURE__ */ jsx("span", { className: "gutter", "data-line-number": group.pair.right?.newNum ?? "" }),
4369
- /* @__PURE__ */ jsx("span", { className: "code", children: group.pair.right ? raw(escapeHtml(group.pair.right.content)) : "" })
4595
+ /* @__PURE__ */ jsx("span", { className: "code", children: renderPairContent(group.pair, "right") })
4370
4596
  ]
4371
4597
  }
4372
4598
  )
@@ -4416,7 +4642,7 @@ function SplitDiff({ hunks, annotationsByLine }) {
4416
4642
  "data-new-line": pair.left?.newNum ?? pair.right?.newNum ?? "",
4417
4643
  children: [
4418
4644
  /* @__PURE__ */ jsx("span", { className: "gutter", "data-line-number": pair.left?.oldNum ?? "" }),
4419
- /* @__PURE__ */ jsx("span", { className: "code", children: pair.left ? raw(escapeHtml(pair.left.content)) : "" })
4645
+ /* @__PURE__ */ jsx("span", { className: "code", children: renderPairContent(pair, "left") })
4420
4646
  ]
4421
4647
  }
4422
4648
  );
@@ -4461,7 +4687,7 @@ function SplitDiff({ hunks, annotationsByLine }) {
4461
4687
  "data-side": "new",
4462
4688
  children: [
4463
4689
  /* @__PURE__ */ jsx("span", { className: "gutter", "data-line-number": pair.right?.newNum ?? "" }),
4464
- /* @__PURE__ */ jsx("span", { className: "code", children: pair.right ? raw(escapeHtml(pair.right.content)) : "" })
4690
+ /* @__PURE__ */ jsx("span", { className: "code", children: renderPairContent(pair, "right") })
4465
4691
  ]
4466
4692
  }
4467
4693
  );
@@ -4469,6 +4695,20 @@ function SplitDiff({ hunks, annotationsByLine }) {
4469
4695
  ] });
4470
4696
  }) });
4471
4697
  }
4698
+ function renderSegments(segments) {
4699
+ return /* @__PURE__ */ jsx(Fragment, { children: segments.map((s) => s.changed ? /* @__PURE__ */ jsx("span", { className: "char-change", children: s.text }) : /* @__PURE__ */ jsx(Fragment, { children: s.text })) });
4700
+ }
4701
+ function renderPairContent(pair, side) {
4702
+ const line = side === "left" ? pair.left : pair.right;
4703
+ if (!line) return "";
4704
+ if (pair.left && pair.right && pair.left.type === "remove" && pair.right.type === "add") {
4705
+ const diff = charDiff(pair.left.content, pair.right.content);
4706
+ if (diff) {
4707
+ return renderSegments(side === "left" ? diff.oldSegments : diff.newSegments);
4708
+ }
4709
+ }
4710
+ return line.content;
4711
+ }
4472
4712
  function pairLines(lines) {
4473
4713
  const pairs = [];
4474
4714
  let i = 0;
@@ -4502,55 +4742,88 @@ function pairLines(lines) {
4502
4742
  }
4503
4743
  return pairs;
4504
4744
  }
4745
+ function buildUnifiedCharDiffs(lines) {
4746
+ const result = /* @__PURE__ */ new Map();
4747
+ let i = 0;
4748
+ while (i < lines.length) {
4749
+ if (lines[i].type === "remove") {
4750
+ const removes = [];
4751
+ while (i < lines.length && lines[i].type === "remove") {
4752
+ removes.push(lines[i]);
4753
+ i++;
4754
+ }
4755
+ const adds = [];
4756
+ while (i < lines.length && lines[i].type === "add") {
4757
+ adds.push(lines[i]);
4758
+ i++;
4759
+ }
4760
+ const pairCount = Math.min(removes.length, adds.length);
4761
+ for (let j = 0; j < pairCount; j++) {
4762
+ const diff = charDiff(removes[j].content, adds[j].content);
4763
+ if (diff) {
4764
+ result.set(removes[j], diff.oldSegments);
4765
+ result.set(adds[j], diff.newSegments);
4766
+ }
4767
+ }
4768
+ } else {
4769
+ i++;
4770
+ }
4771
+ }
4772
+ return result;
4773
+ }
4505
4774
  function UnifiedDiff({ hunks, annotationsByLine }) {
4506
4775
  const lastHunk = hunks[hunks.length - 1];
4507
4776
  const tailStart = lastHunk ? lastHunk.newStart + lastHunk.newCount : 1;
4508
4777
  return /* @__PURE__ */ jsx("div", { className: "diff-table-unified", children: [
4509
- hunks.map((hunk, hunkIdx) => /* @__PURE__ */ jsx("div", { className: "hunk-block", children: [
4510
- /* @__PURE__ */ jsx(
4511
- "div",
4512
- {
4513
- className: "hunk-separator",
4514
- "data-hunk-idx": hunkIdx,
4515
- "data-old-start": hunk.oldStart,
4516
- "data-old-count": hunk.oldCount,
4517
- "data-new-start": hunk.newStart,
4518
- "data-new-count": hunk.newCount,
4519
- children: [
4520
- "@@ -",
4521
- hunk.oldStart,
4522
- ",",
4523
- hunk.oldCount,
4524
- " +",
4525
- hunk.newStart,
4526
- ",",
4527
- hunk.newCount,
4528
- " @@"
4529
- ]
4530
- }
4531
- ),
4532
- hunk.lines.map((line) => {
4533
- const lineNum = line.type === "remove" ? line.oldNum : line.newNum;
4534
- const side = line.type === "remove" ? "old" : "new";
4535
- const anns = annotationsByLine[`${lineNum}:${side}`] ?? [];
4536
- return /* @__PURE__ */ jsx("div", { children: [
4537
- /* @__PURE__ */ jsx(
4538
- "div",
4539
- {
4540
- className: `diff-line ${line.type}${anns.length ? " has-annotation" : ""}`,
4541
- "data-line": lineNum,
4542
- "data-side": side,
4543
- children: [
4544
- /* @__PURE__ */ jsx("span", { className: "gutter-old", "data-line-number": line.oldNum ?? "" }),
4545
- /* @__PURE__ */ jsx("span", { className: "gutter-new", "data-line-number": line.newNum ?? "" }),
4546
- /* @__PURE__ */ jsx("span", { className: "code", children: raw(escapeHtml(line.content)) })
4547
- ]
4548
- }
4549
- ),
4550
- anns.length > 0 ? /* @__PURE__ */ jsx(AnnotationRows, { annotations: anns }) : null
4551
- ] });
4552
- })
4553
- ] })),
4778
+ hunks.map((hunk, hunkIdx) => {
4779
+ const charDiffs = buildUnifiedCharDiffs(hunk.lines);
4780
+ return /* @__PURE__ */ jsx("div", { className: "hunk-block", children: [
4781
+ /* @__PURE__ */ jsx(
4782
+ "div",
4783
+ {
4784
+ className: "hunk-separator",
4785
+ "data-hunk-idx": hunkIdx,
4786
+ "data-old-start": hunk.oldStart,
4787
+ "data-old-count": hunk.oldCount,
4788
+ "data-new-start": hunk.newStart,
4789
+ "data-new-count": hunk.newCount,
4790
+ children: [
4791
+ "@@ -",
4792
+ hunk.oldStart,
4793
+ ",",
4794
+ hunk.oldCount,
4795
+ " +",
4796
+ hunk.newStart,
4797
+ ",",
4798
+ hunk.newCount,
4799
+ " @@"
4800
+ ]
4801
+ }
4802
+ ),
4803
+ hunk.lines.map((line) => {
4804
+ const lineNum = line.type === "remove" ? line.oldNum : line.newNum;
4805
+ const side = line.type === "remove" ? "old" : "new";
4806
+ const anns = annotationsByLine[`${lineNum}:${side}`] ?? [];
4807
+ const segments = charDiffs.get(line);
4808
+ return /* @__PURE__ */ jsx("div", { children: [
4809
+ /* @__PURE__ */ jsx(
4810
+ "div",
4811
+ {
4812
+ className: `diff-line ${line.type}${anns.length ? " has-annotation" : ""}`,
4813
+ "data-line": lineNum,
4814
+ "data-side": side,
4815
+ children: [
4816
+ /* @__PURE__ */ jsx("span", { className: "gutter-old", "data-line-number": line.oldNum ?? "" }),
4817
+ /* @__PURE__ */ jsx("span", { className: "gutter-new", "data-line-number": line.newNum ?? "" }),
4818
+ /* @__PURE__ */ jsx("span", { className: "code", children: segments ? renderSegments(segments) : line.content })
4819
+ ]
4820
+ }
4821
+ ),
4822
+ anns.length > 0 ? /* @__PURE__ */ jsx(AnnotationRows, { annotations: anns }) : null
4823
+ ] });
4824
+ })
4825
+ ] });
4826
+ }),
4554
4827
  /* @__PURE__ */ jsx("div", { className: "hunk-separator hunk-expander-tail", "data-start": tailStart, children: "\u2195 Show remaining lines" })
4555
4828
  ] });
4556
4829
  }
@@ -4567,8 +4840,8 @@ function AnnotationRows({ annotations }) {
4567
4840
  /* @__PURE__ */ jsx("span", { className: "annotation-text", children: a.content }),
4568
4841
  /* @__PURE__ */ jsx("div", { className: "annotation-actions", children: [
4569
4842
  a.is_stale ? /* @__PURE__ */ jsx("button", { className: "btn btn-xs btn-keep", "data-action": "keep", children: "Keep" }) : null,
4570
- /* @__PURE__ */ jsx("button", { className: "btn btn-xs btn-icon", "data-action": "edit", title: "Edit", children: raw('<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.85 2.85 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>') }),
4571
- /* @__PURE__ */ jsx("button", { className: "btn btn-xs btn-icon btn-danger", "data-action": "delete", title: "Delete", children: raw('<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>') })
4843
+ /* @__PURE__ */ jsx("button", { className: "btn btn-xs btn-icon", "data-action": "edit", title: "Edit", children: /* @__PURE__ */ jsx(IconEdit, {}) }),
4844
+ /* @__PURE__ */ jsx("button", { className: "btn btn-xs btn-icon btn-danger", "data-action": "delete", title: "Delete", children: /* @__PURE__ */ jsx(IconTrash, {}) })
4572
4845
  ] })
4573
4846
  ]
4574
4847
  }
@@ -4678,7 +4951,6 @@ function Layout({ title, reviewId, children }) {
4678
4951
  function titleCase(s) {
4679
4952
  return s.replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
4680
4953
  }
4681
- var trashIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>';
4682
4954
  function shortenArgs(args) {
4683
4955
  const shaPattern = /\b([0-9a-f]{7,40})\b/gi;
4684
4956
  const result = { hasLong: false };
@@ -4726,7 +4998,7 @@ function ReviewHistory({ reviews, currentReviewId }) {
4726
4998
  " | Created: ",
4727
4999
  r.created_at
4728
5000
  ] }),
4729
- !isCurrent ? /* @__PURE__ */ jsx("button", { className: "delete-review-btn", "data-delete-id": r.id, title: "Delete review", children: raw(trashIcon) }) : null
5001
+ !isCurrent ? /* @__PURE__ */ jsx("button", { className: "delete-review-btn", "data-delete-id": r.id, title: "Delete review", children: /* @__PURE__ */ jsx(IconTrash16, {}) }) : null
4730
5002
  ] }) }),
4731
5003
  isCurrent && hasOtherReviews ? /* @__PURE__ */ jsx("div", { className: "bulk-actions", children: [
4732
5004
  /* @__PURE__ */ jsx("span", { children: "Bulk actions:" }),
@@ -4736,94 +5008,12 @@ function ReviewHistory({ reviews, currentReviewId }) {
4736
5008
  ] });
4737
5009
  }) }),
4738
5010
  /* @__PURE__ */ jsx("a", { href: "/", className: "btn btn-link", style: "margin-top:16px;display:inline-block", children: "Back to current review" }),
4739
- /* @__PURE__ */ jsx("script", { children: raw(getHistoryScript()) })
5011
+ /* @__PURE__ */ jsx("script", { src: "/static/history.js" })
4740
5012
  ] });
4741
5013
  }
4742
- function getHistoryScript() {
4743
- return `
4744
- (function() {
4745
- function esc(s) {
4746
- var d = document.createElement('div');
4747
- d.textContent = s;
4748
- return d.innerHTML;
4749
- }
4750
-
4751
- function updateBulkVisibility() {
4752
- var bulk = document.querySelector('.bulk-actions');
4753
- if (bulk && !document.querySelector('.delete-review-btn')) {
4754
- bulk.remove();
4755
- }
4756
- }
4757
-
4758
- // Single review delete
4759
- document.querySelectorAll('.delete-review-btn').forEach(function(btn) {
4760
- btn.addEventListener('click', function(e) {
4761
- e.preventDefault();
4762
- e.stopPropagation();
4763
- var id = btn.dataset.deleteId;
4764
- showConfirm('Delete this review? This cannot be undone.', function() {
4765
- fetch('/api/review/' + encodeURIComponent(id), { method: 'DELETE' })
4766
- .then(function(r) { return r.json(); })
4767
- .then(function() {
4768
- btn.closest('.history-item-link').parentElement.remove();
4769
- updateBulkVisibility();
4770
- });
4771
- });
4772
- });
4773
- });
4774
-
4775
- // Bulk delete completed
4776
- var delCompletedBtn = document.getElementById('delete-completed-btn');
4777
- if (delCompletedBtn) {
4778
- delCompletedBtn.addEventListener('click', function() {
4779
- showConfirm('Delete all completed reviews (except current)? This cannot be undone.', function() {
4780
- fetch('/api/reviews/delete-completed', { method: 'POST', headers: { 'Content-Type': 'application/json' } })
4781
- .then(function(r) { return r.json(); })
4782
- .then(function() { location.reload(); });
4783
- });
4784
- });
4785
- }
4786
-
4787
- // Bulk delete all
4788
- var delAllBtn = document.getElementById('delete-all-btn');
4789
- if (delAllBtn) {
4790
- delAllBtn.addEventListener('click', function() {
4791
- showConfirm('Delete ALL reviews except the current one? This cannot be undone.', function() {
4792
- fetch('/api/reviews/delete-all', { method: 'POST', headers: { 'Content-Type': 'application/json' } })
4793
- .then(function(r) { return r.json(); })
4794
- .then(function() { location.reload(); });
4795
- });
4796
- });
4797
- }
4798
-
4799
- function showConfirm(message, onConfirm) {
4800
- var overlay = document.createElement('div');
4801
- overlay.className = 'modal-overlay';
4802
- overlay.innerHTML =
4803
- '<div class="modal">' +
4804
- '<h3>Confirm</h3>' +
4805
- '<p>' + esc(message) + '</p>' +
4806
- '<div class="modal-actions">' +
4807
- '<button class="btn btn-sm modal-cancel">Cancel</button>' +
4808
- '<button class="btn btn-sm btn-danger modal-confirm">Delete</button>' +
4809
- '</div>' +
4810
- '</div>';
4811
- overlay.querySelector('.modal-cancel').addEventListener('click', function() { overlay.remove(); });
4812
- overlay.querySelector('.modal-confirm').addEventListener('click', function() { overlay.remove(); onConfirm(); });
4813
- overlay.addEventListener('click', function(e) { if (e.target === overlay) overlay.remove(); });
4814
- document.body.appendChild(overlay);
4815
- }
4816
- })();
4817
- `;
4818
- }
4819
5014
 
4820
5015
  // src/routes/pages.tsx
4821
5016
  init_queries();
4822
- var zoomOutSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"/></svg>';
4823
- var zoomInSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>';
4824
- var actualSizeSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><text x="12" y="15.5" text-anchor="middle" font-size="9" font-weight="bold" fill="currentColor" stroke="none">1:1</text></svg>';
4825
- var fitSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6"/><path d="M9 21H3v-6"/><path d="M21 3l-7 7"/><path d="M3 21l7-7"/></svg>';
4826
- var revealSvgIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/><line x1="12" y1="11" x2="12" y2="17"/><polyline points="9 14 12 11 15 14"/></svg>';
4827
5017
  var pageRoutes = new Hono3();
4828
5018
  pageRoutes.get("/", async (c) => {
4829
5019
  const reviewId = c.get("reviewId");
@@ -4894,10 +5084,10 @@ pageRoutes.get("/", async (c) => {
4894
5084
  /* @__PURE__ */ jsx("button", { className: "segment", "data-image-mode": "image", style: "display:none", children: "Image" })
4895
5085
  ] }) }),
4896
5086
  /* @__PURE__ */ jsx("div", { className: "diff-toolbar-right", children: [
4897
- /* @__PURE__ */ jsx("button", { className: "image-zoom-btn", "data-zoom-action": "out", title: "Zoom out", children: raw(zoomOutSvg) }),
4898
- /* @__PURE__ */ jsx("button", { className: "image-zoom-btn", "data-zoom-action": "fit", title: "Fit to view", children: raw(fitSvg) }),
4899
- /* @__PURE__ */ jsx("button", { className: "image-zoom-btn", "data-zoom-action": "actual", title: "Actual size (1:1)", children: raw(actualSizeSvg) }),
4900
- /* @__PURE__ */ jsx("button", { className: "image-zoom-btn", "data-zoom-action": "in", title: "Zoom in", children: raw(zoomInSvg) })
5087
+ /* @__PURE__ */ jsx("button", { className: "image-zoom-btn", "data-zoom-action": "out", title: "Zoom out", children: /* @__PURE__ */ jsx(IconZoomOut, {}) }),
5088
+ /* @__PURE__ */ jsx("button", { className: "image-zoom-btn", "data-zoom-action": "fit", title: "Fit to view", children: /* @__PURE__ */ jsx(IconFit, {}) }),
5089
+ /* @__PURE__ */ jsx("button", { className: "image-zoom-btn", "data-zoom-action": "actual", title: "Actual size (1:1)", children: /* @__PURE__ */ jsx(IconActualSize, {}) }),
5090
+ /* @__PURE__ */ jsx("button", { className: "image-zoom-btn", "data-zoom-action": "in", title: "Zoom in", children: /* @__PURE__ */ jsx(IconZoomIn, {}) })
4901
5091
  ] })
4902
5092
  ] })
4903
5093
  ] })
@@ -4938,7 +5128,7 @@ pageRoutes.get("/file/:fileId", async (c) => {
4938
5128
  /* @__PURE__ */ jsx("div", { className: "diff-header", children: [
4939
5129
  /* @__PURE__ */ jsx("div", { className: "diff-header-file", children: [
4940
5130
  /* @__PURE__ */ jsx("span", { className: "file-path", children: diff.filePath }),
4941
- /* @__PURE__ */ jsx("button", { className: "reveal-btn", "data-file-id": file.id, title: "Reveal in file manager", children: raw(revealSvgIcon) })
5131
+ /* @__PURE__ */ jsx("button", { className: "reveal-btn", "data-file-id": file.id, title: "Reveal in file manager", children: /* @__PURE__ */ jsx(IconReveal, {}) })
4942
5132
  ] }),
4943
5133
  /* @__PURE__ */ jsx("div", { className: "diff-header-actions", children: /* @__PURE__ */ jsx("span", { className: `file-status ${diff.status}`, children: diff.status }) })
4944
5134
  ] }),
@@ -5045,10 +5235,10 @@ pageRoutes.get("/review/:reviewId", async (c) => {
5045
5235
  /* @__PURE__ */ jsx("button", { className: "segment", "data-image-mode": "image", style: "display:none", children: "Image" })
5046
5236
  ] }) }),
5047
5237
  /* @__PURE__ */ jsx("div", { className: "diff-toolbar-right", children: [
5048
- /* @__PURE__ */ jsx("button", { className: "image-zoom-btn", "data-zoom-action": "out", title: "Zoom out", children: raw(zoomOutSvg) }),
5049
- /* @__PURE__ */ jsx("button", { className: "image-zoom-btn", "data-zoom-action": "fit", title: "Fit to view", children: raw(fitSvg) }),
5050
- /* @__PURE__ */ jsx("button", { className: "image-zoom-btn", "data-zoom-action": "actual", title: "Actual size (1:1)", children: raw(actualSizeSvg) }),
5051
- /* @__PURE__ */ jsx("button", { className: "image-zoom-btn", "data-zoom-action": "in", title: "Zoom in", children: raw(zoomInSvg) })
5238
+ /* @__PURE__ */ jsx("button", { className: "image-zoom-btn", "data-zoom-action": "out", title: "Zoom out", children: /* @__PURE__ */ jsx(IconZoomOut, {}) }),
5239
+ /* @__PURE__ */ jsx("button", { className: "image-zoom-btn", "data-zoom-action": "fit", title: "Fit to view", children: /* @__PURE__ */ jsx(IconFit, {}) }),
5240
+ /* @__PURE__ */ jsx("button", { className: "image-zoom-btn", "data-zoom-action": "actual", title: "Actual size (1:1)", children: /* @__PURE__ */ jsx(IconActualSize, {}) }),
5241
+ /* @__PURE__ */ jsx("button", { className: "image-zoom-btn", "data-zoom-action": "in", title: "Zoom in", children: /* @__PURE__ */ jsx(IconZoomIn, {}) })
5052
5242
  ] })
5053
5243
  ] })
5054
5244
  ] })
@@ -5099,6 +5289,10 @@ async function startServer(port, reviewId, repoRoot, options) {
5099
5289
  const js = readFileSync8(join7(distDir, "app.global.js"), "utf-8");
5100
5290
  return c.text(js, 200, { "Content-Type": "application/javascript", "Cache-Control": "no-cache" });
5101
5291
  });
5292
+ app.get("/static/history.js", (c) => {
5293
+ const js = readFileSync8(join7(distDir, "history.global.js"), "utf-8");
5294
+ return c.text(js, 200, { "Content-Type": "application/javascript", "Cache-Control": "no-cache" });
5295
+ });
5102
5296
  app.route("/api", apiRoutes);
5103
5297
  app.route("/api/ai", aiApiRoutes);
5104
5298
  app.route("/", pageRoutes);
@@ -5503,7 +5697,7 @@ async function main() {
5503
5697
  console.log("AI service test mode enabled \u2014 using mock AI responses");
5504
5698
  }
5505
5699
  if (debug) {
5506
- console.log(`[debug] Build timestamp: ${"2026-03-20T01:33:08.659Z"}`);
5700
+ console.log(`[debug] Build timestamp: ${"2026-03-21T23:17:12.455Z"}`);
5507
5701
  }
5508
5702
  if (projectDir) {
5509
5703
  process.chdir(projectDir);