Skip to content

feat(admin): proof-of-concept admin UI behind ADMIN_UI_ENABLED flag#84

Merged
aaronbrethorst merged 2 commits into
mainfrom
feature/admin-interface-v2
Jun 6, 2026
Merged

feat(admin): proof-of-concept admin UI behind ADMIN_UI_ENABLED flag#84
aaronbrethorst merged 2 commits into
mainfrom
feature/admin-interface-v2

Conversation

@aaronbrethorst

@aaronbrethorst aaronbrethorst commented Jun 6, 2026

Copy link
Copy Markdown
Member

Summary

Continuation of #66 (original author: @Gitkbc), rebased cleanly onto current main and hardened for merge. Adds a server-rendered admin UI (dashboard, vehicles, users, trips, live map, login/signup) built with Go html/template, Bootstrap/Tailwind, and Leaflet, serving placeholder data for now.

  • Self-contained deployment: templates and static assets are embedded via //go:embed, parsed once at startup, and the Dockerfile now copies web/ so the embed resolves at build time (the original branch built fine locally but failed go build in Docker because web/ was never copied).
  • Disabled by default: the entire admin UI is gated behind ADMIN_UI_ENABLED (default off) with a loud startup warning, since the POC has no real auth — browser navigation can't carry the Bearer token the existing requireAuth expects, so wiring real session auth is deferred. Set ADMIN_UI_ENABLED=true to serve it locally.
  • Preserves the feat: add user-vehicle assignment CRUD endpoints  #60 user-vehicle assignment feature, which the original branch's merge had accidentally deleted.
  • Fail-fast template loading (returns an error, logged + os.Exit, instead of panicking at package init), and buffered rendering so a mid-render failure yields a clean 500 instead of a half-written 200.

Test plan

  • go build ./..., go vet ./..., gofmt -l . — all clean
  • go test ./... — green, including new admin_handlers_test.go (7 route renders, template loader, static-asset serving via embedded FS, unknown-view 500, buffered no-partial-200 contract, route wiring, and the ADMIN_UI_ENABLED gate)
  • Simulated the Docker build context (only the files the Dockerfile COPYs) — go build now succeeds; previously failed with pattern web/static: no matching files found
  • Booted the real server against Postgres with ADMIN_UI_ENABLED=true: all 7 /admin/* routes + /static/js/admin.js return 200, dashboard and Leaflet map render correctly in a browser (only console noise is the known Tailwind-CDN dev warning + a harmless favicon 404), unknown subpath 404s, existing /api/v1/admin/* routes still 401 without a token (no regression)
  • Verified flag-off default: /admin/* and /static/ return 404, /health stays 200

Review notes (non-blocking)

  • Package-global templates: the admin handlers read a package-level var templates, which diverges from the codebase's handleX(store) http.HandlerFunc constructor pattern. It's safe and documented here (routes are only registered after it's set), but when the admin UI graduates past POC and the handlers take real dependencies, they should move to the constructor/closure pattern. Left as-is to avoid premature churn on stub handlers.
  • CDN Tailwind + demo localStorage auth remain from the original POC. Acceptable while the UI is flag-gated off by default; both should be replaced (compiled CSS build; real session auth) before this is ever enabled in production.

Summary by CodeRabbit

  • New Features
    • Added an Admin UI with dashboard, map view, vehicles/users/trips pages, sidebar navigation, and unified layout/styles
    • Interactive in-browser map with mock fleet data and status indicators
    • Login and signup pages with demo client-side authentication and redirect to the dashboard
  • Tests
    • Added test coverage for admin UI templates, handlers, rendering behavior, and environment flag parsing
  • Chore
    • Docker build adjusted to include web assets (Admin UI is disabled by default)

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown

Too much diff to scan? Review this PR in Change Stack to start with the highest-impact changes.

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 22c55317-558f-4b82-b9d5-97b91f58e8ae

📥 Commits

Reviewing files that changed from the base of the PR and between 1bc9195 and 501f326.

📒 Files selected for processing (14)
  • Dockerfile
  • admin_handlers.go
  • admin_handlers_test.go
  • main.go
  • web/static/js/admin.js
  • web/templates/layout/base.html
  • web/templates/layout/header.html
  • web/templates/layout/sidebar.html
  • web/templates/views/dashboard.html
  • web/templates/views/login.html
  • web/templates/views/map.html
  • web/templates/views/trips.html
  • web/templates/views/users.html
  • web/templates/views/vehicles.html

📝 Walkthrough

Walkthrough

Embeds web templates/static into the binary, conditionally registers an unauthenticated Admin UI, adds buffered template rendering with error handling, implements seven admin/public page handlers with mock data, supplies client-side Leaflet map JS, and includes tests verifying parsing, handlers, errors, and static asset serving.

Changes

Admin UI Implementation

Layer / File(s) Summary
Build and asset embedding
Dockerfile, main.go
Docker build copies web/templates and web/static; main.go adds //go:embed and conditionally registers admin UI routes when ADMIN_UI_ENABLED is true.
Template loading and rendering
admin_handlers.go
loadTemplates() parses embedded admin views and public login.html; render() (with renderAdmin/renderPublic) executes templates into a buffer and writes clean 500 on errors.
Route and static asset wiring
admin_handlers.go
registerAdminUI() mounts /static/ from embedded files and registers GET routes for admin pages on the provided http.ServeMux.
Page handler implementations
admin_handlers.go
Seven exported handlers (AdminMapHandler, AdminLoginHandler, AdminSignupHandler, AdminDashboardHandler, AdminVehiclesHandler, AdminUsersHandler, AdminTripsHandler) render their views via centralized rendering and provide hard-coded example data.
Client-side Leaflet map
web/static/js/admin.js
Introduces mockVehicles/mockCorridors, makeMarkerIcon, buildPopup, and initMap() to draw corridors and vehicle markers with popups and update DOM counters; auto-initializes when #main-map exists.
Base layout and components
web/templates/layout/base.html, web/templates/layout/header.html, web/templates/layout/sidebar.html
Adds base.html with inline styles/Tailwind and Leaflet CSS, and new header and sidebar named templates used by admin pages.
Page view templates
web/templates/views/login.html, web/templates/views/map.html, web/templates/views/dashboard.html, web/templates/views/vehicles.html, web/templates/views/users.html, web/templates/views/trips.html
Adds page templates: login.html includes client-side dummy auth, map.html contains the map container/overlays, dashboard.html shows metrics and recent activity, and other views render tables for vehicles/users/trips.
Handler and rendering tests
admin_handlers_test.go
Tests validate template parsing, handler 200 responses and content, unknown-view and execution-error handling (clean 500), ADMIN_UI_ENABLED parsing, and that /static/js/admin.js is served.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Mux as http.ServeMux
  participant Handler as admin handler
  participant Render as render()
  participant Template as Go Template
  participant Buffer as bytes.Buffer
  participant Writer as http.ResponseWriter
  Client->>Mux: GET /admin/dashboard
  Mux->>Handler: dispatch to AdminDashboardHandler
  Handler->>Render: renderAdmin(w, data, "dashboard")
  Render->>Template: template.Execute(Buffer, data)
  alt Template Success
    Template->>Buffer: write rendered HTML
    Render->>Writer: w.WriteHeader(200)
    Render->>Writer: w.Write(Buffer.Bytes())
    Writer->>Client: 200 OK + HTML
  else Template Error
    Render->>Render: log error
    Render->>Writer: w.WriteHeader(500)
    Render->>Writer: w.Write("internal server error")
    Writer->>Client: 500 Clean Error
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a proof-of-concept admin UI that is gated behind an ADMIN_UI_ENABLED flag, which aligns with the extensive feature additions across multiple files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/admin-interface-v2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (1)
web/templates/layout/base.html (1)

15-255: ⚖️ Poor tradeoff

Consider extracting inline CSS to a separate stylesheet.

The 240-line inline CSS block increases the base template's cognitive load and makes styles harder to reuse or test. For a POC this is acceptable, but if the admin UI moves beyond proof-of-concept, extracting styles would improve maintainability.

♻️ Suggested approach

Create web/static/css/admin.css and move the <style> block contents there, then reference it:

<link rel="stylesheet" href="/static/css/admin.css">

This also enables browser caching across page navigations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/templates/layout/base.html` around lines 15 - 255, Move the large inline
<style> block in base.html into a new external stylesheet (e.g., admin.css) and
replace the inline block with a single <link rel="stylesheet"> tag in base.html;
copy all rules (including selectors like .glass-panel, .sidebar-shell,
.bus-marker, `@keyframes` pulse-ring, media queries, etc.) into that file and
ensure the template loads the new stylesheet so styles remain identical and
cacheable across page navigations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@admin_handlers.go`:
- Around line 148-154: AdminSignupHandler renders a signup form that posts to
"/api/v1/auth/signup" but no signup route is registered (only POST
"/api/v1/auth/login" exists), so either update the handler or add the missing
route: either change the "SignupEndpoint" value in AdminSignupHandler to an
existing backend route (e.g., reuse "/api/v1/auth/login" or another valid
endpoint) or register a corresponding signup handler in main (add a POST
"/api/v1/auth/signup" route and implement the signup logic). Locate
AdminSignupHandler and the POST "/api/v1/auth/login" registration in main.go to
make the endpoints consistent.

In `@web/templates/layout/base.html`:
- Line 14: The template currently includes the Tailwind Play CDN via the script
tag ("https://cdn.tailwindcss.com") which cannot be version-pinned or
SRI-protected; remove that script tag from web/templates/layout/base.html and
migrate to a build-time Tailwind setup: install tailwindcss in the project, add
Tailwind to your build pipeline (PostCSS/Vite/etc.), generate a
version-controlled static CSS file from your Tailwind config, and replace the
CDN script reference with a link to the built CSS asset; if you must keep the
Play CDN temporarily, clearly annotate in base.html that it is prototype-only
and not production-ready.
- Around line 9-14: The template base.html currently loads external assets
without SRI or safe hosting; update it to self-host Google Fonts (replace the
fonts.googleapis.com <link> and the font-family href with locally served font
files and a local CSS `@font-face` so you can compute and control integrity), add
integrity and crossorigin="anonymous" attributes for the Leaflet assets (the
<link> to leaflet.css and the <script> for leaflet.js — fetch the exact SRI
hashes from unpkg using the ?meta endpoint and apply them), and remove the
Tailwind Play CDN <script src="https://cdn.tailwindcss.com"> by building a
production Tailwind CSS file (versioned, served statically) and reference that
local stylesheet so you can compute and include an integrity hash for it. Ensure
all external assets kept have matching crossorigin attributes and that local
assets are served from your static pipeline with content-hashing to support
integrity and cache-busting.

In `@web/templates/views/login.html`:
- Line 10: The login page currently uses the unpinned Tailwind CDN URL
"https://cdn.tailwindcss.com" in a <script> tag; replace that unpinned URL with
a pinned release (for example the officially versioned CDN URL or a
jsdelivr/unpkg URL with @<version>) or serve a vendored/local build instead, and
when using a CDN include an SRI integrity attribute and crossorigin attribute;
make the change to the <script src=...> entry in this template so it matches the
pinned/secure approach used for the base layout.
- Line 49: The anchor with href="/admin/dashboard" and link text "Skip to
dashboard" currently bypasses auth; either remove this <a> entirely or make it
an explicit demo bypass by changing its label to something like "Demo: Skip to
dashboard" and add an explicit demo flag (e.g., append ?demo=true or add a
data-demo attribute) so server-side can treat it as non-production only; update
any UI/ARIA text to clearly indicate it is a demo bypass and ensure
server-side/dashboard routing ignores that flag in production.

In `@web/templates/views/map.html`:
- Around line 98-131: The route summary section is missing the element with id
"route-count" that admin.js expects to update; add id="route-count" to the route
count badge or create a distinct element with that id in the Active Routes card
so admin.js can find and update it (refer to the route summary block in map.html
and the update logic in admin.js that queries "`#route-count`"). Ensure the id is
placed on the visible count element (the rounded-full badge showing "4 routes")
and keep the existing classes/structure so styling and layout remain unchanged.
- Around line 10-27: The overlay uses hardcoded numbers but is missing the
element IDs the client expects; add IDs to the three numeric <p> elements so
admin.js can update them (assign id="fleet-active-count" to the Active count,
id="fleet-idle-count" to the Idle count, and id="fleet-total-count" to the Buses
count), and if admin.js also updates route counts ensure there is an element
with id="route-count" present in the template or add it where the route summary
belongs; optionally keep admin.js in sync to populate idle/total if those are
computed.

---

Nitpick comments:
In `@web/templates/layout/base.html`:
- Around line 15-255: Move the large inline <style> block in base.html into a
new external stylesheet (e.g., admin.css) and replace the inline block with a
single <link rel="stylesheet"> tag in base.html; copy all rules (including
selectors like .glass-panel, .sidebar-shell, .bus-marker, `@keyframes` pulse-ring,
media queries, etc.) into that file and ensure the template loads the new
stylesheet so styles remain identical and cacheable across page navigations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8a01197e-e947-42b7-a51d-1866ee0810b6

📥 Commits

Reviewing files that changed from the base of the PR and between 2c5a8b4 and 1bc9195.

📒 Files selected for processing (14)
  • Dockerfile
  • admin_handlers.go
  • admin_handlers_test.go
  • main.go
  • web/static/js/admin.js
  • web/templates/layout/base.html
  • web/templates/layout/header.html
  • web/templates/layout/sidebar.html
  • web/templates/views/dashboard.html
  • web/templates/views/login.html
  • web/templates/views/map.html
  • web/templates/views/trips.html
  • web/templates/views/users.html
  • web/templates/views/vehicles.html

Comment thread admin_handlers.go
Comment on lines +148 to +154
func AdminSignupHandler(w http.ResponseWriter, r *http.Request) {
renderPublic(w, "web/templates/views/login.html", map[string]interface{}{
"Title": "Create Account",
"Mode": "signup",
"LoginEndpoint": "/api/v1/auth/login",
"SignupEndpoint": "/api/v1/auth/signup",
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Wire the signup page to a real backend route or remove it for now.

Line 153 submits to /api/v1/auth/signup, but main.go only registers POST /api/v1/auth/login on Line 80 and no corresponding signup route exists on this mux. With ADMIN_UI_ENABLED=true, /admin/signup renders a flow that can only fail at submit time.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@admin_handlers.go` around lines 148 - 154, AdminSignupHandler renders a
signup form that posts to "/api/v1/auth/signup" but no signup route is
registered (only POST "/api/v1/auth/login" exists), so either update the handler
or add the missing route: either change the "SignupEndpoint" value in
AdminSignupHandler to an existing backend route (e.g., reuse
"/api/v1/auth/login" or another valid endpoint) or register a corresponding
signup handler in main (add a POST "/api/v1/auth/signup" route and implement the
signup logic). Locate AdminSignupHandler and the POST "/api/v1/auth/login"
registration in main.go to make the endpoints consistent.

Comment on lines +9 to +14
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>

<script src="https://cdn.tailwindcss.com"></script>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if SRI is used elsewhere in the codebase

rg -n "integrity=" --type=html -C2

Repository: OneBusAway/vehicle-positions

Length of output: 54


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="web/templates/layout/base.html"
if [ -f "$FILE" ]; then
  nl -ba "$FILE" | sed -n '1,80p'
else
  echo "Missing $FILE"
fi

Repository: OneBusAway/vehicle-positions

Length of output: 115


🏁 Script executed:

#!/bin/bash
# locate any external CDN links/scripts without integrity in this template
FILE="web/templates/layout/base.html"
rg -n '(https?://|//).*' "$FILE" || true

Repository: OneBusAway/vehicle-positions

Length of output: 574


🏁 Script executed:

#!/bin/bash
# check whether SRI is used anywhere besides integrity=
rg -n --hidden "subresource|integrity=|crossorigin=" --type=html -S . | head -n 50

Repository: OneBusAway/vehicle-positions

Length of output: 54


🏁 Script executed:

#!/bin/bash
# check if Tailwind CDN URL in base.html is versioned/pinned
FILE="web/templates/layout/base.html"
rg -n "tailwindcss\.com" "$FILE" || true

Repository: OneBusAway/vehicle-positions

Length of output: 127


🏁 Script executed:

#!/bin/bash
# check for Leaflet or unpkg usage
FILE="web/templates/layout/base.html"
rg -n "unpkg\.com/leaflet|leaflet@" "$FILE" || true

Repository: OneBusAway/vehicle-positions

Length of output: 232


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="web/templates/layout/base.html"
if [ -f "$FILE" ]; then
  nl -ba "$FILE" | sed -n '1,120p'
else
  echo "Missing $FILE"
fi

Repository: OneBusAway/vehicle-positions

Length of output: 115


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="web/templates/layout/base.html"
rg -n '(https?://|//)' "$FILE" || true

Repository: OneBusAway/vehicle-positions

Length of output: 574


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# global check: any SRI usage in templates
rg -n --hidden "integrity=" --type=html . || true

Repository: OneBusAway/vehicle-positions

Length of output: 54


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# global check: crossorigin with SRI (if present)
rg -n --hidden "crossorigin=" --type=html . || true

Repository: OneBusAway/vehicle-positions

Length of output: 54


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="web/templates/layout/base.html"
rg -n "tailwindcss\.com|unpkg\.com/leaflet|fonts\.googleapis\.com" "$FILE" || true

Repository: OneBusAway/vehicle-positions

Length of output: 502


🌐 Web query:

Can you use Subresource Integrity (SRI) with Google Fonts (fonts.googleapis.com/css2) and are SRI hashes provided/stable?

💡 Result:

You cannot use Subresource Integrity (SRI) with Google Fonts (fonts.googleapis.com/css2) because Google does not provide stable, static responses for their CSS API [1][2]. The primary reasons include: 1. Dynamic Responses: The Google Fonts CSS API serves different content based on the user's browser, operating system, and capabilities (e.g., support for woff2, unicode-range, or font-display) [1][3][4]. Because the content of the CSS file changes depending on the User-Agent requesting it, there is no single, stable hash that can represent the resource for all users [3][4]. 2. Lack of Guarantee: Google does not guarantee the stability of the CSS content [5]. They frequently update the responses to work around browser/OS quirks, optimize delivery, or introduce new features, meaning any hash you might generate would likely break as the service evolves [5][6]. 3. No Native SRI Support: Google explicitly does not support SRI for its Fonts CSS service [1][2]. If your security requirements necessitate the use of SRI, the recommended approach is to self-host the font files [1][6]. By downloading the fonts directly from the Google Fonts repository or using tools to capture the files, you can serve them from your own infrastructure (or a CDN you control) and apply static SRI hashes to those specific files [1][7]. This gives you full control over the content, ensuring it never changes unexpectedly [1].

Citations:


🌐 Web query:

Does unpkg support SRI for specific versioned files (e.g., leaflet@1.9.4 dist leaflet.css) and are integrity hashes obtainable?

💡 Result:

Yes, unpkg provides Subresource Integrity (SRI) hashes for files, which can be obtained programmatically [1][2]. While unpkg does not automatically inject SRI attributes into your HTML, you can easily retrieve the necessary integrity hashes by appending the?meta query parameter to the URL of any specific versioned file [1][3][4]. For example, to get the metadata—including the integrity hash—for leaflet version 1.9.4, you can request: https://unpkg.com/leaflet@1.9.4/dist/leaflet.css?meta This will return a JSON object containing the integrity field for that specific file [2][4]. You can then use this value in your script or link tags as follows: Note that because unpkg URLs for versioned files are permanent and immutable (npm does not allow republishing the same version), the integrity hash for a specific file remains consistent [5].

Citations:


🌐 Web query:

Tailwind CDN (cdn.tailwindcss.com) — can SRI be used without pinning to a specific version URL?

💡 Result:

Subresource Integrity (SRI) cannot be used with the Tailwind Play CDN (cdn.tailwindcss.com) because the CDN is not designed for production use, does not support SRI, and relies on dynamic, unversioned, or automatically redirected URLs that do not provide consistent, immutable files [1][2][3]. Subresource Integrity requires a fixed, cryptographic hash to verify that a file has not been altered [1]. The Tailwind Play CDN is intended solely for development purposes, prototyping, or creating minimal reproductions [2][3][4]. Because the CDN script dynamically serves content or redirects to various versions, it does not offer the stability or immutable file structure required to maintain a valid SRI hash [1][5]. Tailwind Labs explicitly advises against using the Play CDN in production environments [2][3][4]. For production, you should use a standard build process (such as the Tailwind CLI, Vite, or PostCSS plugins), which generates a static, compiled CSS file [2][4]. You can then host that static file on your own infrastructure or a CDN of your choice where you can implement SRI by generating and pinning the appropriate integrity hash for your specific, immutable file [6][2].

Citations:


Add integrity protections for CDN assets in base.html (SRI where feasible, self-host otherwise)

  • web/templates/layout/base.html loads Google Fonts from fonts.googleapis.com without SRI; SRI isn’t supported for the Google Fonts CSS API—self-host the font files to enable integrity checks.
  • web/templates/layout/base.html loads Leaflet 1.9.4 without SRI (leaflet.css near lines 12 and leaflet.js near line 276); add integrity + crossorigin="anonymous" (hashes can be obtained from unpkg via ?meta).
  • web/templates/layout/base.html uses Tailwind’s Play CDN (https://cdn.tailwindcss.com), which isn’t production/SRI-friendly—replace it with a built, static, versioned asset you can hash and serve.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/templates/layout/base.html` around lines 9 - 14, The template base.html
currently loads external assets without SRI or safe hosting; update it to
self-host Google Fonts (replace the fonts.googleapis.com <link> and the
font-family href with locally served font files and a local CSS `@font-face` so
you can compute and control integrity), add integrity and
crossorigin="anonymous" attributes for the Leaflet assets (the <link> to
leaflet.css and the <script> for leaflet.js — fetch the exact SRI hashes from
unpkg using the ?meta endpoint and apply them), and remove the Tailwind Play CDN
<script src="https://cdn.tailwindcss.com"> by building a production Tailwind CSS
file (versioned, served statically) and reference that local stylesheet so you
can compute and include an integrity hash for it. Ensure all external assets
kept have matching crossorigin attributes and that local assets are served from
your static pipeline with content-hashing to support integrity and
cache-busting.

<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>

<script src="https://cdn.tailwindcss.com"></script>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

What is the latest stable version of Tailwind CSS and does it provide an official CDN URL with SRI support?

💡 Result:

As of June 6, 2026, the latest stable version of Tailwind CSS is 4.3.0 [1][2]. Regarding the CDN, Tailwind CSS provides a "Play CDN" [3][4], which is intended strictly for development and prototyping purposes, not for production use [3][4]. Because the Play CDN is a dynamic, runtime-compiled tool [3][5], it does not provide an official CDN URL with Subresource Integrity (SRI) support [6]. Tailwind Labs has historically discouraged using CDN builds for production, recommending instead that developers use the official CLI to generate a static, optimized CSS file [3][5].

Citations:


🌐 Web query:

Does https://cdn.tailwindcss.com support pinning a Tailwind version via a query parameter (e.g., ?v=...)? And is there any guidance on using Subresource Integrity (integrity=...) with Tailwind’s Play CDN?

💡 Result:

The Tailwind CSS Play CDN (https://cdn.tailwindcss.com) is designed for development and prototyping purposes only and is not intended for use in production environments [1][2][3]. Version Pinning The Play CDN does not support standard semver-based version pinning via URL query parameters (e.g.,?v=...). The official guidance for pinning versions is to use the standard Tailwind CLI or a build tool like Vite [1][4]. For production, Tailwind Labs explicitly recommends avoiding the Play CDN, as it is a zero-runtime tool that dynamically generates styles, which is not optimized for production performance or stability [1][5]. Subresource Integrity (SRI) There is no official support or guidance for using Subresource Integrity (SRI) with the Play CDN [5]. Because the CDN script acts as a runtime engine that may dynamically load additional logic or configurations—and is not a static CSS file—it does not provide SRI hashes [1][5]. Tailwind Labs has previously noted that implementing SRI for such dynamic CDN builds is non-trivial and does not align with their recommendation to move away from CDN-based builds for production applications [5]. If you require production-grade stability, reproducibility, and security (including the ability to use SRI), the recommended approach is to install Tailwind CSS as a dependency via npm, yarn, or pnpm and use the Tailwind CLI to generate a static, version-controlled CSS file [1][4].

Citations:


Replace Tailwind Play CDN with build-time, versioned Tailwind CSS (Play CDN can’t be pinned or SRI’d).

  • https://cdn.tailwindcss.com (“Play CDN”) is intended for development/prototyping, not production use.
  • Play CDN doesn’t support standard version pinning via URL query params, and there’s no official way to provide SRI integrity for the script.
📦 Recommended solutions

Option 1 (recommended): Move to build-time Tailwind

  • Install tailwindcss (and set up PostCSS/Vite/etc. as your build uses)
  • Generate a version-controlled static CSS asset
  • Remove the CDN script tag and reference the built CSS

Option 2 (temporary): Keep Play CDN only as a clearly documented prototype-only measure (no SRI, no version pinning).

If ADMIN_UI_ENABLED is being planned to default on, prioritize the build-time migration first.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/templates/layout/base.html` at line 14, The template currently includes
the Tailwind Play CDN via the script tag ("https://cdn.tailwindcss.com") which
cannot be version-pinned or SRI-protected; remove that script tag from
web/templates/layout/base.html and migrate to a build-time Tailwind setup:
install tailwindcss in the project, add Tailwind to your build pipeline
(PostCSS/Vite/etc.), generate a version-controlled static CSS file from your
Tailwind config, and replace the CDN script reference with a link to the built
CSS asset; if you must keep the Play CDN temporarily, clearly annotate in
base.html that it is prototype-only and not production-ready.

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Tailwind CDN unpinned - same issue as base.html.

This standalone page uses the same unpinned cdn.tailwindcss.com script. See the earlier comment on web/templates/layout/base.html line 14 for details and recommended fixes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/templates/views/login.html` at line 10, The login page currently uses the
unpinned Tailwind CDN URL "https://cdn.tailwindcss.com" in a <script> tag;
replace that unpinned URL with a pinned release (for example the officially
versioned CDN URL or a jsdelivr/unpkg URL with @<version>) or serve a
vendored/local build instead, and when using a CDN include an SRI integrity
attribute and crossorigin attribute; make the change to the <script src=...>
entry in this template so it matches the pinned/secure approach used for the
base layout.

<p class="text-xs font-semibold uppercase tracking-[0.28em] text-sky-700">Access</p>
<h2 class="mt-2 font-display text-3xl font-bold tracking-tight text-ink">Login or create a demo account</h2>
</div>
<a href="/admin/dashboard" class="rounded-full border border-line px-4 py-2 text-sm font-semibold text-slate-600 transition hover:border-slate-300 hover:text-slate-900">Skip to dashboard</a>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

"Skip to dashboard" link bypasses authentication entirely.

The link allows direct access to /admin/dashboard without any authentication, undermining even the demo auth flow. While this is a POC with client-side auth, the skip link makes the login page optional in a way that could confuse the auth model.

🔐 Recommended fix

Either remove the skip link entirely:

-        <a href="/admin/dashboard" class="rounded-full border border-line px-4 py-2 text-sm font-semibold text-slate-600 transition hover:border-slate-300 hover:text-slate-900">Skip to dashboard</a>

Or make it clearly marked as a demo bypass:

-        <a href="/admin/dashboard" class="rounded-full border border-line px-4 py-2 text-sm font-semibold text-slate-600 transition hover:border-slate-300 hover:text-slate-900">Skip to dashboard</a>
+        <a href="/admin/dashboard" class="rounded-full border border-amber-300 bg-amber-50 px-4 py-2 text-sm font-semibold text-amber-700 transition hover:border-amber-400">⚠️ Demo: Skip login</a>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<a href="/admin/dashboard" class="rounded-full border border-line px-4 py-2 text-sm font-semibold text-slate-600 transition hover:border-slate-300 hover:text-slate-900">Skip to dashboard</a>
Suggested change
<a href="/admin/dashboard" class="rounded-full border border-line px-4 py-2 text-sm font-semibold text-slate-600 transition hover:border-slate-300 hover:text-slate-900">Skip to dashboard</a>
<a href="/admin/dashboard" class="rounded-full border border-amber-300 bg-amber-50 px-4 py-2 text-sm font-semibold text-amber-700 transition hover:border-amber-400">⚠️ Demo: Skip login</a>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/templates/views/login.html` at line 49, The anchor with
href="/admin/dashboard" and link text "Skip to dashboard" currently bypasses
auth; either remove this <a> entirely or make it an explicit demo bypass by
changing its label to something like "Demo: Skip to dashboard" and add an
explicit demo flag (e.g., append ?demo=true or add a data-demo attribute) so
server-side can treat it as non-production only; update any UI/ARIA text to
clearly indicate it is a demo bypass and ensure server-side/dashboard routing
ignores that flag in production.

Comment on lines +10 to +27
<div class="pointer-events-none absolute left-4 top-4 z-[500]">
<div class="glass-panel pointer-events-auto inline-flex items-center gap-5 rounded-2xl border border-white/70 px-5 py-3 shadow-lg">
<div class="text-center">
<p class="display-font text-2xl font-bold text-slate-900">4</p>
<p class="text-[10px] font-semibold uppercase tracking-[0.18em] text-emerald-700">Active</p>
</div>
<div class="h-7 w-px bg-slate-200"></div>
<div class="text-center">
<p class="display-font text-2xl font-bold text-slate-900">2</p>
<p class="text-[10px] font-semibold uppercase tracking-[0.18em] text-amber-600">Idle</p>
</div>
<div class="h-7 w-px bg-slate-200"></div>
<div class="text-center">
<p class="display-font text-2xl font-bold text-slate-900">6</p>
<p class="text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500">Buses</p>
</div>
</div>
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Stats overlay missing element IDs for JavaScript updates.

The stats overlay displays hardcoded values ("4 Active, 2 Idle, 6 Buses"), but admin.js attempts to dynamically update #fleet-active-count and #route-count elements that don't exist in this template. This creates a contract mismatch where the client-side code cannot update the displayed counts.

🔧 Recommended fix

Add the missing element IDs to enable dynamic updates:

       <div class="glass-panel pointer-events-auto inline-flex items-center gap-5 rounded-2xl border border-white/70 px-5 py-3 shadow-lg">
         <div class="text-center">
-          <p class="display-font text-2xl font-bold text-slate-900">4</p>
+          <p id="fleet-active-count" class="display-font text-2xl font-bold text-slate-900">4</p>
           <p class="text-[10px] font-semibold uppercase tracking-[0.18em] text-emerald-700">Active</p>
         </div>
         <div class="h-7 w-px bg-slate-200"></div>
         <div class="text-center">
-          <p class="display-font text-2xl font-bold text-slate-900">2</p>
+          <p id="fleet-idle-count" class="display-font text-2xl font-bold text-slate-900">2</p>
           <p class="text-[10px] font-semibold uppercase tracking-[0.18em] text-amber-600">Idle</p>
         </div>
         <div class="h-7 w-px bg-slate-200"></div>
         <div class="text-center">
-          <p class="display-font text-2xl font-bold text-slate-900">6</p>
+          <p id="fleet-total-count" class="display-font text-2xl font-bold text-slate-900">6</p>
           <p class="text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500">Buses</p>
         </div>
       </div>

You may also need to update admin.js to populate #fleet-idle-count and #fleet-total-count if they're computed values.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div class="pointer-events-none absolute left-4 top-4 z-[500]">
<div class="glass-panel pointer-events-auto inline-flex items-center gap-5 rounded-2xl border border-white/70 px-5 py-3 shadow-lg">
<div class="text-center">
<p class="display-font text-2xl font-bold text-slate-900">4</p>
<p class="text-[10px] font-semibold uppercase tracking-[0.18em] text-emerald-700">Active</p>
</div>
<div class="h-7 w-px bg-slate-200"></div>
<div class="text-center">
<p class="display-font text-2xl font-bold text-slate-900">2</p>
<p class="text-[10px] font-semibold uppercase tracking-[0.18em] text-amber-600">Idle</p>
</div>
<div class="h-7 w-px bg-slate-200"></div>
<div class="text-center">
<p class="display-font text-2xl font-bold text-slate-900">6</p>
<p class="text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500">Buses</p>
</div>
</div>
</div>
<div class="pointer-events-none absolute left-4 top-4 z-[500]">
<div class="glass-panel pointer-events-auto inline-flex items-center gap-5 rounded-2xl border border-white/70 px-5 py-3 shadow-lg">
<div class="text-center">
<p id="fleet-active-count" class="display-font text-2xl font-bold text-slate-900">4</p>
<p class="text-[10px] font-semibold uppercase tracking-[0.18em] text-emerald-700">Active</p>
</div>
<div class="h-7 w-px bg-slate-200"></div>
<div class="text-center">
<p id="fleet-idle-count" class="display-font text-2xl font-bold text-slate-900">2</p>
<p class="text-[10px] font-semibold uppercase tracking-[0.18em] text-amber-600">Idle</p>
</div>
<div class="h-7 w-px bg-slate-200"></div>
<div class="text-center">
<p id="fleet-total-count" class="display-font text-2xl font-bold text-slate-900">6</p>
<p class="text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500">Buses</p>
</div>
</div>
</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/templates/views/map.html` around lines 10 - 27, The overlay uses
hardcoded numbers but is missing the element IDs the client expects; add IDs to
the three numeric <p> elements so admin.js can update them (assign
id="fleet-active-count" to the Active count, id="fleet-idle-count" to the Idle
count, and id="fleet-total-count" to the Buses count), and if admin.js also
updates route counts ensure there is an element with id="route-count" present in
the template or add it where the route summary belongs; optionally keep admin.js
in sync to populate idle/total if those are computed.

Comment on lines +98 to +131
<!-- Route summary -->
<div class="table-card rounded-[20px] border border-white/80 p-4">
<h3 class="display-font mb-3 text-sm font-bold text-slate-900">Active Routes</h3>
<div class="space-y-2">
<div class="flex items-center justify-between rounded-xl bg-slate-50/80 px-3 py-2.5">
<div>
<p class="text-sm font-semibold text-slate-900">Route A</p>
<p class="text-xs text-slate-400">CBD &rarr; Westlands</p>
</div>
<span class="rounded-full bg-emerald-50 px-2.5 py-0.5 text-xs font-semibold text-emerald-700">2 buses</span>
</div>
<div class="flex items-center justify-between rounded-xl bg-slate-50/80 px-3 py-2.5">
<div>
<p class="text-sm font-semibold text-slate-900">Route B</p>
<p class="text-xs text-slate-400">Ngong Rd &rarr; City</p>
</div>
<span class="rounded-full bg-amber-50 px-2.5 py-0.5 text-xs font-semibold text-amber-700">2 buses</span>
</div>
<div class="flex items-center justify-between rounded-xl bg-slate-50/80 px-3 py-2.5">
<div>
<p class="text-sm font-semibold text-slate-900">Route C</p>
<p class="text-xs text-slate-400">Thika Rd &rarr; CBD</p>
</div>
<span class="rounded-full bg-emerald-50 px-2.5 py-0.5 text-xs font-semibold text-emerald-700">1 bus</span>
</div>
<div class="flex items-center justify-between rounded-xl bg-slate-50/80 px-3 py-2.5">
<div>
<p class="text-sm font-semibold text-slate-900">Route D</p>
<p class="text-xs text-slate-400">Eastlands &rarr; City</p>
</div>
<span class="rounded-full bg-emerald-50 px-2.5 py-0.5 text-xs font-semibold text-emerald-700">1 bus</span>
</div>
</div>
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Route summary missing element ID for JavaScript updates.

Similar to the stats overlay, the route count badge shows "4 routes" but lacks id="route-count", which admin.js attempts to update. Currently the JS safely handles this with if (routeCountEl), but the dynamic count won't display.

🔧 Quick fix

Add the missing ID to the route count heading or create a separate element:

-      <div class="table-card rounded-[20px] border border-white/80 p-4">
-        <h3 class="display-font mb-3 text-sm font-bold text-slate-900">Active Routes</h3>
+      <div class="table-card rounded-[20px] border border-white/80 p-4">
+        <div class="mb-3 flex items-center justify-between">
+          <h3 class="display-font text-sm font-bold text-slate-900">Active Routes</h3>
+          <span id="route-count" class="rounded-full bg-slate-100 px-2.5 py-0.5 text-xs font-semibold text-slate-600">4</span>
+        </div>
         <div class="space-y-2">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<!-- Route summary -->
<div class="table-card rounded-[20px] border border-white/80 p-4">
<h3 class="display-font mb-3 text-sm font-bold text-slate-900">Active Routes</h3>
<div class="space-y-2">
<div class="flex items-center justify-between rounded-xl bg-slate-50/80 px-3 py-2.5">
<div>
<p class="text-sm font-semibold text-slate-900">Route A</p>
<p class="text-xs text-slate-400">CBD &rarr; Westlands</p>
</div>
<span class="rounded-full bg-emerald-50 px-2.5 py-0.5 text-xs font-semibold text-emerald-700">2 buses</span>
</div>
<div class="flex items-center justify-between rounded-xl bg-slate-50/80 px-3 py-2.5">
<div>
<p class="text-sm font-semibold text-slate-900">Route B</p>
<p class="text-xs text-slate-400">Ngong Rd &rarr; City</p>
</div>
<span class="rounded-full bg-amber-50 px-2.5 py-0.5 text-xs font-semibold text-amber-700">2 buses</span>
</div>
<div class="flex items-center justify-between rounded-xl bg-slate-50/80 px-3 py-2.5">
<div>
<p class="text-sm font-semibold text-slate-900">Route C</p>
<p class="text-xs text-slate-400">Thika Rd &rarr; CBD</p>
</div>
<span class="rounded-full bg-emerald-50 px-2.5 py-0.5 text-xs font-semibold text-emerald-700">1 bus</span>
</div>
<div class="flex items-center justify-between rounded-xl bg-slate-50/80 px-3 py-2.5">
<div>
<p class="text-sm font-semibold text-slate-900">Route D</p>
<p class="text-xs text-slate-400">Eastlands &rarr; City</p>
</div>
<span class="rounded-full bg-emerald-50 px-2.5 py-0.5 text-xs font-semibold text-emerald-700">1 bus</span>
</div>
</div>
</div>
<!-- Route summary -->
<div class="table-card rounded-[20px] border border-white/80 p-4">
<div class="mb-3 flex items-center justify-between">
<h3 class="display-font text-sm font-bold text-slate-900">Active Routes</h3>
<span id="route-count" class="rounded-full bg-slate-100 px-2.5 py-0.5 text-xs font-semibold text-slate-600">4</span>
</div>
<div class="space-y-2">
<div class="flex items-center justify-between rounded-xl bg-slate-50/80 px-3 py-2.5">
<div>
<p class="text-sm font-semibold text-slate-900">Route A</p>
<p class="text-xs text-slate-400">CBD &rarr; Westlands</p>
</div>
<span class="rounded-full bg-emerald-50 px-2.5 py-0.5 text-xs font-semibold text-emerald-700">2 buses</span>
</div>
<div class="flex items-center justify-between rounded-xl bg-slate-50/80 px-3 py-2.5">
<div>
<p class="text-sm font-semibold text-slate-900">Route B</p>
<p class="text-xs text-slate-400">Ngong Rd &rarr; City</p>
</div>
<span class="rounded-full bg-amber-50 px-2.5 py-0.5 text-xs font-semibold text-amber-700">2 buses</span>
</div>
<div class="flex items-center justify-between rounded-xl bg-slate-50/80 px-3 py-2.5">
<div>
<p class="text-sm font-semibold text-slate-900">Route C</p>
<p class="text-xs text-slate-400">Thika Rd &rarr; CBD</p>
</div>
<span class="rounded-full bg-emerald-50 px-2.5 py-0.5 text-xs font-semibold text-emerald-700">1 bus</span>
</div>
<div class="flex items-center justify-between rounded-xl bg-slate-50/80 px-3 py-2.5">
<div>
<p class="text-sm font-semibold text-slate-900">Route D</p>
<p class="text-xs text-slate-400">Eastlands &rarr; City</p>
</div>
<span class="rounded-full bg-emerald-50 px-2.5 py-0.5 text-xs font-semibold text-emerald-700">1 bus</span>
</div>
</div>
</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/templates/views/map.html` around lines 98 - 131, The route summary
section is missing the element with id "route-count" that admin.js expects to
update; add id="route-count" to the route count badge or create a distinct
element with that id in the Active Routes card so admin.js can find and update
it (refer to the route summary block in map.html and the update logic in
admin.js that queries "`#route-count`"). Ensure the id is placed on the visible
count element (the rounded-full badge showing "4 routes") and keep the existing
classes/structure so styling and layout remain unchanged.

Gitkbc and others added 2 commits June 5, 2026 19:32
Server-rendered admin interface (dashboard, vehicles, users, trips, live
map) built with Go html/template, Bootstrap/Tailwind, and Leaflet. Authored
by Chaitanya (#66); rebased onto current main and hardened for review.

- Embed templates and static assets via //go:embed so the binary is
  self-contained; Dockerfile now copies web/ so the embed resolves at build.
- Parse templates once at startup via loadTemplates(), which returns an error
  (logged + os.Exit) instead of panicking at package init.
- Buffer each render so a mid-render failure yields a clean 500 rather than a
  half-written 200 body.
- Dispatch templates by validated name; unknown views error instead of
  silently rendering the login page.
- Gate the entire admin UI behind ADMIN_UI_ENABLED (default off) with a
  startup warning, since the POC has placeholder data and no real auth.
- Preserve the user-vehicle assignment feature from #60 (the original branch's
  merge had deleted it).
- Add httptest coverage for the handlers, template loader, and route wiring.

Co-authored-by: Aaron Brethorst <aaron@brethorsting.com>
- Render templates by direct map lookup keyed on view name, dropping the
  embeddedTemplates.ExecuteTemplate dispatcher, the "_admin_template" sentinel
  key, adminViewName, and the per-request data-map clone (withAdminTemplate) —
  a layer of indirection and two runtime error paths, with no behavior change.
- Extract the admin-UI gate into adminUIEnabled() (strconv.ParseBool, so
  1/TRUE/t also enable it) — the load-bearing safety mechanism keeping the
  unauthenticated UI off by default — and cover it with a table test.
- Log the previously discarded buf.WriteTo error so a truncated response is
  visible server-side.
- Add tests: all 7 route renders, template loading, static asset serving,
  unknown-view 500, the buffered no-partial-200 contract, and route wiring.

Co-authored-by: Aaron Brethorst <aaron@brethorsting.com>
@aaronbrethorst aaronbrethorst force-pushed the feature/admin-interface-v2 branch from 1bc9195 to 501f326 Compare June 6, 2026 02:33
@aaronbrethorst aaronbrethorst merged commit a40ca64 into main Jun 6, 2026
2 of 3 checks passed
@aaronbrethorst aaronbrethorst deleted the feature/admin-interface-v2 branch June 6, 2026 02:35
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