# Vendor Frontend Assets — Implementation Guide **Purpose:** Establish the vendored-assets pattern in a project that serves static frontend files through the ontoref daemon (or any Axum-based server with a static file route). Covers the full setup: directory layout, just recipes, and template references. **Substitutions required before use:** - `{project_dir}` — absolute path to project root - `{daemon_crate}` — path to the Axum daemon crate (e.g. `crates/myproject-daemon`) - `{assets_route}` — URL prefix the daemon serves assets from (e.g. `/assets`) --- ## Why vendor instead of CDN CDN references in server-rendered templates create three failure modes that vendoring eliminates: 1. **Version drift** — a CDN package can be yanked or silently updated at the minor level depending on the version specifier used. 2. **Offline development** — daemon templates break without network access. 3. **Registration race** — some Cytoscape extensions auto-register against `window.cytoscape` at load time; if the CDN is slow or returns a 404 for a sub-resource (e.g. a CSS that does not exist in that package version), the extension silently fails to register and `cy.pluginName(...)` throws at runtime with no network error visible in the console. The canonical signal that a CDN asset has a sub-resource problem: `curl -sI ` returns 200 but the body starts with "Couldn't find the requested file". Always `curl -fsSL` (fail on HTTP errors) when downloading to catch this. --- ## Directory layout ```text assets/ vendor/ ← all vendored JS; served at {assets_route}/vendor/ @.js ← optional: keep version in filename for cache-busting justfiles/ assets.just ← recipes for downloading/updating vendor assets justfile ← root just file; imports assets.just ``` Vendor files are committed to the repository. They are not gitignored — the goal is reproducible builds without network access. --- ## assets.just structure ```just # Frontend asset management # # Vendored JS libs live in assets/vendor/ and are served by the daemon # at {assets_route}/vendor/. Pin versions explicitly; bump manually. LIB_VERSION := "x.y.z" # Download/update all vendored frontend JS dependencies [doc("Vendor all frontend JS dependencies")] vendor-js: vendor- [doc("Vendor ")] vendor-: mkdir -p assets/vendor curl -fsSL \ "https://cdn.jsdelivr.net/npm/@{{LIB_VERSION}}/.js" \ -o assets/vendor/.js @echo "vendored @{{LIB_VERSION}}" ``` Rules: - One version variable per library, at the top of the file. - `vendor-js` is the aggregate recipe — depends on all individual `vendor-*` recipes. - Use `curl -fsSL` (not `-sL`): `-f` makes curl exit non-zero on HTTP errors, catching 404s before they write a garbage file to disk. - Adding a new library: add a version variable, add a `vendor-` recipe, add it as a dependency of `vendor-js`. --- ## Importing in the root justfile ```just import 'justfiles/ci.just' import 'justfiles/assets.just' ``` --- ## Referencing vendor assets in Tera templates In `{% block head %}`, load vendored scripts **after** their host library: ```html ``` **Do not use** `` for a vendor asset unless you have verified the CSS file actually exists in the npm package (`curl -fsSI ` and check Content-Type). Many Cytoscape extensions ship JS-only and use inline styles; fetching a non-existent CSS produces a silent 404 that can confuse debugging. --- ## Verifying a new CDN asset before vendoring ```sh # 1. Check the package contents on npm curl -s "https://registry.npmjs.org//" | jq '.dist.unpackedSize, .dist.fileCount' # 2. Confirm the specific file exists (fail fast) curl -fsSI "https://cdn.jsdelivr.net/npm/@/.js" | head -3 # 3. Check auto-registration pattern (for Cytoscape extensions) curl -sL "https://cdn.jsdelivr.net/npm/@/.js" | tail -10 # Look for: if (typeof cytoscape !== 'undefined') { register(cytoscape); } # If absent, the extension requires manual registration: cytoscape.use(require('')) ``` --- ## Agent steps to apply this pattern 1. Read `justfiles/assets.just` if it exists; otherwise create it from the structure above. 2. Add the library version variable and `vendor-` recipe. 3. Add the new recipe as a dependency of `vendor-js`. 4. Run `just vendor-` to download the file. 5. Verify the downloaded file is valid JS: `head -3 assets/vendor/.js` — if the first line starts with "Couldn't" or "` with ``. 7. Remove any `` that referenced the same CDN package unless you have verified the CSS exists (step 2 of the verification section above). 8. Commit `assets/vendor/.js` and `justfiles/assets.just` together.