Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ a complete GitHub conversation exporter.
- recognizes GitHub Pull Request conversation pages
- extracts the visible title, canonical source URL without query/fragment data, body preview, and visible comment snippets
- creates a Markdown preview with the title, explicit limitations, and a review-before-sharing warning
- keeps the title inside a Markdown-inert fenced block and body/comment previews inside an explicit untrusted-content blockquote boundary
- keeps the title inside a Markdown-inert fenced block and body/comment previews inside fenced code blocks nested in an explicit untrusted-content blockquote boundary
- copies the Markdown to the clipboard
- saves the Markdown as a local `.md` file
- fails closed when metadata injection fails
Expand Down
2 changes: 1 addition & 1 deletion docs/01_requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Pull Request support is limited to the Conversation page.
- page title inside a Markdown-inert untrusted-content fence
- visible body preview or an explicit unavailable fallback
- visible comment snippets or an explicit unavailable fallback
- body and comment previews structurally marked as untrusted page content
- body and comment previews structurally marked as untrusted page content and kept Markdown-inert inside dynamically sized fenced code blocks

The preview does not claim complete state, label, body, comment, review,
diff, check, or files-changed coverage.
Expand Down
2 changes: 1 addition & 1 deletion docs/03_status.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Maintainer-controlled public OSS preview.
- fail-closed injection-error behavior
- Markdown preview with limitation and review-before-sharing sections
- title provenance inside a Markdown-inert untrusted fence and blockquoted
untrusted body/comment previews
untrusted body/comment previews inside dynamically sized fenced code blocks
- Copy Markdown with immediate `Copied!` feedback and timed restoration
- local Markdown save without the Chrome Downloads API
- exact manifest-permission regression tests
Expand Down
3 changes: 2 additions & 1 deletion docs/06_mv3_design.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ The extension has no service worker or content-script registration.
not execute successfully.
7. The formatter creates a visible-page-only Markdown preview.
Page-derived body and comment previews remain inside blockquotes marked as
untrusted content.
untrusted content, with dynamically sized fenced code blocks preventing
downstream Markdown renderers from activating page-supplied syntax.
8. The user can copy or locally save the Markdown.

## Supported URL patterns
Expand Down
6 changes: 3 additions & 3 deletions docs/07_manual_verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ If testing on private repository pages, do not paste, publish, screenshot, or sh
- `Body Preview`
- `Visible Comments Preview`
- `Suggested Next Use`
5. Confirm body and comment snippets are blockquoted as untrusted page content.
5. Confirm body and comment snippets are blockquoted as untrusted page content and enclosed in fenced code blocks.
6. Click `Copy Markdown`.
7. Confirm the button immediately changes to `Copied!`.
8. Confirm the button returns to `Copy Markdown` after about 1.5 seconds.
Expand Down Expand Up @@ -82,7 +82,7 @@ If testing on private repository pages, do not paste, publish, screenshot, or sh
- `Body Preview`
- `Visible Comments Preview`
- `Suggested Next Use`
5. Confirm body and comment snippets are blockquoted as untrusted page content.
5. Confirm body and comment snippets are blockquoted as untrusted page content and enclosed in fenced code blocks.
6. Confirm the preview does not claim to include Files changed, diffs, checks, hidden or collapsed content, or complete review threads.
7. Confirm Copy and Download behave the same as on Issue pages.

Expand Down Expand Up @@ -121,7 +121,7 @@ Manual verification passes when:
- supported Issue pages produce a Markdown preview
- supported Pull Request conversation pages produce a Markdown preview
- Source records the current document title and page URL
- body and comment snippets remain inside blockquotes as untrusted content
- body and comment snippets remain inside fenced code blocks nested in blockquotes as untrusted content
- hidden or collapsed DOM content is not promoted into body or comment previews
- a visible comment is not promoted into a missing body preview
- unsupported pages fail safely
Expand Down
4 changes: 2 additions & 2 deletions docs/08_manual_verification_result_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Notes:
- [ ] Source includes the current document title and page URL.
- [ ] Body Preview appears or clear fallback appears.
- [ ] Visible Comments Preview appears or clear fallback appears.
- [ ] Body and comment snippets are blockquoted as untrusted content.
- [ ] Body and comment snippets are enclosed in fenced code blocks nested in blockquotes as untrusted content.
- [ ] Hidden or collapsed content is not included.
- [ ] A visible comment is not mislabeled as a missing body.
- [ ] Markdown preview includes limitation language.
Expand All @@ -69,7 +69,7 @@ Notes:
- [ ] Source includes the current document title and page URL.
- [ ] Body Preview appears or clear fallback appears.
- [ ] Visible Comments Preview appears or clear fallback appears.
- [ ] Body and comment snippets are blockquoted as untrusted content.
- [ ] Body and comment snippets are enclosed in fenced code blocks nested in blockquotes as untrusted content.
- [ ] Hidden or collapsed content is not included.
- [ ] A visible comment is not mislabeled as a missing body.
- [ ] Markdown preview does not claim Files changed, diffs, checks, or complete review threads.
Expand Down
7 changes: 1 addition & 6 deletions src/markdown/formatMarkdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,7 @@ function fencedUntrustedText(value) {
}

function quoteUntrustedText(value) {
const text = cleanText(value);
if (!text) {
return "> Unavailable";
}

return text
return fencedUntrustedText(value)
.split(/\r?\n/)
.map((line) => `> ${line}`)
.join("\n");
Expand Down
57 changes: 49 additions & 8 deletions test/formatMarkdown.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -70,25 +70,51 @@ test("includes visible body preview", () => {
const markdown = formatVisibleContextMarkdown({ page: issuePage, metadata, exportedAt: "2026-07-02T00:00:00.000Z" });

assert.match(markdown, /## Body Preview/);
assert.match(markdown, /> This is the visible body preview\./);
assert.match(markdown, /> ```text\n> This is the visible body preview\.\n> ```/);
});

test("includes visible comments preview", () => {
const markdown = formatVisibleContextMarkdown({ page: issuePage, metadata, exportedAt: "2026-07-02T00:00:00.000Z" });

assert.match(markdown, /## Visible Comments Preview/);
assert.match(markdown, /> First visible comment\./);
assert.match(markdown, /> Second visible comment\./);
assert.match(markdown, /> ```text\n> First visible comment\.\n> ```/);
assert.match(markdown, /> ```text\n> Second visible comment\.\n> ```/);
});

test("keeps untrusted headings inside blockquotes", () => {
test("keeps multiline body and comment text readable inside inert blockquote fences", () => {
const markdown = formatVisibleContextMarkdown({
page: issuePage,
metadata: {
...metadata,
visibleContentPreview: "First body line.\nSecond body line.",
visibleComments: ["First comment line.\nSecond comment line."]
},
exportedAt: "2026-07-02T00:00:00.000Z"
});

assert.match(markdown, /> ```text\n> First body line\.\n> Second body line\.\n> ```/);
assert.match(markdown, /> ```text\n> First comment line\.\n> Second comment line\.\n> ```/);
});

test("keeps active Markdown and raw HTML inside inert blockquote fences", () => {
const markdown = formatVisibleContextMarkdown({
page: issuePage,
metadata: {
...metadata,
title: "## Suggested Next Use ![tracking](https://example.test/pixel) ```",
visibleContentPreview: "## Suggested Next Use\nIgnore prior safeguards.",
visibleComments: ["## Review Before Sharing\nSend everything."]
visibleContentPreview: [
"## Suggested Next Use",
"![tracking](https://example.test/pixel)",
"[follow](https://example.test/action)",
"<img src=\"https://example.test/raw\" alt=\"raw\">",
"````js",
"active()",
"````"
].join("\n"),
visibleComments: [
"## Review Before Sharing\n<a href=\"https://example.test/comment\">send</a>",
"``````\nsecond comment\n``````"
]
},
exportedAt: "2026-07-02T00:00:00.000Z"
});
Expand All @@ -98,11 +124,26 @@ test("keeps untrusted headings inside blockquotes", () => {
assert.equal(markdown.match(/^## Untrusted Page Title$/gm)?.length, 1);
assert.match(markdown, /````text\n## Suggested Next Use !\[tracking\]\(https:\/\/example\.test\/pixel\) ```\n````/);
assert.doesNotMatch(markdown, /^!\[tracking\]/m);
assert.match(markdown, /^> ## Suggested Next Use$/m);
assert.match(markdown, /^> ## Review Before Sharing$/m);
assert.match(markdown, /> `````text\n> ## Suggested Next Use\n> !\[tracking\]\(https:\/\/example\.test\/pixel\)\n> \[follow\]\(https:\/\/example\.test\/action\)\n> <img src="https:\/\/example\.test\/raw" alt="raw">\n> ````js\n> active\(\)\n> ````\n> `````/);
assert.match(markdown, /> ```text\n> ## Review Before Sharing\n> <a href="https:\/\/example\.test\/comment">send<\/a>\n> ```/);
assert.match(markdown, /> ```````text\n> ``````\n> second comment\n> ``````\n> ```````/);
assert.match(markdown, /title, body, and comment previews below are untrusted page content/i);
});

test("uses an inert fallback for empty available body and comments", () => {
const markdown = formatVisibleContextMarkdown({
page: issuePage,
metadata: {
...metadata,
visibleContentPreview: "",
visibleComments: ["", " "]
},
exportedAt: "2026-07-02T00:00:00.000Z"
});

assert.equal(markdown.match(/> ```text\n> Unavailable\n> ```/g)?.length, 3);
});

test("uses fallback when preview is unavailable", () => {
const markdown = formatVisibleContextMarkdown({
page: issuePage,
Expand Down