fix(web): UI papercuts — create shortcuts, palette ranking, data browser, trace legend, deep-link Back - #563
Merged
Merged
Conversation
…overlays Every list page whose only primary action is 'create' now uses CreateActionButton, so the button renders a visible N badge and the shortcut actually works: Teams, Dashboards, Metric alerts, S3 sources, Notification providers, Routes, Monitors, Feature flags, Error alert rules and Email domains. Where a page shows the create button in both the header and the empty state, only the header registers the shortcut so a single keypress can't fire twice; where the header button is hidden while the list is empty (notification providers, email domains) the two are mutually exclusive and the empty-state button carries it instead. useKeyboardShortcut also now bails while a Radix dialog, alert dialog, menu or select is open. Previously a bare N pressed with an edit dialog up would navigate the page out from under it.
…a-browser tree
Command palette:
- Results are one list ordered by score instead of fixed sections rendered
in a fixed order. A weak keyword hit in Navigation used to outrank an exact
title match in Settings purely because Navigation renders first ('work' put
Sandboxes above Worker Nodes).
- Title matches now score explicitly on top of the Fuse score. Fuse ranks a
zero-distance *keyword* hit above a fuzzy *title* hit at any per-key weight,
which put Users (tagged 'team') above Teams. Tags still rank, capped well
below any title match.
- Teams and Create Team were missing entirely; the Teams dialog is now opened
by ?new=1 so the palette can deep-link it.
- Project sub-pages are reachable from anywhere ('demo deploy' -> that
project's Deployments), for a bounded set of pages per project.
Data browser:
- Selecting a different table kept the previous table's sort column and
filter, so the query sorted on a field the new table has no column for.
Tree selection went through a bare setSearchParams instead of navigateTo,
which is what resets sort/filter/page for a new target.
- The tree/table split is draggable on desktop via a shadcn resizable wrapper
(react-resizable-panels v4 renames PanelGroup/PanelResizeHandle to
Group/Separator, so the published shadcn snippet does not compile). Width
persists per service. Mobile keeps the overlay drawer, where a resize
handle has nothing to drag against.
- The mobile tree toggle had no accessible name.
…per span The per-span project badge truncated to ~"galachain-gat…" on every row while still costing ~88px of the name column, which on narrow viewports is most of the space the span name has. Each row now carries only the colour dot, and a legend above the waterfall decodes the colours with the full slug; the name stays reachable via the dot's tooltip and aria-label. CrossProjectTraceDetail already had a legend. TraceDetail's inline unified view (?view=unified) did not — it tagged every span but never said what the colours meant, so that view gets one too. Legend entries drop the badge's 88px cap, since truncating the thing that decodes the dots defeats it.
navigate(-1) silently does nothing when a page was opened directly — a shared URL, a new tab, a bookmark — because there is no earlier entry in the history session to pop. The user clicks Back and simply stays put, with no feedback. This is the common case for trace detail, which is exactly the kind of page people paste to each other. Adds useGoBack(fallback): react-router records its position in the session as window.history.state.idx, so when that is 0 (or null, on the first entry) there is nothing of ours behind us and we navigate to the fallback instead — which is where the button's label claims it goes anyway. In-app navigation is unchanged and still pops history. Applied to all 19 call sites across 8 files, each with the parent it belongs to: trace detail -> the project's trace list, the global unified trace -> a contributing project's trace list, alert/dashboard/metric-alert forms -> their lists, cron job -> the project's cron jobs, funnel -> the project's funnels, IP geolocation -> proxy logs. The form ones also covered the post-save navigate(-1), which stranded the user on a saved form.
Rebasing onto main brought in the data-browser redesign (#557), which had already fixed the stale-sort symptom independently: effectiveSortField validates the sort column against the entity's own field names and falls back to unsorted when it doesn't match. Routing the three tree-selection handlers through navigateTo on top of that collapsed the tree on entity selection — verified by running the same switch-tables flow against unmodified origin/main (passes) and against this branch (fails, 'public' collapses so sibling tables are unreachable). Since the reported bug is already fixed upstream, reverting is strictly better than shipping a working fix plus a new regression. The residual gap is the stale *filter*, which still carries across a table switch; that needs a fix derived from the redesigned navigation and is left as a follow-up rather than guessed at here.
dviejokfs
force-pushed
the
fix/ui-common-fixes
branch
from
August 6, 2026 11:52
b350719 to
afa8457
Compare
📓 Changelog previewThis is what your commits will add to the generated ## [Unreleased]
### Fixed
- **web:** Give sole create actions an N shortcut and guard it behind overlays
- **web:** Rank palette results by relevance, add Teams, resizable data-browser tree
- **web:** Show unified-trace projects as a colour legend, not a slug per span
- **web:** Make Back work on deep-linked pages
- **web:** Address self-review findings on the shortcut hook and project dot
### Revert
- **web:** Drop the data-browser tree navigateTo routing |
…ct dot - useKeyboardShortcut re-registered its keydown listener on every render: callers pass an inline arrow, so `callback` was a new identity each time and the effect tore the listener down and re-added it. Held in a ref instead. Affects all 24 call sites, including the 14 that predate this branch. - ProjectDot carried an aria-label on a role-less span, which most screen readers ignore — with the slug text now gone that left the project unreadable. Added role="img". - resizable.tsx used the React UMD global for a type; import it explicitly.
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.
Description
A pass over recurring UI papercuts, driven by dogfooding. Frontend only — 24 files, all under
web/src/, no backend or migration changes.1. Sole create actions get an
NshortcutCreateActionButton(badge + shortcut) already existed but only 14 places used it. Ten list pages had a create button as their only primary action and no shortcut: Teams, Dashboards, Metric alerts, S3 sources, Notification providers, Routes, Monitors, Feature flags, Error alert rules, Email domains.Where a page shows the button in both the header and the empty state, only the header registers the shortcut so one keypress can't fire twice. Where the header button is hidden while the list is empty (notification providers, email domains) the two branches are mutually exclusive and the empty-state button carries it.
Bug found in the shared hook:
useKeyboardShortcutfired whenever focus wasn't in a text field, including with a modal open — pressingNwith the S3 edit dialog up navigated the page out from under it. It now bails while a Radix dialog, alert dialog, menu or select is open. That fix applies to all 14 pre-existing usages too.2. Command palette ranks by relevance
Results were bucketed by section and the sections rendered in a fixed order, so a weak keyword hit in Navigation outranked an exact title match in Settings — searching
workput Sandboxes above Worker Nodes. Now one list ordered by score, with the section demoted to a right-aligned label.Fuse alone wasn't enough: a zero-distance keyword hit beats a fuzzy title hit at any per-key weight, which put Users (tagged
team) above Teams. Title matches now score explicitly (titleBoost, 0–0.6) and the Fuse component is capped at 0.25, so tags still rank but never above a title match.Also: Teams and Create Team were missing from the palette entirely, and project sub-pages are now reachable from anywhere (
demo deploy→ that project's Deployments) for a bounded set of pages per project.3. Data browser
4. Unified trace: project legend instead of a slug per span
The per-span project badge truncated to
galachain-gat…on every row while still costing ~88px of the name column. Rows now carry only the colour dot; a legend above the waterfall decodes the colours with the full slug, and the name stays reachable via tooltip/aria-label.CrossProjectTraceDetailalready had a legend.TraceDetail's inline unified view (?view=unified) did not — it tagged every span but never said what the colours meant — so that view gets one too.5.
Backworks on deep linksnavigate(-1)silently does nothing when a page is opened directly (shared URL, new tab, bookmark) — there's no earlier entry to pop, so the user clicks Back and stays put with no feedback. Trace detail is exactly the kind of page people paste to each other.New
useGoBack(fallback)uses react-router'shistory.state.idxto detect "nothing of ours behind us" and navigates to the parent instead. Applied to all 19 call sites across 8 files, each with its real parent. The forms' post-savenavigate(-1)had the same bug — save on a deep-linked form and you were stranded on it.Type of change
Evidence
Run against a real local instance (slot 15) with a provisioned Postgres service and two seeded tables with deliberately disjoint columns. 25 Playwright checks, all passing. The specs were written to verify these changes and deleted afterwards — they are not part of this PR.
Shortcuts (15 checks) — badge renders as
N;nopens the dialog (Teams, Monitors, Feature flags) or navigates (Backups, Routes, Notifications, Dashboards, Metric alerts, Alert rules); typingninto a field does not fire;nwith a dialog open does not navigate.Palette (7 checks)
Data browser (3 checks)
Trace + Back (7 checks) — legend renders untruncated slugs, dot count equals span count, dots carry no text; and:
That last one is the regression guard: it proves history navigation wasn't just replaced with a hardcoded redirect.
Static:
tsc --noEmitclean.eslintclean on every changed file — the two remaining errors inServiceDataBrowser.tsxand one inAlertRuleForm.tsxare pre-existing and were confirmed against theorigin/mainbaseline.Not verified at runtime
Nshortcut. Its create button only appears once an email provider is configured, which needs real SMTP credentials. Same code shape as the notification-providers case that does pass, and it typechecks./api/otel/global/traces/:idresponse rather than real ingest (that needs ClickHouse and cross-project OTel spans sharing atrace_id). This drives the real components with realistic data shaped by the generatedUnifiedTracetype, but does not prove the backend returns that shape.Checklist
cargo test --lib) — n/a, no Rust changescargo check --libpasses with no warnings — n/a, no Rust changesRelated issues
None.