| 1 | // SPDX-License-Identifier: AGPL-3.0-or-later |
| 2 | |
| 3 | // Package markdown is shithub's canonical markdown rendering pipeline. |
| 4 | // |
| 5 | // One entry point: `Render(ctx, source, opts) (html, refs, mentions)`. |
| 6 | // Every comment, issue/PR body, README, and any future surface that |
| 7 | // takes user-authored markdown flows through here. No other code in |
| 8 | // the tree imports goldmark or bluemonday directly; a lint guard at |
| 9 | // `scripts/lint-markdown-boundary.sh` enforces that boundary. |
| 10 | // |
| 11 | // What's supported (CommonMark + a curated GFM set): |
| 12 | // |
| 13 | // - Headings, paragraphs, lists, blockquotes, code blocks (fenced + indented). |
| 14 | // - GFM tables, strikethrough, autolinks, task lists. |
| 15 | // - `@username` mentions — resolved via Options.Resolvers.User. |
| 16 | // - `#N` and `owner/repo#N` references — resolved via |
| 17 | // Options.Resolvers.Issue, gated by viewer visibility. |
| 18 | // - Emoji shortcodes (`:smile:`) — curated set in `emoji/`. |
| 19 | // - `<details>` / `<summary>`, `<kbd>`, `<sup>`, `<sub>`, |
| 20 | // task-list checkboxes (output by Goldmark with `disabled`). |
| 21 | // - Code-block class allowlist: `language-*` (Chroma uses these). |
| 22 | // |
| 23 | // What's deliberately not supported: |
| 24 | // |
| 25 | // - Raw HTML beyond the strict allowlist (we do NOT match GitHub's |
| 26 | // loose HTML acceptance — we err safer). |
| 27 | // - `data:` URIs (any flavor; documented in docs/markdown.md). |
| 28 | // - `javascript:` / `vbscript:` URLs (rejected by sanitizer). |
| 29 | // - GFM Footnotes (deferred). |
| 30 | // - Math (KaTeX), Mermaid, embedded media (post-MVP). |
| 31 | // |
| 32 | // Pipeline-version contract: |
| 33 | // |
| 34 | // - `Version` (in version.go) stamps every render. |
| 35 | // - Callers store the version alongside cached HTML. |
| 36 | // - On read, callers compare the stored version to `Version`; if |
| 37 | // they differ, the cache is stale and the caller re-renders. We |
| 38 | // never run a one-shot "re-render every comment" job — lazy. |
| 39 | // |
| 40 | // Performance budget: |
| 41 | // |
| 42 | // - 50 KiB body, full extensions: <30 ms p99 on MVP hardware. |
| 43 | // - Inputs above MaxRenderInputBytes are rejected up-front; callers |
| 44 | // enforce the matching cap at the API layer. |
| 45 | package markdown |
| 46 | |
| 47 | // MaxRenderInputBytes caps the input body. Comments / bodies are |
| 48 | // rejected at the API layer at 64 KiB or 256 KiB depending on |
| 49 | // surface; this is the renderer's defensive fallback. |
| 50 | const MaxRenderInputBytes = 1 << 20 // 1 MiB |
| 51 | |
| 52 | // Ref is one resolved reference produced during rendering. The |
| 53 | // caller uses these for downstream notification fan-out (S29) and |
| 54 | // for the issue_references index (S21). |
| 55 | type Ref struct { |
| 56 | // Kind is "issue" or "commit" for now. |
| 57 | Kind string |
| 58 | // Same-repo refs leave Owner/Repo empty. |
| 59 | Owner string |
| 60 | Repo string |
| 61 | // Number is set for issue refs; FullSHA for commit refs. |
| 62 | Number int64 |
| 63 | FullSHA string |
| 64 | // Href is the resolved URL the renderer wrote into the document. |
| 65 | Href string |
| 66 | } |
| 67 | |
| 68 | // Mention is one resolved @username mention. S29's fan-out consumes |
| 69 | // the deduplicated list returned by Render. |
| 70 | type Mention struct { |
| 71 | Username string |
| 72 | Href string |
| 73 | } |
| 74 |