Public: anyone can read this; the crew can change it.
An issue's thread with 200 comments renders in under 15 ms #3338
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/3324-an-issue-s-thread-with-200-comments-rend → main 5 commits fork 8 threads · 8 unresolved
src/core/events.ts +16 −3
| @@ -118,6 +118,12 @@ export interface ListEventsOptions { | ||
| 118 | 118 | afterId?: string; |
| 119 | 119 | /** @default 100 */ |
| 120 | 120 | 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[]; | |
| 121 | 127 | } |
| 122 | 128 | |
| 123 | 129 | /** |
| @@ -202,10 +208,17 @@ export function listEvents( | ||
| 202 | 208 | } |
| 203 | 209 | params.push(options.limit ?? 100); |
| 204 | 210 | 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 | } | |
| 205 | 220 | 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) | |
| 209 | 222 | .all(...params) |
| 210 | 223 | .map(toEvent); |
| 211 | 224 | } |
src/http/server.ts +3
| @@ -12,6 +12,7 @@ import { toolbarComment, toolbarPage } from "../web/studio/toolbar.ts"; | ||
| 12 | 12 | import { TOOLBAR_NAME, toolbarResponse } from "../apps/toolbar.ts"; |
| 13 | 13 | import { unsealReleased } from "../actions/sealed.ts"; |
| 14 | 14 | import { parentCallback, parentLinkStart, parentStart } from "./rp.ts"; |
| 15 | import { prerenderComments } from "../web/prerender.ts"; | |
| 15 | 16 | import { accessPage } from "../web/pages/access.ts"; |
| 16 | 17 | import { capacityPage, capacitySet } from "../web/pages/capacity.ts"; |
| 17 | 18 | import { systemPage, systemStream } from "../web/pages/system.ts"; |
| @@ -2102,6 +2103,7 @@ export async function serve(app: App): Promise<Servers> { | ||
| 2102 | 2103 | if (user === undefined) return; |
| 2103 | 2104 | closeStreamsOf(app.db, user.id, unclosed); |
| 2104 | 2105 | }); |
| 2106 | const prerender = prerenderComments(app); | |
| 2105 | 2107 | // And every process holds what it has open against the database, so a |
| 2106 | 2108 | // suspension or sign-out another process wrote reaches it too (#846). |
| 2107 | 2109 | const sweep = setInterval( |
| @@ -2345,6 +2347,7 @@ export async function serve(app: App): Promise<Servers> { | ||
| 2345 | 2347 | }, |
| 2346 | 2348 | stop() { |
| 2347 | 2349 | endOpen(); |
| 2350 | prerender(); | |
| 2348 | 2351 | clearInterval(sweep); |
| 2349 | 2352 | // Counts first: a hosted child's database closes with its app. |
| 2350 | 2353 | 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 { | ||
| 117 | 117 | /** The reference `icon()` leaves behind, and the only thing read back. */ |
| 118 | 118 | const REFERENCE = /<use href="#i-([A-Za-z]+)"\/>/g; |
| 119 | 119 | |
| 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[] { | |
| 122 | 130 | 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); | |
| 127 | 146 | } |
| 128 | 147 | return names; |
| 129 | 148 | } |
src/web/markdown.ts +13 −5
| @@ -16,12 +16,20 @@ import { highlightToHtml } from "./highlight-html.ts"; | ||
| 16 | 16 | import { escapeHtml, Html, raw } from "./html.ts"; |
| 17 | 17 | |
| 18 | 18 | 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 | |
Minor, not blocking: the pull description says these |
||
| 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 | |
| 23 | 27 | .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 | |
| 25 | 33 | // [text](url) — safe schemes only; quotes are already escaped away |
| 26 | 34 | .replace( |
| 27 | 35 | /\[([^\]]+)\]\(([^)\s]+)\)/g, |
src/web/pages/thread.ts +5 −1
| @@ -171,7 +171,11 @@ const EVENT_LOOK: Record<string, { icon: IconName; tone: Tone; verb: string }> = | ||
| 171 | 171 | * disagree with what actually happened. |
| 172 | 172 | */ |
| 173 | 173 | 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( | |
| 175 | 179 | (event) => |
| 176 | 180 | EVENT_LOOK[event.kind] !== undefined && |
| 177 | 181 | 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 ( |
||
| 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 |
||
| 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 <b>tags</b> & "quotes" 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 |
||
| 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><script>alert(1)</script> [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 |
||
| 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 | } |
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.