MSCore Demo


Every panel on this page is rendered by real MSCore services: Go + HTMX, no client framework, no build step. The only JavaScript here is what makes the live counters and stream tick.

Go backend hx + no hydration ≤130ms pages SSE, server-pushed
Try the full demo
demo@mutesol.com · Demo2026!

Performance, measured live

Real navigation timings for the page you are looking at, plus how it stacks against a hydration-heavy SPA.

…
TTFB
…
Total load

Measured in the browser for this exact page — no observability agent, no client framework. Server-rendered HTML is already done.

MSCore (this page)~120ms
Typical React single-page app~800ms+ before render
Load performance
func main() {
	config.Init()
	sqlite.InitDB()
	app, core := InitApp()
	initEmailSystem()
	core.Scheduler.Start(context.Background())
	srv.ListenAndServe()
}
<span id="perf-ms" class="display-6 fw-bold text-success">—</span>
<script>
  const t0 = performance.now();
  window.addEventListener('load', () => {
    document.getElementById('perf-ms').textContent =
      Math.round(performance.now() - t0) + ' ms';
  });
</script>

Framework comparison

TTFB, JS payload, and time-to-interactive across five stacks. Switch metrics with the tabs; the MSCore TTFB bar reflects the actual measurement for this page.

MSCore + HTMX Rails + Turbo Django + HTMX Next.js (SSR) React SPA

Representative medians from public benchmarks and profiling; your stack and infrastructure will vary. MSCore numbers are measured on this page.


Metrics health dashboard

Runtime health cards polled from MSCore's in-process Collector: goroutines, heap, GC cycles, CPU. The sparkline draws goroutine history from the rolling snapshot buffer.

Loading metrics…
Metrics health dashboard
func (h *Handler) metricsPanel(ctx *xun.Context) error {
	if h.core.MetricsCollector == nil {
		return ctx.View(map[string]any{"MetricsDisabled": true})
	}
	sample, ok := h.core.MetricsCollector.Latest()
	return ctx.View(map[string]any{
		"Sample":    sample,
		"HasSample": ok,
		"Sparkline": h.core.MetricsCollector.Snapshot(),
	})
}
<div hx-get="/demo/panels/metrics"
     hx-trigger="load, every 30s [!document.hidden]"
     hx-swap="innerHTML">
  {{ if .HasSample }}
  <div class="metric-card">
    <span class="label">Goroutines</span>
    <span class="value">{{ .Sample.Goroutines }}</span>
  </div>
  {{ end }}
</div>

Real-time, server-pushed

A heartbeat from MSCore's eventbus, fanned out over SSE. This counter is live: it reflects everyone viewing this page right now.

Live visitor stream
func (h *Handler) streamHeartbeat(ctx *xun.Context) error {
	sub := h.core.EventBus.Hub.Subscribe(HeartbeatRoom)
	defer h.core.EventBus.Hub.Unsubscribe(HeartbeatRoom, sub.ID)
	for {
		select {
		case msg := <-sub.Ch:
			fmt.Fprintf(ctx.Response, "event: %s\ndata: %s\n\n",
				msg.Type, msg.Payload)
		case <-ctx.Request.Context().Done():
			return xun.ErrCancelled
		}
	}
}
<div hx-ext="sse" sse-connect="/demo/stream">
  <span class="badge bg-success-subtle text-success-emphasis">● LIVE</span>
  <span sse-swap="heartbeat" hx-swap="innerHTML"></span>
</div>

Activity feed

Anonymised server events pushed over a native EventSource connection with no HTMX SSE extension needed. Events are synthetic rotations of real activity types; in production they would come from ActivityService.Log.

● LIVE anonymised · rotating · 4s interval
    Activity feed (SSE)
    func (h *Handler) streamActivity(ctx *xun.Context) error {
    	sub := h.core.EventBus.Hub.Subscribe(ActivityRoom)
    	defer h.core.EventBus.Hub.Unsubscribe(ActivityRoom, sub.ID)
    	// hijack TCP for true streaming; chunked fallback if hijack unavailable
    	if hj, ok := ctx.Response.(http.Hijacker); ok {
    		conn, bufrw, _ := hj.Hijack()
    		defer conn.Close()
    		return activityLoop(ctx, bufrw, sub.Ch)
    	}
    	return activityChunked(ctx, sub.Ch)
    }
    <ul id="activity-list"></ul>
    <script>
    const es = new EventSource('/demo/panels/activity');
    es.addEventListener('activity', e => {
      const li = document.createElement('li');
      li.textContent = e.data;
      document.getElementById('activity-list').prepend(li);
    });
    </script>

    Content feed

    Blog posts and recorded sessions fetched over HTMX on load, with no initial payload in the page HTML, no hydration step, no client cache to invalidate.

    Loading content feed…
    Content feed
    func (h *Handler) content(ctx *xun.Context) error {
    	posts, _, err := h.core.ContentSvc.ListPosts(
    		ctx.Request.Context(), "blog", "", 6, true)
    	if err != nil {
    		ctx.WriteStatus(http.StatusInternalServerError)
    		return err
    	}
    	sessions, _, err := h.core.ContentSvc.ListSessions(
    		ctx.Request.Context(), "", "", 6, true)
    	if err != nil {
    		ctx.WriteStatus(http.StatusInternalServerError)
    		return err
    	}
    	return ctx.View(map[string]any{
    		"Posts": posts, "Sessions": sessions,
    	})
    }
    <div hx-get="/demo/panels/content"
         hx-trigger="load"
         hx-swap="innerHTML">
      Loading content feed…
    </div>

    Cursor pagination

    Each "Load more" click appends the next page in-place with no full reload, no offset drift, no duplicate rows. The cursor is opaque to the client.

    Loading posts…
    Cursor pagination
    func (h *Handler) contentPosts(ctx *xun.Context) error {
    	cursor := ctx.Request.URL.Query().Get("cursor")
    	posts, next, err := h.core.ContentSvc.ListPosts(
    		ctx.Request.Context(), "blog", cursor, 4, true)
    	if err != nil {
    		ctx.WriteStatus(http.StatusInternalServerError)
    		return err
    	}
    	return ctx.View(map[string]any{
    		"Posts": posts, "NextCursor": next,
    	})
    }
    <div id="post-list">
      {{ range .Posts }}
      <div class="card mb-2">{{ .Title }}</div>
      {{ end }}
    </div>
    {{ if .NextCursor }}
    <button hx-get="/demo/panels/posts?cursor={{ .NextCursor }}"
            hx-target="#post-list"
            hx-swap="beforeend">Load more</button>
    {{ end }}

    Scheduled release

    This is not a mock-up: the post below is a real draft in the demo tenant, seeded with a release time ~24h out. Microsoft's Scheduler auto-publishes the moment its clock hits publishAt.

    Draft
    Review
    Scheduled
    Published
    "" is drafted with a release time of — the Scheduler auto-publishes it, no cron.
    Scheduled release
    func (p BlogPost) IsScheduled() bool {
    	return !p.Published && p.PublishAt > 0
    }
    <h5>{{ .Title }}</h5>
    {{ if .Published }}
      <span class="badge bg-success">Published</span>
    {{ else if .IsScheduled }}
      <span class="badge bg-info text-dark">Scheduled</span>
      <span class="small text-muted ms-1"
            data-epoch="{{ .PublishAt }}" data-epoch-format="datetime">
      </span>
    {{ else }}
      <span class="badge bg-warning text-dark">Draft</span>
    {{ end }}

    Document engine

    Versioned key–value storage via NoteStore (Badger-backed). Each save creates an immutable MVCC version; the history dropdown restores any revision without touching the current one.

    Loading document…
    Document engine
    func (h *Handler) documentPanel(ctx *xun.Context) error {
    	const key = "demo:doc"
    	var content []byte
    	if v := ctx.Request.URL.Query().Get("version"); v != "" {
    		if ver, err := strconv.ParseUint(v, 10, 64); err == nil {
    			content, _ = h.core.NoteStore.ReadVersion(key, ver)
    		}
    	}
    	if len(content) == 0 {
    		content, _ = h.core.NoteStore.Read(key)
    	}
    	versions, _ := h.core.NoteStore.ListVersions(key)
    	return ctx.View(map[string]any{
    		"Content": string(content), "Versions": versions,
    	})
    }
    <div id="editor" class="ql-editor" readonly></div>
    <select hx-get="/demo/panels/document"
            hx-include="[name=version]"
            hx-target="#doc-panel"
            hx-swap="innerHTML"
            name="version">
      {{ range .Versions }}
      <option value="{{ .Version }}">v{{ .Version }}</option>
      {{ end }}
    </select>

    Media streaming via HTTP 206

    MSCore's media handler honours Range requests so browsers can seek recorded sessions without downloading the full file.

    When a browser requests a recorded session, the server returns HTTP 206 Partial Content so the player can seek without downloading the whole file. MSCore's media handler reads the Range header and streams only the requested bytes.

    Request
    Range: bytes=0-1048575
    Response
    HTTP/1.1 206 Partial Content
    Content-Range: bytes 0-1048575/52428800
    Accept-Ranges: bytes

    Open DevTools → Network → filter by Media to see the 206 responses as you seek.


    Authentication flow

    Visual walkthrough of MSCore's login sequence: credential lookup, OTP gate, JWT issuance, and the HttpOnly cookie handoff. All steps happen inside the same process with no external auth service.

    Browser MSCore Database POST /login GetByEmail Account row IssueToken (HS256) Set-Cookie: session 302 → /admin or /dash Subsequent requests GET /admin (cookie) ValidateToken → CtxAccount Handler (perm-gated) 200 page
    Auth flow
    // pkg/auth/handler.go — role-based redirect after login
    result, err := h.AuthService.Login(ctx.Request.Context(), bind.Data,
    	resolvedAgent.ID, ip, ctx.Request.UserAgent())
    if err != nil {
    	return hhttp.WriteHTMXError(ctx, h.SysLog, err, bind)
    }
    sessionAge := sessionCookieAge(result.Account.Settings.AutoLogout)
    cookies.SetSignedCookie(ctx, "session", result.Token.JWT, sessionAge)
    
    ret := "/dash"
    if result.Account.HasPermission("admin.read") { ret = "/admin" }
    if referer, err := url.Parse(ctx.RequestReferer()); err == nil {
    	if q := referer.Query().Get("return"); q != "" { ret = q }
    }
    ctx.Redirect(ret)
    <!-- Login form — POST targets the MSCore auth handler -->
    <form hx-post="/login" hx-target="this" hx-swap="outerHTML">
      <input type="email"    name="email"    class="form-control mb-2" required>
      <input type="password" name="password" class="form-control mb-3" required>
      <button type="submit" class="btn btn-primary w-100">Sign in</button>
    </form>
    <!-- Role-based redirect happens server-side — no JS needed -->

    Role-based access control

    Hover a role to see which permissions it holds. MSCore's RBAC layer checks these at the handler boundary with a single RequirePermission middleware call, no policy files.

    Hover a role to highlight its permissions. Roles are additive — assign multiple and permissions union automatically.

    Permissions
    content.read content.write files.read files.write admin.read admin.write support.read support.write
    Roles
    user content.read · files.read
    professional + content.write · files.write
    support + support.read · support.write
    admin all permissions
    RBAC
    // requestctx.CtxAccount.HasPermission — pkg/shared/requestctx/requestctx.go
    func (c *CtxAccount) HasPermission(p string) bool {
    	return slices.Contains(c.Permissions, p)
    }
    
    // Gate any handler with a one-liner:
    func getAdminUsers(ctx *xun.Context) error {
    	account, ok := requestctx.GetAccount(ctx)
    	if !ok || !account.HasPermission("admin.read") {
    		return ctx.WriteStatus(http.StatusForbidden)
    	}
    	// … serve the response
    }
    {{ $acc := .Data.account }}
    {{ if $acc.HasPermission "admin.read" }}
      <a href="/admin" class="nav-link">Admin panel</a>
    {{ end }}
    {{ if $acc.HasPermission "content.read" }}
      <a href="/dash/content" class="nav-link">Content</a>
    {{ end }}

    File access control

    Every file request runs a three-gate decision tree: authentication, permission, then the AccessChecker interface for per-bucket policy. Approved requests are served as HTTP 206 range responses.

    Every GET /files/{key} request passes through enforcePolicy. The decision tree runs in order — the first failing gate short-circuits and returns the appropriate HTTP status.

    GET /files/{key}
    ↓
    Public bucket? → 200 serve
    ↓ no
    RequireAuth? no session → 401
    ↓ authed
    RequirePermission? missing → 403
    ↓ permitted
    AccessChecker.CanAccess? false → 403
    ↓ allowed
    Serve file (206 if Range header present)
    AccessChecker interface
    type AccessChecker interface {
        CanAccess(ctx context.Context,
            accountID, bucket, key string,
        ) (bool, error)
    }
    
    // Default: NoOpAccessChecker (allows all authed)
    // Production: inject your own domain logic
    Bucket config
    BucketConfig{
        Name: "client-docs",
        Policy: BucketPolicy{
            RequireAuth:       true,
            RequirePermission: "files.read",
        },
    }
    File access control
    // pkg/files/handler.go — policy gate on every file request
    func (h *Handler) enforcePolicy(ctx *xun.Context,
    	cfg BucketConfig, key string) bool {
    	acc, _ := requestctx.GetAccount(ctx)
    	if acc == nil {
    		if cfg.Policy.RequireAuth {
    			ctx.WriteStatus(http.StatusUnauthorized); return false
    		}
    		return true // public bucket — no check
    	}
    	if cfg.Policy.RequirePermission != "" &&
    		!acc.HasPermission(cfg.Policy.RequirePermission) {
    		ctx.WriteStatus(http.StatusForbidden); return false
    	}
    	ok, _ := h.Access.CanAccess(ctx.Request.Context(), acc.ID, cfg.Name, key)
    	if !ok { ctx.WriteStatus(http.StatusForbidden) }
    	return ok
    }
    <!-- File links use standard hrefs — the server enforces the policy -->
    {{ range .Files }}
    <a href="/files/{{ .Key }}"
       class="list-group-item list-group-item-action">
      {{ .Key }}
      {{ if .Public }}<span class="badge bg-secondary">public</span>{{ end }}
    </a>
    {{ end }}

    Session timeout, your choice

    Fixed expiry or a sliding window: watch the difference. The button does a real POST /demo/ping through the same middleware chain your sessions use.

    Fixed expiry — 30 min, then you're out
    keeps draining no matter what you do
    Sliding window — every action buys 30 more minutes
    snaps back to full on every ping
    30:00 simulate use fires a real POST /demo/ping — the sliding bar resets, the fixed bar just keeps draining.
    Session timeout
    func sessionCookieAge(autoLogout string) int64 {
    	switch autoLogout {
    	case "0":
    		return 10 * 365 * 24 * 60 * 60
    	case "15":
    		return 15 * 60
    	case "30":
    		return 30 * 60
    	case "60":
    		return 60 * 60
    	case "120":
    		return 120 * 60
    	default:
    		return config.CookieExipiration / 1000
    	}
    }
    <span id="session-countdown" class="fw-bold font-monospace">30:00</span>
    <button hx-post="/session/ping?active=true"
            hx-swap="none">simulate click</button>

    Forgot password / OTP

    The full reset flow in one diagram: no enumeration on the request path, a one-time 6-digit code, and a fresh bcrypt hash.

    Visitor
    "I forgot my password"
    POST /request-reset
    RequestPasswordReset
    INSERT INTO otps
    6-digit code, 15 min TTL
    Email password_otp
    response is nil either way — no enumeration
    POST /reset-password
    validate OTP → bcrypt → fresh hash
    DELETE FROM otps
    redirect → /login
    Forgot password / OTP
    func (s *AuthService) RequestPasswordReset(ctx context.Context, emailAddr string) error {
    	acc, err := s.User.GetByEmail(ctx, emailAddr)
    	if err != nil {
    		return nil // always nil — no account enumeration
    	}
    	code, err := s.issueOTP(ctx, acc.ID, OTPPasswordReset)
    	if err != nil {
    		return nil
    	}
    	if err := s.Mailer.SendTemplate(s.SysLog, acc.Email, "Reset your password",
    		"password_otp", map[string]string{"name": acc.Name.Firstname, "otp": code}); err != nil {
    		s.SysLog.WithError(err).Error("auth: failed to send password reset email")
    	}
    	return nil
    }
    <form hx-post="/reset-password" hx-target="this" hx-swap="outerHTML">
      <input type="email"    name="email"    class="form-control mb-2"
             placeholder="Email" required>
      <input type="text"     name="otp"      class="form-control mb-2"
             placeholder="6-digit code" required>
      <input type="password" name="password" class="form-control mb-3"
             placeholder="New password" required>
      <button type="submit" class="btn btn-primary w-100">Reset Password</button>
    </form>

    Notification preferences

    Per-account preferences loaded live from UserSvc.GetNotifPrefs. Security change notifications are always on and locked; the panel shows the read-only state the demo account sees.

    Loading preferences…
    Notification preferences
    func (h *Handler) notifPrefsPanel(ctx *xun.Context) error {
    	acc, err := h.core.UserSvc.GetByEmail(ctx.Request.Context(), DemoEmail)
    	if err != nil {
    		return ctx.View(map[string]any{"PrefsUnavailable": true})
    	}
    	prefs, err := h.core.UserSvc.GetNotifPrefs(ctx.Request.Context(), acc.ID)
    	if err != nil {
    		return ctx.View(map[string]any{"PrefsUnavailable": true})
    	}
    	return ctx.View(map[string]any{"Prefs": prefs})
    }
    {{ if .PrefsUnavailable }}
    <p class="text-muted">Preferences unavailable.</p>
    {{ else }}
    <ul class="list-group">
      <li class="list-group-item d-flex justify-content-between">
        Product updates
        <span class="badge {{ if .Prefs.ProductUpdates }}bg-success{{ else }}bg-secondary{{ end }}">
          {{ if .Prefs.ProductUpdates }}On{{ else }}Off{{ end }}
        </span>
      </li>
      <li class="list-group-item d-flex justify-content-between text-muted">
        Security changes
        <span class="badge bg-secondary">Off (locked)</span>
      </li>
    </ul>
    {{ end }}

    Identity vs. capability tokens

    MSCore's /auth/me endpoint returns identity by default. With ?include=roles it appends a capability block; the same user, one extra query parameter, role-aware clients unlocked.

    Before — identity only
    {
      "id": "u_abc123",
      "email": "user@example.com",
      "username": "jane",
      "name": {
        "firstname": "Jane",
        "lastname": "Doe"
      }
    }
    After — identity + roles enrichment
    {
      "id": "u_abc123",
      "email": "user@example.com",
      "username": "jane",
      "name": {
        "firstname": "Jane",
        "lastname": "Doe"
      },
      "roles": {
        "user_id": "u_abc123",
        "role": [
          {
            "role_name": "professional",
            "permissions": [
              "content.read",
              "files.read"
            ]
          }
        ],
        "permissions": [
          "content.read",
          "files.read"
        ]
      }
    }

    One call to GET /api/v1/me returns the full role + permission tree. Mobile clients gate features locally — no extra /roles probe needed.

    Mobile API /me
    // pkg/auth/mobile_handler.go — identity + roles in a single call
    func (h *MobileHandler) getMe(ctx *xun.Context) error {
    	ctxAcc, ok := requestctx.GetAccount(ctx)
    	if !ok {
    		return hhttp.WriteJSONAppError(ctx, h.SysLog, apperr.Unauthorized(""))
    	}
    	acc, err := h.UserService.GetByID(ctx.Request.Context(), ctxAcc.ID)
    	if err != nil { return hhttp.WriteJSONAppError(ctx, h.SysLog, err) }
    
    	if roles, err := h.UserService.GetRolesByID(ctx.Request.Context(), acc.ID); err == nil {
    		acc.Roles = roles // full permissions union attached here
    	}
    	return hhttp.WriteJSON(ctx, http.StatusOK, MeResponse{
    		Account: h.UserService.ToAccountResponse(acc),
    	})
    }
    // Mobile client — single call returns identity + roles
    GET /api/v1/me
    Authorization: Bearer <jwt>
    
    // Response includes full role + permission tree:
    {
      "id": "u_123", "email": "user@example.com",
      "roles": {
        "role": [{ "role_name": "professional",
                   "permissions": ["content.read"] }],
        "permissions": ["content.read"]
      }
    }

    Timezone handling

    MSCore stores timestamps as raw UTC Unix milliseconds. The browser converts them to the viewer's local time. Pick any zone below to see the same epoch rendered differently with zero server round-trips.

    What the server stores
    // handler.go
    CreatedDate: …
    // int64 unix ms — no timezone

    The server never formats dates. It stores and returns raw millisecond epochs — int64. No timezone, no locale, no format string.

    What the browser shows
    Default:
    Datetime:
    Relative:

    Switching timezone updates every [data-epoch] on the page instantly — no server round-trip, no page reload.

    Timezone formatting
    // Server stores raw int64 unix ms — no formatting, no timezone.
    // epochs.js converts every [data-epoch] in the browser automatically.
    func (h *Handler) getPost(ctx *xun.Context) error {
    	post, err := h.core.ContentSvc.GetPost(ctx.Request.Context(), id)
    	if err != nil { return err }
    	return ctx.View(map[string]any{
    		"CreatedDate": post.CreatedDate, // int64 unix ms
    		"PublishAt":   post.PublishAt,   // int64 unix ms
    	})
    }
    <!-- epochs.js converts data-epoch automatically on DOM ready and HTMX swaps -->
    <span data-epoch="{{ .CreatedDate }}"></span>
    <span data-epoch="{{ .CreatedDate }}" data-epoch-format="datetime"></span>
    <span data-epoch="{{ .PublishAt }}"   data-epoch-format="relative"></span>
    <!-- Timezone selector updates all timestamps without a server round-trip -->
    <select onchange="window.epochsSetTZ(this.value)">
      <option value="">Auto (browser)</option>
      <option value="America/New_York">Eastern</option>
      <option value="Europe/London">London</option>
      <option value="Asia/Tokyo">Tokyo</option>
    </select>

    Scheduler status

    Live task table that refreshes every 30s while the tab is visible. All registered tasks, their intervals, last-run times, and error counts, served from the in-process MSCore Scheduler.

    Loading scheduler status…
    Scheduler status
    func (h *Handler) schedulerStatus(ctx *xun.Context) error {
    	return ctx.View(map[string]any{
    		"Tasks": h.core.Scheduler.Status(),
    	})
    }
    <div hx-get="/demo/panels/scheduler"
         hx-trigger="load, every 30s [!document.hidden]"
         hx-swap="innerHTML">
      {{ range .Tasks }}
      <tr>
        <td>{{ .Name }}</td>
        <td>{{ .LastRun }}</td>
        <td>{{ .Status }}</td>
      </tr>
      {{ end }}
    </div>

    Scheduler swim lanes

    Animated view of every registered background task. Lane width and pulse speed are proportional to each task's real interval, from the 4-second activity publisher to the 24-hour demo account reset.

    Each row is a registered task. The pulse animates on the task's real interval — offset so they don't all fire at once.


    Maintenance windows

    Active and upcoming windows are read from the cache (no DB query on the hot path). The lifecycle timeline below shows a window from creation to automatic close; bypass-listed roles see the site normally throughout.

    Checking maintenance status…

    Animated timeline showing a maintenance window lifecycle. The bypass list lets /health, /login, and API auth routes through even while the gate is active.

    🛠
    Created
    ⚡
    Active (503)
    ✓
    Auto-close
    🟢
    Operational
    Scheduled Window created in admin panel
    Always bypassed: /health · /login · /api/v1/auth · /admin · /webhooks · static assets
    Maintenance status
    func (h *Handler) maintenancePanel(ctx *xun.Context) error {
    	active, msg := h.core.MaintenanceSvc.IsActiveFromCache()
    	upcoming, _ := h.core.MaintenanceSvc.Upcoming(ctx.Request.Context())
    	return ctx.View(map[string]any{
    		"Active":   active,
    		"Message":  msg,
    		"Upcoming": upcoming,
    		"NowMs":    time.Now().UnixMilli(),
    	})
    }
    <div hx-get="/demo/panels/maintenance"
         hx-trigger="load, every 30s [!document.hidden]"
         hx-swap="innerHTML">
      {{ if .Active }}
      <div class="alert alert-warning">{{ .Message }}</div>
      {{ else }}
      <span class="text-success">No active maintenance</span>
      {{ end }}
    </div>

    Contact & support

    Send a message using the demo account via MSCore's SupportSvc.OpenTicket creates a real ticket in the database. The admin side of the support queue is visible after login.

    Loading…

    Color mode, without a round-trip

    Auto / light / dark is pure client-side: write theme to localStorage, flip data-bs-theme, done.

    Stored in localStorage.theme, applied to data-bs-theme on <html> — same mechanism that restyles every highlight.js block on this page.
    Color mode
    // Color mode is pure client-side — no Go handler needed.
    <select onchange="(function (v) {
      localStorage.setItem('theme', v);
      var a = v === 'auto'
        ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
        : v;
      document.documentElement.setAttribute('data-bs-theme', a);
    })(this.value)">
      <option value="auto">Auto</option>
      <option value="light">Light</option>
      <option value="dark">Dark</option>
    </select>

    See it behind a login

    The admin-side version of these panels runs on real accounts, real sessions and the full RBAC stack.

    Try the full demo