Skip to content

feat(insights): make the page actionable — verdict banner, context, run stats - #635

Draft
mthines wants to merge 9 commits into
mainfrom
claude/insights-page-ux-ip9o7y
Draft

feat(insights): make the page actionable — verdict banner, context, run stats#635
mthines wants to merge 9 commits into
mainfrom
claude/insights-page-ux-ip9o7y

Conversation

@mthines

@mthines mthines commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Why

/insights (still flagged off) surfaced raw diagnostics with no "so what" — a failure count with nowhere to click, an "unattributed" client with no explanation, a truncated scope list with no way to see the rest, and a Runs ledger nobody could interpret. Separately, the page's health figures mixed in the dashboard's own page-loads as if they were agent traffic, and there was no way to see whether an agent's usage was trending better or worse over time.

A follow-up review of the assembled page found the deeper problem: it still read as instrumentation rather than as something that changes what a reader does next. Its headline figure could not move, its two leaderboards were dead ends, and turning the flag on stranded onboarding on a page nothing links to.

What changed

Making the page legible (first pass)

  • Add an at-a-glance HealthSummary verdict banner above the diagnostic panels, reusing the already-fetched usage summary
  • Make Friction/Who's-reading/Scope-consumption actionable: each failure names its dominant client+scope context, "unattributed" gets an explanatory tooltip, and the scope list expands instead of truncating
  • Surface each run's read/write/scope counts inline in RunsList instead of only after expanding, and reframe the section as an audit trail rather than a health signal
  • Exclude dashboard-originated calls from HealthSummary/UsageHealth (excludeDashboardReads) — browsing the Explorer yourself isn't "how your agents are actually using them"; AgentBreakdown's "who is reading" keeps the full set since showing that split is its job
  • Add a shared "Agent activity" range picker (24h/7d/30d/90d) driving HealthSummary/UsageHealth/AgentBreakdown from one fetch, plus week-over-week trend chips (healthTrend)

Making it actionable (review pass)

  • The verdict is two-dimensional now. healthVerdict grades reliability and readCoverage's records-per-read and reports the worse of the two, with the banner naming which drove it. Success rate alone never moved: usage_events.outcome is ok | cap_exceeded | rate_limited | permission_denied | error, none of which means "the agent asked for lore and got nothing" — an empty-scope read is ok. So a healthy account read "100% of calls succeeded" every day in the page's largest, first slot, while the signal a reader came for sat three columns into the section below. Coverage counts record-bearing tools only (memory.list/read/search), since memory.write/org.* are structurally record_count: 0; a window with no reads reports an absence ("no reads"), never a 0.0 that would read as failure.
  • healthTrend needs a real baseline — 20 calls in the previous window, not merely non-zero. One prior call against a thousand rendered "+99,900%".
  • Both leaderboards click through into the Explorer. A ScopeConsumption row links to /lore?scope=SCOPE, a HotColdLore row to /lore?scope=SCOPE&q=KEY. The unattributed row stays inert — it has no scope to narrow to, and linking it would show a different set of lore than the bar measures. Two encoding constraints: scope is deliberately not a ?filters= dimension, and the Explorer has no ?lesson= param (its detail sheet is local state), so scope + search is the closest honest target.
  • Insights inherits Overview's onboarding duties along with its home slot. The flag removes /overview from nav, and /overview was the only host of PendingInvitesBanner/OnboardingChecklist/GithubAppTeaser — and the only caller of buildOnboardingSteps({ autoGenerateToken: true }), which mints a brand-new user's first API token. A flag-on signup landed on an analytics page with no token, no setup instructions, and no way to find either. All four move onto insights/page.tsx, the root redirect points at /insights, and Insights takes the first nav slot so the rail matches the claim. All three surfaces self-hide once irrelevant, so an established account sees the page unchanged.
  • RangePicker takes a label, so the page's two independent radiogroups stop sharing one accessible name.
  • The banner and the Agent activity caption state that dashboard browsing is excluded rather than leaving it implicit.

How to verify

  • pnpm nx typecheck web && pnpm nx lint web && pnpm nx test web
  • cd packages/web && npx vitest run --config vitest.storybook.config.ts --changed=main

Visual baselines for HealthSummary and InsightsPage were regenerated (-u) for the banner's new two-figure layout; two new HealthSummary stories cover the coverage-driven verdict and the write-only window.


Generated by Claude Code

…tionable

Three concrete UX gaps on the flagged /insights page, addressed with data
already fetched (no new endpoint):

- Friction rows named only a tool+outcome+count with nowhere to go from
  there. failuresByToolOutcome now also reports the dominant (client,
  scope_type) context behind each failure, so "187 memory.read errors"
  reads as "mostly cli, branch (92%)" — a concrete place to go look.
- AgentBreakdown's "unattributed" client row had no explanation, unlike the
  identical bucket in ScopeConsumption. Added the same tooltip explaining
  why it exists and that it isn't actionable.
- ScopeConsumption's "+N more scopes" was inert label text with no way to
  actually see those scopes. It's now a toggle that expands the full list
  (scrollable past a point) and collapses back.

Regenerated the three touched visual baselines and added an interaction
test for the new expand/collapse behavior, which had no prior coverage.
…ats inline

Answers the page's core "so what" gap: HealthSummary reads the same /usage
fetch's pre-rolled summary + the existing top failure to render one
healthy/degraded/unhealthy verdict above the diagnostic panels, instead of
making every reader parse Friction/Latency/Coverage gaps to find out whether
anything is wrong.

Also surfaces each run's read/record/write/scope counts in the collapsed
RunsList row (previously visible only after expanding), and reframes the
Runs section's description as an audit trail rather than a health signal.
R2 Hoist Shared Constant: export TREND_WINDOW_DAYS from
lib/queries/dashboard.ts and have HealthSummary reference it instead of
hardcoding "62 days" twice — the duplicate prose literal could silently
drift from the real fetch window if it ever changes.

Applied via polish (code-quality simplify mode), confidence 97%, scoped
typecheck + lint clean, full web test suite green (95 files / 1351 tests).
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

The dashboard preview for this PR — redeployed on each push that changes the web app.

Project Deployment Actions Updated
lorekit Error Error Sep 2, 2026 8:12pm UTC

Warning

The preview deploy for d65facf failed — no preview is available.

The Vercel deployment did not complete. See the workflow logs for the CLI output.

A blocked deployment is most often the commit author's email not matching a GitHub account with project access. Fix the cause, then push again or comment /web-preview to retry. · Workflow logs

@dash0-dev

dash0-dev Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Reviewed your changes — no blocking issues, 0 findings. All 4 prior findings confirmed fixed; description now covers the review-pass work too.
Updated 2026-09-02 19:45 UTC

Review details

Reviewed for commit d65facf.

Gate Status Details
Description vs. code Body's new "review pass" section now covers the verdict/trend/leaderboard/onboarding delta.
Prior review feedback All 4 review threads resolved and re-verified fixed at current head.
Documentation docs/decisions.md gained 4 new rationale sections matching the delta 1:1.
Self-review signals No debug logs, leftover TODOs, or unreviewed stubs in the delta.
Code review No inline finding survived — delta is well-tested with matching edge-case coverage.

CI — All required checks green (Typecheck/Test/Lint, Storybook interaction+visual); Vercel preview deploy still pending (expected on a draft).

Run mode — full · 766 lines in delta

Memories — 323 indexed · 4 used

  • reviewer-comment-relevance::pr635-3915466899 — hex-color finding — confirmed still fixed, not re-raised
  • reviewer-comment-relevance::pr635-3915466910 — interface-name-collision finding — confirmed still fixed, not re-raised
  • reviewer-comment-relevance::pr635-3913395038 — RunsList accessible-name finding — confirmed still fixed, not re-raised
  • reviewer-comment-relevance::pr635-3913395031 — HealthSummary no-calls-state finding — confirmed still fixed, not re-raised

Quality — produced 0 → posted inline 0, cleared 0

Integrations — not activated

Optimality (2.4c) — ran — reviewed the two-dimensional verdict design and leaderboard click-throughs; no more-optimal alternative identified, 0 proposals

Standards (2.4d) — ran — packages/web/CLAUDE.md design-token + REST-only rules re-checked against the delta; 0 violations

Skipped files — none (14 screenshot PNGs reviewed as binary diffs only)

Reviewed by the pr-reviewer agent — open it to read how these gates and findings are produced.

@dash0-dev dash0-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 new non-blocking findings — see inline comments and the [sticky report](https://github.com//pull/635#issuecomment-5508511164).

Comment thread packages/web/src/components/insights/InsightsPage.tsx Outdated
Comment thread packages/web/src/components/settings/RunsList.tsx Outdated
InsightsPage gated the HealthSummary verdict banner on
usageByTool.length > 0, so HealthSummary's own tested "No calls
recorded" branch (totalCalls === 0) could never render in production —
a truly-empty account got no banner at all, unlike every other section
on the page. Drop the redundant length guard; HealthSummary already
owns isLoading/isError/data-gated rendering via its own totalCalls
check.

Also fixes the Storybook Empty story, which relied on the shared MSW
/usage fixture's nonzero summary.total_events (128) despite empty
by_tool — previously invisible because HealthSummary never rendered
there. Adds a zeroed /usage override so the story's "every section
renders its empty state" claim stays true, and regenerates the
affected screenshot baseline.

Addresses dash0-dev review comment on PR #635
(#635 (comment)).

Claude-Session: https://claude.ai/code/session_01VxLF67tz1z63us1ZuG4zfN
The reads/records/writes/scopes summary was nested inside the run
row's <button>, so screen readers announced all four stats on every
focus/tab alongside the session badge, correlation id, and timestamp
already in the button. Add aria-hidden to keep it visible but out of
the accessible name — the expanded <dl> already gives screen-reader
users a properly labelled equivalent of the same numbers, so nothing
is lost, only de-duplicated.

Addresses dash0-dev review comment on PR #635
(#635 (comment)).

Claude-Session: https://claude.ai/code/session_01VxLF67tz1z63us1ZuG4zfN
…ivity range picker with trend chips

Operational health, the verdict banner, and coverage/latency diagnostics on
/insights previously mixed in the reader's own page-loads alongside real
agent traffic, understating the true agent success rate — the Explorer's
read-activity cards already draw this "browsing isn't consumption" line
(migration 00054), Insights just never had it. HealthSummary/UsageHealth now
filter it out via excludeDashboardReads; AgentBreakdown's "who is reading"
keeps the full set since showing that split is its whole point.

Also adds a shared, bounded RangePicker (24h/7d/30d/90d) driving
HealthSummary/UsageHealth/AgentBreakdown together via one new hook
(useInsightsUsage, which also fetches the immediately preceding window), so
a reader can compare "is my agent reading better this week than last" via
week-over-week trend chips on the verdict banner, reusing the existing
TrendChip/pctChange infrastructure. ScopeConsumption keeps its own separate
window, as before.

No public-facing docs/llms.txt update: /insights isn't described in the
public MDX docs (dashboard-only UX refinement, no new MCP tool/REST
route/CLI command/config key).

Claude-Session: https://claude.ai/code/session_01VxLF67tz1z63us1ZuG4zfN

mthines commented Sep 2, 2026

Copy link
Copy Markdown
Owner Author

The Web — Vercel preview (changed) / Deploy Vercel preview check failed on 4cd98c0 with:

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/home/runner/work/lorekit/lorekit/scripts/ci/web-preview-comment.mjs'

Not this PR's failure — the actual Vercel deploy/alias succeeded; only the follow-up "post preview URL" step broke. This PR's base predated PR #636 ("Extract web-preview comment renderer to a testable module"), which added scripts/ci/web-preview-comment.mjs on main. Since pull_request-triggered workflows read the workflow YAML from main but this job checks out the PR head commit, the newer workflow definition referenced a script this branch didn't have yet.

Fix: merged origin/main into this branch (7ceea5c) to pick up the script. Clean merge, no conflicts. Re-validated typecheck/lint on the affected projects post-merge (0 errors).


Generated by Claude Code

Comment thread packages/web/src/components/dashboard/HealthSummary.tsx Outdated
Comment thread packages/web/src/lib/usage-health.ts Outdated
…ce to avoid name collision

Addresses two non-blocking dash0-dev review findings on commit 7ceea5c:
raw hex verdict-icon colors instead of the package's --color-* tokens,
and a HealthSummary interface sharing its name with the HealthSummary
React component that consumes it.

Claude-Session: https://claude.ai/code/session_01VxLF67tz1z63us1ZuG4zfN
… to go

A persona review of /insights found the page reads as instrumentation rather
than as something that changes what a user does next. Six fixes, all on the
existing surface:

- The verdict weighs COVERAGE as well as reliability and reports the worse of
  the two, naming which drove it. Success rate alone never moved: the
  `usage_events.outcome` vocabulary has no "found nothing" state, so an
  empty-scope read is `ok` and a healthy account read "100% of calls
  succeeded" every day in the page's first slot, while "agents are asking for
  lore that isn't there" sat three columns into the section below. Coverage
  counts record-bearing tools only, and a window with no reads reports an
  absence rather than a 0.0 that would read as failure.
- `healthTrend` needs 20 calls in the previous window, not merely non-zero. A
  one-call baseline rendered "+99,900%".
- Both leaderboards click through into the Explorer — `?scope=` for a scope
  row, `?scope=&q=` for a lesson. The unattributed row stays inert; it has no
  scope to narrow to.
- Insights inherits Overview's onboarding duties along with its home slot. The
  flag hid `/overview`, which was the only host of the pending-invite banner,
  the first-run checklist and the GitHub App teaser — and the only caller of
  the first-token mint. A flag-on signup landed on an analytics page with no
  token and no instructions.
- `RangePicker` takes a `label`, so the page's two radiogroups stop sharing one
  accessible name.
- The banner and the Agent activity caption say that dashboard browsing is
  excluded, rather than leaving it implicit.

Claude-Session: https://claude.ai/code/session_01VxLF67tz1z63us1ZuG4zfN
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

❌ The dashboard preview for d65facf failed to deploy (failure).

Open the run logs for the failure. Comment /web-preview to retry once it is fixed.

mthines commented Sep 2, 2026

Copy link
Copy Markdown
Owner Author

Web — Vercel preview (changed) is red on d65facf — not this PR's failure

Standing down on this check with no code change. What is failing, why it isn't this branch's, and the one patch I'd propose for the diagnostic gap it exposed.

What is failing

Web — Vercel preview (changed) / Deploy Vercel preview — specifically the Deploy Vercel preview step, which exits 124 after exactly 900032 ms: the composite action's DEPLOY_TIMEOUT: 15m on timeout … vercel deploy --prebuilt.

It is not part of the CI gate. CI Summary — the single required check — lists needs: [changes, check, plugin, integration, web-test, migration-order, deploy-scope, preview-filter, bench-tests, edge-typecheck], and web-preview is deliberately absent. CI Summary is green on this head, as are Detect changes, Typecheck, Test & Lint (affected) and Web — Storybook interaction + visual tests (changed).

Why it isn't this PR's

  1. The build succeeds. The runner-side vercel build completes — ✓ Generating static pages (51/51), Build Completed in .vercel/output, /insights at 16.2 kB / 339 kB First Load JS. Only the prebuilt upload's polling never reaches Ready. Once .vercel/output exists on disk, application code has no remaining influence on whether Vercel marks the deployment Ready.
  2. The same author deployed cleanly 5× on this branch, most recently c1bfff5 at 15:04 UTC with a 2-minute deploy step. Every commit here is authored Claude <noreply@anthropic.com>, so the workflow's own hint — "the commit author's email can't be matched to a GitHub account with access to the project" — is refuted by this branch's own history.
  3. 4cd98c0's earlier red was a different step (Comment preview URL on the PR), with the deploy itself green in 2m21s. Different failure, not a recurrence.
  4. The same signature exists on an unrelated PR. web-preview.yml run 479 — 2026-09-01, PR Hide branch scopes from the Explorer's scope picker #629 — failed identically at ~17 minutes.

The re-run was spent, and it failed the same way

Run 33676149673 (workflow_dispatch of web-preview.yml, force: true, redeploying the unchanged head): Deploy Vercel preview ran 19:55:14 → 20:12:08 and exited 124 again. Two consecutive attempts, ~4.5 hours apart, both with a successful build.

The CLI emitted no progress output at all across those 15 minutes — no Inspect: line, no Queued, no Building. Consistent with a deployment that never leaves Vercel's queue (an org concurrency or deployment-rate ceiling would present exactly this way), which is an account/project-side matter rather than something reachable from this diff.

The diagnostic gap this exposed — proposed patch, not applied here

.github/actions/vercel-preview-deploy/action.yml intends to capture the CLI's exit code and report the reason itself:

# NOT `set -e`: we capture `vercel`'s exit code and report the reason
# ourselves rather than aborting on it.
set -uo pipefail
out="$(timeout --kill-after=30s "$DEPLOY_TIMEOUT" vercel deploy --prebuilt --token="$VERCEL_TOKEN" 2>"$err_file")"
code=$?

That path is dead. GitHub runs a composite run: step under bash --noprofile --norc -e -o pipefail, and set -uo pipefail does not clear the inherited -e — so the failing assignment aborts the step before code=$?. Nothing is echoed, and vercel-deploy-error.txt is never written, which is why the sticky comment fell back to the generic "The Vercel deployment did not complete. See the workflow logs for the CLI output" instead of the specific 15m-timeout message the action composes. The stderr the reporting path exists to quote is also lost with the temp file.

The minimal fix is to make the step's own intent explicit rather than relying on the inherited shell flags:

      shell: bash --noprofile --norc {0}

or, keeping the default shell, set +e alongside the existing set -uo pipefail.

I have deliberately not applied that here — it is CI-infrastructure work unrelated to this PR's subject (the Insights page), and widening the diff to carry it would make this branch harder to review, not easier. It's a one-line change whenever someone wants it, and it would have turned this investigation into a single log line.

What I did not do

Raising DEPLOY_TIMEOUT would hide a deployment that genuinely never completes, and the guard is there precisely to stop a blocked deploy from running to the 6-hour job default. There is no fix inside this PR's scope, and no fix elsewhere in the repo to port into it.


Generated by Claude Code

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