-
Notifications
You must be signed in to change notification settings - Fork 0
Admin Logging Tab
Per-session in-memory ring buffer of every logFrontendEvent and ppTrack call, surfaced in the admin panel under Logging (/admin.html -> tab select -> Logging). Motivating pain point: debugging on mobile without remote devtools. Filed as #366.
Mobile debugging was a slow guessing loop. Symptoms (filter modal disappears, dropdown does nothing, checkbox stuck) came in via screenshots; nobody had visibility into what actually fired. console.log is invisible on a phone and Chrome remote devtools require a laptop plus USB plus a matching Chrome version.
This tab is the replacement. Every log call the frontend makes lands in a localStorage-backed ring, and the admin panel renders it as a filterable table. The bug that landed in c4c5e0301 (mobile filter modal premature-closes) was found this way in about ten minutes: enable Capture DEBUG, tap the X, look at the trace, see _handleOpenState decided isOpen=false firing 2ms before the X handler. Grep for who else strips .open. Done.
- Where the code lives
- How it captures
- The tab UI
- What every module in the codebase should push
- Debugging workflow that actually works
| Piece | Path |
|---|---|
| Ring buffer + subscribe API | js/lib/log-buffer.js |
| Analytics hook (every ppTrack call pushes) | js/lib/analytics.js |
Topbar hook (logFrontendEvent mirrors to buffer) |
js/lib/topbar.js |
| Tab component | js/admin/components/logging.js |
| Wiring into TAB_LOADERS | js/admin/main.js |
| Permission gate |
js/admin/permissions.js (logging: ['view_analytics']) |
| Tab option + section | admin.html |
| Load tag on every page |
<script type="module" src="js/lib/log-buffer.js?v=..."> in every HTML |
| Tests |
tests/logBuffer.test.js (16 cases, jsdom env) |
Ring size is 500 entries, oldest drops first. Backed by localStorage (not sessionStorage) so the admin tab in one browser tab sees logs from a game page in another tab -- same origin, same storage.
Capture level:
| Level | Default? | Kept? |
|---|---|---|
| DEBUG | no | Dropped unless the user turns on "Capture DEBUG" in the tab (persisted to localStorage['pp:loglevel'] for the session) OR appends ?loglevel=debug to any URL. |
| INFO | yes | Always captured. Everything the filter change handlers, refreshReports, and unusual state transitions push. |
| WARN | yes | Always captured. Failed fetches, cache misses, retryable errors. |
| ERROR | yes | Always captured. Rate-limited via a 60s per-signature cooldown so a tight loop cannot flood the buffer. |
Where the push happens:
-
logFrontendEvent(level, msg, ctx)injs/lib/topbar.js-- pushes to buffer AND fires the existingppTrack('log', ...) -> site_eventspipeline. -
window.ppTrack(eventType, metadata)injs/lib/analytics.js-- pushes to buffer BEFORE the Supabase fetch so a slow network cannot mask visibility. - Any module can push directly:
window.ppLogBuffer.pushLog('INFO', 'label', { source: 'my-module', ...ctx }).
Non-log ppTrack events (page_view, search_query, etc.) also land in the buffer, tagged with event_type in the ctx. That's how the tab's Module dropdown ends up populated with values like game-page:filter, page_view, search_query.
Toolbar:
- Level filter -- All / DEBUG / INFO / WARN / ERROR.
-
Module filter -- All modules or a specific
ctx.source/ctx.event_type/ctx.modulevalue. Falls back to(untagged). - Search msg + ctx -- substring search across the message and stringified ctx.
- Live tail -- default on, auto-scrolls to the bottom on every new entry.
-
Capture DEBUG -- flips capture level for the session (persists in
localStorage). - Copy JSON -- copies the filtered set to the clipboard. Paste into an issue for reports.
- Clear -- empties the ring for this session (confirmation prompt).
Table columns: Time, Level (color-coded), Module, Message + ctx (JSON pre-formatted below the message).
Meta line: <N> shown / <M> captured (session) — tells you at a glance how much your filter is narrowing the view.
The rule of thumb: any code path that does a non-trivial lookup, fallback, async operation, OR user interaction should log enough context to diagnose failures without a debugger. ~/.claude/rules/general-rules.md covers this in more detail. In practice for this project:
- Always tag the ctx with
source:{ source: 'module:name', ...ctx }. The Module dropdown pivots on this. - Log the outcome AND the branch that produced it (which API, which field name, which fallback fired).
- On failure, include enough IDs to find the row in Supabase (appId, report id, session).
- DEBUG for normal successes, INFO for lifecycle events (session start/stop, page view, filter interaction), WARN for unexpected states, ERROR for actual failures.
- Never log inside tight loops -- one summary line per logical operation.
Mobile-side (any bug reported on a phone):
- Reproduce the symptom in the phone browser (any tab, no remote devtools needed).
- Open
admin.htmlin a new tab in the same browser. - Tab select -> Logging. Enable Capture DEBUG if the events are likely at DEBUG level.
- Filter Module = the surface you touched (
game-page:filter,wireFilterPanelClose, etc.). - Read the trace top-to-bottom. Note the ORDER of events -- inverted order often points at a race (a2 firing before a1 means something happened between them that you didn't expect).
- Copy JSON, paste into the GitHub issue as the bug report.
Desktop-side (a bug caught while browsing):
- Same steps, minus the "in a new tab" part.
- Admin Deployments Tab -- companion for "is my push live yet"
-
~/.claude/rules/general-rules.md-- the debug-logging rule that governs what every code path should push