Imported from z3d/city-dashboard-brisbane (
AGENTS.md). Install upstream withnpx skills add z3d/city-dashboard-brisbane. Copyright stays with the author.
City Dashboard
Single-file city dashboard focused on Brisbane, deployed optionally as a Cloudflare Worker.
Commands
npm run dev- local Cloudflare Worker athttp://localhost:8787npm run deploy- deploy with Wranglernode scripts/check-ios12-compat.js- scanindex.htmlfor iOS 12-incompatible JavaScriptnode --check src/worker.js- syntax-check the Workernpm run validate- run the complete local validation suite- Or open
index.htmldirectly in a browser for features that do not require the Worker
Architecture
index.html # Single-file dashboard, source of truth for frontend
src/worker.js # Cloudflare Worker API proxy routes
public/index.html # Symlink to ../index.html; do not replace with a copy
iOS 12 Safari Compatibility
index.html must stay compatible with iOS 12 Safari:
- No arrow functions; use
function() {} - No
letorconst; usevar - No template literals
- No
for...of - No destructuring, spread, rest, optional chaining, or nullish coalescing
- No
Promise.allSettled,Object.entries, orArray.flat - Use
XMLHttpRequest, not frontendfetch() - Run
node scripts/check-ios12-compat.jsafter frontend JavaScript changes
Worker code in src/worker.js runs on Cloudflare's V8 runtime and may use modern JavaScript.
Data And Privacy
This public fork is intended to be generic. Do not add personal integrations, hardcoded device names, home proxy URLs, credentials, tokens, or private account identifiers.
The birthday feature must ship with birthdays: []. Names and dates are user-entered local settings and must never be added to source defaults or examples. Per-birthday timezones use the allowlisted IANA names in COMMON_TIMEZONES.
Before publishing or pushing, scan for secrets and identifying data:
rg -n "token|secret|password|api[_-]?key|email|github.com/.+/.+|workers.dev|AIza|sk-" .
git status --short
Keep .dev.vars, .env, .Codex/settings.local.json, .codex/settings.local.json, and .claude/ ignored.
Patterns
- Config is stored in
configand persisted to localStorage with thebrisbane_prefix. - DOM elements are cached in
elementsfrominitElements(). - New cards must be added to
DEFAULT_CARD_ORDER. - Card display modes use
xxxDisplayModewithcard,ticker,banner, orboth. - Escape all third-party API text at the HTML sink with
escapeHtml(); usesafeDomId()for API-derived element IDs. - Frontend XHRs inherit a 30-second timeout and same-URL GET deduplication. Worker upstream requests inherit a 20-second deadline. Preserve both wrappers.
- Needs Attention is a global smart strip controlled by
showNeedsAttentionandneedsAttentionMaxItems. It renders in#needsAttention, uses enabled feature toggles as eligibility, and builds alerts from cachedlast*Data/config without extra API fetches. It currently excludes bin and sports alerts, and only treats electricity as attention-worthy when prices are high. - Weather alert banners must never fire on the first feed snapshot. Re-baseline silently after a processing gap longer than 15 minutes so sleeping wall displays do not replay stale transitions when they wake.
/api/feed-healthis per-isolate, read-only observability populated by the central response wrapper. It must not write KV or add upstream requests.- Bin card conditional dismissal can sync through
GET/PUT /api/dashboard-statuswhen the worker has aSTATUS_KVorSETTINGS_KVbinding. It falls back to the existing localStorage keys:binTakenOutDatehides the pre-collection card until the bring-in window, whilebinDismissedDatehides the bring-in "Done" state. - Radar/Satellite conditional display depends on weather data:
bomRadarShowWhenRainyandradarShowWhenRainyuse weather codes or 50%+ rain chance.fetchWeather()must still run for these conditions even when the Weather card is off. - Module-level Worker caches are load reducers, not cross-isolate rate limiters. Quota-limited upstreams must use a shared KV cache.
DASHBOARD_TOKENis mandatory for every API route except/api/health, and authentication must keep usingtimingSafeEqual().- Open-Meteo timestamps should use
timeformat=unixtimeand parse withnew Date(timestamp * 1000). - Electricity prices use the Worker
/api/electricityroute. NEMWEB removed the oldGRAPH_5QLD1.csvfeed; parse the latestDispatchIS_Reports/PUBLIC_DISPATCHIS_*.zipand QLD1DISPATCH,PRICERRP instead. The card graph reads/api/electricity/history(AEMO 5MIN report, module cache only — a 5-min KV key would cost 288 puts/day), fetched at most every 5 minutes byfetchElectricityHistory(). - Every ticker slide in
buildTickerSlides()must carry acardId(ornewsIdxfor headlines): tapping a ticker/status-strip reading clones that card's.card-innerinto#cardPopupOverlay(showCardPopup), finance opens its spotlight.validate-project.jsfails on an untagged slide. - Ticker sparklines: a slide's optional
spark(plain numbers) andsparkClassrender throughtickerSparkHtml(). Finance uses/api/financehistory(a month of daily closes, which also supplies the day-change reference); fuel uses the Worker'sfuel_hist_*KV record (one point per 6h per station set, ~4 puts/day, skipped without a KV binding).
Adapting Another City
Fork the repo, then give it to an LLM coding agent with the target city's requirements. Ask it to update coordinates, transit APIs, waste collection data, radar/satellite defaults, electricity market, airport/flight bounding box, copy, docs, and deployment notes.
