dashboard: collapse alert stack traces, and fix the nav scroll spy - #32
Open
CamiloSierraH wants to merge 3 commits into
Open
CamiloSierraH wants to merge 3 commits into
CamiloSierraH wants to merge 3 commits into
Conversation
An alert message is the rule's template with one row's values substituted, and those values carry whatever the server said. A ClickHouse exception embeds its stack trace in the message text: one measured in a real bundle ran 1725 characters over 15 lines, of which the first 216 were the error. The panel rendered every row in full with white-space:pre-wrap, so replication_queue_errors at its 50-row limit produced 750 lines of stack frames and pushed the rest of the page out of sight. Three things now collapse behind native <details>, each only when there is something to hide: - a message is shown up to the stack trace and capped at 260 characters, with the trailing "(version X (official build))" dropped — it is the same on every row and the header already states the version; the complete text sits under "full message and stack trace" - instances past the fifth move under "N more instances" - a rule description shows its first paragraph, the "what to check" half under "more about this rule" Nothing leaves the page: DATA.alerts still carries every row and every frame, and bundle-layout.md tells the skill to read it rather than the rendered page. A short single-line message gets no toggle at all. Worst case measured with the real exception: 750 lines become 6. make dashboard-preview now also writes bin/alerts_preview.html, a fixture covering every case — stack trace, over-cap instances, a message needing no disclosure, a rule whose query failed, a rule that was not applicable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The sticky nav was supposed to mark the section you are reading. It never did, on any bundle. Every optional panel starts at display:none, and a non-rendered element reports getBoundingClientRect().top = 0. Zero always passes the "its top is above the line" test, so the last such section in the document won every pass — on a typical bundle sec-async-inserts, whose nav link is hidden too. The class landed on an invisible link and the band looked dead. Confirmed in a browser before the fix: after scrolling to Storage the active link was #sec-async-inserts. The section list is now rebuilt on each pass and filtered to those actually rendered that also have a nav link. It syncs on load and resize, not only on scroll, because panels un-hide after their data renders — the same lateness that makes a snapshot wrong. The first section is current at the top of the page and the last at the bottom, where a short final section can never reach the threshold, and the active link carries aria-current. Hover and active shared one CSS rule, so the current section was indistinguishable from whatever the pointer was over; active now gets an accent underline and heavier weight. The rAF coalescing this first used is gone: requestAnimationFrame never fires under a headless --virtual-time-budget, which made the behaviour untestable, and the pass is ~20 rect reads with no writes against scroll events the browser already caps at the frame rate. Reading live also stays correct when a disclosure expands and shifts everything below it. Verified in a browser across all 15 rendered sections of the preview page and 15 scroll positions of a live dashboard; every one highlights its own link. theme_test's threshold assertion now checks that the line derives from the measured band height rather than pinning the tolerance value, which widened from 8 to 16 px so a subpixel rounding after an anchor jump cannot flip the comparison. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Five moderate issues remain in alert truncation and scroll-spy synchronization behavior.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 2
Open (2)
What changed in this PR
Improves dashboard readability by collapsing verbose alerts and fixing navigation scroll highlighting.
Changes:
- Adds collapsible alert messages, instances, and descriptions.
- Rebuilds scroll-spy behavior with active-link accessibility styling.
- Adds tests, previews, and documentation.
| File | Reviewed change |
|---|---|
skills/clickhouse-diagnostic/references/bundle-layout.md |
Documents reading full alert payloads. |
README.md |
Documents dashboard behavior and previews. |
Makefile |
Generates dashboard preview pages. |
internal/dashboard/theme_test.go |
Updates scroll threshold coverage. |
internal/dashboard/nav_spy_test.go |
Tests scroll-spy behavior. |
internal/dashboard/keeper_preview_test.go |
Adds trace alerts to the preview. |
internal/dashboard/generator.go |
Implements alert disclosures and scroll-spy changes. |
internal/dashboard/alerts_preview_test.go |
Adds alert preview fixtures. |
internal/dashboard/alerts_panel_test.go |
Tests alert-collapse behavior. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Both found by review on #32. The 260-character cap was exclusive of the ellipsis. The word-boundary branch stayed inside it, but the no-space fallback — reachable with a long path or any 260-character run without a space after position 156 — emitted slice(0,260) plus the ellipsis, so 261. Slicing one short makes the documented cap inclusive. The disclosure toggle compared the inline head against a version-stripped copy of the message. For a message that is NOTHING BUT a version suffix, stripping empties head, the `if(!head) head=flat` fallback restores it, and the comparison against the stripped (empty) copy reported a difference — so the panel rendered a <details> whose body repeated the line above it. Comparing head against the flat message instead states the actual question, "is the inline line already the whole message", and drops flatTrimmed entirely. Unreachable with the shipped rules, since every template in alerts/ wraps its substitutions in literal prose and the version regex is $-anchored, so the prose always survives. Fixed anyway: the predicate is simpler than the one it replaces, and the next rule that interpolates a bare server string would hit it. Verified by extracting the function out of the template and running it over eight message shapes — version-only, short with a version suffix, short plain, long with and without spaces, a stack trace, empty and null. Before: the two failures above. After: none, with every other shape unchanged. alerts_panel_test.go now pins both forms and bans the pre-fix ones. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Two independent dashboard fixes, one commit each.
1. Alert messages bury the page under stack traces
An alert message is the rule's template with one row's values substituted, and those values carry whatever the server said. A ClickHouse exception embeds its stack trace in the message text — one measured in a real support bundle ran 1725 characters over 15 lines, of which the first 216 were the error. The panel rendered every row in full with
white-space:pre-wrap, soreplication_queue_errorsat its 50-row limit produced 750 lines of stack frames and pushed the rest of the page out of sight.Three things now collapse behind native
<details>, each only when there is something to hide:(version X (official build))droppedN more instancesmore about this ruleThe version suffix is the same on every row and the header already states it; dropping it is what turns each instance into one line instead of two. A short single-line message gets no toggle at all.
Nothing leaves the page.
DATA.alertsstill carries every row and every frame —bundle-layout.mdnow tells the skill to read it rather than the rendered page. Worst case measured with the real exception: 750 lines become 6, with the full text behind six disclosures.2. The nav scroll spy never highlighted anything
A scroll spy already existed. It had never worked on any bundle.
Every optional panel starts at
display:none, and a non-rendered element reportsgetBoundingClientRect().top = 0. Zero always passes the "its top is above the line" test, so the last such section in the document won every pass — on a typical bundlesec-async-inserts, whose nav link is hidden too. The class landed on an invisible link and the band looked dead. Confirmed in a browser before changing anything: after scrolling to Storage, the active link was#sec-async-inserts (HIDDEN LINK).The section list is now rebuilt on each pass and filtered to those actually rendered that also have a nav link. It syncs on load and resize, not only on scroll, because panels un-hide after their data renders — the same lateness that makes a one-time snapshot wrong. The first section is current at the top of the page, the last at the bottom where a short final section can never reach the threshold, and the active link carries
aria-current.Hover and active shared one CSS rule, so the current section was indistinguishable from whatever the pointer was over. Active now gets an accent underline and heavier weight.
The
requestAnimationFramecoalescing this first used is gone: rAF never fires under a headless--virtual-time-budget, which made the behaviour untestable, and the pass is ~20 rect reads with no writes against scroll events the browser already caps at the frame rate. Reading live also stays correct when a disclosure expands and shifts everything below it.Verification
Both fixes were driven in a real browser (headless Chrome), not only asserted against the template.
<script>injection attempt. Escaping holds; the short message correctly gets no toggle.node --checkclean on the inline script, light and dark themes both correct.go vet ./...andgo test ./...pass. Two new test files pin the caps, the escaping, the payload retention and each part of the spy fix.theme_test's threshold assertion now checks that the line derives from the measured band height rather than pinning the tolerance, which widened from 8 to 16 px so a subpixel rounding after an anchor jump cannot flip the comparison.Seeing it
make dashboard-previewwrites two pages:bin/alerts_preview.html— every alert case: stack trace, over-cap instances, a message needing no disclosure, a rule whose query failed, a rule that was not applicable.bin/keeper_incident_preview.html— 15 sections to scroll through plus the stack-trace alerts, so both fixes are visible on one page.Review follow-up (5e700b0)
Two off-by-ones in
alertMessageParts, both raised on review:slice(0,260)plus the ellipsis, so 261. Reachable with a long path or any 260-character run without a space after position 156. Now slices toALERT_HEAD_CHARS-1.if(!head) head=flatfallback restores it, and the comparison against the empty copy reported a difference — rendering a disclosure whose body repeated the line above it. Replaced withtruncated: head !== flat, which states the actual question and dropsflatTrimmedentirely.The second is unreachable with the shipped rules — every template in
alerts/wraps its substitutions in literal prose andALERT_VERSION_REis$-anchored — but the predicate is simpler than the one it replaced, so it went in.The verification below notes a version-only message was among the cases driven in node; it was, but the assertion checked escaping and the head, not whether the toggle appeared. The function is now run over eight shapes with both properties asserted (head length within the cap, no disclosure whose body equals its inline line): before, exactly these two failed; after, none, with every other shape unchanged.
alerts_panel_test.gopins both fixed forms and bans the pre-fix ones.🤖 Generated with Claude Code