Changelog¶
All notable changes to md-bridge are documented in this file.
The format is based on Keep a Changelog and this project adheres to Semantic Versioning. Each version section answers three questions for the reader:
- Added — new behaviour or files that did not exist before.
- Changed — behaviour or files that already existed and now work differently.
- Removed — behaviour or files that no longer exist.
If a section is empty in a release, the section is omitted entirely.
Unreleased¶
0.12.0 — 2026-07-24¶
This release makes md-bridge safe to run as a self-hosted public service:
a same-origin deploy topology, optional access control and rate limiting,
work and queue limits with safe defaults, and a local access log with an
operations guide. The web app also gains the Mermaid render toggle on the
md-to-pdf screen. The deploy guide (deployment/oracle-cloud) covers the
public-deployment specifics.
Added¶
- Same-origin production topology. The official deploy serves the web app and the API behind one origin with Caddy, with the request body cap enforced at the proxy as well as the app and a deploy smoke check that fetches both the served page and the API. See the deploy guide. (#435)
- Optional access control and rate limiting. Set
MD_BRIDGE_API_TOKENto require an API key (X-API-Keyheader) on the file-upload routes, andMD_BRIDGE_RATE_LIMIT(withMD_BRIDGE_RATE_WINDOW_SECONDS, default 60) to rate-limit requests. Both are off in the bare app; the official deploy recipe enables rate limiting. The upload cap is configurable viaMD_BRIDGE_MAX_UPLOAD_MB(default 500). See the deploy guide for how these behave behind the proxy. (#436) - Work and queue limits. Heavy conversions run behind a concurrency gate with
a wait queue and a per-request timeout:
MD_BRIDGE_MAX_CONCURRENCY(default 2),MD_BRIDGE_QUEUE_MAX(default 8),MD_BRIDGE_QUEUE_WAIT_SECONDS(default 10), andMD_BRIDGE_CONVERT_TIMEOUT_SECONDS(default 300). Over capacity returns 503 and an exceeded timeout returns 504;MD_BRIDGE_MAX_PDF_PAGES(default 0, unlimited) rejects an oversized PDF with 422. (#437) - Local access log and operations guide. A middleware logs one line per
non-health
/apirequest (method, path, status, duration), never the document content. The bootstrap compose caps on-disk logs, and the deploy guide gains rollback, diagnosis, and backup sections. Local-only: no metrics endpoint, no external exporter. (#438) - Render Mermaid toggle on the md-to-pdf screen. The md-to-pdf page exposes the
existing
render_mermaidoption as a switch, off by default. A fenced ```mermaid block renders to a diagram; a block that fails to parse stays as its source code. (#439)
Changed¶
- Conversion concurrency and timeout now default on. Unlike the opt-in auth
and rate-limit knobs, the work limits ship with safe defaults: at most two heavy
conversions at once and a 300-second per-request timeout. A single-user local
install is unaffected in practice; a busy deployment no longer lets unbounded
parallel renders compete for memory. Set the env vars above to tune, or the
timeout to
0to disable it. (#437)
0.11.0 — 2026-07-22¶
The web app grows a settings home, a local history, and reusable presets, and the converter learns to read text trapped inside images. Conversion defaults are unchanged: the new converter option is opt-in and off by default, and the web features are additive and browser-local.
Added¶
- Per-image OCR. With
ocr_imagesset toall, eligible embedded images are run through OCR and the recognized text is inlined next to each image. Only the 50 largest candidates per document that clear a size and page-area floor and are not CMYK are processed, and the mode is skipped when the full-pageneeds_ocrpre-pass already fires (page OCR takes precedence). OCR is opt-in and not in the default install:allneeds the optional OCR extra plus theMD_BRIDGE_OCR_ENABLEDenvironment variable, and it forces inline base64 images. Seedocs/API.md. (#140) - Preferences page. A single
/preferencespage groups four settings: default language, default PDF theme, dark mode, and reduce-motion. The theme and reduce-motion defaults persist undermd-bridge:prefs; language and dark mode keep their existingmd-bridge:localeandmd-bridge:themekeys. Reset clears the wholemd-bridge:*namespace. No server, no accounts. (#64) - Local conversion history. The pdf-to-md page keeps a browser-local list of
recent conversions (name, size, options, outcome, timestamp), capped at 20
newest-first. Re-download a result while its blob is alive in the tab, or re-run
from the source file. The list is metadata only: the source file and result blob
are never written to
localStorageor any server-side store, and the file a conversion uploads is processed in a temporary directory and then discarded. (#63) - Conversion presets. The md-to-pdf page can save the current theme and
custom CSS as a named preset under
md-bridge:presets:md-to-pdfand re-apply it in one click, with JSON import and export to share a set. Capped at 12 per format pair. (#62)
0.10.0 — 2026-07-21¶
Seven converter heuristics, each opt-in and off by default, so a document converted with the defaults is byte-identical to 0.9.0.
Added¶
- Table column alignment. With
table_column_alignon, the converter reads each column's text extent and emits the GFM alignment markers (:---,---:,:---:) in the separator row. It classifies by comparing the left and right gaps, so left-aligned multi-word text is not misread as centered. (#175) - Tight and loose list spacing. With
tight_loose_listson, the converter reads the vertical gaps between PDF list items and preserves them as CommonMark tight or loose lists.list_loose_threshold(default 1.5x the dominant body font size) sets where a gap counts as loose. (#168) - Image width hints. With
image_width_hintson, an extracted image carries its source width as an attr-list hint ({width=N}). The width is converted from PDF points to CSS pixels (x96/72), so a round-trip through the renderer keeps the image at its original size instead of shrinking it. (#169) - Image click links. With
image_link_anchorson, an image that the PDF wraps in a click action is emitted as[](target), preserving the link through the conversion. The three document post-passes (smart typography, reference-link collapsing, and the needs_ocr text-density check) recognize the construct and operate on the external target, never the image source. (#170) - Nested ordered lists. With
nested_ordered_listson, a nested ordered sublist keeps its own first-item start and indents four spaces per level so the renderer nests it (<ol start="N">). Depth is measured from the list's outermost margin, so a sublist with more items than its parent still nests. (#194) - Grid tables. With
multiline_table_formatset togrid, a table that has any cell spanning more than one line is emitted as a Pandoc grid table so the line breaks survive; an all-single-line table stays a pipe table. Rendering a grid table back to a PDF needs the optionalgrid-tablesextra (markdown-grids, MIT). Adopted in ADR-001. (#166) - Definition lists. With
detect_definition_listson, a run of at least two term/definition pairs (a short body-font term at the margin, then an indented body definition) is emitted asTerm/: definition, rendered to<dl><dt><dd>. The detector is deliberately strict to keep false positives low: a heading or a styled label classifies as a heading and is excluded, and the run, length, font, and indent guards make ordinary prose unlikely to read as a term.definition_list_max_term_lengthanddefinition_list_min_indent_pttune the bounds. Adopted in ADR-001. (#161)
Changed¶
- Dependencies. Bumped the nginx base image digest (#425), the web
typescript-eslintgroup (#426),@testing-library/jest-domto 7 (#427), the CI actions group (#428), andactions/setup-pythonto 7 (#429).
0.9.0 — 2026-07-17¶
Added¶
- Highlighted text round-trips.
pdf-to-markdownreads PDF text-highlight annotations and emits the covered text as==text==;markdown-to-pdfrenders==text==to<mark>. Opt-in viaextract_highlights(off by default, so a document without highlights is byte-identical). A span whose text already holds==is left unmarked so the delimiter cannot break. (#162) - Figure anchors. With
emit_figure_anchorson (off by default), a numbered figure caption (Figure 3,Fig. 2.1,Figura 5) gives its image a stable{#fig-N .figure}id, so a cross-reference can link to it. Ids dedupe across the document and against heading anchors. Only figures: the renderer cannot attach an id to a table, tracked in #414. (#165) - GFM alert callouts.
> [!NOTE],> [!TIP],> [!IMPORTANT],> [!WARNING], and> [!CAUTION]render as a bordered box with an icon and a localized label (EN/PT/ES) instead of a plain blockquote. A blockquote without a marker is untouched. (#159) - Custom containers.
warning...:::blocks (the MkDocs/VuePress admonition syntax) render as the same callout box; the common names map onto the five types. (#164) - Strikethrough and task lists in the PDF.
~~text~~renders as<del>and- [ ]/- [x]render as a disabled GitHub-style checkbox, closing two of the renderer deltas the dialect ADR named. (#143)
0.8.0 — 2026-07-16¶
Added¶
- Theme library page.
/themesis now a full catalogue: a grid of every theme from the API, a family filter (serif / sans / mono), a live preview that stacks the theme (and optional custom CSS) over the base in an isolated frame, a read-only view of the theme CSS, and buttons to use a theme or download its.css. The preview renders realistic samples (article, resume, email, contract, blog) so a theme can be judged against real content. (#392, #398) - Ten new PDF themes. letter, manuscript, newsprint, notebook, novel, resume, slate, slides, techbook, and whitepaper, bringing the catalogue to 21. Each is a CSS overlay on the base stylesheet. (#393)
- Mermaid diagrams in md-to-pdf. A
mermaidcode fence renders to a diagram at print time through a vendored bundle, so it works offline and deterministically with no CDN. Opt-in viarender_mermaid(off by default; a document without the option renders exactly as before). (#394) - Live theme preview in md-to-pdf. Before converting, the pasted Markdown is shown styled by the selected theme, so the theme's effect is visible without a round-trip. (#397)
- Custom CSS in md-to-pdf. An optional CSS block layers after the theme, shown live in the preview and applied to the PDF. Empty by default, so output is unchanged when unused. (#395)
- Format-matrix status filter. The conversion matrix on Home filters by status (All / Shipped / Roadmap / Wanted), each with a count. (#396)
0.7.0 — 2026-07-16¶
Added¶
- Generated OpenAPI TypeScript client. The web app's request and
response types come from the FastAPI schema now instead of being
hand-typed.
python -m app.export_openapisnapshots the schema toapps/web/src/lib/openapi.json, andnpm run gen:apiturns it intoapps/web/src/lib/api-types.ts. A CI step regenerates both and fails on any diff, so a backend schema change that skips the regen cannot land. (#32) - Styled blockquotes in the Markdown preview. Quotes emitted by
detect_blockquotesrender with an accent left side-rule, a soft accent tint, and muted italic text instead of the browser default. Quoted text and quoted links both meet WCAG AA in light and dark. (#218)
0.6.0 — 2026-07-15¶
Added¶
- Self-contained Markdown with inline images.
pdf-to-mdcan embed extracted images as base64data:URIs instead of dropping them or writing sidecar files, so a single.mdtravels intact. The APIwith_imagesflag turns it on and the CLI exposes--inline-images. (#372, #373) - GFM task lists. Source checkbox glyphs map to
- [ ]/- [x]items behinddetect_task_lists, off by default so output stays byte-identical. (#172) - OCR page cap.
MD_BRIDGE_OCR_MAX_PAGESbounds how many pages the OCR pre-pass rasterizes on a shared or hosted deployment;0(the default) keeps it unbounded. (#208) - OCR per-page timeout.
MD_BRIDGE_OCR_PAGE_TIMEOUT(default 60s) bounds how long a single page's Tesseract run may take. A timed-out page returnsocr_failednaming the page instead of pinning the worker thread. (#364) - Keyboard-dismissable toasts. Success and warning toasts carry a focusable, localized close button that also pauses the auto-dismiss while it has focus. (#355)
Changed¶
- The Markdown to PDF renderer is offline-only. It no longer fetches
external resources and blocks network egress, WebSocket, popup, and
file:escapes, so a hostile Markdown document cannot reach the network or the filesystem through Chromium. (#363, #369, #371) - Batch keyboard reorder is discoverable and standards-based. Rows
describe the Space-to-grab, arrow-to-move interaction through
aria-describedbyand announce each move in a live region, replacing the deprecatedaria-grabbed. (#358) - Batch and chrome strings are fully localized. The remove and reorder controls, the theme toggle, the spinner label, and unknown-error text come from the typed en/pt/es catalog instead of leaking English or Portuguese. (#354)
- Uploads are read once instead of buffered twice. The upload reader reads the parsed upload a single time, bounded at the size cap, instead of growing a bytearray and copying it into bytes, roughly halving peak memory. (#365)
Fixed¶
Content-Dispositionis well-formed for any filename. The download header strips quotes, CR/LF, and path separators from the name and adds an RFC 5987filename*, closing a header-injection path. (#362)- Invalid options return
422, not500. A rejectedallow_htmlvalue no longer crashes the error handler while it serializes the validation error. (#361) - A corrupt or non-PDF upload returns the
422error envelope instead of a plain-text500. (#360, #370) - The success toast no longer fires over a failed batch, and it survives parent re-renders instead of resetting its timer on every keystroke. (#353, #355)
- Removing a batch item mid-run aborts its in-flight request and unschedules it instead of finishing discarded work invisibly. (#357)
- A persisted theme slug missing from the server catalog falls back to
defaultinstead of leaving the picker unselected and posting a stale slug. (#356) - The drop-zone highlight no longer flickers while the pointer drags over child elements. (#359)
0.5.0 — 2026-07-08¶
Added¶
- Converter refinements: caption-derived image alt text (#149),
smart-typography ASCII folding for quotes, ellipses, and dashes (#171),
an abbreviation glossary emitted as
*[abbr]:definitions (#163), quote attribution paired with the blockquote above it (#173), deterministic heading-anchor slugs (#152), reference-style links for repeated URLs (#158), and autolinks for bare URLs and emails (#157).
Changed¶
- The
/pdf-to-mdpage replaces the compare and options panes with a batch queue and drag-to-reorder. (#294, #304)
See the v0.5.0 release for the full list.
0.4.0 — 2026-06-08¶
Added¶
- Theme system for Markdown → PDF. A request can now pick a visual
theme through
options.theme;GET /api/themeslists the registered themes andGET /api/themes/{slug}/cssserves a theme's stylesheet. Three themes ship alongside the neutraldefault: academic (serif, justified body, decimal section numbering), business (sans-serif, red accent masthead and table headers), and minimal (low-chrome draft layout, rule-only tables). Each theme is a CSS overlay stacked ondefault.css, so it carries only its own identity and inherits a complete base layout. An unknown slug returns400 unknown_theme. (#23 registry + endpoints, #22 CSS templates) - Markdown to DOCX. A
/convert/md-to-docxpage andPOST /api/md-to-docxrender Markdown to a deterministic Word document, with a format-pair registry behindGET /api/formats. (#60, #271, #279) - Per-request page setup for Markdown to PDF: page size, margins, and a running header/footer. (#243, #272, #275)
- Heading detection reaches H4-H6 (#234), hard line breaks are preserved (#232), recurrent page headers and footers are subtracted (#221), font sizes cluster into heading bands (#220), and the docs site ships in three languages (PT, ES) with a selector. (#29)
See the v0.4.0 release for the full list.
0.3.0 — 2026-06-01¶
Minor release. Two converter behaviour changes lead the headline: the OCR
pre-pass now runs automatically when the OCR stack is installed (no flag),
and pdf-to-markdown now emits pure Markdown instead of raw <small> and
<sup> HTML tags. The release also ships automatic trilingual OCR (English,
Portuguese, Spanish), GFM strikethrough detection, four list and front-matter
conversion fixes, and the docs build-out (heuristics, FAQ, API recipes,
deployment recipes). The lean default install is unchanged: no OCR binary, no
behaviour shift, a scan still returns 422 ocr_required.
Added¶
- Automatic multi-language OCR (English, Portuguese, Spanish). The
opt-in OCR pre-pass now defaults to
eng+por+spa, so a scanned document in any of the three is read without configuring a language per document; Tesseract scores each region across the three models, which is deterministic. Spanish (tesseract-ocr-spa) is added to the OCR Docker image, the CI Tesseract install, and the docs. PT-PT and PT-BR share the singlepormodel.MD_BRIDGE_OCR_LANGstill pins a language. A narrower per-document auto-detect is a follow-up. (#199) - GFM strikethrough in pdf-to-markdown. Text struck through in the
source PDF (a line drawn across the glyphs) now converts to
~~text~~instead of dropping the semantic. PyMuPDF surfaces the stroke on the span'schar_flagswhen the page is read withTEXT_COLLECT_STYLES; the detection is version-gated, so an older PyMuPDF degrades to "no strikethrough" rather than misbehaving. Strikethrough nests outside emphasis (~~**text**~~). (#142) - Optional Tesseract OCR pre-pass for scanned PDFs by @0exec
(first external contribution from this account). New
apps/api/app/services/ocr.pyruns whenMD_BRIDGE_OCR_ENABLED=1and the inspect diagnostics reportneeds_ocr: true. PDFs that already carry a text layer skip OCR entirely. Response payload gainedocr_applied: bool. New Docker stageruntime-ocr(lean defaultruntimestage unchanged),[ocr]extras inpyproject.toml, and CI installstesseract-ocrfor the integration tests. (#86, closes #5) - Descriptive iframe title on the PDF preview by @zhouzhou626
(third PR landed by this contributor). New
mdToPdf.previewIframeTitledictionary key across EN, PT-BR, ES, bound atapps/web/src/pages/MdToPdf.tsx:119. Screen readers now announce "Generated PDF preview, frame" instead of inheriting the page heading. WCAG 4.1.2 (Name, Role, Value). (#87, closes #72) - Heuristics documentation page at
docs/heuristics.md: document profile, heading detection, TOC normalization, list recovery, table cleanup, inline formatting, paragraph stitching, header/footer suppression, front matter, and the deliberately out-of-scope items. Each section names the function inpackages/pdf-to-markdown/scripts/convert.py. (#92) - FAQ page at
docs/faq.md: why OCR is opt-in, why the converter persists nothing, how to add a new locale, heuristics vs language model, Playwright + headless Chromium trade-off, server-side vs client-side conversion, offline operation. (#93) - API recipes page at
docs/api-recipes.md: copy-paste recipes for the four HTTP endpoints from curl, Python requests, and JavaScript fetch (browser + Node), plus error-envelope reading, CORS configuration, and rate-limit guidance. (#97, closes #26) - Deployment recipes page at
docs/deployment-other.md: Render, Fly.io, and Railway walkthroughs with blueprints, free-tier caveats, and a consolidated common-gotchas section (VITE_API_URLbuild-time, CORS, cold-start timeouts, 500 MB upload cap). (#98, partial fix for #7)
Changed¶
- OCR runs by default when the stack is installed. Previously the
OCR pre-pass only ran with
MD_BRIDGE_OCR_ENABLED=1. Now installing the OCR stack (the[ocr]extra plus the Tesseract binary, or theruntime-ocrimage) is itself the opt-in: a scanned PDF is OCR'd automatically, with no flag. A lean install without the stack carries neither the binary nor the binding, so OCR stays off and a scan still returns422 ocr_required— the lean default is unchanged.MD_BRIDGE_OCR_ENABLEDnow overrides the auto decision both ways:1/trueforces on,0/falseforces off. (#207) - pdf-to-markdown emits pure Markdown instead of raw HTML tags.
Small-font blocks (captions, footnotes, copyright lines) now render
as plain paragraphs instead of
<small>...</small>, and superscript spans render as Pandoc^x^instead of<sup>...</sup>. This keeps the output clean for RAG pipelines, plain Markdown viewers, search indexers, and Pandoc. The size hint on small text is dropped (Markdown has no clean equivalent); literal carets in prose are left bare. Theneeds_ocrpayload warning no longer strips a<small>wrapper that is no longer emitted, so small-font text now counts as content (the hardocr_requiredgate is unaffected — it reads raw PDF chars). Opt-in raw-HTML preservation is deferred to the allow-list policy. (#141, follow-ups #154 and #148) - Contribution-guide section in every open issue body. All 27 open issues now carry a standardized "How to contribute" block covering claim workflow, branch naming, Conventional Commits, test pyramid, no AI co-authors, squash merge, and reversibility declaration. The section is idempotent: re-running the batch skips issues that already carry it.
- Scorecard exceptions doc (
docs/scorecard-exceptions.md) updated to cover the three new Pinned-Dependencies paths introduced by the OCR pipeline (apps/api/Dockerfile:49,60and.github/workflows/ci.yml:28) plus a new Maintained section documenting the time-based auto-resolution. (#90) - Contributors recognition: @zhouzhou626 gains the
a11ytype (#88); @0exec joins the contributors block withcode,doc,test,infratypes (#89). Avatar URLs use numeric ID form so username changes do not break the links.
Removed¶
- Orphan
useConvert.tshook atapps/web/src/hooks/. DeclaredusePdfToMd()anduseMdToPdf()but had zero callsites. Pages call the API functions directly fromsrc/lib/api.ts; the batch flow usesuseBatchConvert. The hook was superseded during early development. (#91) - Unused
docs/brand/social-preview.pngasset. Not referenced frommkdocs.yml, README, any markdown, or any HTML meta tag. Noog:imageis wired in the docs site or the React app. (#91) - Dead
mkdocs.ymlnav entry that pointed atdocs/deployment/oracle-cloud.md, which never existed in repo history. Replaced with the newDeploy: deployment-other.mdentry so MkDocs builds without the orphan-page warning. (#98)
Fixed¶
- Multi-paragraph list items no longer collapse. A list item that
spanned more than one paragraph in the PDF used to emit the second
paragraph at top level, splitting the list. The page assembly now
tracks the open item and nests a paragraph indented past the marker
inside the
<li>. (#167) - Code blocks under a list item stay in the item. A fenced code block indented past the marker used to escape to top level and split the list; it now nests inside the item as an indented code block (the shipped renderer does not nest a fence at the content column, so the language hint is dropped for the nested case). (#197)
- YAML front matter keeps list, nested, and multi-line values. The
markdown-to-pdf reader parsed front matter with a hand-written
split-on-colon loop that flattened or dropped anything that was not a
flat
key: value; it now uses PyYAML, with the block hardened against a billion-laughs / deep-nesting denial of service. (#150) - A full-width rule is no longer misread as strikethrough. PyMuPDF's
strikeout flag fires for any horizontal line or thin rule crossing text
at mid-height, including a page rule or section divider. The converter
now cross-checks the drawn geometry: a stroke that overruns the span
toward the margins is a rule, not a strike, so the spurious
~~is dropped. A genuine strike (a line spanning the struck text) still converts. (#202)
0.2.3 — 2026-05-20¶
Minor release. Headline change is the first external contribution:
@ko4lax landed the WCAG 2.1 AA audit and remediation in PR #54. The
release also ships the new design system catalogue (PRs #58 + #65)
that future UI work tracks back to, plus the trilingual screenshots
refresh, the Spanish locale page tests, the all-contributors adoption,
and a handful of CI hygiene fixes accumulated since v0.2.2.
Added¶
- WCAG 2.1 AA accessibility audit and remediation by @ko4lax (first
external contributor on the project).
DropZone.tsxrefactored so the file input is a sibling of therole="button"wrapper instead of nested inside it (closes the axe nested-interactive-controls violation); skip-to-content link added inApp.tsx; nav landmark labelledMain navigation; batch progress carries anaria-live="polite"announcement. Newdocs/accessibility-audit.mddocuments findings with WCAG identifiers (wcag2a,wcag2aa,wcag21a,wcag21aa) and reproduction steps. Newapps/web/e2e/audit.spec.tswires@axe-core/playwrightinto the existing E2E job so CI fails on critical or serious violations going forward. (#54, closes #36) docs/design/design system catalogue. Self-contained HTML (design-thinking.html) with hi-fi mockups and paste-ready issue specs for eight features: F1 CSS theme picker, F2 theme library, F3 per-conversion options panel, F4 format hub (DOCX/EPUB/HTML/RTF), F5 language workshop, F6 conversion presets, F7 local history, and F8 preferences page. The HTML reusesapps/web/src/styles/tokens.cssverbatim so visual changes to the React app propagate to the catalogue. Published with the docs site at /design/; MkDocs nav now includes a Design system entry; CONTRIBUTING.md routes contributors who pick updesign-requiredissues at the catalogue first; README gains a dedicated Design system section above Limits. Seeds six new feature issues (#59 F3, #60 F4, #61 F5, #62 F6, #63 F7, #64 F8). (#58)docs/design/screenshots/retina captures (1440x900 at 2x device scale) of every catalogue section: hero, principles, foundations, roadmap, plus F1..F8 mockup spreads. The design landing page and the GitHub-view README render the gallery so contributors preview the catalogue before opening the HTML. (#65)docs/screenshots/home-es.pngSpanish home-page screenshot at the same 2880x1800 retina resolution as the EN and PT companions. README's screenshot section now shows the trilingual UI in a 3-column table (EN / PT / ES).docs/screenshots/demo.gifwas regenerated with the three locales at the start so the README hero shows the trilingual capability before the conversion flow. (#49)- Spanish locale coverage added to the page-level integration tests:
About.test.tsxasserts the ES title,Home.test.tsxasserts the ES hero headline, andNavigation.test.tsxflips the whole UI to ES via the language toggle and asserts both the headline and the About link translate. The Portuguese tests stay in place. (#49) - all-contributors specification adopted.
.all-contributorsrcat the repo root tracks every contributor and their kind of contribution (code, doc, translation, design, review, test, infra, maintenance). README gains a## Contributorssection between## Licenseand## If md-bridge helped youthat renders the current list.CONTRIBUTING.mddocuments how to be credited: no bot to install, no extra PR; the maintainer regenerates the README block during release prep. (#48) @ko4laxcredited as a contributor with categoriescode,doc,test,infra,translationper the diff classification in PR #54. Avatar URL uses the numeric-ID form per the maintainer credit rule. (#67).github/workflows/pr-linked-issue.ymlposts a single one-line comment on every issue closed via "Closes #N" naming the PR author, so attribution survives in the casual reader's view. (#56, refined in #57)
Changed¶
- All
docs/screenshots/*.pngrefreshed at the current 2880x1800 retina resolution from the post-v0.2.2 UI state (after the About rewrite, the extensibility positioning, and the warning i18n fix). Thepdf-to-md,pdf-to-md-batch,md-to-pdf,about, andswaggercaptures now match the UI a contributor sees today.demo.gifwas regenerated with eight frames covering the trilingual home pages and the full conversion flow. (#51) Validate PR titleis now a required status check onmain. Previously the Conventional Commits validation ran on every PR but did not block the merge if it failed (caught when PR #49 merged with the malformed scopefeat(web,docs):despite a red title check). Branch protection now requires every PR title to parse cleanly as<type>(<scope>)<!>: <description>before the merge button enables. CONTRIBUTING.md's branch-protection list is updated to include the sixth required check. (#50)- CONTRIBUTING.md regression test guidance promoted from a single paragraph to a step-by-step checklist under "Writing a good regression test". Documents the failing-diff format, tier choice, fixture vs synthetic input, and the no-silent-skips rule with a worked example from PR #20. (#52)
@zhouzhou626's entry in.all-contributorsrcswitched to the numeric-ID avatar URL (was producing identicon fallback) and gained thedoccontribution credit for PR #52. CONTRIBUTING.md codifies the post-merge maintainer credit rule as a five-step mechanical checklist any future maintainer (or AI assistant) can apply without judgement. (#53)
Fixed¶
MdToPdf.tsxwas passingt.pdfToMd.ready("Ready" / "Pronto" / "Listo") to theConvertButton'ssuccesslabel slot instead of the page-ownedt.mdToPdf.success("PDF ready." / equivalents). The branch was unreachable under the currentstatus={batch.running ? 'loading' : 'idle'}state machine, so no user saw the wrong copy, but the dead path would have surfaced the next time anyone wired the success state. Aligned to the correct key. (#66)
0.2.2 — 2026-05-19¶
Patch release. Headline change is the trilingual warning fix; the rest is
governance, infrastructure, and documentation polish accumulated since
v0.2.1.
Fixed¶
/api/pdf-to-mdwarnings now follow the active UI locale. The backend used to emit hardcoded English strings ("Very little text was extracted…"); PT and ES users saw English while the rest of the UI was in their locale. Backend emits stable codes (needs_ocr,images_not_persisted); the frontend dictionary translates per locale. The lookup falls back to the raw string for unknown codes so future warnings stay forward-compatible. (#40, PR #42)apps/api/app/main.pySwagger metadata pointedcontact.urland theAPI_DESCRIPTIONmarkdown link at the placeholderhttps://github.com/your-org/md-bridge. The Swagger UI at/docssurfaced both. Replaced with the real repository URL. (PR #46)docker-publish.ymlsmoke test for the Web image was runningnginx -tagainst the bundled config, which containsproxy_pass http://api:8000. In an isolated container theapihostname does not resolve, so the parse failed and the workflow reported a red CI even though the publish itself succeeded. The smoke now asserts that the Vite build stage producedindex.htmland copied it to the nginx web root. (PR #20)
Added¶
- Conventional Commits 1.0.0 is now the project's commit and
PR-title convention. New CI workflow
semantic-pr.ymlrejects PR titles that do not match<type>(<scope>)<!>: <description>.CONTRIBUTING.mdgains a full reference section with the recognised types, bump rules, and worked examples. Therelease-drafter.ymlconfig gains anautolabelerblock. (PR #19) CONTRIBUTING.mdnow documents the issue-claiming process: contributors comment to claim, maintainer assigns via the native GitHubassigneefield, seven-day window before the issue returns to the pool. (PR #41)- Issue templates (
bug_report.md,feature_request.md) now require a test plan with explicit file paths and tiers. Feature template also gains Architect and Design notes sections so the tri-disciplinary review pattern shows up before the issue is filed. (PR #43) docs/screenshots/warning-i18n.pngvisual proof for #40 (deterministic Pillow render, no AI image generation). (PR #45)
Changed¶
- Pre-commit hooks moved from a separate "Optional" section near
Tests to Local setup in
CONTRIBUTING.mdso new contributors see them at the same moment they install Python and Node. The "Strongly recommended" framing replaces "Optional". A new paragraph explains that the hooks deliberately do not check branch staleness; branch protection onmain("require branches to be up to date before merging") handles that server-side. PR template checklist gains the two matching items. (PR #44) - Project descriptions across
package.json,apps/api/pyproject.toml,README.md, anddocs/index.mdnow state the extensibility intent explicitly: md-bridge is a document converter that ships PDF ↔ Markdown today and welcomes new format pairs as contributions land. The GitHub repo description and topics were updated to match. (PR #39) - About page copy rewritten across
en,pt, andesin an OSS-professional register. New copy leads with positioning ("open source, self-hosted, deterministic, no model inference, no telemetry") and names the heuristic stack (PyMuPDF + headless Chromium) directly. "Built with" becomes "Open source" with explicit MIT-licence andCONTRIBUTING.mdpointers. (PR #21) - Theme picker for Markdown → PDF (#14) reorganised as an umbrella
issue with three sister sub-issues: design (#22, CSS templates),
backend (#23, registry +
/api/themes), frontend (#24, picker dropdown). The pattern is now the project's reference for multi-discipline features.
0.2.1 — 2026-05-19¶
Fixed¶
docker-publish.ymlnow builds multi-platform images (linux/amd64+linux/arm64). The Oracle Cloud Always Free deployment recipe targets ARM Ampere A1 VMs but the previous amd64-only publishes faileddocker pullon ARM hosts with a manifest-mismatch error. Apple Silicon developers were affected by the same issue. A post-publish smoke job verifies both arches by pulling the image and running a minimal probe. (#12)
0.2.0 — 2026-05-19¶
First minor release. Ships the new trilingual UI plus a wider set of visibility, distribution, and contributor-onboarding work.
Added¶
- Spanish (
es) locale in the web UI. The header toggle now lists EN / PT / ES. Locale detection and the<html lang>attribute were generalised so future locales drop in without further code changes. Translations are native-quality; tests and the Playwright spec exercise all three locales. (#9 by @zhouzhou626 — first external contributor.) - Oracle Cloud Always Free deployment recipe under
deployment/oracle-cloud/: step-by-stepREADME.md,bootstrap.shthat installs Docker + Caddy + the stack on a fresh ARM Ampere A1 VM, and a referenceCaddyfile.example. Cost: zero. The docs site picks up the page under a new "Deploy" nav section. - Release-drafter workflow that keeps a draft GitHub Release in
sync with merged PRs on
main. Categories are driven by PR labels (enhancement,bug,security,documentation,chore, ...) and the next semver bump is resolved automatically (major / minor / patch). Config in.github/release-drafter.yml. workflow_dispatchtrigger ondocker-publish.ymlso a manual re-publish from the Actions UI is now possible without an unrelated commit.- Documentation site at https://vinicq.github.io/md-bridge/.
MkDocs Material build deployed to GitHub Pages on every doc change.
mkdocs.ymlplusdocs/index.mdanddocs/getting-started.mdprovide a curated landing experience separate from the GitHub README. - Docker images on GHCR: a release-triggered workflow publishes
ghcr.io/vinicq/md-bridge-apiandghcr.io/vinicq/md-bridge-webso users candocker pullinstead of building locally. Tags follow the semver scheme; both images are public. - OpenSSF Scorecard workflow that runs weekly + on push, surfaces the result in the Security tab, and exposes a public score at scorecard.dev. README gains a Scorecard badge alongside CI and CodeQL.
- Brand assets under
docs/brand/(logo, wordmark, social preview). Programmatic Pillow geometry, deterministic, no AI generation. - Demo GIF at
docs/screenshots/demo.gif, used as the README hero. - Star history chart and a "If md-bridge helped you" CTA at the bottom of the README.
0.1.1 — 2026-05-19¶
Maintenance and governance release. No behaviour changes in the converter; only infrastructure, security posture, and contributor ergonomics.
Added¶
- Optional
pre-commitconfiguration that runsruffand basic hygiene hooks (trailing whitespace, EOF newline, YAML/TOML syntax, merge conflict markers, large files) before every commit. Documented inCONTRIBUTING.md. .github/workflows/dependabot-auto-merge.ymlenablesgh pr merge --autofor Dependabot PRs that are patch bumps (X.Y.Z → X.Y.Z+1) or transitive (indirect) dependency updates. Branch protection still gates the actual merge on every required status check; minor and major bumps stay in the manual review queue.- Branch protection on
maindocumented inCONTRIBUTING.md: required status checks for Backend, Web, End-to-end, and the two CodeQL jobs; force-push and deletion blocked; linear history required. SECURITY.mdnow lists the GitHub-native defenses that are active on the repository so contributors know what they get for free (secret scanning, push protection, CodeQL, Dependabot, private vulnerability reporting).
Changed¶
- GitHub Actions bumped to current majors:
actions/checkoutv4 → v6,actions/setup-pythonv5 → v6,github/codeql-actionv3 → v4,actions/setup-nodev4 → v6,actions/upload-artifactv4 → v7. Clears the Node.js 20 deprecation warnings on the runner. - Docker base images bumped: web
node:22-alpine→node:26-alpine, web runtimenginx:1.27-alpine→nginx:1.31-alpine. - npm devDependencies bumped:
typescript-eslint8.59.3 → 8.59.4 (patch),@types/node24.12.4 → 25.9.0.
Security¶
- Enabled GitHub-native repo features via API: secret scanning, push protection, private vulnerability reporting, vulnerability alerts, Dependabot security updates.
- Branch protection on
mainrequires every CI and CodeQL status check to pass before a merge can land.
0.1.0 — 2026-05-19¶
First tagged release. md-bridge is a self-hosted PDF and Markdown converter with a FastAPI backend and a React frontend.
Added¶
- PDF to Markdown conversion with heading detection, list recovery, table extraction, and YAML front matter.
- Markdown to PDF rendering through headless Chromium with a bundled A4 stylesheet.
- Batch mode in the UI: drop one file or a whole folder; each file is converted sequentially and can be downloaded as it lands.
/api/inspect-pdfendpoint returns diagnostics (fonts, sizes, tagged-PDF flag, OCR hint) so the UI can warn before conversion.- Bilingual UI in English (default) and Portuguese, with the choice
persisted to
localStorage. - Interactive API docs at
/docs(Swagger UI) and/redoc, plus a walkthrough indocs/API.md. - Docker Compose stack for one-command boot of API + Web with healthchecks.
- Test pyramid with 124 tests (92 unit, 26 integration, 6 end-to-end), every one of which runs on CI against the committed ISTQB CTAL-TA syllabus fixture. No silent CI skips.
- CI workflow for backend pytest, web build + lint + vitest, and Playwright end-to-end.
- CodeQL static security analysis on every push and pull request, with a weekly scheduled scan, covering both Python and TypeScript.
- Backend linting via
ruffwith theE F W I UP Brule set, enforced in CI. - Frontend linting via ESLint, enforced in CI.
- Open source governance files:
LICENSE(MIT),CONTRIBUTING.md,CODE_OF_CONDUCT.md,SECURITY.md,.github/dependabot.yml, issue and PR templates,.editorconfig.