diffsplain 0.8.3 → 0.10.0

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.
Files changed (94) hide show
  1. package/README.md +26 -5
  2. package/dist/assets/{angular-html-e4D_THVy.js → angular-html-2u-u5H-N.js} +1 -1
  3. package/dist/assets/{angular-ts-Cx3870JF.js → angular-ts-4_HVGlKp.js} +1 -1
  4. package/dist/assets/{apl-DZuu22Wh.js → apl-icaNWZX3.js} +1 -1
  5. package/dist/assets/{astro-K-NemFAk.js → astro-HvIOE4vz.js} +1 -1
  6. package/dist/assets/{blade-ConbVu3U.js → blade-BpKJV8ev.js} +1 -1
  7. package/dist/assets/{c-DtmaBqln.js → c-BNvtxyRQ.js} +1 -1
  8. package/dist/assets/{cobol-BMHoPOfB.js → cobol-8js4hfxa.js} +1 -1
  9. package/dist/assets/{coffee-DfweJ19Y.js → coffee-DXg_piQ9.js} +1 -1
  10. package/dist/assets/{cpp-CPh_zIOW.js → cpp-BZDFAjyh.js} +1 -1
  11. package/dist/assets/{crystal-CAEX7C1J.js → crystal-aCF3ira4.js} +1 -1
  12. package/dist/assets/{css-C8ihGqCA.js → css-DfRtUkhE.js} +1 -1
  13. package/dist/assets/{edge-DVZPwQ9j.js → edge-vS6_AmQv.js} +1 -1
  14. package/dist/assets/{elixir-D03TKulP.js → elixir-Ce0nUHSq.js} +1 -1
  15. package/dist/assets/{elm-JUX-2xVV.js → elm-DmaeyizZ.js} +1 -1
  16. package/dist/assets/{erb-HbktAFZJ.js → erb-DD6OUyvt.js} +1 -1
  17. package/dist/assets/{git-rebase-Ccd_ELAP.js → git-rebase-HbJMn7iS.js} +1 -1
  18. package/dist/assets/{glimmer-js-BcinUfi2.js → glimmer-js-BRuqbNaV.js} +1 -1
  19. package/dist/assets/{glimmer-ts-DuN2UkN9.js → glimmer-ts-ZBpkdEEA.js} +1 -1
  20. package/dist/assets/{glsl-BWmCEPsR.js → glsl-CrQ4GScf.js} +1 -1
  21. package/dist/assets/{graphql-DJGYoBFW.js → graphql-Tn6PBdQY.js} +1 -1
  22. package/dist/assets/{hack-ZPqBE9w3.js → hack-CoApvrbo.js} +1 -1
  23. package/dist/assets/{haml-CZDZk6Gw.js → haml-COKq8Xjp.js} +1 -1
  24. package/dist/assets/{handlebars-d23PDS0n.js → handlebars-CAna2DTA.js} +1 -1
  25. package/dist/assets/{html-BgzX2DyB.js → html-Ch4xJ7YF.js} +1 -1
  26. package/dist/assets/{html-derivative-Bq6M-ZFw.js → html-derivative-0ITK-MAI.js} +1 -1
  27. package/dist/assets/{http-DVhMl_cW.js → http-DiMWQ8Qr.js} +1 -1
  28. package/dist/assets/{hurl-CbspBAqF.js → hurl-OcXymC0J.js} +1 -1
  29. package/dist/assets/index-BmcCug2M.js +1574 -0
  30. package/dist/assets/index-CADUNiTy.css +2 -0
  31. package/dist/assets/{java--YZbq0z2.js → java-CFb3qN4r.js} +1 -1
  32. package/dist/assets/{javascript-cvRuifRJ.js → javascript-CRmk2JrF.js} +1 -1
  33. package/dist/assets/{jinja-BeZmr3LP.js → jinja-Da6lDTEy.js} +1 -1
  34. package/dist/assets/{jison-qrpGZaxs.js → jison-Bnivzmtu.js} +1 -1
  35. package/dist/assets/{json-4xUzsNJn.js → json-m8vJP_KK.js} +1 -1
  36. package/dist/assets/{jsx-BlXrTlSg.js → jsx-C3HX_Sbu.js} +1 -1
  37. package/dist/assets/{julia-IN3jzmBc.js → julia-Ckg-wSHA.js} +1 -1
  38. package/dist/assets/{just-C_50nLQH.js → just-DCjIAyvO.js} +1 -1
  39. package/dist/assets/{latex-Cd-_2IS1.js → latex-CrYSm4pl.js} +1 -1
  40. package/dist/assets/{liquid-CEJf6Xg5.js → liquid-MQUFSPYU.js} +1 -1
  41. package/dist/assets/{lua-BlaV_n8i.js → lua-DcuzJJ77.js} +1 -1
  42. package/dist/assets/{marko-Cb-NPdc_.js → marko-4JNmQaB4.js} +1 -1
  43. package/dist/assets/{mdc-Dt4ywVAS.js → mdc-C_j8HvRL.js} +1 -1
  44. package/dist/assets/{nginx-C_6SKViP.js → nginx-ta3Gn1HE.js} +1 -1
  45. package/dist/assets/{nim-BbkBjoeZ.js → nim-BwWLst9M.js} +1 -1
  46. package/dist/assets/{perl-CYPU6TVa.js → perl-DfW7Uq5O.js} +1 -1
  47. package/dist/assets/{php-B6Oqilrc.js → php-DGf5G3Cf.js} +1 -1
  48. package/dist/assets/{pug-C9Poo2KU.js → pug-CWI-NaYz.js} +1 -1
  49. package/dist/assets/{qml-DkMURMPv.js → qml-Csblgh7U.js} +1 -1
  50. package/dist/assets/{r-Bm-Gfi0p.js → r-QGooaGDI.js} +1 -1
  51. package/dist/assets/{razor-B0LqOok3.js → razor-Dp9ihLYf.js} +1 -1
  52. package/dist/assets/{regexp-jAbMHzwe.js → regexp-DwUnLcVr.js} +1 -1
  53. package/dist/assets/{rst-BlWHfg8c.js → rst-BY8GIwDJ.js} +1 -1
  54. package/dist/assets/{ruby-Bt5lKwPL.js → ruby-VgzI9htT.js} +1 -1
  55. package/dist/assets/{sas-TuY2-8EF.js → sas-DYLsm-0j.js} +1 -1
  56. package/dist/assets/{scss-Dh1SsWlK.js → scss-tIFkeUQX.js} +1 -1
  57. package/dist/assets/{shellscript-CThiwmnp.js → shellscript-CHjuQ4Pl.js} +1 -1
  58. package/dist/assets/{shellsession-DQ5iRUWd.js → shellsession-Bv0S0hj9.js} +1 -1
  59. package/dist/assets/{soy-BfM-BrZG.js → soy-D6U1tZod.js} +1 -1
  60. package/dist/assets/{sql-F617W1uN.js → sql-CGz8kjKv.js} +1 -1
  61. package/dist/assets/{stata-Ctov3ki2.js → stata-C9ytERUq.js} +1 -1
  62. package/dist/assets/{surrealql-C-i2bJGB.js → surrealql-DRfRnaYu.js} +1 -1
  63. package/dist/assets/{svelte-oZT41pTl.js → svelte-wYnk_1sI.js} +1 -1
  64. package/dist/assets/{templ-CxX9Hf3e.js → templ-DWrGVE0u.js} +1 -1
  65. package/dist/assets/{tex-GyzJq8GJ.js → tex-CYTzoUJh.js} +1 -1
  66. package/dist/assets/{ts-tags-BugzKu0x.js → ts-tags-1TIXEGea.js} +1 -1
  67. package/dist/assets/{tsx-DLqwDEUq.js → tsx-a3BoAoOh.js} +1 -1
  68. package/dist/assets/{twig-DADiR9Vq.js → twig-e3dvNv6o.js} +1 -1
  69. package/dist/assets/{typescript-BlDIG_FO.js → typescript-BBhnpnTf.js} +1 -1
  70. package/dist/assets/{vue-COXmmg2A.js → vue-DzzVbu9q.js} +1 -1
  71. package/dist/assets/{vue-html-Cy_3KYj1.js → vue-html-quBNv38K.js} +1 -1
  72. package/dist/assets/{vue-vine-CUqOY8dY.js → vue-vine-CDuR69FE.js} +1 -1
  73. package/dist/assets/{xml-C9JY7Wf6.js → xml-DMifOLSe.js} +1 -1
  74. package/dist/assets/{xsl-DBCl8hSu.js → xsl-D8Ef-5HK.js} +1 -1
  75. package/dist/assets/{yaml-BbMebKkS.js → yaml-5c38gpmA.js} +1 -1
  76. package/dist/index.html +2 -2
  77. package/package.json +12 -3
  78. package/scripts/agent-config.mjs +184 -0
  79. package/scripts/agent-exclusions.mjs +10 -0
  80. package/scripts/agent-review.mjs +94 -0
  81. package/scripts/build-diff-data.mjs +211 -49
  82. package/scripts/cli-args.mjs +63 -9
  83. package/scripts/coding-agents.mjs +255 -77
  84. package/scripts/generate-summaries.mjs +872 -426
  85. package/scripts/local-target.mjs +106 -0
  86. package/scripts/present.mjs +333 -117
  87. package/scripts/review-chat-context.mjs +510 -0
  88. package/scripts/review-chat-controller.mjs +691 -0
  89. package/scripts/review-chat-provider.mjs +363 -0
  90. package/scripts/review-chat.mjs +8 -0
  91. package/scripts/serve-built.mjs +245 -27
  92. package/scripts/summary-path.mjs +32 -11
  93. package/dist/assets/index-C2p_1iAk.css +0 -2
  94. package/dist/assets/index-divlsuKs.js +0 -1568
@@ -6,6 +6,12 @@ import { readFile, stat } from 'node:fs/promises';
6
6
  import { createServer } from 'node:http';
7
7
  import { dirname, extname, resolve, sep } from 'node:path';
8
8
  import { fileURLToPath } from 'node:url';
9
+ import {
10
+ ReviewChatError,
11
+ createCodingAgentChatProvider,
12
+ createReviewChat,
13
+ } from './review-chat.mjs';
14
+ import { publicError } from './review-chat-context.mjs';
9
15
 
10
16
  const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
11
17
  const clientRoot = resolve(root, 'dist');
@@ -27,6 +33,13 @@ const portValue = option('--port', '2299');
27
33
  const host = option('--host', 'localhost').replace(/^\[|\]$/g, '');
28
34
  const access = option('--access', randomBytes(32).toString('base64url'));
29
35
  const previousAccess = option('--previous-access', '');
36
+ const chatSnapshot = resolve(option('--chat-snapshot', output));
37
+ const chatAgent = option('--chat-agent');
38
+ const chatBinary = option('--chat-binary', chatAgent);
39
+ const chatModel = option('--chat-model');
40
+ const chatReasoning = option('--chat-reasoning');
41
+ const chatAccessMode = option('--chat-access-mode', 'snapshot-only');
42
+ const chatAccessRoot = option('--chat-access-root');
30
43
  if (!/^\d+$/.test(portValue) || Number(portValue) > 65_535) {
31
44
  throw new Error('--port must be a number from 0 to 65535');
32
45
  }
@@ -36,6 +49,12 @@ if (!/^[A-Za-z0-9_-]{32,}$/.test(access)) {
36
49
  if (previousAccess && !/^[A-Za-z0-9_-]{32,}$/.test(previousAccess)) {
37
50
  throw new Error('--previous-access must be an unguessable URL-safe value');
38
51
  }
52
+ if (!['checkout-read-only', 'snapshot-only'].includes(chatAccessMode)) {
53
+ throw new Error('--chat-access-mode must be checkout-read-only or snapshot-only');
54
+ }
55
+ if (chatAccessMode === 'checkout-read-only' && !chatAccessRoot) {
56
+ throw new Error('--chat-access-root is required for checkout chat access');
57
+ }
39
58
  const incrementPort =
40
59
  rawArgs.includes('--increment-port') || !rawArgs.includes('--port');
41
60
 
@@ -270,6 +289,60 @@ function jsonResponse(value, status = 200) {
270
289
  });
271
290
  }
272
291
 
292
+ const chatBodyLimitBytes = 64 * 1024;
293
+
294
+ function chatJsonContentType(value) {
295
+ return typeof value === 'string' &&
296
+ /^application\/json(?:\s*;\s*charset=utf-8)?$/i.test(value);
297
+ }
298
+
299
+ function chatRequestBody(request) {
300
+ if (!chatJsonContentType(request.headers['content-type'])) {
301
+ throw new ReviewChatError('Chat commands need application/json.', 415);
302
+ }
303
+ const contentLength = Number(request.headers['content-length']);
304
+ if (Number.isFinite(contentLength) && contentLength > chatBodyLimitBytes) {
305
+ throw new ReviewChatError('Chat command body is too large.', 413);
306
+ }
307
+ return new Promise((resolvePromise, reject) => {
308
+ const chunks = [];
309
+ let bytes = 0;
310
+ let settled = false;
311
+ const fail = (error) => {
312
+ if (settled) return;
313
+ settled = true;
314
+ request.resume();
315
+ reject(error);
316
+ };
317
+ request.on('data', (chunk) => {
318
+ bytes += chunk.length;
319
+ if (bytes > chatBodyLimitBytes) {
320
+ fail(new ReviewChatError('Chat command body is too large.', 413));
321
+ return;
322
+ }
323
+ chunks.push(chunk);
324
+ });
325
+ request.once('error', fail);
326
+ request.once('end', () => {
327
+ if (settled) return;
328
+ settled = true;
329
+ try {
330
+ const value = JSON.parse(Buffer.concat(chunks).toString('utf8'));
331
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
332
+ throw new ReviewChatError('Chat commands must be JSON objects.');
333
+ }
334
+ resolvePromise(value);
335
+ } catch (error) {
336
+ reject(
337
+ error instanceof ReviewChatError
338
+ ? error
339
+ : new ReviewChatError('Chat command JSON is not valid.'),
340
+ );
341
+ }
342
+ });
343
+ });
344
+ }
345
+
273
346
  async function send(nodeResponse, webResponse) {
274
347
  nodeResponse.writeHead(
275
348
  webResponse.status,
@@ -287,6 +360,65 @@ let selectedPort = Number(portValue);
287
360
  let readyState;
288
361
  let closing = false;
289
362
 
363
+ function broadcastEvent(name, data = {}) {
364
+ const payload = JSON.stringify(data);
365
+ for (const client of eventClients) {
366
+ if (!client.destroyed) client.write(`event: ${name}\ndata: ${payload}\n\n`);
367
+ }
368
+ }
369
+
370
+ function lifecycleKind(kind) {
371
+ return kind === 'compact' ? 'history compaction' : 'answer';
372
+ }
373
+
374
+ function lifecycleStatus(type) {
375
+ return {
376
+ start: 'started',
377
+ progress: 'still running',
378
+ complete: 'completed',
379
+ failure: 'failed',
380
+ timeout: 'timed out',
381
+ cancel: 'cancelled',
382
+ }[type] || type;
383
+ }
384
+
385
+ function elapsedText(milliseconds) {
386
+ if (milliseconds < 1_000) return `${milliseconds}ms`;
387
+ return `${Math.round(milliseconds / 1_000)}s`;
388
+ }
389
+
390
+ function logChatLifecycle(event) {
391
+ const elapsed = event.type === 'start' ? '' : ` (${elapsedText(event.elapsedMs)})`;
392
+ const error = event.error ? `: ${event.error}` : '';
393
+ console.log(
394
+ `Diffsplain chat #${event.runId}: ${lifecycleKind(event.kind)} ${lifecycleStatus(event.type)}${elapsed}${error}`,
395
+ );
396
+ }
397
+
398
+ function logObservedError(area, error) {
399
+ console.log(`Diffsplain ${area.replaceAll('-', ' ')} error: ${publicError(error)}`);
400
+ }
401
+
402
+ const chatProviderAccess = chatAccessMode === 'checkout-read-only'
403
+ ? { mode: chatAccessMode, root: resolve(chatAccessRoot) }
404
+ : { mode: chatAccessMode, reason: 'target-mismatch' };
405
+ const chatProvider = chatAgent
406
+ ? createCodingAgentChatProvider({
407
+ agent: chatAgent,
408
+ binary: chatBinary,
409
+ model: chatModel,
410
+ reasoning: chatReasoning,
411
+ accessMode: chatProviderAccess,
412
+ })
413
+ : undefined;
414
+ const reviewChat = createReviewChat({
415
+ snapshotPath: chatSnapshot,
416
+ provider: chatProvider,
417
+ accessMode: chatProviderAccess,
418
+ onChange: () => broadcastEvent('chat'),
419
+ onLifecycle: logChatLifecycle,
420
+ });
421
+
290
422
  function requestAuthorityError(request) {
291
423
  const localAddress = request.socket.localAddress || '';
292
424
  if (!validHost(request.headers.host, localAddress)) {
@@ -298,14 +430,22 @@ function requestAuthorityError(request) {
298
430
  return undefined;
299
431
  }
300
432
 
433
+ function allowedMethods(url) {
434
+ return url.pathname === '/api/chat' ? ['GET', 'POST'] : ['GET', 'HEAD'];
435
+ }
436
+
437
+ function methodError(request, url) {
438
+ if (allowedMethods(url).includes(request.method)) return undefined;
439
+ return webResponse('Method not allowed', { status: 405 });
440
+ }
441
+
301
442
  function requestContext(request) {
302
443
  const url = parseRequestUrl(request);
303
444
  if (!url) return { error: clientError() };
304
445
  const authorityError = requestAuthorityError(request);
305
446
  if (authorityError) return { error: authorityError };
306
- if (!['GET', 'HEAD'].includes(request.method)) {
307
- return { error: webResponse('Method not allowed', { status: 405 }) };
308
- }
447
+ const error = methodError(request, url);
448
+ if (error) return { error };
309
449
  return { url };
310
450
  }
311
451
 
@@ -349,20 +489,99 @@ async function routeResponse(url) {
349
489
  return fetchAsset(url);
350
490
  }
351
491
 
492
+ function chatAccessError(url) {
493
+ if (validAccess(url.searchParams.get('access'))) return undefined;
494
+ return webResponse('Forbidden', { status: 403 });
495
+ }
496
+
497
+ function chatCommandError(error) {
498
+ if (error instanceof ReviewChatError) {
499
+ return { status: error.status, message: error.message };
500
+ }
501
+ return { status: 400, message: 'Chat command failed.' };
502
+ }
503
+
504
+ async function sendChatCommand(request, response) {
505
+ try {
506
+ const result = reviewChat.command(await chatRequestBody(request));
507
+ const status = result.accepted ? 202 : 200;
508
+ await send(response, jsonResponse(result.state, status));
509
+ } catch (error) {
510
+ logObservedError('chat-command', error);
511
+ const failure = chatCommandError(error);
512
+ await send(response, jsonResponse({ error: failure.message }, failure.status));
513
+ }
514
+ }
515
+
516
+ async function handleChatRequest(request, response, url) {
517
+ const accessError = chatAccessError(url);
518
+ if (accessError) {
519
+ await send(response, accessError);
520
+ return;
521
+ }
522
+ if (request.method === 'GET') {
523
+ await send(response, jsonResponse(reviewChat.getState()));
524
+ return;
525
+ }
526
+ await sendChatCommand(request, response);
527
+ }
528
+
529
+ async function handleEventsRequest(_request, response, url) {
530
+ await sendEvents(response, url);
531
+ }
532
+
533
+ function handlerFor(url) {
534
+ return {
535
+ '/events': handleEventsRequest,
536
+ '/api/chat': handleChatRequest,
537
+ }[url.pathname];
538
+ }
539
+
540
+ function throwForcedHandlerFailure(url) {
541
+ if (forcedHandlerFailure(url)) throw new Error('forced request handler failure');
542
+ }
543
+
544
+ async function handleRoutedRequest(request, response, url) {
545
+ const handler = handlerFor(url);
546
+ if (handler) {
547
+ await handler(request, response, url);
548
+ return;
549
+ }
550
+ await send(response, await routeResponse(url));
551
+ }
552
+
352
553
  async function handleRequest(request, response) {
353
554
  const context = requestContext(request);
354
555
  if (context.error) {
355
556
  await send(response, context.error);
356
557
  return;
357
558
  }
358
- if (forcedHandlerFailure(context.url)) {
359
- throw new Error('forced request handler failure');
360
- }
361
- if (context.url.pathname === '/events') {
362
- await sendEvents(response, context.url);
363
- return;
364
- }
365
- await send(response, await routeResponse(context.url));
559
+ throwForcedHandlerFailure(context.url);
560
+ await handleRoutedRequest(request, response, context.url);
561
+ }
562
+
563
+ function stopWatchers() {
564
+ unwatchFile(output);
565
+ if (chatSnapshot !== output) unwatchFile(chatSnapshot);
566
+ }
567
+
568
+ function closeEventClients() {
569
+ for (const client of eventClients) client.end();
570
+ eventClients.clear();
571
+ }
572
+
573
+ function closeServer() {
574
+ if (server.listening) server.close();
575
+ }
576
+
577
+ function close(exitCode = 0) {
578
+ if (closing) return;
579
+ closing = true;
580
+ process.exitCode = exitCode;
581
+ stopWatchers();
582
+ reviewChat.close();
583
+ closeEventClients();
584
+ closeServer();
366
585
  }
367
586
 
368
587
  async function containHandlerFailure(response) {
@@ -381,20 +600,29 @@ async function containHandlerFailure(response) {
381
600
  }
382
601
 
383
602
  const server = createServer((request, response) => {
384
- void handleRequest(request, response).catch(() =>
385
- containHandlerFailure(response),
386
- );
603
+ void handleRequest(request, response).catch((error) => {
604
+ logObservedError('request-handler', error);
605
+ return containHandlerFailure(response);
606
+ });
387
607
  });
388
608
 
389
609
  watchFile(output, { interval: 100 }, (current, previous) => {
390
610
  if (current.mtimeMs === previous.mtimeMs && current.size === previous.size) {
391
611
  return;
392
612
  }
393
- for (const client of eventClients) {
394
- if (!client.destroyed) client.write('event: update\ndata: {}\n\n');
395
- }
613
+ reviewChat.refresh();
614
+ broadcastEvent('update');
396
615
  });
397
616
 
617
+ if (chatSnapshot !== output) {
618
+ watchFile(chatSnapshot, { interval: 100 }, (current, previous) => {
619
+ if (current.mtimeMs === previous.mtimeMs && current.size === previous.size) {
620
+ return;
621
+ }
622
+ reviewChat.refresh();
623
+ });
624
+ }
625
+
398
626
  function urlFor(address, port) {
399
627
  const formattedAddress = address.includes(':') ? `[${address}]` : address;
400
628
  return `http://${formattedAddress}:${port}`;
@@ -454,15 +682,5 @@ server.on('error', (error) => {
454
682
 
455
683
  listen();
456
684
 
457
- function close(exitCode = 0) {
458
- if (closing) return;
459
- closing = true;
460
- process.exitCode = exitCode;
461
- unwatchFile(output);
462
- for (const client of eventClients) client.end();
463
- eventClients.clear();
464
- if (server.listening) server.close();
465
- }
466
-
467
685
  process.on('SIGINT', () => close());
468
686
  process.on('SIGTERM', () => close());
@@ -20,6 +20,37 @@ export function defaultCacheRoot({
20
20
  return resolve(platformRoot, 'diffsplain');
21
21
  }
22
22
 
23
+ function pullRequestTarget(pr, remote) {
24
+ const pullRequest =
25
+ pr?.match(/\/pull\/(\d+)(?:\/|$)/)?.[1] || pr || undefined;
26
+ return { kind: 'pr', pullRequest, remote };
27
+ }
28
+
29
+ function branchTarget(branch, base, remote) {
30
+ return { kind: 'branch', branch, base: base || 'default', remote };
31
+ }
32
+
33
+ function checkoutTarget(base, remote) {
34
+ return { kind: 'checkout', base: base || 'default', remote };
35
+ }
36
+
37
+ function localSummaryTarget(base, head) {
38
+ if (!base) return { kind: 'worktree' };
39
+ if (!head) return { kind: 'base-worktree', base };
40
+ return { kind: 'range', base, head };
41
+ }
42
+
43
+ function summaryTarget({ pr, branch, checkout, base, head, remote }) {
44
+ const candidates = [
45
+ [Boolean(pr), pullRequestTarget(pr, remote)],
46
+ [Boolean(branch), branchTarget(branch, base, remote)],
47
+ [Boolean(checkout), checkoutTarget(base, remote)],
48
+ ];
49
+ const selected = candidates.find(([matches]) => matches);
50
+ if (selected) return selected[1];
51
+ return localSummaryTarget(base, head);
52
+ }
53
+
23
54
  export function summaryPath({
24
55
  callerDirectory,
25
56
  repo,
@@ -33,17 +64,7 @@ export function summaryPath({
33
64
  remote = 'origin',
34
65
  }) {
35
66
  if (explicit) return resolve(callerDirectory, explicit);
36
- const pullRequest =
37
- pr?.match(/\/pull\/(\d+)(?:\/|$)/)?.[1] || pr || undefined;
38
- const target = pr
39
- ? { kind: 'pr', pullRequest, remote }
40
- : branch
41
- ? { kind: 'branch', branch, base: base || 'default', remote }
42
- : checkout
43
- ? { kind: 'checkout', base: base || 'default', remote }
44
- : base && head
45
- ? { kind: 'range', base, head }
46
- : { kind: 'worktree' };
67
+ const target = summaryTarget({ pr, branch, checkout, base, head, remote });
47
68
  const key = createHash('sha256')
48
69
  .update(JSON.stringify({ repo, target }))
49
70
  .digest('hex')
@@ -1,2 +0,0 @@
1
- /*! tailwindcss v4.2.1 | MIT License | https://tailwindcss.com */
2
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-orange-50:oklch(98% .016 73.684);--color-orange-100:oklch(95.4% .038 75.164);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-yellow-50:oklch(98.7% .026 102.212);--color-stone-50:oklch(98.5% .001 106.423);--color-stone-100:oklch(97% .001 106.424);--color-stone-200:oklch(92.3% .003 48.717);--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--text-7xl:4.5rem;--text-7xl--line-height:1;--text-8xl:6rem;--text-8xl--line-height:1;--text-9xl:8rem;--text-9xl--line-height:1;--font-weight-bold:700;--tracking-widest:.1em;--radius-md:.375rem;--radius-lg:.5rem;--ease-in:cubic-bezier(.4, 0, 1, 1);--ease-out:cubic-bezier(0, 0, .2, 1);--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-ping:ping 1s cubic-bezier(0, 0, .2, 1) infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--animate-bounce:bounce 1s infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.\!visible{visibility:visible!important}.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.isolate{isolation:isolate}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.inline-grid{display:inline-grid}.table{display:table}.w-24{width:calc(var(--spacing) * 24)}.flex-shrink,.shrink{flex-shrink:1}.grow{flex-grow:1}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-bounce{animation:var(--animate-bounce)}.animate-ping{animation:var(--animate-ping)}.animate-pulse{animation:var(--animate-pulse)}.resize{resize:both}.flex-wrap{flex-wrap:wrap}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.bg-\[\#f5f0e6\],.bg-\[rgb\(245_240_230\)\]{background-color:#f5f0e6}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-100{background-color:var(--color-amber-100)}.bg-orange-50{background-color:var(--color-orange-50)}.bg-orange-100{background-color:var(--color-orange-100)}.bg-stone-50{background-color:var(--color-stone-50)}.bg-stone-100{background-color:var(--color-stone-100)}.bg-stone-200{background-color:var(--color-stone-200)}.bg-yellow-50{background-color:var(--color-yellow-50)}.bg-clip-text{-webkit-background-clip:text;background-clip:text}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.text-7xl{font-size:var(--text-7xl);line-height:var(--tw-leading,var(--text-7xl--line-height))}.text-8xl{font-size:var(--text-8xl);line-height:var(--tw-leading,var(--text-8xl--line-height))}.text-9xl{font-size:var(--text-9xl);line-height:var(--tw-leading,var(--text-9xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[…\]{color:…}.text-white{color:var(--color-white)}.capitalize{text-transform:capitalize}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.italic{font-style:italic}.underline{text-decoration-line:underline}.shadow,.shadow\/elevation{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.grayscale{--tw-grayscale:grayscale(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.invert{--tw-invert:invert(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}}@font-face{font-display:swap;font-family:Geist;font-style:normal;font-weight:100 900;src:url(./geist-latin-BGnTDqni.woff2)format("woff2")}@font-face{font-display:swap;font-family:Geist Mono;font-style:normal;font-weight:100 900;src:url(./geist-mono-latin-pyAoGB9p.woff2)format("woff2")}:root{--font-geist-sans:"Geist";--font-geist-mono:"Geist Mono";--canvas:#e9e2d5;--paper:#fbf8f0;--paper-deep:#f1ebdf;--ink:#20231f;--muted:#73766d;--quiet:#aaa99f;--line:#d8d1c4;--line-dark:#333831;--green:#2f6c4d;--green-soft:#dce9dc;--red:#a14f45;--red-soft:#f1ddda;--blue:#426b7c;--blue-soft:#dce9ed;--amber:#9c7027;--amber-soft:#eee3c7;--shadow:0 28px 80px #382d1e24}*{box-sizing:border-box}html,body{max-width:100%;min-height:100%;overflow-x:clip}#root{min-width:0;max-width:100%}body{background:var(--canvas);color:var(--ink);font-family:var(--font-geist-sans), Arial, sans-serif;text-rendering:optimizelegibility;margin:0}button,input{color:inherit;font:inherit}button,a{-webkit-tap-highlight-color:transparent}button:focus-visible,a:focus-visible,input:focus-visible{outline:2px solid var(--blue);outline-offset:3px}a{color:inherit}.app-shell{background:radial-gradient(circle at 6% 2%, #fffcf3d1, transparent 24rem), radial-gradient(circle at 94% 88%, #cbd3be85, transparent 26rem), var(--canvas);flex-direction:column;height:100dvh;min-height:700px;padding:0 24px 24px;display:flex;overflow:hidden}.topbar{flex:0 0 82px;grid-template-columns:minmax(230px,.8fr) minmax(300px,1.5fr) minmax(150px,.45fr);align-items:center;gap:32px;width:100%;max-width:1680px;margin:0 auto;display:grid}.brand-block,.change-block,.sync-state{min-width:0}.brand-block{align-items:center;gap:13px;display:flex}.brand-mark{border:1px solid var(--ink);font:700 10px/1 var(--font-geist-mono), monospace;letter-spacing:-.06em;flex:0 0 38px;grid-template-columns:1fr 1fr;height:38px;display:grid}.brand-mark span{justify-content:center;align-items:center;display:flex}.brand-mark span:first-child{background:var(--ink);color:var(--paper)}.brand-name,.brand-tag,.change-block p,.change-meta,.sync-state strong,.sync-state span,.eyebrow,.renamed-from,.summary-pane h2,.summary-lead,.note-section p,.agent-signoff,.loading-shell h1,.loading-shell p{margin:0}.brand-name{letter-spacing:-.02em;font-size:14px;font-weight:720}.brand-tag{color:var(--muted);margin-top:2px;font-size:11px}.change-block{border-left:1px solid #20231f29;padding-left:26px}.change-meta{color:var(--muted);font:600 10px/1.2 var(--font-geist-mono), monospace;letter-spacing:.06em;text-transform:uppercase;align-items:center;gap:8px;display:flex}.change-meta a{-webkit-text-decoration-color:var(--quiet);-webkit-text-decoration-color:var(--quiet);-webkit-text-decoration-color:var(--quiet);text-decoration-color:var(--quiet);text-underline-offset:3px}.meta-separator{color:var(--quiet)}.change-block>p{text-overflow:ellipsis;white-space:nowrap;margin-top:6px;font-size:13px;font-weight:560;overflow:hidden}.sync-state{justify-content:flex-end;align-items:center;gap:10px;display:flex}.sync-state>div{flex-direction:column;min-width:0;display:flex}.sync-state strong{font-size:11px;font-weight:650}.sync-state>div>span{color:var(--muted);margin-top:2px;font-size:10px}.live-dot{background:var(--green);box-sizing:content-box;border:4px solid #2f6c4d24;border-radius:999px;width:7px;height:7px;position:relative}.live-dot:after{border:1px solid var(--green);border-radius:inherit;content:"";animation:2.2s ease-out infinite live-ring;position:absolute;inset:-5px}.sync-state--error .live-dot{background:var(--red);border-color:#a14f4524}.sync-state--error .live-dot:after{border-color:var(--red);animation:none}.sync-state--notes .live-dot{background:var(--blue);border-color:#426b7c24}.sync-state--notes .live-dot:after{border-color:var(--blue)}.sync-state--notes-error .live-dot{background:var(--amber);border-color:#9c702724}.sync-state--notes-error .live-dot:after{border-color:var(--amber);animation:none}@keyframes live-ring{0%{opacity:.7;transform:scale(.65)}75%,to{opacity:0;transform:scale(1.55)}}.reader{flex-direction:column;flex:1;width:100%;max-width:1680px;min-height:0;margin:0 auto;display:flex}.reader-toolbar{background:var(--ink);color:#f8f4ea;border-radius:15px 15px 0 0;flex:0 0 60px;grid-template-columns:60px minmax(0,1fr) auto 60px;align-items:stretch;display:grid;overflow:hidden}.nav-button,.file-picker-trigger,.picker-row,.text-button,.picker-close{cursor:pointer;border:0}.nav-button{color:#f8f4ea;font:400 22px/1 var(--font-geist-mono), monospace;background:0 0;justify-content:center;align-items:center;transition:background .16s,color .16s;display:flex}.nav-button--previous{border-right:1px solid #ffffff1a}.nav-button--next{border-left:1px solid #ffffff1a}.nav-button:hover{color:#fff;background:#ffffff17}.nav-button span{transition:transform .18s}.nav-button--previous:hover span{transform:translate(-3px)}.nav-button--next:hover span{transform:translate(3px)}.file-picker-trigger{text-align:left;background:0 0;align-items:center;gap:13px;min-width:0;padding:0 20px;display:flex}.file-picker-trigger:hover .current-path{color:#fff}.file-count{color:#bcbcb3;font:600 10px/1 var(--font-geist-mono), monospace;letter-spacing:.04em;flex:none}.file-count i{color:#686d65;margin:0 4px;font-style:normal}.status-pin{background:var(--blue);border-radius:999px;flex:0 0 7px;width:7px;height:7px;display:inline-block}.status-pin--added{background:#71b78c}.status-pin--deleted{background:#db8478}.status-pin--renamed{background:#d5aa5c}.status-pin--binary{background:#a391c4}.current-path{color:#e6e2d9;font:520 12px/1.2 var(--font-geist-mono), monospace;text-overflow:ellipsis;white-space:nowrap;transition:color .16s;overflow:hidden}.picker-hint{color:#80847d;align-items:center;gap:3px;margin-left:auto;display:flex}kbd{font:500 9px/1 var(--font-geist-mono), monospace;border:1px solid #50554f;border-radius:4px;padding:3px 4px}.file-stats{font:600 10px/1 var(--font-geist-mono), monospace;align-items:center;gap:12px;padding:0 22px;display:flex}.status-label{color:#c7cbc4;letter-spacing:.04em;text-transform:uppercase;border:1px solid #525751;border-radius:99px;padding:6px 9px}.status-label--added{color:#a8d4b7;border-color:#71b78c73}.status-label--deleted{color:#ebb1a9;border-color:#db847873}.status-label--renamed{color:#e4c486;border-color:#d5aa5c73}.status-label--binary{color:#c7b9df;border-color:#a391c473}.addition{color:#8fcba5}.deletion{color:#e09b91}.page{background:var(--paper);box-shadow:var(--shadow);transform-origin:50% 8%;border:1px solid #20231f29;border-top:0;border-radius:0 0 18px 18px;flex:1;grid-template-columns:minmax(0,1.72fr) minmax(340px,.88fr);min-height:0;display:grid;overflow:hidden}.page--next{animation:.36s cubic-bezier(.22,.78,.28,1) page-next}.page--previous{animation:.36s cubic-bezier(.22,.78,.28,1) page-previous}.page--pick{animation:.3s cubic-bezier(.22,.78,.28,1) page-pick}@keyframes page-next{0%{opacity:0;transform:translate(26px)rotateY(-1deg)}to{opacity:1;transform:translate(0)rotateY(0)}}@keyframes page-previous{0%{opacity:0;transform:translate(-26px)rotateY(1deg)}to{opacity:1;transform:translate(0)rotateY(0)}}@keyframes page-pick{0%{opacity:0;transform:translateY(9px)scale(.997)}to{opacity:1;transform:translateY(0)scale(1)}}.diff-pane,.summary-pane{flex-direction:column;min-width:0;min-height:0;display:flex}.diff-pane{background:var(--paper)}.summary-pane{background:linear-gradient(#ffffff57, #fff0), var(--paper-deep);border-left:1px solid var(--line)}.pane-heading{border-bottom:1px solid var(--line);flex:0 0 84px;justify-content:space-between;align-items:center;gap:20px;padding:0 28px;display:flex}.eyebrow{color:var(--muted);font:650 9px/1.2 var(--font-geist-mono), monospace;letter-spacing:.12em}.pane-heading h1{letter-spacing:-.03em;text-overflow:ellipsis;white-space:nowrap;max-width:52vw;margin:5px 0 0;font-size:19px;font-weight:620;overflow:hidden}.renamed-from{color:var(--muted);font:500 10px/1.4 var(--font-geist-mono), monospace;margin-top:3px}.diff-actions{flex:none;align-items:center;gap:16px;display:flex}.text-button{color:var(--ink);text-underline-offset:4px;background:0 0;padding:7px 0;font-size:10px;font-weight:650;-webkit-text-decoration:underline #aaa99f;text-decoration:underline #aaa99f}.text-button:hover{-webkit-text-decoration-color:var(--ink);-webkit-text-decoration-color:var(--ink);-webkit-text-decoration-color:var(--ink);text-decoration-color:var(--ink)}.diff-scroll{overflow-anchor:none;overscroll-behavior:contain;flex:1;min-height:0;overflow:auto}.diff-scroll-content{min-height:100%}.diff-renderer-shell{min-width:100%}.diff-renderer{--diffs-addition-color-override:var(--green);--diffs-bg:var(--paper);--diffs-bg-context-override:var(--paper);--diffs-bg-hover-override:#20231f0a;--diffs-bg-separator-override:var(--blue-soft);--diffs-deletion-color-override:var(--red);--diffs-fg:var(--ink);--diffs-fg-number-override:var(--quiet);--diffs-font-family:var(--font-geist-mono), monospace;--diffs-font-size:11.5px;--diffs-font-weight:450;--diffs-gap-block:14px;--diffs-light:var(--ink);--diffs-light-bg:var(--paper);--diffs-line-height:21px;--diffs-min-number-column-width:3ch;--diffs-tab-size:2;min-width:100%;display:block}.diff-footer{border-top:1px solid var(--line);color:var(--muted);font:500 9px/1 var(--font-geist-mono), monospace;flex:0 0 38px;justify-content:space-between;align-items:center;padding:0 28px;display:flex}.binary-card{flex-direction:column;align-items:flex-start;max-width:420px;margin:auto;padding:12vh 36px 80px;display:flex}.binary-icon{background:linear-gradient(135deg, #ffffff3d, transparent), var(--ink);color:var(--paper);font:650 11px/1 var(--font-geist-mono), monospace;justify-content:center;align-items:center;width:48px;height:54px;margin-bottom:30px;display:flex;position:relative}.binary-icon:after{border-right:12px solid var(--paper);content:"";border-top:12px solid #0000;position:absolute;top:0;right:0}.binary-card h2{letter-spacing:-.04em;margin:10px 0 14px;font-size:26px;font-weight:590;line-height:1.08}.binary-card>p:last-child{color:var(--muted);margin:0;font-size:13px;line-height:1.6}.summary-scroll{flex:1;min-height:0;padding:31px 34px 44px;overflow:auto}.summary-kicker{color:var(--muted);font:650 9px/1 var(--font-geist-mono), monospace;letter-spacing:.12em;justify-content:space-between;align-items:center;display:flex}.summary-kicker span:first-child{align-items:center;gap:8px;display:flex}.summary-kicker span:first-child:before{background:var(--ink);content:"";width:19px;height:1px}.summary-kicker--pending span:first-child:before{background:var(--blue)}.summary-pane h2{letter-spacing:-.055em;max-width:12em;margin-top:35px;font-size:clamp(26px,2vw,36px);font-weight:570;line-height:1.04}.summary-lead{color:#545851;max-width:40em;margin-top:17px;font-size:14px;line-height:1.55}.note-skeleton{max-width:40em;margin-top:25px}.note-skeleton-group{gap:10px;display:grid}.note-skeleton-group+.note-skeleton-group{border-top:1px solid var(--line);margin-top:30px;padding-top:20px}.note-skeleton-line{background:linear-gradient(100deg,#ddd6ca 20%,#ebe5da 42%,#ddd6ca 64%) 0 0/300% 100%;border-radius:2px;height:11px;animation:1.6s ease-in-out infinite note-skeleton-shimmer;display:block}.note-skeleton-line--label{width:23%;height:8px;margin-bottom:3px}.note-skeleton-line--long{width:94%}.note-skeleton-line--medium{width:72%}.note-skeleton-line--short{width:48%}@keyframes note-skeleton-shimmer{0%{background-position:100% 0}to{background-position:-100% 0}}.note-section{border-top:1px solid var(--line);margin-top:30px;padding-top:19px}.note-section>p:not(.eyebrow){color:#454942;margin-top:10px;font-size:12.5px;line-height:1.62}.note-section--pending>p:not(.eyebrow){color:var(--blue)}.note-section ul{margin:11px 0 0;padding:0;list-style:none}.note-section li{color:#454942;padding:5px 0 5px 18px;font-size:12px;line-height:1.55;position:relative}.note-section li:before{background:var(--ink);content:"";width:8px;height:1px;position:absolute;top:14px;left:0}.note-section--risk{background:#eee5d8;border:1px solid #d6c9b7;border-radius:8px;padding:17px 18px 14px}.note-section--risk .eyebrow{color:#805a22}.note-section--risk li:before{background:var(--amber)}.agent-signoff{border-top:1px solid var(--line);color:#4e524b;flex:0 0 58px;align-items:center;gap:10px;padding:0 34px;font-size:10px;display:flex}.agent-glyph{background:var(--ink);color:var(--paper);border-radius:99px;flex:0 0 25px;justify-content:center;align-items:center;height:25px;font-size:10px;display:flex}.agent-signoff>span:last-child{flex-direction:column;display:flex}.agent-signoff small{color:var(--muted);font:450 8px/1.3 var(--font-geist-mono), monospace;margin-top:2px}.picker-backdrop{z-index:50;background:#181b178c;justify-content:center;align-items:flex-start;padding:min(14vh,130px) 24px 32px;animation:.18s ease-out backdrop-in;display:flex;position:fixed;inset:0}@keyframes backdrop-in{0%{opacity:0}to{opacity:1}}.picker-dialog{background:var(--paper);border:1px solid #ffffff80;border-radius:15px;flex-direction:column;width:100%;max-width:780px;max-height:min(680px,76vh);animation:.24s cubic-bezier(.22,.78,.28,1) dialog-in;display:flex;overflow:hidden;box-shadow:0 40px 100px #12141152}@keyframes dialog-in{0%{opacity:0;transform:translateY(-10px)scale(.985)}to{opacity:1;transform:translateY(0)scale(1)}}.picker-header{justify-content:space-between;align-items:flex-start;padding:25px 27px 20px;display:flex}.picker-header h2{letter-spacing:-.04em;margin:5px 0 0;font-size:22px;font-weight:600}.picker-close{background:var(--paper-deep);border:1px solid var(--line);color:var(--muted);font:550 9px/1 var(--font-geist-mono), monospace;border-radius:5px;min-width:44px;min-height:44px;padding:7px 8px}.picker-close:hover{color:var(--ink);border-color:#aaa79e}.picker-search{border-bottom:1px solid var(--line);border-top:1px solid var(--line);grid-template-columns:26px minmax(0,1fr) auto;align-items:center;margin:0 27px;padding:0 3px;transition:background .16s,border-color .16s;display:grid}.picker-search:focus-within{background:var(--blue-soft);border-color:var(--blue)}.picker-search>span{color:var(--muted);font-size:22px}.picker-search input{font:500 13px/1 var(--font-geist-mono), monospace;background:0 0;border:0;outline:0;min-width:0;height:52px}.picker-search input::placeholder{color:#a6a49b}.picker-search small{color:var(--muted);font:500 9px/1 var(--font-geist-mono), monospace}.picker-list{flex:1;min-height:0;padding:10px 13px 16px;position:relative;overflow:auto}.picker-row{text-align:left;background:0 0;border-radius:7px;grid-template-columns:30px 8px minmax(0,1fr) auto;align-items:center;gap:11px;width:100%;min-height:48px;padding:0 14px;display:grid}.picker-row:hover,.picker-row--active{background:#ece7dc}.picker-row--active{box-shadow:inset 2px 0 var(--ink)}.picker-index{color:var(--quiet);font:500 9px/1 var(--font-geist-mono), monospace}.picker-path{font:520 11px/1.2 var(--font-geist-mono), monospace;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.picker-change-count{font:600 9px/1 var(--font-geist-mono), monospace;gap:9px;display:flex}.picker-change-count i{color:var(--green);font-style:normal}.picker-change-count b{color:var(--red);font-weight:600}.picker-empty{color:var(--muted);text-align:center;padding:38px 20px;font-size:12px}.loading-shell{background:radial-gradient(circle at 50% 34%, #f9f5eb, transparent 30rem), var(--canvas);text-align:center;flex-direction:column;justify-content:center;align-items:center;height:100dvh;min-height:600px;padding:32px;display:flex}.empty-shell{background:radial-gradient(circle at 50% 48%, #f9f5eb, transparent 30rem), var(--canvas);flex-direction:column;min-height:100dvh;padding:0 28px 28px;display:flex}.empty-topline{border-bottom:1px solid #20231f24;flex:0 0 82px;justify-content:space-between;align-items:center;width:100%;max-width:1480px;margin:0 auto;display:flex}.empty-message{text-align:center;flex-direction:column;flex:1;justify-content:center;align-items:center;padding:80px 24px 140px;display:flex}.empty-message h1{letter-spacing:-.07em;margin:15px 0 0;font-size:clamp(38px,6vw,72px);font-weight:540;line-height:1}.empty-message>p:last-child{color:var(--muted);max-width:420px;margin:20px 0 0;font-size:13px;line-height:1.6}.empty-message code{color:var(--ink);font-family:var(--font-geist-mono), monospace}.loading-mark{background:var(--ink);color:var(--paper);font:650 11px/1 var(--font-geist-mono), monospace;justify-content:center;align-items:center;width:42px;height:42px;margin-bottom:25px;display:flex}.loading-shell h1{letter-spacing:-.05em;margin-top:10px;font-size:36px;font-weight:570}.loading-line{background:#d0c8ba;width:190px;height:2px;margin-top:28px;overflow:hidden}.loading-line span{background:var(--ink);width:42%;height:100%;animation:1.4s ease-in-out infinite loading-slide;display:block}@keyframes loading-slide{0%{transform:translate(-110%)}to{transform:translate(350%)}}.loading-note{color:var(--muted);margin-top:18px;font-size:11px}.loading-note code{font-family:var(--font-geist-mono), monospace}.connection-error{background:var(--red);color:#fff;border-radius:6px;max-width:calc(100vw - 40px);padding:10px 14px;font-size:11px;position:fixed;bottom:20px;left:50%;transform:translate(-50%)}.sr-only{clip:rect(0, 0, 0, 0);clip-path:inset(50%);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}@media (width<=980px){.app-shell{height:auto;min-height:100dvh;padding:0 14px 18px;overflow:visible}.topbar{flex-basis:72px;grid-template-columns:1fr auto;gap:18px}.change-block{display:none}.reader{min-height:calc(100dvh - 90px)}.page{display:block;overflow:visible}.diff-pane{height:min(68vh,720px)}.summary-pane{border-left:0;border-top:1px solid var(--line);min-height:620px}.summary-scroll{overflow:visible}.summary-pane h2{max-width:16em}}@media (width<=680px){.app-shell{padding-left:0;padding-right:0}.topbar{padding:0 16px}.brand-tag,.sync-state>div>span,.picker-hint,.status-label,.diff-footer span:last-child{display:none}.sync-state{gap:7px}.reader-toolbar{border-radius:0;grid-template-columns:48px minmax(0,1fr) auto 48px}.file-picker-trigger{gap:8px;padding:0 10px}.file-count{display:none}.file-stats{gap:8px;padding:0 9px}.page{border-left:0;border-right:0;border-radius:0}.pane-heading{flex-direction:column;justify-content:center;align-items:flex-start;height:auto;min-height:104px;padding:18px 17px}.pane-heading h1{max-width:82vw}.diff-actions{gap:15px}.diff-renderer{--diffs-font-size:10.5px}.diff-footer{padding:0 17px}.summary-scroll{padding:28px 22px 38px}.agent-signoff{padding:0 22px}.picker-backdrop{align-items:flex-end;padding:0}.picker-dialog{border-radius:15px 15px 0 0;max-height:86vh}.picker-header{padding:22px 19px 18px}.picker-search{margin:0 19px}.picker-list{padding-left:7px;padding-right:7px}.picker-row{padding-left:10px;padding-right:10px}}@media (width<=360px){.reader-toolbar{grid-template-columns:48px minmax(0,1fr) 48px}.file-stats{display:none}}@media (prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}}