Skip to content

feat(frontend): serve every library from the instance, and rework the About dialog - #178

Merged
frousselet merged 1 commit into
mainfrom
feat/offline-vendored-assets
Aug 28, 2026
Merged

feat(frontend): serve every library from the instance, and rework the About dialog#178
frousselet merged 1 commit into
mainfrom
feat/offline-vendored-assets

Conversation

@frousselet

Copy link
Copy Markdown
Owner

Summary

The interface loaded twelve front-end libraries from jsDelivr, unpkg and
d3js.org on every page. This serves all of them from the instance itself, from
the pins in core/dependencies.py, so an isolated deployment works and no third
party is told who browses a compliance platform. The pins were behind, so they
also move to their current releases.

While in the same registry, the About dialog is reworked into two columns and
now reports whether a newer release is published.

Related issue

n/a

Type of change

  • Bug fix
  • New feature
  • Refactor / tech debt
  • Documentation
  • Build / CI / tooling

Changes

  • No CDN. Each front-end library is declared in core/dependencies.py with
    the exact files it needs and their Subresource-Integrity digests.
    manage.py vendor_assets mirrors them into static/vendor/; the Docker build
    runs it, and a direct install fetches what is missing on its first launch.
    A download that does not match its digest is refused rather than written.
  • The mirror is generated, never committed (.gitignore), so a stale copy in
    git cannot outrank the registry. The two files vendored by hand (d3,
    dagre) are removed accordingly.
  • Templates load the files through {% static %} and carry no version of their
    own. Tests enforce the contract: the pinned version must appear in the URL the
    file is fetched from, every vendor/... path a template asks for must be a
    file something mirrors, and no template may name a CDN host.
  • Command surface: vendor_assets, --check (verify), --force (re-download
    after a bump), --print-hashes (produce the digests for a new pin).
  • Upgrades: Bootstrap Icons 1.13.1, htmx 2.0.10, Tom Select 2.6.2, DOMPurify
    3.4.14, Sortable 1.15.7, ECharts 5.6.0. ECharts stays on 5.x - 6.0 is a major
    with breaking changes and deserves its own change.
  • About dialog: two columns (identity left, inventory right), no collapse, a
    close cross instead of a footer button. Entries read owner / name - version,
    ordered by owner then name, in columns read top to bottom.
  • Update check: the dialog says whether a newer release is published, read
    from the project's GitHub releases. Fires when the dialog opens, never on a
    page load; cached six hours across workers, failures cached too.
    UPDATE_CHECK_ENABLED=False switches it off.
  • Two new environment variables, both documented in .env.example and the
    generated settings reference: VENDOR_ASSETS_AUTO_DOWNLOAD,
    UPDATE_CHECK_ENABLED.

Screenshots

Captured against the running instance with the About dialog open:

  • Light and dark: two columns, identity centred on the left, inventory on
    the right with the three stacks sharing the same column split, no truncated
    names.
  • Mobile (390px): stacks to one column, identity first, inventory in a single
    column below; the update pill fits on one line.

The dialog is not part of the docs/screenshots/ set, so no 2560x1440 capture
was added there.

Checklist

  • Tests added or updated, and pytest passes (3177 passed)
  • ruff check passes
  • New feature exposed as MCP tools in mcp/tools.py (check_for_updates;
    list_dependencies description updated for the new owner field)
  • New feature has REST API endpoints under the app's api/
    (GET /api/v1/update-check)
  • UI renders correctly in light and dark mode
  • UI renders correctly on mobile
  • User-facing strings use {% trans %} with French translations (no
    duplicate msgid; compilemessages passes)
  • Lifecycle / workflow respected - n/a, no lifecycle touched
  • README.md updated
  • CHANGELOG.md updated under ## [Unreleased]
  • Brand guidelines respected; no em dash characters
  • Relevant spec updated (docs/specs/m0-accounts/README.md)
  • Project / documentation screenshots at 2560x1440 - n/a, the dialog is not
    in the documented set
  • Audit-grade rigor preserved

🤖 Generated with Claude Code

… About dialog

Three changes to what the interface loads and what it says about itself.

**No library is loaded from a CDN any more.** Bootstrap, Bootstrap Icons,
htmx, Tom Select, pell, DOMPurify, Sortable, ECharts, D3, dagre, Leaflet
and the GitLab Sans faces came from jsDelivr, unpkg and d3js.org on every
page. That made the interface unusable on an isolated network, announced
every visitor of a compliance platform to a third party, and put someone
else's uptime in the critical path of a login screen.

They are now declared in core/dependencies.py - the registry the About
modal already answered from - with the exact files each needs and their
Subresource-Integrity digests, and mirrored into static/vendor/ by
`manage.py vendor_assets`. The Docker build runs it, so the published
image needs no network; a direct install fetches whatever is missing on
its first launch and finds it in place on every later one. A download
that does not match its digest is refused rather than written. The
mirror is generated, never committed, so the pins cannot be outranked by
a stale copy in git - the two files vendored by hand are removed
accordingly.

Templates reference the files through {% static %} and carry no version
of their own, which is what lets the tests hold the registry to the
truth: the pinned version must appear in the URL the file is fetched
from, every vendor/... path a template asks for must be a file something
mirrors, and no template may name a CDN host at all.

The pins were behind, so they move to their current releases: Bootstrap
Icons 1.13.1, htmx 2.0.10, Tom Select 2.6.2, DOMPurify 3.4.14, Sortable
1.15.7, ECharts 5.6.0. ECharts stays on the 5.x line - 6.0 is a major
with breaking changes and deserves its own change.

**The About dialog is two columns**, the identity card on the left and
the component inventory on the right, with no collapse to open and a
close cross instead of a footer button. Each library reads
`owner / name - version`, the owner coming from its repository URL, and
they are ordered by owner then name so a publisher's libraries sit
together. The inventory is laid out in columns that read top to bottom.

**It also says whether a newer release exists**, read from the GitHub
releases of the project. The call fires when the modal opens, never on a
page load, and the answer is cached across workers for six hours;
failures are cached too, so a GitHub outage cannot turn the modal into a
five-second wait. `UPDATE_CHECK_ENABLED=False` switches it off for a
deployment whose policy forbids the instance calling out. Exposed at
`GET /api/v1/update-check` and as the `check_for_updates` MCP tool.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@frousselet
frousselet merged commit 1b9767e into main Aug 28, 2026
7 checks passed
@frousselet
frousselet deleted the feat/offline-vendored-assets branch August 28, 2026 10:27
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.

2 participants