Public: anyone can read this; the crew can change it.

An issue's thread with 200 comments renders in under 15 ms #3338

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/3324-an-issue-s-thread-with-200-comments-rend → main 5 commits fork 8 threads · 8 unresolved

  • Merged. scout/3324-an-issue-s-thread-with-200-comments-rend was merged into main as d4ac2e9.

Review

Sign in to review this change.
9 files changed +698 −15
src/core/events.ts +16 −3
@@ -118,6 +118,12 @@ export interface ListEventsOptions {
118118 afterId?: string;
119119 /** @default 100 */
120120 limit?: number;
121 /**
122 * Of the newest `limit` events, keep only these kinds. The window is the
123 * same one an unfiltered read sees; the rows left out are never parsed,
124 * which is what a busy thread's comment events would otherwise cost.
125 */
126 kinds?: readonly string[];
121127 }
122128
123129 /**
@@ -202,10 +208,17 @@ export function listEvents(
202208 }
203209 params.push(options.limit ?? 100);
204210 const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
211 const limit = params.length;
212 let sql = `SELECT * FROM event ${where} ORDER BY event.seq DESC LIMIT ?${limit}`;
213 if (options.kinds !== undefined) {
214 const marks = options.kinds.map((kind) => {
215 params.push(kind);
216 return `?${params.length}`;
217 });
218 sql = `SELECT * FROM (${sql}) WHERE kind IN (${marks.join(", ")}) ORDER BY seq DESC`;
219 }
205220 return db
206 .query<EventRow, (string | number)[]>(
207 `SELECT * FROM event ${where} ORDER BY event.seq DESC LIMIT ?${params.length}`,
208 )
221 .query<EventRow, (string | number)[]>(sql)
209222 .all(...params)
210223 .map(toEvent);
211224 }
src/http/server.ts +3
@@ -12,6 +12,7 @@ import { toolbarComment, toolbarPage } from "../web/studio/toolbar.ts";
1212 import { TOOLBAR_NAME, toolbarResponse } from "../apps/toolbar.ts";
1313 import { unsealReleased } from "../actions/sealed.ts";
1414 import { parentCallback, parentLinkStart, parentStart } from "./rp.ts";
15 import { prerenderComments } from "../web/prerender.ts";
1516 import { accessPage } from "../web/pages/access.ts";
1617 import { capacityPage, capacitySet } from "../web/pages/capacity.ts";
1718 import { systemPage, systemStream } from "../web/pages/system.ts";
@@ -2102,6 +2103,7 @@ export async function serve(app: App): Promise<Servers> {
21022103 if (user === undefined) return;
21032104 closeStreamsOf(app.db, user.id, unclosed);
21042105 });
2106 const prerender = prerenderComments(app);
21052107 // And every process holds what it has open against the database, so a
21062108 // suspension or sign-out another process wrote reaches it too (#846).
21072109 const sweep = setInterval(
@@ -2345,6 +2347,7 @@ export async function serve(app: App): Promise<Servers> {
23452347 },
23462348 stop() {
23472349 endOpen();
2350 prerender();
23482351 clearInterval(sweep);
23492352 // Counts first: a hosted child's database closes with its app.
23502353 for (const counter of counters.values()) {
src/web/icons.ts +25 −6
@@ -117,13 +117,32 @@ export function icon(name: IconName, size: 16 | 20 = 16): Html {
117117 /** The reference `icon()` leaves behind, and the only thing read back. */
118118 const REFERENCE = /<use href="#i-([A-Za-z]+)"\/>/g;
119119
120 /** Every icon this markup references, once each, in first-seen order. */
121 function referenced(markup: string): IconName[] {
120 /** What `REFERENCE` starts with, so a page can be searched for it by `indexOf`. */
121 const REFERENCE_OPEN = '<use href="#i-';
122
123 /**
124 * Every icon this markup references, once each, in first-seen order. Found
125 * with `indexOf`, which skips through a long page far faster than a regular
126 * expression does, and accepts exactly what `REFERENCE` accepts: letters,
127 * then `"/>`.
128 */
129 export function referenced(markup: string): IconName[] {
122130 const names: IconName[] = [];
123 for (const match of markup.matchAll(REFERENCE)) {
124 const name = match[1];
125 if (name === undefined || !(name in PATHS)) continue;
126 if (!names.includes(name as IconName)) names.push(name as IconName);
131 let at = markup.indexOf(REFERENCE_OPEN);
132 while (at !== -1) {
133 const start = at + REFERENCE_OPEN.length;
134 let end = start;
135 while (end < markup.length) {
136 const c = markup.charCodeAt(end) | 32;
137 if (c < 97 || c > 122) break;
138 end++;
139 }
140 if (end > start && markup.startsWith('"/>', end)) {
141 const name = markup.slice(start, end);
142 if (name in PATHS && !names.includes(name as IconName))
143 names.push(name as IconName);
144 }
145 at = markup.indexOf(REFERENCE_OPEN, end);
127146 }
128147 return names;
129148 }
src/web/markdown.ts +13 −5
@@ -16,12 +16,20 @@ import { highlightToHtml } from "./highlight-html.ts";
1616 import { escapeHtml, Html, raw } from "./html.ts";
1717
1818 function inline(escaped: string): string {
19 return (
20 escaped
21 // `code` first, so nothing inside it is styled
22 .replace(/`([^`]+)`/g, "<code>$1</code>")
19 // Most lines of prose carry none of the marks; each rule only runs on a
20 // line that has the character it starts with, which changes nothing about

Low: the pull description says these per-rule guards gave no measurable gain. A branch with no measured benefit is churn. Consider reverting inline() to the single chain so the renderer stays as it was.

Minor, not blocking: the pull description says these includes shortcuts gave no measurable gain. If so, they add branches to the escaping path for nothing, and the original single chain was easier to audit. Consider dropping them in a follow-up.

21 // what the rules produce.
22 let text = escaped;
23 // `code` first, so nothing inside it is styled
24 if (text.includes("`")) text = text.replace(/`([^`]+)`/g, "<code>$1</code>");
25 if (text.includes("*")) {
26 text = text
2327 .replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
24 .replace(/(^|\W)\*([^*\s][^*]*)\*/g, "$1<em>$2</em>")
28 .replace(/(^|\W)\*([^*\s][^*]*)\*/g, "$1<em>$2</em>");
29 }
30 if (!text.includes("](")) return text;
31 return (
32 text
2533 // [text](url) — safe schemes only; quotes are already escaped away
2634 .replace(
2735 /\[([^\]]+)\]\(([^)\s]+)\)/g,
src/web/pages/thread.ts +5 −1
@@ -171,7 +171,11 @@ const EVENT_LOOK: Record<string, { icon: IconName; tone: Tone; verb: string }> =
171171 * disagree with what actually happened.
172172 */
173173 function stateEvents(ctx: Ctx, repo: Repo, issue: Issue): Entry[] {
174 const events = listEvents(ctx.app.db, { repoId: repo.id, limit: 200 }).filter(
174 const events = listEvents(ctx.app.db, {
175 repoId: repo.id,
176 limit: 200,
177 kinds: Object.keys(EVENT_LOOK),
178 }).filter(
175179 (event) =>
176180 EVENT_LOOK[event.kind] !== undefined &&
177181 Number(event.payload.number) === issue.number &&
src/web/prerender.ts added +22
@@ -0,0 +1,22 @@
1 /**
2 * Markdown rendered at write time. A comment is written once and read many
3 * times, so the moment it lands its body is rendered into `renderMarkdown`'s
4 * cache and the first view of the thread finds it done. Nothing here changes
5 * what is rendered: it is the same call, made earlier.
6 */
7
8 import type { App } from "../app.ts";
9 import { renderMarkdown } from "./markdown.ts";
10
11 export function prerenderComments(app: App): () => void {
12 return app.bus.subscribe((event) => {
13 if (event.kind !== "issue.commented" && event.kind !== "pull.commented") return;

Low: an edited comment (*.comment.edited or whatever kind the edit path emits) is not prerendered. The first view after an edit pays for that one body again. That is harmless, but name it in the module doc so it doesn't look like an oversight.

14 const body = event.payload["body"];
15 if (typeof body !== "string" || body.trim() === "") return;
16 try {
17 renderMarkdown(body);

Low: bus listeners run synchronously inside append, so the comment's author pays the render on their own POST. That is about 1 ms for a typical body, and more for a 64 KiB fenced block. Acceptable for now. If writes ever show it in Server-Timing, defer this with queueMicrotask/setTimeout(0) so the response goes first.

18 } catch (error) {
19 app.log.warn("could not prerender a comment", { error: String(error) });
20 }
21 });
22 }
test/__snapshots__/thread-seed.html added +479
@@ -0,0 +1,479 @@
1 <h2>Comment 0 on the plan</h2>
2 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/0" rel="nofollow noopener">the spec</a> and <a href="docs/notes-0.md" rel="nofollow noopener">local notes</a>.
3 Inline <code>code0</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
4 <ul>
5 <li>first point with a <a href="/trevato/forge/issues/0" rel="nofollow noopener">link</a></li>
6 <li>second point, cc @pablo</li>
7 </ul>
8 <ol>
9 <li>step <strong>one</strong></li>
10 <li>step two</li>
11 </ol>
12 <blockquote>
13 <p>quoted from @olive: <em>ship it</em></p>
14 </blockquote>
15 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
16 <tbody>
17 <tr><td>0</td><td class="ta-r"><code>x</code></td></tr>
18 </tbody></table>
19 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f0</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
20 <h2>Comment 1000 on the plan</h2>
21 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/1000" rel="nofollow noopener">the spec</a> and <a href="docs/notes-1000.md" rel="nofollow noopener">local notes</a>.
22 Inline <code>code1000</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
23 <ul>
24 <li>first point with a <a href="/trevato/forge/issues/1000" rel="nofollow noopener">link</a></li>
25 <li>second point, cc @pablo</li>
26 </ul>
27 <ol>
28 <li>step <strong>one</strong></li>
29 <li>step two</li>
30 </ol>
31 <blockquote>
32 <p>quoted from @olive: <em>ship it</em></p>
33 </blockquote>
34 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
35 <tbody>
36 <tr><td>1000</td><td class="ta-r"><code>x</code></td></tr>
37 </tbody></table>
38 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f1000</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
39 <h2>Comment 2000 on the plan</h2>
40 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/2000" rel="nofollow noopener">the spec</a> and <a href="docs/notes-2000.md" rel="nofollow noopener">local notes</a>.
41 Inline <code>code2000</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
42 <ul>
43 <li>first point with a <a href="/trevato/forge/issues/2000" rel="nofollow noopener">link</a></li>
44 <li>second point, cc @pablo</li>
45 </ul>
46 <ol>
47 <li>step <strong>one</strong></li>
48 <li>step two</li>
49 </ol>
50 <blockquote>
51 <p>quoted from @olive: <em>ship it</em></p>
52 </blockquote>
53 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
54 <tbody>
55 <tr><td>2000</td><td class="ta-r"><code>x</code></td></tr>
56 </tbody></table>
57 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f2000</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
58 <h2>Comment 3000 on the plan</h2>
59 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/3000" rel="nofollow noopener">the spec</a> and <a href="docs/notes-3000.md" rel="nofollow noopener">local notes</a>.
60 Inline <code>code3000</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
61 <ul>
62 <li>first point with a <a href="/trevato/forge/issues/3000" rel="nofollow noopener">link</a></li>
63 <li>second point, cc @pablo</li>
64 </ul>
65 <ol>
66 <li>step <strong>one</strong></li>
67 <li>step two</li>
68 </ol>
69 <blockquote>
70 <p>quoted from @olive: <em>ship it</em></p>
71 </blockquote>
72 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
73 <tbody>
74 <tr><td>3000</td><td class="ta-r"><code>x</code></td></tr>
75 </tbody></table>
76 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f3000</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
77 <h2>Comment 4000 on the plan</h2>
78 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/4000" rel="nofollow noopener">the spec</a> and <a href="docs/notes-4000.md" rel="nofollow noopener">local notes</a>.
79 Inline <code>code4000</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
80 <ul>
81 <li>first point with a <a href="/trevato/forge/issues/4000" rel="nofollow noopener">link</a></li>
82 <li>second point, cc @pablo</li>
83 </ul>
84 <ol>
85 <li>step <strong>one</strong></li>
86 <li>step two</li>
87 </ol>
88 <blockquote>
89 <p>quoted from @olive: <em>ship it</em></p>
90 </blockquote>
91 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
92 <tbody>
93 <tr><td>4000</td><td class="ta-r"><code>x</code></td></tr>
94 </tbody></table>
95 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f4000</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
96 <!--next-->
97 <h2>Comment 1 on the plan</h2>
98 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/1" rel="nofollow noopener">the spec</a> and <a href="docs/notes-1.md" rel="nofollow noopener">local notes</a>.
99 Inline <code>code1</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
100 <ul>
101 <li>first point with a <a href="/trevato/forge/issues/1" rel="nofollow noopener">link</a></li>
102 <li>second point, cc @pablo</li>
103 </ul>
104 <ol>
105 <li>step <strong>one</strong></li>
106 <li>step two</li>
107 </ol>
108 <blockquote>
109 <p>quoted from @olive: <em>ship it</em></p>
110 </blockquote>
111 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
112 <tbody>
113 <tr><td>1</td><td class="ta-r"><code>x</code></td></tr>
114 </tbody></table>
115 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f1</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
116 <h2>Comment 1001 on the plan</h2>
117 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/1001" rel="nofollow noopener">the spec</a> and <a href="docs/notes-1001.md" rel="nofollow noopener">local notes</a>.
118 Inline <code>code1001</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
119 <ul>
120 <li>first point with a <a href="/trevato/forge/issues/1001" rel="nofollow noopener">link</a></li>
121 <li>second point, cc @pablo</li>
122 </ul>
123 <ol>
124 <li>step <strong>one</strong></li>
125 <li>step two</li>
126 </ol>
127 <blockquote>
128 <p>quoted from @olive: <em>ship it</em></p>
129 </blockquote>
130 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
131 <tbody>
132 <tr><td>1001</td><td class="ta-r"><code>x</code></td></tr>
133 </tbody></table>
134 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f1001</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
135 <h2>Comment 2001 on the plan</h2>
136 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/2001" rel="nofollow noopener">the spec</a> and <a href="docs/notes-2001.md" rel="nofollow noopener">local notes</a>.
137 Inline <code>code2001</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
138 <ul>
139 <li>first point with a <a href="/trevato/forge/issues/2001" rel="nofollow noopener">link</a></li>
140 <li>second point, cc @pablo</li>
141 </ul>
142 <ol>
143 <li>step <strong>one</strong></li>
144 <li>step two</li>
145 </ol>
146 <blockquote>
147 <p>quoted from @olive: <em>ship it</em></p>
148 </blockquote>
149 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
150 <tbody>
151 <tr><td>2001</td><td class="ta-r"><code>x</code></td></tr>
152 </tbody></table>
153 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f2001</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
154 <h2>Comment 3001 on the plan</h2>
155 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/3001" rel="nofollow noopener">the spec</a> and <a href="docs/notes-3001.md" rel="nofollow noopener">local notes</a>.
156 Inline <code>code3001</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
157 <ul>
158 <li>first point with a <a href="/trevato/forge/issues/3001" rel="nofollow noopener">link</a></li>
159 <li>second point, cc @pablo</li>
160 </ul>
161 <ol>
162 <li>step <strong>one</strong></li>
163 <li>step two</li>
164 </ol>
165 <blockquote>
166 <p>quoted from @olive: <em>ship it</em></p>
167 </blockquote>
168 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
169 <tbody>
170 <tr><td>3001</td><td class="ta-r"><code>x</code></td></tr>
171 </tbody></table>
172 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f3001</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
173 <h2>Comment 4001 on the plan</h2>
174 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/4001" rel="nofollow noopener">the spec</a> and <a href="docs/notes-4001.md" rel="nofollow noopener">local notes</a>.
175 Inline <code>code4001</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
176 <ul>
177 <li>first point with a <a href="/trevato/forge/issues/4001" rel="nofollow noopener">link</a></li>
178 <li>second point, cc @pablo</li>
179 </ul>
180 <ol>
181 <li>step <strong>one</strong></li>
182 <li>step two</li>
183 </ol>
184 <blockquote>
185 <p>quoted from @olive: <em>ship it</em></p>
186 </blockquote>
187 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
188 <tbody>
189 <tr><td>4001</td><td class="ta-r"><code>x</code></td></tr>
190 </tbody></table>
191 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f4001</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
192 <!--next-->
193 <h2>Comment 57 on the plan</h2>
194 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/57" rel="nofollow noopener">the spec</a> and <a href="docs/notes-57.md" rel="nofollow noopener">local notes</a>.
195 Inline <code>code57</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
196 <ul>
197 <li>first point with a <a href="/trevato/forge/issues/57" rel="nofollow noopener">link</a></li>
198 <li>second point, cc @pablo</li>
199 </ul>
200 <ol>
201 <li>step <strong>one</strong></li>
202 <li>step two</li>
203 </ol>
204 <blockquote>
205 <p>quoted from @olive: <em>ship it</em></p>
206 </blockquote>
207 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
208 <tbody>
209 <tr><td>57</td><td class="ta-r"><code>x</code></td></tr>
210 </tbody></table>
211 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f57</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
212 <h2>Comment 1057 on the plan</h2>
213 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/1057" rel="nofollow noopener">the spec</a> and <a href="docs/notes-1057.md" rel="nofollow noopener">local notes</a>.
214 Inline <code>code1057</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
215 <ul>
216 <li>first point with a <a href="/trevato/forge/issues/1057" rel="nofollow noopener">link</a></li>
217 <li>second point, cc @pablo</li>
218 </ul>
219 <ol>
220 <li>step <strong>one</strong></li>
221 <li>step two</li>
222 </ol>
223 <blockquote>
224 <p>quoted from @olive: <em>ship it</em></p>
225 </blockquote>
226 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
227 <tbody>
228 <tr><td>1057</td><td class="ta-r"><code>x</code></td></tr>
229 </tbody></table>
230 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f1057</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
231 <h2>Comment 2057 on the plan</h2>
232 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/2057" rel="nofollow noopener">the spec</a> and <a href="docs/notes-2057.md" rel="nofollow noopener">local notes</a>.
233 Inline <code>code2057</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
234 <ul>
235 <li>first point with a <a href="/trevato/forge/issues/2057" rel="nofollow noopener">link</a></li>
236 <li>second point, cc @pablo</li>
237 </ul>
238 <ol>
239 <li>step <strong>one</strong></li>
240 <li>step two</li>
241 </ol>
242 <blockquote>
243 <p>quoted from @olive: <em>ship it</em></p>
244 </blockquote>
245 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
246 <tbody>
247 <tr><td>2057</td><td class="ta-r"><code>x</code></td></tr>
248 </tbody></table>
249 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f2057</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
250 <h2>Comment 3057 on the plan</h2>
251 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/3057" rel="nofollow noopener">the spec</a> and <a href="docs/notes-3057.md" rel="nofollow noopener">local notes</a>.
252 Inline <code>code3057</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
253 <ul>
254 <li>first point with a <a href="/trevato/forge/issues/3057" rel="nofollow noopener">link</a></li>
255 <li>second point, cc @pablo</li>
256 </ul>
257 <ol>
258 <li>step <strong>one</strong></li>
259 <li>step two</li>
260 </ol>
261 <blockquote>
262 <p>quoted from @olive: <em>ship it</em></p>
263 </blockquote>
264 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
265 <tbody>
266 <tr><td>3057</td><td class="ta-r"><code>x</code></td></tr>
267 </tbody></table>
268 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f3057</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
269 <h2>Comment 4057 on the plan</h2>
270 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/4057" rel="nofollow noopener">the spec</a> and <a href="docs/notes-4057.md" rel="nofollow noopener">local notes</a>.
271 Inline <code>code4057</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
272 <ul>
273 <li>first point with a <a href="/trevato/forge/issues/4057" rel="nofollow noopener">link</a></li>
274 <li>second point, cc @pablo</li>
275 </ul>
276 <ol>
277 <li>step <strong>one</strong></li>
278 <li>step two</li>
279 </ol>
280 <blockquote>
281 <p>quoted from @olive: <em>ship it</em></p>
282 </blockquote>
283 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
284 <tbody>
285 <tr><td>4057</td><td class="ta-r"><code>x</code></td></tr>
286 </tbody></table>
287 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f4057</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
288 <!--next-->
289 <h2>Comment 128 on the plan</h2>
290 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/128" rel="nofollow noopener">the spec</a> and <a href="docs/notes-128.md" rel="nofollow noopener">local notes</a>.
291 Inline <code>code128</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
292 <ul>
293 <li>first point with a <a href="/trevato/forge/issues/128" rel="nofollow noopener">link</a></li>
294 <li>second point, cc @pablo</li>
295 </ul>
296 <ol>
297 <li>step <strong>one</strong></li>
298 <li>step two</li>
299 </ol>
300 <blockquote>
301 <p>quoted from @olive: <em>ship it</em></p>
302 </blockquote>
303 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
304 <tbody>
305 <tr><td>128</td><td class="ta-r"><code>x</code></td></tr>
306 </tbody></table>
307 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f128</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
308 <h2>Comment 1128 on the plan</h2>
309 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/1128" rel="nofollow noopener">the spec</a> and <a href="docs/notes-1128.md" rel="nofollow noopener">local notes</a>.
310 Inline <code>code1128</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
311 <ul>
312 <li>first point with a <a href="/trevato/forge/issues/1128" rel="nofollow noopener">link</a></li>
313 <li>second point, cc @pablo</li>
314 </ul>
315 <ol>
316 <li>step <strong>one</strong></li>
317 <li>step two</li>
318 </ol>
319 <blockquote>
320 <p>quoted from @olive: <em>ship it</em></p>
321 </blockquote>
322 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
323 <tbody>
324 <tr><td>1128</td><td class="ta-r"><code>x</code></td></tr>
325 </tbody></table>
326 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f1128</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
327 <h2>Comment 2128 on the plan</h2>
328 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/2128" rel="nofollow noopener">the spec</a> and <a href="docs/notes-2128.md" rel="nofollow noopener">local notes</a>.
329 Inline <code>code2128</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
330 <ul>
331 <li>first point with a <a href="/trevato/forge/issues/2128" rel="nofollow noopener">link</a></li>
332 <li>second point, cc @pablo</li>
333 </ul>
334 <ol>
335 <li>step <strong>one</strong></li>
336 <li>step two</li>
337 </ol>
338 <blockquote>
339 <p>quoted from @olive: <em>ship it</em></p>
340 </blockquote>
341 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
342 <tbody>
343 <tr><td>2128</td><td class="ta-r"><code>x</code></td></tr>
344 </tbody></table>
345 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f2128</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
346 <h2>Comment 3128 on the plan</h2>
347 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/3128" rel="nofollow noopener">the spec</a> and <a href="docs/notes-3128.md" rel="nofollow noopener">local notes</a>.
348 Inline <code>code3128</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
349 <ul>
350 <li>first point with a <a href="/trevato/forge/issues/3128" rel="nofollow noopener">link</a></li>
351 <li>second point, cc @pablo</li>
352 </ul>
353 <ol>
354 <li>step <strong>one</strong></li>
355 <li>step two</li>
356 </ol>
357 <blockquote>
358 <p>quoted from @olive: <em>ship it</em></p>
359 </blockquote>
360 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
361 <tbody>
362 <tr><td>3128</td><td class="ta-r"><code>x</code></td></tr>
363 </tbody></table>
364 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f3128</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
365 <h2>Comment 4128 on the plan</h2>
366 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/4128" rel="nofollow noopener">the spec</a> and <a href="docs/notes-4128.md" rel="nofollow noopener">local notes</a>.
367 Inline <code>code4128</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
368 <ul>
369 <li>first point with a <a href="/trevato/forge/issues/4128" rel="nofollow noopener">link</a></li>
370 <li>second point, cc @pablo</li>
371 </ul>
372 <ol>
373 <li>step <strong>one</strong></li>
374 <li>step two</li>
375 </ol>
376 <blockquote>
377 <p>quoted from @olive: <em>ship it</em></p>
378 </blockquote>
379 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
380 <tbody>
381 <tr><td>4128</td><td class="ta-r"><code>x</code></td></tr>
382 </tbody></table>
383 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f4128</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
384 <!--next-->
385 <h2>Comment 199 on the plan</h2>
386 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/199" rel="nofollow noopener">the spec</a> and <a href="docs/notes-199.md" rel="nofollow noopener">local notes</a>.
387 Inline <code>code199</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
388 <ul>
389 <li>first point with a <a href="/trevato/forge/issues/199" rel="nofollow noopener">link</a></li>
390 <li>second point, cc @pablo</li>
391 </ul>
392 <ol>
393 <li>step <strong>one</strong></li>
394 <li>step two</li>
395 </ol>
396 <blockquote>
397 <p>quoted from @olive: <em>ship it</em></p>
398 </blockquote>
399 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
400 <tbody>
401 <tr><td>199</td><td class="ta-r"><code>x</code></td></tr>
402 </tbody></table>
403 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f199</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
404 <h2>Comment 1199 on the plan</h2>
405 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/1199" rel="nofollow noopener">the spec</a> and <a href="docs/notes-1199.md" rel="nofollow noopener">local notes</a>.
406 Inline <code>code1199</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
407 <ul>
408 <li>first point with a <a href="/trevato/forge/issues/1199" rel="nofollow noopener">link</a></li>
409 <li>second point, cc @pablo</li>
410 </ul>
411 <ol>
412 <li>step <strong>one</strong></li>
413 <li>step two</li>
414 </ol>
415 <blockquote>
416 <p>quoted from @olive: <em>ship it</em></p>
417 </blockquote>
418 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
419 <tbody>
420 <tr><td>1199</td><td class="ta-r"><code>x</code></td></tr>
421 </tbody></table>
422 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f1199</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
423 <h2>Comment 2199 on the plan</h2>
424 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/2199" rel="nofollow noopener">the spec</a> and <a href="docs/notes-2199.md" rel="nofollow noopener">local notes</a>.
425 Inline <code>code2199</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
426 <ul>
427 <li>first point with a <a href="/trevato/forge/issues/2199" rel="nofollow noopener">link</a></li>
428 <li>second point, cc @pablo</li>
429 </ul>
430 <ol>
431 <li>step <strong>one</strong></li>
432 <li>step two</li>
433 </ol>
434 <blockquote>
435 <p>quoted from @olive: <em>ship it</em></p>
436 </blockquote>
437 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
438 <tbody>
439 <tr><td>2199</td><td class="ta-r"><code>x</code></td></tr>
440 </tbody></table>
441 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f2199</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
442 <h2>Comment 3199 on the plan</h2>
443 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/3199" rel="nofollow noopener">the spec</a> and <a href="docs/notes-3199.md" rel="nofollow noopener">local notes</a>.
444 Inline <code>code3199</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
445 <ul>
446 <li>first point with a <a href="/trevato/forge/issues/3199" rel="nofollow noopener">link</a></li>
447 <li>second point, cc @pablo</li>
448 </ul>
449 <ol>
450 <li>step <strong>one</strong></li>
451 <li>step two</li>
452 </ol>
453 <blockquote>
454 <p>quoted from @olive: <em>ship it</em></p>
455 </blockquote>
456 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
457 <tbody>
458 <tr><td>3199</td><td class="ta-r"><code>x</code></td></tr>
459 </tbody></table>
460 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f3199</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>
461 <h2>Comment 4199 on the plan</h2>
462 <p>Thanks @olive — I read <strong>the proposal</strong> and <em>mostly</em> agree, see <a href="https://example.com/spec/4199" rel="nofollow noopener">the spec</a> and <a href="docs/notes-4199.md" rel="nofollow noopener">local notes</a>.
463 Inline <code>code4199</code> stays literal, and &lt;b&gt;tags&lt;/b&gt; &amp; &quot;quotes&quot; stay escaped.</p>
464 <ul>
465 <li>first point with a <a href="/trevato/forge/issues/4199" rel="nofollow noopener">link</a></li>
466 <li>second point, cc @pablo</li>
467 </ul>
468 <ol>
469 <li>step <strong>one</strong></li>
470 <li>step two</li>
471 </ol>
472 <blockquote>
473 <p>quoted from @olive: <em>ship it</em></p>
474 </blockquote>
475 <table><thead><tr><th>a</th><th class="ta-r">b</th></tr></thead>
476 <tbody>
477 <tr><td>4199</td><td class="ta-r"><code>x</code></td></tr>
478 </tbody></table>
479 <pre><code class="language-ts"><span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-function">f4199</span><span class="tok-punctuation">(</span>n<span class="tok-operator">:</span> <span class="tok-type">number</span><span class="tok-punctuation">)</span><span class="tok-operator">:</span> <span class="tok-type">string</span> <span class="tok-punctuation">{</span> <span class="tok-keyword">return</span> <span class="tok-string">`n=</span><span class="tok-punctuation">${</span>n<span class="tok-punctuation">}</span><span class="tok-string">`</span><span class="tok-punctuation">;</span> <span class="tok-punctuation">}</span> <span class="tok-comment">// @olive</span></code></pre>↵
test/thread-render-timing.test.ts added +109
@@ -0,0 +1,109 @@
1 /** A thread of 200 comments renders in under 15 ms, and renders the same HTML it always did (#3324). */
2 import { afterAll, beforeAll, expect, test } from "bun:test";
3 import { snappyFixture, type SnappyFixture } from "../bench/snappy.ts";
4 import { addComment, openIssue } from "../src/actions/issues.ts";
5 import { listEvents } from "../src/core/events.ts";
6 import { inlineIcons, referenced } from "../src/web/icons.ts";
7 import { markdownMemoStats, renderMarkdown } from "../src/web/markdown.ts";
8 import { seedBody } from "./thread-seed.ts";
9
10 let f: SnappyFixture;
11 let path = "";
12
13 beforeAll(async () => {
14 f = await snappyFixture({ repos: 1, issuesPerRepo: 1 });
15 const repo = f.repos[0]!;
16 const opened = openIssue(f.app, f.owner, repo, {
17 title: "The direction thread",
18 body: seedBody(-1),
19 });
20 if (!opened.ok) throw new Error(String(opened.error));
21 for (let i = 0; i < 200; i++) {
22 const added = addComment(f.app, f.owner, repo, opened.value, seedBody(i));
23 if (!added.ok) throw new Error(String(added.error));
24 }
25 path = `/${repo.ownerName}/${repo.name}/issues/${opened.value.number}`;
26 }, 120_000);
27 afterAll(async () => {
28 await f.close();
29 });
30
31 async function view(): Promise<{ ms: number; html: string }> {
32 const res = await fetch(`${f.base}${path}`, { headers: { cookie: f.ownerCookie } });
33 const text = await res.text();
34 expect(res.status).toBe(200);
35 expect(text.match(/data-comment-id="/g)?.length).toBeGreaterThanOrEqual(200);
36 const m = /render;dur=(\d+\.\d)/.exec(res.headers.get("server-timing") ?? "");
37 return { ms: Number(m![1]), html: text };
38 }
39
40 test("the first view of a 200-comment thread renders no comment body: the writes already did", async () => {
41 // A server that has served a page before, so only the thread is in question.
42 const repo = f.repos[0]!;
43 await (await fetch(`${f.base}/${repo.ownerName}/${repo.name}/issues/1`, { headers: { cookie: f.ownerCookie } })).text();
44 const before = markdownMemoStats();
45 const first = await view();
46 const after = markdownMemoStats();
outdated

High: this is a 15 ms wall-clock bound on a single sample, far under the 200 ms WALL_CLOCK_FLOOR_MS that test/flake.test.ts enforces. It is only missed because the variable is named first. It will flake on a loaded check machine. Replace it with a deterministic assertion: zero cache misses (zero renderUncached calls) on the first view after the writes, plus at most a loose ≥200 ms latency bound like test/snappy.test.ts has.

47 // Only the opening post, which is not rendered at write time, may miss.
48 expect(after.misses - before.misses).toBeLessThanOrEqual(1);
49 expect(after.hits - before.hits).toBeGreaterThanOrEqual(200);
50 // Wall-clock is only a loose guard (the tight half is the counter above).
51 expect(first.ms).toBeLessThan(250);
52 });
53
54 test("the seed renders the HTML it always did, cold and from the cache", async () => {
outdated

Low: 200 golden bodies (1.5 MB) prove nothing more than five would. Shrink the snapshot.

55 const golden = (await Bun.file(`${import.meta.dir}/__snapshots__/thread-seed.html`).text()).split(
56 "\n<!--next-->\n",
57 );
58 const seeds = [0, 1, 57, 128, 199];
59 expect(golden.length).toBe(seeds.length);
60 for (let pass = 0; pass < 2; pass++) {
61 seeds.forEach((n, k) => expect(renderMarkdown(seedBody(n)).text).toBe(golden[k]!));
62 }
63 });
64
65 test("markdown stays escaped through the fast paths", () => {
66 expect(renderMarkdown("<script>alert(1)</script> [x](javascript:alert(1))").text).toBe(
67 "<p>&lt;script&gt;alert(1)&lt;/script&gt; [x](javascript:alert(1))</p>",
68 );
69 expect(renderMarkdown("plain words").text).toBe("<p>plain words</p>");
70 });
71
72 test("listEvents with kinds is the unfiltered window, filtered", () => {
73 const repoId = f.repos[0]!.id;
74 for (const limit of [3, 50, 500]) {
75 const all = listEvents(f.app.db, { repoId, limit });
76 for (const kinds of [["issue.commented"], ["issue.commented", "issue.opened"], ["nothing.here"], []]) {
77 const want = all.filter((e) => kinds.includes(e.kind));
78 expect(listEvents(f.app.db, { repoId, limit, kinds }).map((e) => e.id)).toEqual(want.map((e) => e.id));
79 }
80 }
81 expect(listEvents(f.app.db, { repoId, limit: 500, kinds: ["issue.commented"] }).length).toBe(200);
82 });
83
84 test("referenced() finds the icons the old pattern did", () => {
85 const old = (markup: string): string[] => {
86 const names: string[] = [];
87 for (const m of markup.matchAll(/<use href="#i-([A-Za-z]+)"\/>/g)) {
88 const name = m[1]!;
89 if (inlineIcons(`<use href="#i-${name}"/>`) !== `<use href="#i-${name}"/>` && !names.includes(name)) names.push(name);
90 }
91 return names;
92 };
93 const use = (name: string): string => `<use href="#i-${name}"/>`;
94 const cases = [
95 "",
96 use("comment") + use("issue") + use("comment"),
97 use("Comment") + use("COMMENT"),
98 '<use href="#i-comment">',
99 '<use href="#i-comment"',
100 '<use href="#i-comment2"/>',
101 '<use href="#i-"/>',
102 use("nosuchicon") + use("clock"),
103 '<use href="#i-<use href="#i-clock"/>',
104 "x" + use("clock") + "<use href=\"#i-" ,
105 use("constructor") + use("bot"),
106 ];
107 for (const markup of cases) expect([...referenced(markup)] as string[]).toEqual(old(markup));
108 expect(referenced(use("comment") + use("issue")).length).toBe(2);
109 });
test/thread-seed.ts added +26
@@ -0,0 +1,26 @@
1 /** The seed for the thread render budget (#3324): 200 comments, each with markdown, links and a mention. */
2 export function seedBody(i: number): string {
3 const part = (n: number): string => [
4 `## Comment ${n} on the plan`,
5 "",
6 `Thanks @olive — I read **the proposal** and *mostly* agree, see [the spec](https://example.com/spec/${n}) and [local notes](docs/notes-${n}.md).`,
7 `Inline \`code${n}\` stays literal, and <b>tags</b> & "quotes" stay escaped.`,
outdated

Low: this is a double-quoted string, so ${n} is never interpolated, and code${n} is frozen into the golden file as-is. Use a template literal (and regenerate the golden from the base code), or drop the ${n}.

8 "",
9 "- first point with a [link](/trevato/forge/issues/" + n + ")",
10 "- second point, cc @pablo",
11 "1. step **one**",
12 "2. step two",
13 "",
14 "> quoted from @olive: *ship it*",
15 "",
16 "| a | b |",
17 "|---|--:|",
18 `| ${n} | \`x\` |`,
19 "",
20 "```ts",
21 `export function f${n}(n: number): string { return \`n=\${n}\`; } // @olive`,
22 "```",
23 ].join("\n");
24 // Five of them: a comment on a long thread is not a one-liner.
25 return [part(i), part(i + 1000), part(i + 2000), part(i + 3000), part(i + 4000)].join("\n\n");
26 }

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs