heartwood every commit a ring

Bump frontend deps and polish README

6bbfaa3a by Isaac Bythewood · 2 days ago

Bump frontend deps and polish README

frontend/package.json: bootstrap 5.1 -> 5.3, chart.js 3.7 -> 4.5,
sass 1.53 -> 1.97. Refresh bun.lock to match.

property_graphs.js: replace chart.js v4-removed `drawBorder: false`
with `border: { display: false }`, and coerce doughnut labels to
String (fixes a layout error in v4 when a status code 200 comes
back as a number).

README.md: add Stack table, Configuration env-var table, Make targets
table; correct "node + npm" requirement (bun runs everything via
`bun run --bun`).

templates/properties/property_report.md: drop em dashes per style.

.gitignore: add /frontend/dist/.
modified .gitignore
@@ -2,6 +2,7 @@/dist//data//node_modules//frontend/dist//frontend/node_modules/.envdb.sqlite3
modified README.md
@@ -25,6 +25,22 @@ from the Django era can be migrated in via `./status migrate <django.sqlite3>`  embedded Typst, no chromium subprocess; markdown rendered from a template)## Stack| Concern         | Crate / Tool                                          ||-----------------|-------------------------------------------------------|| Web framework   | axum + tokio                                          || Database        | sqlx + SQLite (WAL, `synchronous=NORMAL`)             || Auth            | tower-cookies signed sessions                         || Template engine | minijinja                                             || HTTP client     | reqwest (rustls)                                      || Crawler         | scraper + html5ever + robotstxt + hickory-resolver    || Lighthouse      | `bun run --bun node_modules/.bin/lighthouse`          || Email           | lettre (direct-to-MX, opportunistic STARTTLS)         || PDF             | embedded Typst (`typst` + `typst-pdf` + `typst-kit`)  || Static assets   | Vite + Bun, Bootstrap 5, Chart.js, monaspace font     |## RequirementsYou need docker installed for a quick production start, or you can read the
@@ -33,9 +49,10 @@ You need docker installed for a quick production start, or you can read theFor local development:- rust (cargo) for the backend- bun for the frontend bundler (Vite)- node + npm only for the `lighthouse` npm CLI; bun doesn't run Lighthouse  correctly (bun issue #4958)- bun for everything JS: the frontend bundler (Vite) AND the `lighthouse` CLI.  The rust binary invokes lighthouse via `bun run --bun node_modules/.bin/lighthouse`,  which symlinks `node` → `bun` so the shim's `#!/usr/bin/env node` shebang  resolves to bun's runtime. No nodejs/npm required.- chromium for Lighthouse's own audits (PDF reports do not need chromium;  they go through embedded Typst)
@@ -50,6 +67,38 @@ Server boots, applies migrations, and starts the scheduler in-process. OpenURL.## ConfigurationAll config comes from `.env` (loaded via `dotenvy`):| Variable | Required | Purpose ||---|---|---|| `STATUS_PASSWORD` | yes | Single operator password || `BASE_URL` | yes for prod | Used in absolute URLs (sitemap, og tags, alert email links). No trailing slash || `PORT` | no (default `8000`) | HTTP listen port || `STATUS_COOKIE_SECRET` | no | 32+ bytes for signing the session cookie. Falls back to a SHA-512 of the password, so rotating the password invalidates sessions || `ALERT_EMAIL` | no | Recipient for outage / recovery emails. Leave unset to disable email || `DISCORD_WEBHOOK_URL` | no | Discord webhook for outage / recovery embeds. Leave unset to disable || `STATUS_DATA_DIR` | no (default `./data`) | Where the SQLite db lives. Production sets this to `/data` || `STATUS_ROOT` | no | Override the project root (where `templates/`, `dist/`, `migrations/` are read from) || `CHROMIUM_BIN` | no | Path to chromium for Lighthouse. Falls back to `PATH` lookup, then a `/opt/playwright-browsers/` glob |## Make targets| Target | What it does ||---|---|| `make run` (default) | Vite watch + `cargo run` on port 8000, plus the in-process scheduler || `make build` | Vite assets + release binary (`target/release/status`) || `make start` | Run the release binary (after `make build`) || `make pull` | rsync the production sqlite db from `git remote server` into `data/` || `make migrate FROM=<path-to-django.sqlite3>` | One-shot import of an existing Django status database, preserving Property UUIDs so public status URLs keep working. Add `FORCE=1` to wipe first || `make push` | `git push` to every configured remote || `make clean` | Remove `target/`, `dist/`, `frontend/node_modules/`, root `node_modules/`, and `data/` |There are no tests or linters configured.## Importing an existing Django status DBIf you have a SQLite from the Django version of this project, you can keep
modified frontend/bun.lock
@@ -6,12 +6,12 @@      "dependencies": {        "@fontsource/monaspace-argon": "^5.2.5",        "@popperjs/core": "^2.11.5",        "bootstrap": "^5.1.3",        "chart.js": "^3.7.1",        "bootstrap": "^5.3.3",        "chart.js": "^4.5.1",        "js-cookie": "^3.0.1",      },      "devDependencies": {        "sass": "^1.53.0",        "sass": "^1.97.3",        "vite": "^6.3.1",      },    },
@@ -71,6 +71,8 @@    "@fontsource/monaspace-argon": ["@fontsource/monaspace-argon@5.2.5", "", {}, "sha512-EJ+jq1Smm3BB+8RK/gwB1uzjrSKdycZkKm8OZGCYvqJiTChKcGe7b2lmj0PmQbb/+lAEdCBIAcFLlPaWhtRANA=="],    "@kurkle/color": ["@kurkle/color@0.3.4", "", {}, "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="],    "@parcel/watcher": ["@parcel/watcher@2.5.6", "", { "dependencies": { "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "node-addon-api": "^7.0.0", "picomatch": "^4.0.3" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.6", "@parcel/watcher-darwin-arm64": "2.5.6", "@parcel/watcher-darwin-x64": "2.5.6", "@parcel/watcher-freebsd-x64": "2.5.6", "@parcel/watcher-linux-arm-glibc": "2.5.6", "@parcel/watcher-linux-arm-musl": "2.5.6", "@parcel/watcher-linux-arm64-glibc": "2.5.6", "@parcel/watcher-linux-arm64-musl": "2.5.6", "@parcel/watcher-linux-x64-glibc": "2.5.6", "@parcel/watcher-linux-x64-musl": "2.5.6", "@parcel/watcher-win32-arm64": "2.5.6", "@parcel/watcher-win32-ia32": "2.5.6", "@parcel/watcher-win32-x64": "2.5.6" } }, "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ=="],    "@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.6", "", { "os": "android", "cpu": "arm64" }, "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A=="],
@@ -155,7 +157,7 @@    "bootstrap": ["bootstrap@5.3.8", "", { "peerDependencies": { "@popperjs/core": "^2.11.8" } }, "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg=="],    "chart.js": ["chart.js@3.9.1", "", {}, "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w=="],    "chart.js": ["chart.js@4.5.1", "", { "dependencies": { "@kurkle/color": "^0.3.0" } }, "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw=="],    "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
modified frontend/package.json
@@ -8,12 +8,12 @@  "dependencies": {    "@fontsource/monaspace-argon": "^5.2.5",    "@popperjs/core": "^2.11.5",    "bootstrap": "^5.1.3",    "chart.js": "^3.7.1",    "bootstrap": "^5.3.3",    "chart.js": "^4.5.1",    "js-cookie": "^3.0.1"  },  "devDependencies": {    "sass": "^1.53.0",    "sass": "^1.97.3",    "vite": "^6.3.1"  }}
modified frontend/static_src/properties/scripts/property_graphs.js
@@ -103,11 +103,13 @@ document.addEventListener("DOMContentLoaded", function () {      },      scales: {        x: {          grid: { color: accent.grid, drawBorder: false },          grid: { color: accent.grid },          border: { display: false },          ticks: { autoSkip: true, maxRotation: 25, font: tickFont, color: accent.ticks },        },        y: {          grid: { color: accent.grid, drawBorder: false },          grid: { color: accent.grid },          border: { display: false },          ticks: {            beginAtZero: true,            font: tickFont,
@@ -149,7 +151,7 @@ function buildDoughnut(canvasId, dataId) {  new Chart(ctx, {    type: "doughnut",    data: {      labels: data.map((d) => d.label),      labels: data.map((d) => String(d.label)),      datasets: [        {          data: data.map((d) => d.count),
modified templates/properties/property_report.md
@@ -6,7 +6,7 @@- Current status: **{{ property.current_status }}**- Average response: **{{ property.avg_response_time }} ms**- Recent uptime: {% if property.recent_uptime_pct is not none %}**{{ property.recent_uptime_pct }}%**{% else %}—{% endif %}- Recent uptime: {% if property.recent_uptime_pct is not none %}**{{ property.recent_uptime_pct }}%**{% else %}n/a{% endif %}- Total checks logged: **{{ property.total_checks }}**## Lighthouse
@@ -32,7 +32,7 @@ _No lighthouse data._## SEO insights{% if property.crawler_insights and property.crawler_insights | length > 0 %}{% for i in property.crawler_insights %}- [{{ i.severity }}] [{{ i.type }}] {{ i.issue }} — `{{ i.url }}`- [{{ i.severity }}] [{{ i.type }}] {{ i.issue }}: `{{ i.url }}`{% endfor %}{% else %}_No crawl results yet._