Skip to content

refactor(html): keep the frontend css and js as files, embedded at build time - #864

Merged
andiwand merged 1 commit into
mainfrom
feat/frontend-asset-files
Sep 8, 2026
Merged

refactor(html): keep the frontend css and js as files, embedded at build time#864
andiwand merged 1 commit into
mainfrom
feat/frontend-asset-files

Conversation

@andiwand

@andiwand andiwand commented Sep 8, 2026

Copy link
Copy Markdown
Member

frontend.cpp was 92 KB, 78 KB of it css and javascript inside raw string literals. Nothing that reads either language could see it — no highlighting, no formatter, no linter — and the three test/browser harnesses each parsed the C++ by string index to recover a script and wrote a gitignored copy beside their pages. msvc caps a string literal at 16380 bytes, so pdf_annotation_js was already split over two literals reassembled through an Asset::content_tail, guarded by a consteval fits_a_literal; spreadsheet.js is within a few hundred bytes of the same surgery.

What changes

The 21 assets now live as files under src/odr/internal/html/frontend/, and cmake/frontend_assets.cmake embeds them into a generated header of inline constexpr std::string_views. frontend.cpp drops from 2744 to 295 lines and keeps only the asset table — which now reads as the documented index of what each view writes — plus the locating and writing.

The bytes back each view as a char array of '\xNN' character literals: an array has no length cap, and the literal form survives both a signed and an unsigned char, where a 0xNN integer narrows on one and a negative decimal on the other. Verified against the ndk's aarch64-linux-android24, whose char is unsigned.

test/browser/*/serve no longer extract anything. A shared serve.py falls back to the asset directory, so a page links the file the library embeds rather than a copy of it, and the three .gitignores go away.

What does not change

This is not the data/ directory #648 removed. That was a runtime split — a directory every consumer had to ship and point at. This is a build-time one: nothing is shipped beside the library, no consumer gains a path, and android, apple, wasm, python and the jni all build the same target as before. No public API, no install rule, no conan option.

Byte-identical output

HtmlOutputTests reproduces every reference output exactly — diff -rq against test/data/reference-output is empty, resources/*.css and *.js included. Two details serve that:

  • The newline that used to open each raw literal (and that a shipped resource file still carries) is now written once, in written(), so the embedded and the linked form stay the same bytes.
  • pdf-annotation.js keeps the blank line at the seam of the two old literals.

Checks

  • Full suite: 1604 tests, 1598 passed, 6 pre-existing skips.
  • HtmlOutputTests + diff -rq against the reference output: no differences.
  • -Wall -Wextra -Werror -fsyntax-only with Homebrew g++-15 and with the ndk clang (--target=aarch64-linux-android24), both clean.
  • clang-tidy on frontend.cpp: only the pre-existing bugprone-derived-method-shadowing-base-method findings in untouched file.hpp.
  • The generator is pure cmake — no python, no node — takes 0.06s, and ninja settles to a no-op build.

🤖 Generated with Claude Code

@andiwand
andiwand force-pushed the feat/frontend-asset-files branch from 2af5585 to 102de6a Compare September 8, 2026 20:53
@andiwand

andiwand commented Sep 8, 2026

Copy link
Copy Markdown
Member Author

Rebased onto main (#863 and #865 have landed) and force-pushed.

The asset files carry the current scripts. They were snapshots of the literals as of #862, so the four the sheet editor changed — spreadsheet.js, sheet-editing.js, spreadsheet.css, spreadsheet-dark.css — were regenerated from main's literals; the other 17 were already byte-identical, which is what makes the leading-newline convention in written() provable rather than assumed. spreadsheet.js is 18292 bytes now, past the 16380 this PR removes the need for, and its spreadsheet_js_tail half is gone with content_tail and fits_a_literal.

Checks:

  • HtmlOutputTests: 289 tests, 283 pass, 6 pre-existing skips, and diff -rq of both generated output/ trees against the reference pins is empty.
  • translate on an .ods, .odt, .pdf and .xlsx against a build of origin/main: byte-identical.
  • The four sheet check pages through the new serve (51 + 20 + 14 + 8, green), and the annotation and viewport harnesses resolve their assets out of frontend/ too.
  • The embedded sheet-editing.js decoded back out of the generated header holds formulaInput, odr.editing.undo and odr-sheet-editor.

Rebase decisions worth a look: serve for the sheet checks lists editing.html; decision 8 in the design doc loses the "a raw string literal caps at 16380 bytes" reason for two scripts, since this PR removes it, and keeps "the read-only view would carry the editor it never runs"; the doc's table rows point at html/frontend/*.js, the sheet-editor row included.

Review: two comments carried facts that this PR dates — the char-array rationale said the longest script "sits a few hundred under" the cap (it is over it now), and the text-overflow note said where the property "sat". Both cut to the rule they state. Nothing else needed changing.

Note for whoever advances the reference-output pins next: resources/{spreadsheet.js,sheet-editing.js,spreadsheet.css,spreadsheet-dark.css} there are stale since #863/#865 — no page bytes changed, so CI's compare renders nothing and stays green, but the next regeneration should pick them up.

…ild time

`frontend.cpp` was 92 KB, 78 KB of it css and javascript inside raw string
literals. Nothing that reads either language could see it: no highlighting, no
formatter, no linter, and the three `test/browser` harnesses each parsed the C++
by string index to recover a script and wrote a gitignored copy beside their
pages. msvc caps a string literal at 16380 bytes, so `pdf_annotation_js` was
already split over two literals reassembled through an `Asset::content_tail`,
guarded by a `consteval fits_a_literal`, and `spreadsheet.js` has since been
split the same way.

The 21 assets now live as files under `src/odr/internal/html/frontend/`, and
`cmake/frontend_assets.cmake` embeds them into a generated header of
`inline constexpr std::string_view`s. The bytes back them as a `char` array of
`'\xNN'` character literals: an array has no length cap, and the literal form
survives both a signed and an unsigned `char` — a `0xNN` integer narrows on one
and a negative one on the other.

This is not the `data/` directory #648 removed. Nothing is shipped beside the
library, no consumer gains a path to point at, and android, apple, wasm, python
and the jni all build the same target as before. `frontend.cpp` keeps the asset
table, which now reads as the documented index of what each view writes, and
loses `content_tail` and `fits_a_literal`.

The emitted html is byte-identical: `HtmlOutputTests` reproduces every reference
output, `resources/*.css` and `*.js` included. The newline that used to open
each literal — and that a shipped resource file still carries — is written once
now, in `written()`, so the two forms stay the same bytes. `pdf-annotation.js`
keeps the blank line at the seam of the two old literals for the same reason.

`test/browser/*/serve` no longer extract anything: a shared `serve.py` falls
back to the asset directory, so a page links the file the library embeds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DChmZKEn3WdXWKu9KRbJMz
@andiwand
andiwand force-pushed the feat/frontend-asset-files branch from 102de6a to 7485f7f Compare September 8, 2026 20:53
@andiwand
andiwand merged commit d98b21a into main Sep 8, 2026
27 checks passed
@andiwand
andiwand deleted the feat/frontend-asset-files branch September 8, 2026 20:57
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