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

144 lines
5.3 KiB
Markdown

# 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
```text
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
```just
# 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
```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
<!-- 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
```sh
# 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.