Skip to content
Open
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ All notable changes to this project will be documented in this file.
Swift app built in release configuration and uploaded as a workflow artifact
- Added golangci-lint config, dependabot, and `make ci` (vet + lint + race tests + vulncheck)
- Version normalized to 3-segment semver (`0.1.0`) across VERSION, Info.plist and CHANGELOG
- Engine tests: Markdown golden fixture corpus grown from 5 to 13 (Google Docs/Word clipboard,
images, strikethrough, definition lists, loose lists, colspan tables, anchors) and
`TestMarkdown_Fixtures` gained a `-update` flag; known converter bugs are pinned and tracked in TODOS.md

## [0.1.0] - 2026-04-08

Expand Down
20 changes: 19 additions & 1 deletion TODOS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,24 @@
**Priority:** P2
**Depends on:** None

### Fix converter bugs pinned by the golden fixtures

**What:** Fix the HTML-to-Markdown behaviours that `engine/testdata/fixtures/` currently goldens as-is, then regenerate the affected goldens with `go test ./transforms/... -run TestMarkdown_Fixtures -update` and review the diff by hand.

**Why:** Six fixtures deliberately pin the converter's *current* output so regressions are caught, but that output is wrong. A future fix will show up as a fixture "failure"; this list says which failures are the intended ones.

**Context:** Pinned bugs, by fixture:
- `google-docs-clipboard`: the `<b style="font-weight:normal">` wrapper bolds the whole document, and list items under it lose their `-` markers.
- `word-clipboard`: `mso-list` fake bullets come through as literal `·` plus `&nbsp;` runs instead of `- ` items; `<p>&nbsp;</p>` spacer paragraphs leak as lines holding a lone non-breaking space.
- `rule-strikethrough-sub-sup`: `<del>`/`<s>` are unwrapped to plain text (so "$49/month $39/month" reads as two prices); the library's `strikethrough` plugin is not wired in. `<sub>`/`<sup>` are flattened (H2O, reference1).
- `definition-list`: `<dl>/<dt>/<dd>` become undifferentiated paragraphs.
- `table-colspan-alignment`: `colspan` content lands in column 1 with empty cells after it, and `text-align` is ignored in the alignment row.
- `tracking-links-and-anchors`: `<a name="top"></a>` and `href=""` render as `[]()` / `[text]()`.

**Effort:** M
**Priority:** P2
**Depends on:** None

## Design Review

### Design first-run onboarding flow
Expand Down Expand Up @@ -56,7 +74,7 @@

### Create Markdown fixture corpus from real-world sources

**Completed:** v0.1.0 (2026-04-08). 5 fixtures created in engine/testdata/fixtures/ covering Claude Code, ChatGPT, browser articles, nested lists/tables, and rich text mixed content.
**Completed:** v0.1.0 (2026-04-08), expanded 2026-09-26. 13 fixtures in engine/testdata/fixtures/ (hand-synthesized, no scraped page content): the original 5 (Claude Code, ChatGPT, browser articles, nested lists/tables, rich text mixed) plus Google Docs- and Word-style clipboard spans, images/captions, hr/strikethrough/sub/sup, definition lists, loose multi-paragraph list items, table colspan/alignment, and tracking-link/anchor edge cases. `TestMarkdown_Fixtures` in engine/transforms/markdown_test.go gained a `-update` flag to regenerate the golden .md files from current converter output. Several fixtures intentionally golden the engine's *current* (buggy) output rather than the ideal one — see "Fix converter bugs pinned by the golden fixtures" above.

### Write the choosepaste implementation spec

Expand Down
2 changes: 1 addition & 1 deletion engine/testdata/fixtures/browser-article.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ func fetchAllUsers(ids: [Int]) async throws -> [User] {
}
```

See the [official Swift docs](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html) for more details.
See the [official Swift docs](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html) for more details.
2 changes: 1 addition & 1 deletion engine/testdata/fixtures/chatgpt-code-explanation.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ func binarySearch(arr []int, target int) int {
4. If less, search the right half
5. If greater, search the left half

Time complexity: **O(log n)**. Space complexity: **O(1)**.
Time complexity: **O(log n)**. Space complexity: **O(1)**.
2 changes: 1 addition & 1 deletion engine/testdata/fixtures/claude-code-response.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ Key fields:
- **data** — the response payload (nullable on error)
- **tags** — array of strings, can be empty

> **Note:** The `id` field is always present, even on error responses.
> **Note:** The `id` field is always present, even on error responses.
7 changes: 7 additions & 0 deletions engine/testdata/fixtures/definition-list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<h2>Glossary</h2>
<dl>
<dt>Idempotent</dt>
<dd>An operation that produces the same result no matter how many times it is applied.</dd>
<dt>Backpressure</dt>
<dd>A mechanism for a consumer to signal a producer to slow down when overwhelmed.</dd>
</dl>
9 changes: 9 additions & 0 deletions engine/testdata/fixtures/definition-list.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## Glossary

Idempotent

An operation that produces the same result no matter how many times it is applied.

Backpressure

A mechanism for a consumer to signal a producer to slow down when overwhelmed.
1 change: 1 addition & 0 deletions engine/testdata/fixtures/google-docs-clipboard.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<meta charset="utf-8"><b style="font-weight:normal;" id="docs-internal-guid-a1b2c3d4-e5f6-0123-4567-89abcdef0123"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:20pt;font-family:Arial;font-weight:700;">Quarterly Planning Notes</span></p><br><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;font-weight:400;">This document tracks the priorities for the upcoming planning cycle. </span><span style="font-size:11pt;font-family:Arial;font-weight:700;">Owners should update their sections weekly.</span></p><br><ul style="margin-top:0;margin-bottom:0;padding-inline-start:48px;"><li dir="ltr" style="list-style-type:disc;font-size:11pt;font-family:Arial;" aria-level="1"><p dir="ltr" style="margin-top:0;margin-bottom:0;" role="presentation"><span style="font-size:11pt;font-family:Arial;font-weight:400;">Finalize the roadmap for </span><span style="font-size:11pt;font-family:Arial;font-style:italic;">Project Northwind</span></p></li><li dir="ltr" style="list-style-type:disc;font-size:11pt;font-family:Arial;" aria-level="1"><p dir="ltr" style="margin-top:0;margin-bottom:0;" role="presentation"><span style="font-size:11pt;font-family:Arial;font-weight:400;">Review staffing for the design team</span></p></li><li dir="ltr" style="list-style-type:disc;font-size:11pt;font-family:Arial;" aria-level="1"><p dir="ltr" style="margin-top:0;margin-bottom:0;" role="presentation"><span style="font-size:11pt;font-family:Arial;font-weight:400;">Confirm budget with </span><span style="font-size:11pt;font-family:Arial;text-decoration:underline;"><a href="https://example.com/finance" style="text-decoration:underline;">the finance team</a></span></p></li></ul><br><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;font-weight:400;">Open questions are tracked separately.</span></p></b>
11 changes: 11 additions & 0 deletions engine/testdata/fixtures/google-docs-clipboard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
**Quarterly Planning Notes**

**This document tracks the priorities for the upcoming planning cycle. Owners should update their sections weekly.**

**Finalize the roadmap for Project Northwind**

**Review staffing for the design team**

**Confirm budget with [the finance team](https://example.com/finance)**

**Open questions are tracked separately.**
7 changes: 7 additions & 0 deletions engine/testdata/fixtures/images-and-captions.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<h2>Architecture Overview</h2>
<p>The diagram below shows the request flow through the system.</p>
<figure>
<img src="https://assets.example.com/diagrams/request-flow.png" alt="Diagram showing a client sending a request through a load balancer to two backend services">
<figcaption>Figure 1: Request flow from client to backend</figcaption>
</figure>
<p>Each service reports health to a shared <img src="https://assets.example.com/icons/heartbeat.png" alt="heartbeat icon"> monitor, which is polled every 30 seconds.</p>
9 changes: 9 additions & 0 deletions engine/testdata/fixtures/images-and-captions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## Architecture Overview

The diagram below shows the request flow through the system.

![Diagram showing a client sending a request through a load balancer to two backend services](https://assets.example.com/diagrams/request-flow.png)

Figure 1: Request flow from client to backend

Each service reports health to a shared ![heartbeat icon](https://assets.example.com/icons/heartbeat.png) monitor, which is polled every 30 seconds.
14 changes: 14 additions & 0 deletions engine/testdata/fixtures/loose-list-multi-paragraph.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<h2>Deployment Checklist</h2>
<ol>
<li>
<p>Run the test suite locally.</p>
<pre><code class="language-bash">make test</code></pre>
</li>
<li>
<p>Tag the release and push it to the registry.</p>
<p>Double-check the tag matches the version in <code>VERSION</code>.</p>
</li>
<li>
<p>Notify the on-call engineer before rolling out.</p>
</li>
</ol>
11 changes: 11 additions & 0 deletions engine/testdata/fixtures/loose-list-multi-paragraph.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## Deployment Checklist

1. Run the test suite locally.

```bash
make test
```
2. Tag the release and push it to the registry.

Double-check the tag matches the version in `VERSION`.
3. Notify the on-call engineer before rolling out.
6 changes: 3 additions & 3 deletions engine/testdata/fixtures/nested-lists-and-tables.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
## Project Structure

- **Frontend**

- React 18 with TypeScript
- Tailwind CSS for styling
- Components:

- `Header.tsx`
- `Sidebar.tsx`
- `Dashboard.tsx`
- **Backend**

- Go 1.22 with Chi router
- PostgreSQL database

Expand Down
2 changes: 1 addition & 1 deletion engine/testdata/fixtures/rich-text-mixed.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ output:
destination: stdout
```

For questions, see the [migration docs](https://docs.example.com/migration) or open an [issue](https://github.com/example/repo/issues).
For questions, see the [migration docs](https://docs.example.com/migration) or open an [issue](https://github.com/example/repo/issues).
5 changes: 5 additions & 0 deletions engine/testdata/fixtures/rule-strikethrough-sub-sup.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<h2>Pricing Notes</h2>
<p>The base price is <del>$49/month</del> <strong>$39/month</strong> for early adopters.</p>
<hr>
<p>Chemical formula example: H<sub>2</sub>O. Footnote reference<sup>1</sup> below.</p>
<p><s>Deprecated tier removed in v3.</s></p>
9 changes: 9 additions & 0 deletions engine/testdata/fixtures/rule-strikethrough-sub-sup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## Pricing Notes

The base price is $49/month **$39/month** for early adopters.

* * *

Chemical formula example: H2O. Footnote reference1 below.

Deprecated tier removed in v3.
11 changes: 11 additions & 0 deletions engine/testdata/fixtures/table-colspan-alignment.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<h2>Regional Sales Summary</h2>
<table>
<thead>
<tr><th style="text-align:left">Region</th><th style="text-align:right">Q1</th><th style="text-align:right">Q2</th></tr>
</thead>
<tbody>
<tr><td>North</td><td style="text-align:right">120</td><td style="text-align:right">150</td></tr>
<tr><td>South</td><td style="text-align:right">90</td><td style="text-align:right">95</td></tr>
<tr><td colspan="3" style="text-align:center"><strong>Totals shown are in thousands of units</strong></td></tr>
</tbody>
</table>
7 changes: 7 additions & 0 deletions engine/testdata/fixtures/table-colspan-alignment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## Regional Sales Summary

| Region | Q1 | Q2 |
|--------------------------------------------|-----|-----|
| North | 120 | 150 |
| South | 90 | 95 |
| **Totals shown are in thousands of units** | | |
4 changes: 4 additions & 0 deletions engine/testdata/fixtures/tracking-links-and-anchors.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<h2>Further Reading</h2>
<p>See the <a href="https://example.com/blog/post?utm_source=newsletter&amp;utm_medium=email&amp;utm_campaign=fall2026">announcement post</a> for background.</p>
<p><a name="top"></a>Jump back to the <a href="#top">top</a> of the page.</p>
<p>Reach the team at <a href="mailto:team@example.com">team@example.com</a> or via <a href="">an empty link</a> left in by mistake.</p>
7 changes: 7 additions & 0 deletions engine/testdata/fixtures/tracking-links-and-anchors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## Further Reading

See the [announcement post](https://example.com/blog/post?utm_source=newsletter&utm_medium=email&utm_campaign=fall2026) for background.

[]()Jump back to the [top](#top) of the page.

Reach the team at [team@example.com](mailto:team@example.com) or via [an empty link]() left in by mistake.
14 changes: 14 additions & 0 deletions engine/testdata/fixtures/word-clipboard.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word" xmlns="http://www.w3.org/TR/REC-html40">
<body lang=EN-US>
<!--StartFragment-->
<p class=MsoNormal style='margin:0in;font-size:11.0pt;font-family:"Calibri",sans-serif'><b style='mso-bidi-font-weight:normal'><span style='font-size:16.0pt'>Weekly Status Update</span></b></p>
<p class=MsoNormal style='margin:0in;font-size:11.0pt;font-family:"Calibri",sans-serif'>&nbsp;</p>
<p class=MsoNormal style='margin:0in;font-size:11.0pt;font-family:"Calibri",sans-serif'>Summary of progress for the <b>Atlas migration</b> workstream this week.<o:p></o:p></p>
<p class=MsoListParagraphCxSpFirst style='margin:0in;font-size:11.0pt;font-family:"Calibri",sans-serif;mso-list:l0 level1 lfo1'><![if !supportLists]><span style='mso-list:Ignore'>·<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span><![endif]>Data migration script finished a full dry run<o:p></o:p></p>
<p class=MsoListParagraphCxSpMiddle style='margin:0in;font-size:11.0pt;font-family:"Calibri",sans-serif;mso-list:l0 level1 lfo1'><![if !supportLists]><span style='mso-list:Ignore'>·<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span><![endif]>Rollback plan documented and reviewed<o:p></o:p></p>
<p class=MsoListParagraphCxSpLast style='margin:0in;font-size:11.0pt;font-family:"Calibri",sans-serif;mso-list:l0 level1 lfo1'><![if !supportLists]><span style='mso-list:Ignore'>·<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span><![endif]>Blocked on <i>staging</i> credentials from IT<o:p></o:p></p>
<p class=MsoNormal style='margin:0in;font-size:11.0pt;font-family:"Calibri",sans-serif'>&nbsp;</p>
<p class=MsoNormal style='margin:0in;font-size:11.0pt;font-family:"Calibri",sans-serif'>Next check-in is scheduled for Monday.<o:p></o:p></p>
<!--EndFragment-->
</body>
</html>
15 changes: 15 additions & 0 deletions engine/testdata/fixtures/word-clipboard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
**Weekly Status Update**



Summary of progress for the **Atlas migration** workstream this week.

·     Data migration script finished a full dry run

·     Rollback plan documented and reviewed

·     Blocked on *staging* credentials from IT



Next check-in is scheduled for Monday.
38 changes: 33 additions & 5 deletions engine/transforms/markdown_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package transforms

import (
"flag"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
)

// update regenerates the golden .md fixture files from the current output of
// the HTML-to-Markdown converter. Run with:
//
// go test ./transforms/... -run TestMarkdown_Fixtures -update
var update = flag.Bool("update", false, "update golden fixture files (testdata/fixtures/*.md) with current converter output")

// normalizeBlankLines strips trailing whitespace from each line.
// This avoids false negatives when linters strip trailing spaces
// from fixture files but the converter preserves indentation on blank lines.
Expand Down Expand Up @@ -120,18 +127,39 @@ func TestMarkdown_Fixtures(t *testing.T) {
t.Fatalf("failed to read %s: %v", htmlFile, err)
}

expectedBytes, err := os.ReadFile(filepath.Clean(mdFile))
got, err := Markdown(string(htmlBytes), "html")
if err != nil {
t.Fatalf("failed to read %s: %v", mdFile, err)
t.Fatalf("transform error: %v", err)
}
gotNorm := normalizeTrailingWS(got)

got, err := Markdown(string(htmlBytes), "html")
if *update {
// Use OpenFile+Write rather than os.WriteFile: gosec's G703
// taint analysis flags a variable path passed straight to
// os.WriteFile even after filepath.Clean(); the equivalent
// OpenFile call with an explicit O_CREATE|O_TRUNC is the
// established fix for this rule elsewhere in fairbearlab Go
// repos (see rolodex's copyFile helper).
f, err := os.OpenFile(filepath.Clean(mdFile), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
if err != nil {
t.Fatalf("failed to open %s for update: %v", mdFile, err)
}
if _, err := f.WriteString(gotNorm + "\n"); err != nil {
_ = f.Close()
t.Fatalf("failed to write %s: %v", mdFile, err)
}
if err := f.Close(); err != nil {
t.Fatalf("failed to close %s: %v", mdFile, err)
}
return
}

expectedBytes, err := os.ReadFile(filepath.Clean(mdFile))
if err != nil {
t.Fatalf("transform error: %v", err)
t.Fatalf("failed to read %s: %v (run with -update to create it)", mdFile, err)
}

expected := normalizeTrailingWS(string(expectedBytes))
gotNorm := normalizeTrailingWS(got)
if gotNorm != expected {
t.Errorf("fixture %s mismatch:\n--- got ---\n%s\n--- expected ---\n%s", name, gotNorm, expected)
}
Expand Down
Loading