← back

The Admin Dashboard I Didn't Trust to Screen-Share

#product-management#analytics#data-minimization#ai-pm#responsible-ai

An admin dashboard is a product for one user and a liability for everyone else, and that reframing changed what I built and what I turned off by default.

Today the Duskglow admin dashboard went from "deployed and I can see data" to "deployed and I trust what I see enough to screen-share it." Those two states are farther apart than they sound. The work that separated them is the kind of work that rarely shows up on a feature list but determines whether you can actually use what you shipped.

The Dashboard Is a Product for One User

When I scoped an admin dashboard two days ago, I treated it like a data surface: get the queries right, get the auth right, render the numbers. What I missed is that a dashboard is also a product, and the user is me. Every product decision you'd make for a user-facing feature applies: default states matter, safe-by-default matters, information architecture matters, the line between "useful now" and "useful in six months" matters.

I shipped a filter bug I didn't notice until I was actually using the dashboard: my own admin account and the test account were being filtered out at the source query, rather than excluded from the aggregates. The metrics were clean, but so was the user_activity table. I couldn't see my own flags. I couldn't verify the test account's behavior. I couldn't spot-check whether an exclusion had been set correctly because the excluded account simply wasn't there.

The fix was ten lines. The lesson was larger. An exclusion filter has a scope, and the scope has a default. Applying it everywhere is aggressive; applying it nowhere is useless. Usually the right move is to filter at the metric layer and preserve the raw layer, with a visual marker so you know which rows would have been excluded. I now have an "excluded" column that sorts to the top on one click. It took me having the dashboard in my hands to realize that was the shape I actually wanted.

Default States Are Policy

My second fix was more interesting. After attorney and security review confirmed there was no compliance or infrastructure risk to showing real user display names on the admin dashboard, my recruiter advisor flagged the remaining risk: screen-sharing. If I ever demonstrate the dashboard in an interview or post a screenshot to LinkedIn, real names on screen contradict the data minimization narrative the privacy case study spends three thousand words arguing for.

My first instinct was to add a masking toggle and default it off. One click before screen-sharing would cover the demo case, and normal admin work could proceed with real names visible. Reasonable, revealing the default bias: treat normal use as the norm and demos as the exception.

Then I flipped it. The cost of forgetting the toggle once is asymmetric. A leaked name in a recorded demo is not recoverable. A click to reveal names during normal admin work is negligible friction. Defaults are a policy statement, not a UX preference. If the failure mode of "forgot to toggle" is worse than the friction of "toggle every time," the default should be the safe state.

This is the same principle underlying pre-model crisis detection, allowlist-based RLS, and the data minimization architecture itself: make the safe choice the path of least resistance. It's cheap to do at design time and expensive to retrofit after an incident.

Metrics Are a Queue, Not a Wall

Scoping produced a longer list of metrics than I could build in a single session. The temptation with any dashboard project is to ship everything at once, because incremental dashboards feel underwhelming. That instinct is wrong.

Here's the list I locked in sequence this afternoon, after deferring the Tier 3 polish items:

Tier 1 (ship next): crisis detection panel (with timestamp, user ID, and detection source per event), recent feedback panel (last five rows), and an activation rate card. These are the metrics I actually need to see every time I open the dashboard. Crisis events are the safety signal. Feedback is the qualitative voice of the beta. Activation rate is the product-market fit proxy at current scale.

Tier 2 (ship after Phase 2 lands): average messages per entry (currently zero because the message_count column exists but isn't populated yet), Day 2 retention, personality tone distribution, organize feature usage rate. These metrics depend on infrastructure work that hasn't happened yet. Building them now would either require hacks or would ship with permanently zero values.

This is the move I'm trying to make more often: recognize when a metric's prerequisite is infrastructure rather than another metric, and queue accordingly. Shipping a card that reads "0.0" because the underlying data isn't populated doesn't help. It creates noise in the dashboard, and when the data finally populates you might not notice the signal change because you've already trained yourself to ignore the card.

The Session Expiry Problem

One hour of this session went to a 401 that looked like a 500. The dashboard returned "Failed to load dashboard data" (the generic fallback error), and the actual root cause was that my session had expired after hours of the tab being open. The Edge Function error handler pattern-matched against 403 and 429 specifically and fell through to a useless generic message for anything else.

Fix: add a 401 branch that says "session expired." But the diagnostic moment was worth recording. Error messages that hide the actual error class force users into hypothesis generation. A user seeing "Failed to load dashboard data" has no signal about whether the problem is network, auth, rate limit, or service failure. Each has a different remediation. A session error that's mislabeled as a generic failure costs the user time proportional to how long it takes them to consider auth as a possibility.

Error handling is information architecture under stress. The user has the least patience and the least cognitive bandwidth in the moment the error occurs. That's exactly when you need the most signal density in the message.

Mental Models

A dashboard is a product for one user. Default states, information architecture, and friction models apply to tools you build for yourself with the same weight as they do to user-facing features. Maybe more. You're the only person who'll ever catch the problems.

Defaults are policy. Every default is an asymmetric bet about which failure mode is worse, and when the unsafe default is unrecoverable (a leaked name, a missed compliance step, a safety bypass), the safe state wins even at the cost of daily friction.

Filter at the metric, preserve at the row. The right place to apply an exclusion filter is almost always the computed layer, not the source query. Raw data should stay visible with a marker; derived metrics should be clean. This pattern generalizes far beyond admin dashboards. It's the same discipline that separates audit logs from user-facing views.

Queue metrics by their infrastructure dependency, not their desirability. A metric whose underlying data doesn't exist yet is an invitation to noise. Ship the metrics whose prerequisites are met. Queue the rest against the infrastructure work that unblocks them.

Error messages are information architecture under stress. The user has the least patience at the moment of failure. That's when signal density matters most. Generic error fallbacks are a form of tax you're levying on every future debugging session.