Skip to content

fix: six say-vs-do mismatches — PDF/A name, 413 hint, format lists, .htm, env - #140

Merged
MrChengLen merged 3 commits into
mainfrom
pr-format-consistency
Sep 25, 2026
Merged

MrChengLen merged 3 commits into
mainfrom
pr-format-consistency

Conversation

@MrChengLen

@MrChengLen MrChengLen commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

What

Six verified places where the UI, API or docs said one thing and the code did another. Each fix has a regression test that fails on main.

  1. PDF/A downloads as .pdf. pdf → pdfa results were named <stem>.pdfa, which no OS opens as a PDF. They are now <stem>_pdfa.pdf for the single download, the batch ZIP entries and the JS fallback name. The _pdfa suffix follows the _compressed / _pages convention for outputs that share their source's extension.
  2. 413 hint. Anonymous uploads were told "Register free to upload up to 50 MB"; the free tier allows 100 MB. The number now comes from app/core/quotas.py.
  3. Drop-zone "Supported:" lists. Added AVIF, HEIF, ICO, HTML, EML, FLV, WMV, AAC, WMA and OPUS (compress mode: AVIF). Only the label is translated now, so five dead msgids leave the catalogs. A parity test compares the rendered captions (DE + EN) with /api/v1/formats.
  4. /formats categories. AVIF now sits under Images and EML under Documents; both were in "Other".
  5. .htm dead end on /convert/html-to-pdf.
    • The picker offered .htm, but no converter accepted it.
    • .htm is now an alias of HTML → PDF: same HtmlToPdfConverter, same _deny_url_fetcher guard.
    • Non-UTF-8 HTML, such as Word's windows-1252 "Save as Web Page" output, now reaches WeasyPrint as bytes. Its <meta charset> is honoured, so umlauts no longer come out as �.
    • The SSRF test now covers both input paths.
  6. FILEMORPH_OFFICE_ENGINE was ignored.
    • Settings only read OFFICE_ENGINE. Both names are accepted now; the documented one wins within the same source.
    • docker-compose.office.yml no longer pins the value under environment:, which overrode .env.

Commits

  • 301c891: fixes 1–5
  • bdc5870: fix 6. This changes runtime behaviour, so it is a separate commit that can be reverted on its own.
  • 3f826e7: scripts/make_testdata_format_fixes.py, byte-stable fixtures for the manual test round (text PDF, windows-1252 .htm, UTF-8 .html). Only the script ships.

Please merge with a merge commit, not squash. Rebased onto main after #137 and #139.

⚠️ Behaviour change for self-hosters

A FILEMORPH_OFFICE_ENGINE of libreoffice or mammoth now takes effect; until now the app silently ran auto. libreoffice needs the filemorph:office image.

Verification

  • Full suite locally: 1203 passed, 63 skipped. The skipped tests are gated on WeasyPrint, pikepdf or ffmpeg, which this Windows host lacks; they run in CI. Commit A on its own: 1197 passed.
  • ruff, format, i18n drift-check and pip-audit are clean.
  • Every new test was checked to fail on main for the intended reason.
  • Security audit and code review ran on the change and again on the review fixes; no open findings.

Deferred (out of scope, tracked separately)

  • The same outdated format list still appears in the homepage FAQ, llms.txt, the JSON-LD feature list and the README.
  • Tier numbers in docs/api-usage-guide.md and docs/self-hosting.md are stale, and the duplicate-name doc says _2 where the code produces _1.
  • safe_download_name truncation drops the extension for names longer than ~191 characters, on all routes.
  • .github/workflows/docker.yml has been stored with CRLF since f72dedc.

🤖 Generated with Claude Code

MrChengLen and others added 3 commits September 25, 2026 15:37
…s, .htm

PDF/A results downloaded as `<name>.pdfa`, an extension no OS opens as a
PDF. `_DOWNLOAD_SUFFIX` in app/api/routes/convert.py maps the pdfa target
to `<name>_pdfa.pdf` for the single download and the batch ZIP entries;
app/static/js/app.js mirrors it for its no-Content-Disposition fallback
(a test keeps the two maps in sync). The `_pdfa` suffix follows the
`_compressed` / `_pages` convention for outputs that share their
source's extension; a plain `<name>.pdf` was rejected because it
collides with the source in the Downloads folder.

The anonymous 413 hint on /convert and /compress still promised "up to
50 MB" after registering; the free tier allows 100 MB. The number now
comes from get_quota("free"), and the stale tier numbers in the adjacent
comments are gone.

The homepage drop-zone captions left out AVIF, HEIF, ICO, HTML, EML,
FLV, WMV, AAC, WMA and OPUS (compress mode: AVIF). Only the "Supported:"
label is translated now (existing msgid); the format tokens are plain
text, so five dead msgids leave the catalogs and a new converter needs
no catalog work. A parity test compares the rendered captions (DE + EN)
with /api/v1/formats. Rendering the lists from the registry was
considered and rejected: route plumbing for the same guarantee.

/formats filed AVIF and EML under "Other"; they now sit under Images and
Documents, and a test fails for any uncategorised source format.

/convert/html-to-pdf offered .htm in its file picker, but no converter
accepted it, so the user got stuck at "Please select a target format".
.htm is now registered on HtmlToPdfConverter: same class, same
_deny_url_fetcher guard. Dropping .htm from `accept` was rejected, since
drag & drop would still dead-end. Because .htm is typically Word's
windows-1252 "Save as Web Page" export, non-UTF-8 HTML now reaches
WeasyPrint as bytes (_html_source), so its <meta charset> is honoured
instead of every umlaut becoming U+FFFD in a "successful" conversion;
the SSRF test now covers both the str and the bytes path and asserts
that each resource URL hits the guard. The write_pdf() line references
of the CVE-2026-55073 assessment (ci.yml, CHANGELOG) move with the code.

Each fix comes with a regression test that fails on main.

Full suite 1197 green (63 skipped locally: WeasyPrint/pikepdf/ffmpeg-gated,
run in CI); ruff + format + i18n-drift + pip-audit clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lias

The docs, the office compose overlay and the converter's error message
all name the DOCX -> PDF engine variable FILEMORPH_OFFICE_ENGINE, but
Settings.office_engine only read OFFICE_ENGINE, so following the docs
silently did nothing. The field now reads both via AliasChoices; the
documented name wins when both are set in the same source. Making
OFFICE_ENGINE canonical instead was rejected: every doc, the overlay and
the error message already use the prefixed name, and the deployments
that followed them are the ones this fixes.

docker-compose.office.yml also pinned FILEMORPH_OFFICE_ENGINE=auto under
`environment:`, which Compose ranks above the base file's `env_file:
.env`. With the alias in place that pin would have overridden the
operator's .env and silently turned a legacy OFFICE_ENGINE=mammoth into
auto. The overlay no longer sets the variable (auto is the default), and
.env.example lists it commented out so a copied template can't shadow a
legacy value either.

Behaviour change for self-hosters: a FILEMORPH_OFFICE_ENGINE of
libreoffice or mammoth now takes effect instead of auto; libreoffice
needs the office image. The CHANGELOG says so. Kept as a separate commit
so it can be reverted on its own.

Full suite 1203 green (63 skipped locally: WeasyPrint/pikepdf/ffmpeg-gated,
run in CI); ruff + format + i18n-drift + pip-audit clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scripts/make_testdata_format_fixes.py writes byte-stable fixtures for the
manual test round of the two preceding commits: a text PDF (reportlab
invariant mode) for the PDF/A download name, and a windows-1252 .htm with a
<meta charset> plus a UTF-8 .html, both with umlauts, for the HTML -> PDF
encoding fix. Output goes to the gitignored docs-internal/testdata/ folder;
only the script ships, same pattern as make_testdata_ia_rework.py.

Full suite 1203 green (63 skipped locally: WeasyPrint/pikepdf/ffmpeg-gated,
run in CI); ruff + format + i18n-drift + pip-audit clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MrChengLen
MrChengLen force-pushed the pr-format-consistency branch from 88337d8 to 3f826e7 Compare September 25, 2026 13:38
@MrChengLen
MrChengLen merged commit be9f926 into main Sep 25, 2026
5 checks passed
@MrChengLen
MrChengLen deleted the pr-format-consistency branch September 25, 2026 13:41
MrChengLen added a commit that referenced this pull request Sep 25, 2026
…registry

The homepage FAQ answer "Which file formats can I convert?", the /llms.txt
format sentence, the JSON-LD featureList and the README drop-zone mockup
still listed the formats from before HEIF, AVIF, ICO, HTML, EML, FLV, WMV,
AAC, WMA and Opus were added. #140 fixed the drop-zone caption, so the
homepage FAQ contradicted the caption above it. All four now name every
source format of get_public_conversions(), the data /api/v1/formats
serves. The README "Supported Formats" table lacked HTML and EML (now a
"Web & email" row: they only convert to PDF, and in the Documents row they
would have implied EML -> TXT/HTML) and PDF as an image output.

Kept as hand-written text plus a parity test, the pattern #140 set for the
drop-zone caption on the same page. Rejected: deriving the lists from the
registry at runtime. The JSON-LD is built in app/core at import time and
can't import the /formats category map from a route module, so that map
would have to move; format_label() prints WEBM and MD, so a casing map and
alias de-duplication would be needed; the FAQ would become a
five-placeholder msgid. More moving parts than four text edits, for a list
that changes a few times a year — and the README can't be derived at all.

tests/test_format_lists_match_registry.py (7 tests) compares each surface
with the registry: the FAQ in EN and DE, including a check that /de/ shows
the German answer (a fuzzy catalog entry would fall back to the English
text, which lists the right formats); llms.txt; the JSON-LD "Convert ..."
entries; the README mockup; the README table inputs, and each table row's
outputs against the union of what that row's inputs convert to. Run before
the text edits, all 7 failed and named exactly the missing formats.

test_jsonld_webapplication_has_featurelist rejected "avif" anywhere in the
feature list. It was written on 2026-06-05, before AVIF conversion shipped
(7caa75e, 2026-07-15), to block an AVIF auto-routing claim. It now ignores
bracketed format lists and still rejects AVIF everywhere else;
Accept-header auto-routing still doesn't ship. A keyword list was tried
first and dropped in review: it missed rewordings and flagged the real
DOCX two-engine routing. The jsonld.py comment now points at that test
instead of docs/claims-audit.md, which isn't in the public repo.

i18n: extract + update left the DE entry fuzzy with the old list and the
old EN msgid as #~; translated, flag and obsolete entry dropped, compiled.
DE 776/776 translated, no fuzzy.

Reviewed: security-auditor PASS; code-reviewer findings applied.
Full suite 1216 green (63 skipped); ruff + i18n-drift + pip-audit clean;
Tailwind bundle unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant