acdev 1.0.15 → 1.0.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "acdev",
3
- "version": "1.0.15",
3
+ "version": "1.0.16",
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
@@ -1051,6 +1051,148 @@ function sortedJobs() {
1051
1051
  );
1052
1052
  }
1053
1053
 
1054
+ /**
1055
+ * @param {string} raw
1056
+ * @returns {string}
1057
+ */
1058
+ function escapeHtml(raw) {
1059
+ return String(raw)
1060
+ .replaceAll('&', '&')
1061
+ .replaceAll('<', '&lt;')
1062
+ .replaceAll('>', '&gt;')
1063
+ .replaceAll('"', '&quot;')
1064
+ .replaceAll("'", '&#39;');
1065
+ }
1066
+
1067
+ /**
1068
+ * @param {string} raw
1069
+ * @returns {string | null}
1070
+ */
1071
+ function safeMarkdownHref(raw) {
1072
+ const href = String(raw || '').trim();
1073
+ if (!href) return null;
1074
+ if (href.startsWith('#') || href.startsWith('/')) return href;
1075
+ try {
1076
+ const url = new URL(href, window.location.href);
1077
+ return ['http:', 'https:', 'mailto:'].includes(url.protocol) ? url.href : null;
1078
+ } catch {
1079
+ return null;
1080
+ }
1081
+ }
1082
+
1083
+ /**
1084
+ * @param {string} raw
1085
+ * @returns {string}
1086
+ */
1087
+ function renderInlineMarkdown(raw) {
1088
+ const stash = [];
1089
+ const hold = (html) => `\u0000${stash.push(html) - 1}\u0000`;
1090
+ let out = escapeHtml(raw);
1091
+ out = out.replace(/`([^`]+)`/g, (_, code) => hold(`<code>${code}</code>`));
1092
+ out = out.replace(/\*\*([^*]+)\*\*/g, (_, body) => hold(`<strong>${body}</strong>`));
1093
+ out = out.replace(/__([^_]+)__/g, (_, body) => hold(`<strong>${body}</strong>`));
1094
+ out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, label, href) => {
1095
+ const safe = safeMarkdownHref(href);
1096
+ if (!safe) return label;
1097
+ return hold(
1098
+ `<a href="${escapeHtml(safe)}" target="_blank" rel="noopener noreferrer">${label}</a>`
1099
+ );
1100
+ });
1101
+ return out.replace(/\u0000(\d+)\u0000/g, (_, idx) => stash[Number(idx)] || '');
1102
+ }
1103
+
1104
+ /**
1105
+ * @param {HTMLElement | null} container
1106
+ * @param {string} raw
1107
+ */
1108
+ function renderMarkdownBlock(container, raw) {
1109
+ if (!container) return;
1110
+ container.innerHTML = '';
1111
+ const text = String(raw || '').replace(/\r\n/g, '\n').trim();
1112
+ if (!text) return;
1113
+
1114
+ const lines = text.split('\n');
1115
+ let paragraph = [];
1116
+ let listEl = null;
1117
+ let listType = '';
1118
+
1119
+ const flushParagraph = () => {
1120
+ if (!paragraph.length) return;
1121
+ const p = document.createElement('p');
1122
+ p.innerHTML = renderInlineMarkdown(paragraph.join(' '));
1123
+ container.appendChild(p);
1124
+ paragraph = [];
1125
+ };
1126
+
1127
+ const flushList = () => {
1128
+ if (!listEl) return;
1129
+ container.appendChild(listEl);
1130
+ listEl = null;
1131
+ listType = '';
1132
+ };
1133
+
1134
+ for (let i = 0; i < lines.length; i += 1) {
1135
+ const line = lines[i];
1136
+ if (!line.trim()) {
1137
+ flushParagraph();
1138
+ flushList();
1139
+ continue;
1140
+ }
1141
+
1142
+ const fence = line.match(/^```([\w-]+)?\s*$/);
1143
+ if (fence) {
1144
+ flushParagraph();
1145
+ flushList();
1146
+ const codeLines = [];
1147
+ for (i += 1; i < lines.length; i += 1) {
1148
+ if (/^```\s*$/.test(lines[i])) break;
1149
+ codeLines.push(lines[i]);
1150
+ }
1151
+ const pre = document.createElement('pre');
1152
+ pre.className = 'markdown-code';
1153
+ const code = document.createElement('code');
1154
+ if (fence[1]) code.dataset.language = fence[1];
1155
+ code.textContent = codeLines.join('\n');
1156
+ pre.appendChild(code);
1157
+ container.appendChild(pre);
1158
+ continue;
1159
+ }
1160
+
1161
+ const heading = line.match(/^(#{1,6})\s+(.*)$/);
1162
+ if (heading) {
1163
+ flushParagraph();
1164
+ flushList();
1165
+ const tag = `h${heading[1].length}`;
1166
+ const el = document.createElement(tag);
1167
+ el.className = `markdown-heading markdown-heading-${heading[1].length}`;
1168
+ el.innerHTML = renderInlineMarkdown(heading[2]);
1169
+ container.appendChild(el);
1170
+ continue;
1171
+ }
1172
+
1173
+ const bullet = line.match(/^\s*([-*+])\s+(.*)$/);
1174
+ const ordered = line.match(/^\s*\d+\.\s+(.*)$/);
1175
+ if (bullet || ordered) {
1176
+ flushParagraph();
1177
+ const type = bullet ? 'ul' : 'ol';
1178
+ if (!listEl || listType !== type) {
1179
+ flushList();
1180
+ listEl = document.createElement(type);
1181
+ listType = type;
1182
+ }
1183
+ const li = document.createElement('li');
1184
+ li.innerHTML = renderInlineMarkdown((bullet || ordered)[2] || (bullet || ordered)[1] || '');
1185
+ listEl.appendChild(li);
1186
+ continue;
1187
+ }
1188
+
1189
+ paragraph.push(line.trim());
1190
+ }
1191
+
1192
+ flushParagraph();
1193
+ flushList();
1194
+ }
1195
+
1054
1196
  /**
1055
1197
  * @param {HTMLElement | null} container
1056
1198
  * @param {object | null | undefined} pr
@@ -1089,8 +1231,8 @@ function renderPrStatusNotes(container, pr) {
1089
1231
 
1090
1232
  if (review.body) {
1091
1233
  const body = document.createElement('div');
1092
- body.className = 'pr-status-note-body';
1093
- body.textContent = review.body;
1234
+ body.className = 'pr-status-note-body markdown-view';
1235
+ renderMarkdownBlock(body, review.body);
1094
1236
  item.appendChild(body);
1095
1237
  }
1096
1238
 
@@ -1107,6 +1249,10 @@ function renderPrStatusNotes(container, pr) {
1107
1249
  container.appendChild(requested);
1108
1250
  }
1109
1251
  }
1252
+ if (typeof window !== 'undefined') {
1253
+ window.__acdevMarkdownRender = renderMarkdownBlock;
1254
+ window.__acdevMarkdownInline = renderInlineMarkdown;
1255
+ }
1110
1256
  function formatTime(ts) {
1111
1257
  try {
1112
1258
  return new Date(ts).toLocaleTimeString([], {
@@ -1771,8 +1917,8 @@ function renderDiff(diff, opts = {}) {
1771
1917
  const chip = document.createElement('div');
1772
1918
  chip.className = 'diff-pending-chip';
1773
1919
  const body = document.createElement('div');
1774
- body.className = 'diff-pending-chip-body';
1775
- body.textContent = c.body;
1920
+ body.className = 'diff-pending-chip-body markdown-view';
1921
+ renderMarkdownBlock(body, c.body);
1776
1922
  const actions = document.createElement('div');
1777
1923
  actions.className = 'diff-pending-chip-actions';
1778
1924
  const edit = document.createElement('button');
@@ -1922,8 +2068,7 @@ function renderDiff(diff, opts = {}) {
1922
2068
  }
1923
2069
  continue;
1924
2070
  }
1925
-
1926
- if (line.startsWith('@@')) {
2071
+ if (line.startsWith('@@ ')) {
1927
2072
  const m = line.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
1928
2073
  if (m) {
1929
2074
  leftNo = Number(m[1]);
@@ -2065,8 +2210,8 @@ function renderPendingCommentList(jobId) {
2065
2210
  }
2066
2211
 
2067
2212
  const body = document.createElement('div');
2068
- body.className = 'review-pending-item-body';
2069
- body.textContent = c.body;
2213
+ body.className = 'review-pending-item-body markdown-view';
2214
+ renderMarkdownBlock(body, c.body);
2070
2215
  main.appendChild(body);
2071
2216
  const actions = document.createElement('div');
2072
2217
  actions.className = 'review-pending-item-actions';
package/public/styles.css CHANGED
@@ -1620,15 +1620,67 @@ a { color: var(--primary); text-underline-offset: 3px; }
1620
1620
  background: var(--surface-2);
1621
1621
  }
1622
1622
 
1623
- .pr-status-note-meta {
1623
+ .markdown-view {
1624
+ line-height: 1.45;
1625
+ word-break: break-word;
1626
+ }
1627
+
1628
+ .markdown-view > :first-child {
1629
+ margin-top: 0;
1630
+ }
1631
+
1632
+ .markdown-view > :last-child {
1633
+ margin-bottom: 0;
1634
+ }
1635
+
1636
+ .markdown-view p,
1637
+ .markdown-view ul,
1638
+ .markdown-view ol,
1639
+ .markdown-view pre,
1640
+ .markdown-view blockquote,
1641
+ .markdown-view h1,
1642
+ .markdown-view h2,
1643
+ .markdown-view h3,
1644
+ .markdown-view h4,
1645
+ .markdown-view h5,
1646
+ .markdown-view h6 {
1647
+ margin: 0 0 8px;
1648
+ }
1649
+
1650
+ .markdown-view ul,
1651
+ .markdown-view ol {
1652
+ padding-left: 20px;
1653
+ }
1654
+
1655
+ .markdown-view code {
1656
+ font-family: "JetBrains Mono", ui-monospace, monospace;
1624
1657
  font-size: 12px;
1625
- color: var(--text-muted);
1626
- margin-bottom: 4px;
1658
+ padding: 1px 4px;
1659
+ background: var(--bg);
1660
+ border-radius: 4px;
1661
+ }
1662
+
1663
+ .markdown-view pre {
1664
+ margin: 0 0 8px;
1665
+ padding: 8px 10px;
1666
+ background: var(--bg);
1667
+ border: 1px solid var(--border-soft);
1668
+ border-radius: 8px;
1669
+ overflow-x: auto;
1670
+ }
1671
+
1672
+ .markdown-view pre code {
1673
+ padding: 0;
1674
+ background: transparent;
1675
+ }
1676
+
1677
+ .markdown-view a {
1678
+ color: var(--primary);
1679
+ text-decoration: underline;
1627
1680
  }
1628
1681
 
1629
1682
  .pr-status-note-body {
1630
- white-space: pre-wrap;
1631
- line-height: 1.45;
1683
+ font-size: 13px;
1632
1684
  }
1633
1685
 
1634
1686
  /* —— Review file selection —— */
@@ -1943,7 +1995,7 @@ body.diff-fs-open {
1943
1995
 
1944
1996
  .diff-pending-chip-body {
1945
1997
  color: var(--text);
1946
- white-space: pre-wrap;
1998
+ font-size: 12px;
1947
1999
  }
1948
2000
 
1949
2001
  .diff-pending-chip-actions {
@@ -1953,6 +2005,10 @@ body.diff-fs-open {
1953
2005
  flex-wrap: wrap;
1954
2006
  }
1955
2007
 
2008
+ .review-pending-item-body {
2009
+ font-size: 13px;
2010
+ }
2011
+
1956
2012
  .review-pending-list {
1957
2013
  display: flex;
1958
2014
  flex-direction: column;