acdev 1.0.1 → 1.0.2

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 (2) hide show
  1. package/package.json +1 -1
  2. package/public/app.js +113 -36
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "acdev",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Local CLI + web UI for running AI agents on GitHub issues via git worktrees",
5
5
  "type": "module",
6
6
  "bin": {
package/public/app.js CHANGED
@@ -108,6 +108,10 @@ let reviewSearch = loadReviewSearch();
108
108
  let activeInlineCommentKey = null;
109
109
  /** @type {string | null} id of line comment currently being edited */
110
110
  let editingCommentId = null;
111
+ /** @type {'inline' | 'pending' | null} where the edit form should render */
112
+ let editingCommentSurface = null;
113
+ /** Original body when edit started — restored on Cancel after live sync. */
114
+ let editingCommentOriginalBody = null;
111
115
  /** @type {Record<string, {
112
116
  * generalComment: string,
113
117
  * lineComments: Array<{ id: string, path: string, line: number, side: 'LEFT' | 'RIGHT', body: string }>,
@@ -447,6 +451,8 @@ function clearReviewDraft(jobId) {
447
451
  delete reviewDrafts[jobId];
448
452
  activeInlineCommentKey = null;
449
453
  editingCommentId = null;
454
+ editingCommentSurface = null;
455
+ editingCommentOriginalBody = null;
450
456
  try {
451
457
  sessionStorage.removeItem(reviewDraftStorageKey(jobId));
452
458
  } catch {
@@ -454,6 +460,86 @@ function clearReviewDraft(jobId) {
454
460
  }
455
461
  }
456
462
 
463
+ /** Clear in-progress line-comment edit state. */
464
+ function clearCommentEditState() {
465
+ editingCommentId = null;
466
+ editingCommentSurface = null;
467
+ editingCommentOriginalBody = null;
468
+ }
469
+
470
+ /**
471
+ * @param {string} jobId
472
+ * @param {string} commentId
473
+ * @param {string} body
474
+ */
475
+ function updateLineCommentBody(jobId, commentId, body) {
476
+ const d = loadReviewDraft(jobId);
477
+ const target = d.lineComments.find((x) => x.id === commentId);
478
+ if (!target) return;
479
+ target.body = body;
480
+ saveReviewDraft(jobId);
481
+ }
482
+
483
+ /**
484
+ * Refresh the non-active edit surface so both views stay in sync.
485
+ * @param {string} jobId
486
+ */
487
+ function mirrorCommentEditToOtherSurface(jobId) {
488
+ if (editingCommentSurface === 'inline') {
489
+ renderPendingCommentList(jobId);
490
+ syncSubmitReviewButton(jobId);
491
+ return;
492
+ }
493
+ if (editingCommentSurface === 'pending') {
494
+ const job = jobsById[jobId];
495
+ if (job) {
496
+ const diff =
497
+ job.status === 'awaiting_review'
498
+ ? filterDiffForReviewJob(job)
499
+ : job.diff || '';
500
+ renderDiff(diff, {
501
+ interactive: job.status === 'awaiting_review',
502
+ jobId,
503
+ });
504
+ }
505
+ syncSubmitReviewButton(jobId);
506
+ }
507
+ }
508
+
509
+ /**
510
+ * Commit or discard an in-progress comment edit.
511
+ * @param {string} jobId
512
+ * @param {string} commentId
513
+ * @param {string | null} nextBody null = cancel / restore original
514
+ */
515
+ function finishCommentEdit(jobId, commentId, nextBody) {
516
+ if (nextBody == null) {
517
+ if (editingCommentOriginalBody != null) {
518
+ updateLineCommentBody(jobId, commentId, editingCommentOriginalBody);
519
+ }
520
+ } else {
521
+ updateLineCommentBody(jobId, commentId, nextBody);
522
+ }
523
+ clearCommentEditState();
524
+ refreshReviewCommentUi(jobId);
525
+ }
526
+
527
+ /**
528
+ * Start editing a pending line comment on a specific surface.
529
+ * @param {string} commentId
530
+ * @param {'inline' | 'pending'} surface
531
+ * @param {string} jobId
532
+ */
533
+ function startCommentEdit(commentId, surface, jobId) {
534
+ const draft = loadReviewDraft(jobId);
535
+ const target = draft.lineComments.find((x) => x.id === commentId);
536
+ editingCommentId = commentId;
537
+ editingCommentSurface = surface;
538
+ editingCommentOriginalBody = target ? target.body : '';
539
+ activeInlineCommentKey = null;
540
+ refreshReviewCommentUi(jobId);
541
+ }
542
+
457
543
  /**
458
544
  * Re-render diff + pending list after draft mutation.
459
545
  * @param {string} jobId
@@ -480,6 +566,7 @@ function refreshReviewCommentUi(jobId) {
480
566
  * saveLabel?: string,
481
567
  * onSave: (body: string) => void,
482
568
  * onCancel: () => void,
569
+ * onInput?: (body: string) => void,
483
570
  * }} opts
484
571
  */
485
572
  function buildCommentEditForm(opts) {
@@ -490,6 +577,9 @@ function buildCommentEditForm(opts) {
490
577
  ta.rows = 3;
491
578
  ta.value = opts.initialBody;
492
579
  ta.placeholder = 'Edit comment…';
580
+ if (opts.onInput) {
581
+ ta.addEventListener('input', () => opts.onInput(ta.value));
582
+ }
493
583
  const actions = document.createElement('div');
494
584
  actions.className = 'diff-inline-actions';
495
585
  const save = document.createElement('button');
@@ -514,6 +604,7 @@ function buildCommentEditForm(opts) {
514
604
  ta.focus();
515
605
  const len = ta.value.length;
516
606
  ta.setSelectionRange(len, len);
607
+ ta.scrollIntoView({ block: 'nearest', inline: 'nearest' });
517
608
  });
518
609
  return form;
519
610
  }
@@ -1317,7 +1408,7 @@ function renderDiff(diff, opts = {}) {
1317
1408
  plus.addEventListener('click', (e) => {
1318
1409
  e.stopPropagation();
1319
1410
  activeInlineCommentKey = lineCommentKey(path, line, side);
1320
- editingCommentId = null;
1411
+ clearCommentEditState();
1321
1412
  renderDiff(diff, opts);
1322
1413
  renderPendingCommentList(jobId);
1323
1414
  syncSubmitReviewButton(jobId);
@@ -1327,7 +1418,7 @@ function renderDiff(diff, opts = {}) {
1327
1418
  cell.addEventListener('click', (e) => {
1328
1419
  if (e.target.closest('button, textarea, a')) return;
1329
1420
  activeInlineCommentKey = lineCommentKey(path, line, side);
1330
- editingCommentId = null;
1421
+ clearCommentEditState();
1331
1422
  renderDiff(diff, opts);
1332
1423
  renderPendingCommentList(jobId);
1333
1424
  syncSubmitReviewButton(jobId);
@@ -1382,22 +1473,16 @@ function renderDiff(diff, opts = {}) {
1382
1473
  thread.appendChild(meta);
1383
1474
 
1384
1475
  for (const c of existing) {
1385
- if (editingCommentId === c.id) {
1476
+ if (editingCommentId === c.id && editingCommentSurface === 'inline') {
1386
1477
  thread.appendChild(
1387
1478
  buildCommentEditForm({
1388
1479
  initialBody: c.body,
1389
- onSave: (body) => {
1390
- const d = loadReviewDraft(jobId);
1391
- const target = d.lineComments.find((x) => x.id === c.id);
1392
- if (target) target.body = body;
1393
- saveReviewDraft(jobId);
1394
- editingCommentId = null;
1395
- refreshReviewCommentUi(jobId);
1396
- },
1397
- onCancel: () => {
1398
- editingCommentId = null;
1399
- refreshReviewCommentUi(jobId);
1480
+ onInput: (body) => {
1481
+ updateLineCommentBody(jobId, c.id, body);
1482
+ mirrorCommentEditToOtherSurface(jobId);
1400
1483
  },
1484
+ onSave: (body) => finishCommentEdit(jobId, c.id, body),
1485
+ onCancel: () => finishCommentEdit(jobId, c.id, null),
1401
1486
  })
1402
1487
  );
1403
1488
  continue;
@@ -1415,9 +1500,7 @@ function renderDiff(diff, opts = {}) {
1415
1500
  edit.className = 'btn btn-muted-text btn-sm';
1416
1501
  edit.textContent = 'Edit';
1417
1502
  edit.addEventListener('click', () => {
1418
- editingCommentId = c.id;
1419
- activeInlineCommentKey = null;
1420
- refreshReviewCommentUi(jobId);
1503
+ startCommentEdit(c.id, 'inline', jobId);
1421
1504
  });
1422
1505
  const remove = document.createElement('button');
1423
1506
  remove.type = 'button';
@@ -1427,7 +1510,7 @@ function renderDiff(diff, opts = {}) {
1427
1510
  const d = loadReviewDraft(jobId);
1428
1511
  d.lineComments = d.lineComments.filter((x) => x.id !== c.id);
1429
1512
  saveReviewDraft(jobId);
1430
- if (editingCommentId === c.id) editingCommentId = null;
1513
+ if (editingCommentId === c.id) clearCommentEditState();
1431
1514
  refreshReviewCommentUi(jobId);
1432
1515
  });
1433
1516
  actions.appendChild(edit);
@@ -1684,22 +1767,16 @@ function renderPendingCommentList(jobId) {
1684
1767
  loc.textContent = `${c.path}:${c.line} (${c.side})`;
1685
1768
  main.appendChild(loc);
1686
1769
 
1687
- if (editingCommentId === c.id) {
1770
+ if (editingCommentId === c.id && editingCommentSurface === 'pending') {
1688
1771
  main.appendChild(
1689
1772
  buildCommentEditForm({
1690
1773
  initialBody: c.body,
1691
- onSave: (body) => {
1692
- const d = loadReviewDraft(jobId);
1693
- const target = d.lineComments.find((x) => x.id === c.id);
1694
- if (target) target.body = body;
1695
- saveReviewDraft(jobId);
1696
- editingCommentId = null;
1697
- refreshReviewCommentUi(jobId);
1698
- },
1699
- onCancel: () => {
1700
- editingCommentId = null;
1701
- refreshReviewCommentUi(jobId);
1774
+ onInput: (body) => {
1775
+ updateLineCommentBody(jobId, c.id, body);
1776
+ mirrorCommentEditToOtherSurface(jobId);
1702
1777
  },
1778
+ onSave: (body) => finishCommentEdit(jobId, c.id, body),
1779
+ onCancel: () => finishCommentEdit(jobId, c.id, null),
1703
1780
  })
1704
1781
  );
1705
1782
  item.appendChild(main);
@@ -1718,9 +1795,7 @@ function renderPendingCommentList(jobId) {
1718
1795
  edit.className = 'btn btn-muted-text btn-sm';
1719
1796
  edit.textContent = 'Edit';
1720
1797
  edit.addEventListener('click', () => {
1721
- editingCommentId = c.id;
1722
- activeInlineCommentKey = null;
1723
- refreshReviewCommentUi(jobId);
1798
+ startCommentEdit(c.id, 'pending', jobId);
1724
1799
  });
1725
1800
  const remove = document.createElement('button');
1726
1801
  remove.type = 'button';
@@ -1730,7 +1805,7 @@ function renderPendingCommentList(jobId) {
1730
1805
  const d = loadReviewDraft(jobId);
1731
1806
  d.lineComments = d.lineComments.filter((x) => x.id !== c.id);
1732
1807
  saveReviewDraft(jobId);
1733
- if (editingCommentId === c.id) editingCommentId = null;
1808
+ if (editingCommentId === c.id) clearCommentEditState();
1734
1809
  refreshReviewCommentUi(jobId);
1735
1810
  });
1736
1811
  actions.appendChild(edit);
@@ -2321,7 +2396,7 @@ function renderReview(jobs) {
2321
2396
  const focusInPending = Boolean(
2322
2397
  els.reviewPendingList?.contains(document.activeElement)
2323
2398
  );
2324
- if (!(editingCommentId && focusInPending)) {
2399
+ if (!(editingCommentId && editingCommentSurface === 'pending' && focusInPending)) {
2325
2400
  renderPendingCommentList(job.id);
2326
2401
  }
2327
2402
  syncSubmitReviewButton(job.id);
@@ -2353,7 +2428,9 @@ function renderReview(jobs) {
2353
2428
 
2354
2429
  const focusInDiff = els.diffViewer?.contains(document.activeElement);
2355
2430
  const preservingDiffCompose =
2356
- (activeInlineCommentKey || editingCommentId) && focusInDiff;
2431
+ (Boolean(activeInlineCommentKey) ||
2432
+ (editingCommentId && editingCommentSurface === 'inline')) &&
2433
+ focusInDiff;
2357
2434
  if (!preservingDiffCompose) {
2358
2435
  renderDiff(displayDiff, { interactive: editable, jobId: job.id });
2359
2436
  }