ontoref/reflection/templates/vendor-frontend-assets-prompt.md
Jesús Pérez da083fb9ec
Some checks failed
Nickel Type Check / Nickel Type Checking (push) Has been cancelled
Rust CI / Security Audit (push) Has been cancelled
Rust CI / Check + Test + Lint (nightly) (push) Has been cancelled
Rust CI / Check + Test + Lint (stable) (push) Has been cancelled
.coder/m
2026-03-29 00:19:56 +00:00

5.3 KiB

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 <url> 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

assets/
  vendor/          ← all vendored JS; served at {assets_route}/vendor/<file>
    <lib>@<ver>.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

# Frontend asset management
#
# Vendored JS libs live in assets/vendor/ and are served by the daemon
# at {assets_route}/vendor/<file>. 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-<lib>

[doc("Vendor <lib>")]
vendor-<lib>:
    mkdir -p assets/vendor
    curl -fsSL \
        "https://cdn.jsdelivr.net/npm/<lib>@{{LIB_VERSION}}/<lib>.js" \
        -o assets/vendor/<lib>.js
    @echo "vendored <lib>@{{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-<lib> recipe, add it as a dependency of vendor-js.

Importing in the root justfile

import 'justfiles/ci.just'
import 'justfiles/assets.just'

Referencing vendor assets in Tera templates

In {% block head %}, load vendored scripts after their host library:

<!-- Host library (CDN is acceptable for well-established, stable libs) -->
<script src="https://cdn.jsdelivr.net/npm/cytoscape@3.30.2/dist/cytoscape.min.js"></script>

<!-- Extension: vendored locally — loads after host, auto-registers on window.cytoscape -->
<script src="{assets_route}/vendor/cytoscape-navigator.js"></script>

Do not use <link rel="stylesheet"> for a vendor asset unless you have verified the CSS file actually exists in the npm package (curl -fsSI <url> 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

# 1. Check the package contents on npm
curl -s "https://registry.npmjs.org/<lib>/<version>" | jq '.dist.unpackedSize, .dist.fileCount'

# 2. Confirm the specific file exists (fail fast)
curl -fsSI "https://cdn.jsdelivr.net/npm/<lib>@<version>/<file>.js" | head -3

# 3. Check auto-registration pattern (for Cytoscape extensions)
curl -sL "https://cdn.jsdelivr.net/npm/<lib>@<version>/<file>.js" | tail -10
# Look for: if (typeof cytoscape !== 'undefined') { register(cytoscape); }
# If absent, the extension requires manual registration: cytoscape.use(require('<lib>'))

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-<lib> recipe.
  3. Add the new recipe as a dependency of vendor-js.
  4. Run just vendor-<lib> to download the file.
  5. Verify the downloaded file is valid JS: head -3 assets/vendor/<lib>.js — if the first line starts with "Couldn't" or "<!DOCTYPE", the download silently failed; re-run with curl -fsSL (the -f flag was likely missing).
  6. Update the Tera template: replace the CDN <script src="..."> with <script src="{assets_route}/vendor/<lib>.js"></script>.
  7. Remove any <link rel="stylesheet"> that referenced the same CDN package unless you have verified the CSS exists (step 2 of the verification section above).
  8. Commit assets/vendor/<lib>.js and justfiles/assets.just together.