Prometheus Design System v0.2.0 β€” WIP

Codebase Audit Report

Exhaustive analysis of the Prometheus Mantine UI codebase β€” every component, token, pattern, and inconsistency.

Audit Scope

Audited path: prometheus-source/web/ui/mantine-ui/src/  Β·  Package version: 0.310.0  Β·  Audit date: 2026-03-30

1. Tech Stack & Dependencies

Core Framework

UI Layer

State Management

Data Fetching

Routing & URL State

Code Editor

Charting

Search/Filtering

Utilities

2. Design Tokens Currently in Use

2.1 Color Values

Navigation Header β€” Hardcoded RGB

rgb(65, 73, 81)   β€” AppShell.Header bg and AppShell.Navbar bg (App.tsx)
#fff              β€” Header text color (c="#fff")
white             β€” Logo link text color (style={{ color: "white" }})
Theme-Blind Colors
These are hardcoded raw RGB/hex values, not Mantine tokens. They do not respond to dark/light mode switching.

Custom Mantine Color Palette (App.tsx, createTheme)

"codebox-bg": [
  "#f5f5f5", "#e7e7e7", "#cdcdcd", "#b2b2b2", "#9a9a9a",
  "#8b8b8b", "#848484", "#717171", "#656565", "#575757"
]

This 10-step gray scale is registered but only referenced for codebox backgrounds.

Badge Health Colors (Badge.module.css) β€” light-dark() aware

ClassLight bgLight textDark bgDark text
.statsBadgegray-1gray-7gray-8gray-5
.labelBadgegray-1gray-7gray-8gray-5
.healthOkgreen-1green-9green-9green-1
.healthErrred-1red-9darken(red-9, 0.25)red-1
.healthWarnyellow-1yellow-9yellow-9yellow-1
.healthInfoblue-1blue-9blue-9blue-1
.healthUnknowngray-2gray-7gray-7gray-4

Panel Health Border Colors (Panel.module.css) β€” light-dark() aware

ClassLightDark
.panelHealthOkgreen-3green-8
.panelHealthErrred-3red-9
.panelHealthWarnorange-3yellow-9
.panelHealthUnknowngray-3gray-6

All borders are 5px solid, applied with !important.

RulesPage β€” Hardcoded Inline Border Colors

// RulesPage.tsx (hardcoded, not using Panel.module.css)
err:     "5px solid var(--mantine-color-red-4)"
unknown: "5px solid var(--mantine-color-gray-5)"
ok:      "5px solid var(--mantine-color-green-4)"

These use CSS variables but are inline styles β€” inconsistent with the Panel.module.css approach used by AlertsPage.

CodeMirror Editor Theme Colors (codemirror/theme.ts)

Base theme (mode-independent):

.cm-completionDetail: #999
.cm-selectionMatch background: #e6f3ff
.cm-diagnostic-error border: #e65013
.cm-completionIcon-text: #ee9d28

Light theme:

.cm-tooltip bg: #f8f8f8
.cm-tooltip border: rgba(52, 79, 113, 0.2)
autocomplete hover: #ddd
selected item: #d6ebff
completionInfo bg: #d6ebff
selection bg: #add6ff
matchingBracket: color #000, bg #dedede
matched text: #0066bf
completionIcon: #007acc
completionIcon-constant: #007acc
completionIcon-function/method: #652d90
completionIcon-keyword: #616161

Dark theme:

caret: #fff
completionInfo bg: #333338
selection bg: #767676
matchingBracket bg: #616161
matched text: #7dd3fc
completionIcon/constant: #7dd3fc
completionIcon-function/method: #d8b4fe
completionIcon-keyword: #cbd5e1

PromQL Syntax Highlighting Colors (codemirror/theme.ts)

Light mode (promqlHighlighter):

number:           #09885a
string:           #a31515
keyword:          #008080
function/var:     #008080
labelName:        #800000
modifier:         #008080
invalid:          red
comment:          #888 (italic)

Dark mode (darkPromqlHighlighter):

number:           #22c55e
string:           #fca5a5
keyword:          #14bfad
function/var:     #14bfad
labelName:        #ff8585
modifier:         #14bfad
invalid:          #ff3d3d
comment:          #9ca3af (italic)

PromQL CSS Classes (promql.css)

.promql-code font-family: "DejaVu Sans Mono", monospace
.promql-keyword:    light-dark(#008080, #14bfad)
.promql-label-name: light-dark(#800000, #ff8585)
.promql-string:     light-dark(#a31515, #fca5a5)
.promql-ellipsis:   light-dark(rgb(170,170,170), rgb(170,170,170))
.promql-duration:   light-dark(#09885a, #22c55e)
.promql-number:     light-dark(#09885a, #22c55e)

Chart Series Color Pool (colorPool.ts)

uPlot Chart CSS Colors (uplot.css)

.u-under background (dark): #1f1f1f
.u-over box-shadow: 0px 0px 0px 0.5px #ccc
.u-select background: rgba(255, 200, 150, 0.2)
cursor lines: light-dark(#607d8b, #90adbc) β€” dashed border
.u-tooltip bg: light-dark(rgba(255,255,255,0.95), rgba(25,25,25,0.95))
.u-tooltip color: light-dark(gray-9, gray-5)
.u-tooltip border: 2px solid light-dark(gray-6, gray-6)
.u-tooltip border-radius: 4px

SeriesName Hover (SeriesName.module.css)

.labelPair:hover background: #add6ffa0
.labelPair:hover color: #495057
.labelPair:hover border-radius: 3px

TreeNode State Colors (TreeNode.module.css β€” light-dark() aware)

.nodeText bg: light-dark(gray-1, dark-5)
.nodeTextSelected bg: light-dark(gray-4, gray-7)
.nodeTextSelected border: light-dark(gray-5, dark-2)
.nodeText:hover bg: light-dark(gray-2, dark-4)
.nodeTextError bg: light-dark(red-1, darken(red-5, 70%))
.errorText color: light-dark(red-9, red-3)

Highlight.js Theme (highlightjs.css)

Light: text: gray-7, bg: gray-0, comment: gray-6, keyword: violet-8, tag: red-9, literal: blue-6, string: blue-9, variable: lime-9, class: orange-9

Dark: text: dark-1, bg: dark-8, comment: dark-3, keyword: violet-3, tag: yellow-4, literal: blue-4, string: green-6, variable: blue-2, class: orange-5

App.module.css Nav Link

.link color: gray-0
.link:hover bg: gray-6
.link[aria-current="page"] bg: blue-filled, color: white
.link border-radius: radius-sm
.link padding: rem(8px) rem(12px)

2.2 Font Families

ContextFont
App body (Mantine default)System UI stack (-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, ...)
CodeMirror scroller / autocomplete"DejaVu Sans Mono", monospace
CodeMirror placeholder-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, ...
CodeMirror completionInfo tooltip'Open Sans', 'Lucida Sans Unicode', 'Lucida Grande', sans-serif
PromQL code display"DejaVu Sans Mono", monospace (via .promql-code)
Codicon icon font"codicon" from ./fonts/codicon.ttf
No custom heading font is defined anywhere. Body and UI text use Mantine's system-default stack throughout.

2.3 Font Sizes

TokenValueUsage
fz="xl"Mantine xlGroup name headings (AlertsPage, RulesPage), InfoPageCard titles
fz="sm"Mantine smFile path labels, Anchor links
fz="xs"Mantine xsDataTable, graph legend (--mantine-font-size-xs), notification timestamps
fz={20}20px (unitless)Prometheus logo text (App.tsx, hardcoded number)
fz="1em"relativeAlert action anchors
size="1em"relativeDataTable timestamp Text
0.8emrelativeuPlot tooltip, legend
0.9emrelativeuPlot labels
12pxabsoluteuPlot .u-label
0.9remβ€”RangeInput, TimeInput, QueryPanel icon sizes
font-size: 16pxabsoluteCodeMirror .cm-completionIcon
0.6875rem * var(--mantine-scale)tokenManual labelBadge font size

2.4 Font Weights

WeightUsage
400Accordion label default
500Nav link (.link), Table icon header text, notifications text
600InfoPageCard title (fw={600}), group name (fw={600}), label badge (font-weight: 700)
700Notifications header text (fw={700}), .hljs-strong, histogram table headers
bolduPlot legend .series-label, uPlot tooltip headers

2.5 Spacing Values

All spacing uses Mantine's token system (xs, sm, md, lg, xl) except where noted.

Hardcoded numeric values found:

Mantine spacing tokens used: p="md", p="xs", px="md", padding="md", py="md", mt="xs", mt="lg", mt="xl", mb="sm", mb="md", mb="xl", gap="xs", gap="sm", gap="lg", gap="xl", ml="xs", ml="md"

2.6 Border Radii

ContextValue
Nav linkvar(--mantine-radius-sm)
AppShell contentvar(--mantine-radius-default)
Accordion rootvar(--mantine-radius-default)
Badge labelcalc(62.5rem * var(--mantine-scale)) β€” pill shape
uPlot tooltip4px (hardcoded)
TreeNode box4 (unitless, hardcoded β€” likely pixels)
SeriesName hover3px (hardcoded)
LoadingOverlay"sm" (via overlayProps)
FlagsPage sort iconrem(21px)

2.7 Shadows / Elevations

ValueUsage
shadow="xs"InfoPageCard, AlertsPage card, RulesPage card
shadow="md"Status menu, settings popover, notifications popover
shadow="none"RuleDefinition code card

2.8 Transition / Animation Values

ValueUsage
opacity 0.1s ease-in-outRuleDefinition query button hover reveal
transition: transform var(--accordion-transition-duration, 200ms) easeAccordion chevron rotation
background-color 150ms easeAccordion item contained/separated variants

2.9 Breakpoints

Mantine defaults are used exclusively (no custom breakpoints defined):

No custom breakpoints are defined in createTheme().

3. Component Inventory

3.1 Custom Components

ComponentFilePropsDescription
InfoPageCardcomponents/InfoPageCard.tsxchildren, title?, icon?Wrapper Card with icon+title header for status pages
InfoPageStackcomponents/InfoPageStack.tsxchildrenCentered max-1000px Stack for info pages
LabelBadgescomponents/LabelBadges.tsxlabels, wrapper?, style?Performance-optimized label pill display (single DOM span, not Mantine Badge)
EndpointLinkcomponents/EndpointLink.tsxendpoint, globalUrlParses and renders scrape endpoint URL with query params as badges
ErrorBoundarycomponents/ErrorBoundary.tsxchildren, title?Class-based error boundary, resets on route change
SettingsMenucomponents/SettingsMenu.tsxβ€”Popover with all user-configurable settings
ThemeSelectorcomponents/ThemeSelector.tsxβ€”ActionIcon cycling between light/dark/auto color schemes
StateMultiSelectcomponents/StateMultiSelect.tsxoptions, optionClass, optionCount?, placeholder, values, onChangeCustom multi-select using Mantine Combobox primitives with colored Pill display
StatePillcomponents/StateMultiSelect.tsxvalue, onRemove?Uppercase bold Pill for state display
NotificationsIconcomponents/NotificationsIcon.tsxβ€”Indicator+ActionIcon+Popover for live notifications
NotificationsProvidercomponents/NotificationsProvider.tsxchildrenSSE-based notification context provider
ReadinessWrappercomponents/ReadinessWrapper.tsxchildrenGuards pages behind Prometheus readiness check; shows WAL replay progress
RuleDefinitioncomponents/RuleDefinition.tsxrule: RuleCodeMirror read-only PromQL display + badges for duration/labels/annotations
Accordioncomponents/Accordion/Accordion.tsxFull Mantine-compatible Accordion APIFork of Mantine Accordion with overflow-wrap: break-word on panel
SeriesNamepages/query/SeriesName.tsxlabels, formatRenders metric label set; formatted mode adds clickable label pairs that copy matchers
TreeNodepages/query/TreeNode.tsxnode, selectedNode, setSelectedNode, parentEl?, reportNodeState?, reverse, childIdxRecursive PromQL AST tree node with live query execution and connector lines
ExpressionInputpages/query/ExpressionInput.tsxinitialExpr, metricNames, executeQuery, treeShown, setShowTree, duplicatePanel, removePanelCodeMirror-based PromQL input with full extensions, metrics explorer modal
Graphpages/query/Graph.tsxexpr, node, endTime, range, resolution, showExemplars, displayMode, yAxisMin, retriggerIdx, onSelectRangeRange query executor + LoadingOverlay + UPlotChart wrapper
UPlotChartpages/query/UPlotChart.tsxdata, range, width, showExemplars, displayMode, yAxisMin, onSelectRangeuPlot chart wrapper translating Prometheus data to uPlot format
DataTablepages/query/DataTable.tsxdata, limitResults, setLimitResultsTable display for instant query results
RangeInputpages/query/RangeInput.tsxrange, onChangeRangeTextInput with +/- ActionIcons and stepped range values
TimeInputpages/query/TimeInput.tsxtime, range, description, onChangeTimeDateTimePicker with prev/next ActionIcons
ResolutionInputpages/query/ResolutionInput.tsxresolution, range, onChangeResolutionSelect for auto/fixed/custom resolution + conditional TextInput
TargetLabelspages/targets/TargetLabels.tsxlabels, discoveredLabelsCollapsible label display with ActionIcon toggle for discovered labels
AlertsPagepages/AlertsPage.tsxβ€”Paged, filtered alert rules grouped by rule group
RulesPagepages/RulesPage.tsxβ€”Paged, filtered recording+alerting rules
TargetsPagepages/targets/TargetsPage.tsxβ€”Scrape pool selector + filter bar + ScrapePoolList
FlagsPagepages/FlagsPage.tsxβ€”Sortable flags table with search
StatusPagepages/StatusPage.tsxβ€”Build + runtime info tables
ConfigPagepages/ConfigPage.tsxβ€”YAML config via CodeHighlight
TSDBStatusPagepages/TSDBStatusPage.tsxβ€”TSDB head stats tables
AgentPagepages/AgentPage.tsxβ€”Static informational page for agent mode

3.2 Mantine Components Used β€” Full Inventory with Frequency

Mantine ComponentFiles Used InNotes
ActionIconApp.tsx, ThemeSelector, NotificationsIcon, SettingsMenu, RuleDefinition, ExpressionInput, QueryPanel, RangeInput, TimeInput, TargetsPage, TargetLabels, SD pagesMost common interactive element
AlertErrorBoundary, ReadinessWrapper, QueryPage, Graph, DataTable, AlertsPage, RulesPage, ScrapePoolsList, SD lists, QueryPanelUsed for all error, warning, info states
AnchorDataTable, EndpointLink, AlertsPage, RulesPage, ScrapePoolsList, SD pagesLinks within content
AppShell + subcomponentsApp.tsxCore layout
BadgeEndpointLink, RuleDefinition, AlertsPage, RulesPage, ScrapePoolsListHealth and state indicators
BoxApp.tsx, NotificationsIcon, TreeNode, Graph, RuleDefinition, QueryPanel, DataTableGeneric container/layout
BurgerApp.tsxMobile menu toggle
ButtonApp.tsx (nav links), QueryPage, ExpressionInputCTA and nav
CardNotificationsIcon, AlertsPage, RulesPage, RuleDefinitionContent containers
CenterFlagsPage, QueryPanelCentering wrapper
CheckboxSettingsMenu, QueryPanelSettings toggles
CodeTreeNodeInline code display
CodeHighlightConfigPageYAML display
CollapseTargetLabelsExpandable discovered labels
Combobox (+ subcomponents)StateMultiSelectCustom multi-select
DateTimePickerTimeInputGraph end time picker
DividerAlertsPageSection separator
FieldsetSettingsMenuSettings grouping
GroupNearly all filesMost common layout primitive
IndicatorNotificationsIconNotification count badge
InputBaseExpressionInputCodeMirror host element
List / List.ItemTreeNodeLabel example lists in tooltips
LoaderExpressionInput, TreeNodeInline loading indicators
LoadingOverlayGraph.tsxGraph refetch overlay
Menu + subcomponentsApp.tsx (status nav), ExpressionInputDropdowns
ModalExpressionInput (metrics explorer), SD lists (relabeling)Full-screen modals
NotificationsApp.tsx (provider), ExpressionInput (show), SeriesName (show)Toast notifications
NumberInputSettingsMenuPagination settings
PaginationAlertsPage, RulesPageGroup pagination
Pill / PillsInputStateMultiSelectMulti-select pills
Popover + subcomponentsSettingsMenu, QueryPanel, NotificationsIconOverlay panels
ProgressReadinessWrapperWAL replay progress
RingProgressScrapePoolsList, SD listsPool health ring
ScrollAreaNotificationsIcon, DataTableScrollable regions
SegmentedControlQueryPanel (graph mode), DataTable (histogram scale)Segmented toggles
SelectTargetsPage, SD page, ResolutionInputDropdown selects
SkeletonApp.tsx (Suspense fallback), QueryPanel, Graph, TargetsPage, SD pageLoading skeletons
SpaceQueryPanelExplicit spacing
StackMany filesVertical layout
Table + subcomponentsDataTable, FlagsPage, StatusPage, TSDBStatusPage, AlertsPage, RulesPage, ScrapePoolsList, RuleDefinitionData display
Tabs + subcomponentsQueryPanelTable/Graph/Explain tabs
TextNearly all filesTypography
TextInputAlertsPage, RulesPage, TargetsPage, SD page, FlagsPage, RangeInput, ResolutionInputSearch/filter inputs
TooltipAlertsPage, RulesPage, RuleDefinition, TreeNode, ScrapePoolsListHover info
UnstyledButtonFlagsPageSortable column header

3.3 Mantine Hooks Used

HookUsage
useDisclosureApp.tsx (mobile nav), TargetLabels (label toggle)
useComboboxStateMultiSelect
useMantineColorSchemeThemeSelector
useComputedColorSchemeExpressionInput, UPlotChart, RuleDefinition
useElementSizeGraph.tsx (width for uPlot)
useDebouncedValueAlertsPage, RulesPage, TargetsPage (search debounce 250ms)
useLocalStorageAlertsPage, RulesPage, ScrapePoolsList, SD lists
useIdTreeNode, Accordion
useUncontrolledAccordion

3.4 Tabler Icons Used

Navigation: IconSearch, IconBellFilled, IconBell, IconBook, IconChevronDown, IconChevronRight, IconCloudDataConnection, IconDatabase, IconDeviceDesktopAnalytics, IconFlag, IconHeartRateMonitor, IconInfoCircle, IconServer, IconServerCog, IconTable

Actions: IconSettings, IconMoonFilled, IconSunFilled, IconBrightnessFilled, IconDotsVertical, IconPlus, IconMinus, IconTrash, IconCopy, IconAlignJustified, IconBinaryTree, IconTerminal, IconAdjustmentsHorizontal, IconLayoutNavbarCollapse, IconLayoutNavbarExpand, IconChevronLeft, IconChevronRight, IconChevronUp, IconSelector

Status: IconAlertTriangle, IconAlertCircle, IconNetworkOff, IconMessageExclamation, IconPointFilled

Domain: IconClockPause, IconClockPlay, IconRefresh, IconHourglass, IconRepeat, IconTimeline, IconBell (rules), IconSpy, IconRun, IconWall, IconChartLine, IconChartAreaFilled, IconGraph

4. Layout Patterns

4.1 App Shell Structure

BrowserRouter > QueryParamProvider > MantineProvider > CodeHighlightAdapterProvider
  └─ QueryClientProvider
      └─ AppShell (header height=56, navbar width=300, breakpoint=sm, padding=md)
          β”œβ”€ AppShell.Header (bg=rgb(65,73,81), c=#fff)
          β”‚   └─ Group h="100%" px="md" wrap="nowrap"
          β”‚       β”œβ”€ Group style={{flex:1}} justify="space-between"
          β”‚       β”‚   β”œβ”€ Group gap={40} (logo + nav links)  [desktop]
          β”‚       β”‚   └─ Group visibleFrom="xs" (action icons) [desktop]
          β”‚       └─ Burger hiddenFrom="sm"  [mobile]
          β”œβ”€ AppShell.Navbar py="md" px={4} bg=... c=... [mobile only]
          β”‚   └─ navLinks + Group hiddenFrom="xs" (action icons)
          └─ AppShell.Main
              └─ ErrorBoundary > Suspense > Routes

4.2 Status/Info Page Pattern

Used by: StatusPage, TSDBStatusPage, AgentPage, FlagsPage

InfoPageStack (Stack gap="lg" maw={1000} mx="auto" mt="xs")
  └─ InfoPageCard (Card shadow="xs" withBorder p="md")
      β”œβ”€ Group (icon + title) [optional]
      └─ {children} (usually Table)

4.3 Accordion-Based List Pattern

Used by: AlertsPage, RulesPage, TargetsPage, ServiceDiscoveryPage

Stack mt="xs"
  β”œβ”€ Group (filters: StateMultiSelect + TextInput)
  β”œβ”€ Pagination (hideWithOnePage)
  └─ [Card (group header)]
      └─ Accordion multiple variant="separated"
          └─ Accordion.Item (health border class)
              β”œβ”€ Accordion.Control
              β”‚   └─ Group justify="space-between" (name + badges)
              └─ Accordion.Panel
                  └─ CustomInfiniteScroll > Table/RuleDefinition

4.4 Query Page Panel Pattern

Stack gap="xl" (outer, for multiple panels)
  └─ Stack gap="lg" (per QueryPanel)
      β”œβ”€ ExpressionInput (Group align="flex-start" wrap="nowrap" gap="xs")
      β”‚   β”œβ”€ InputBase (CodeMirror host, flex=auto)
      β”‚   └─ Button "Execute"
      β”œβ”€ [TreeView β€” conditional Suspense]
      └─ Tabs (Table | Graph | Explain)
          β”œβ”€ Tabs.Panel "table"
          β”‚   └─ TableTab > DataTable > Table
          β”œβ”€ Tabs.Panel "graph"
          β”‚   β”œβ”€ Group (RangeInput + TimeInput + ResolutionInput + controls)
          β”‚   └─ Graph (Box > LoadingOverlay + UPlotChart)
          └─ Tabs.Panel "explain"
              └─ ExplainView

4.5 Common Layout Compositions

Filter bar (reused across AlertsPage, RulesPage, TargetsPage, SD page):

<Group>
  <StateMultiSelect ... />
  <TextInput flex={1} leftSection={<IconSearch />} ... />
  [<ActionIcon /> // TargetsPage only β€” collapse/expand]
</Group>

Card group header (AlertsPage, RulesPage):

<Group mb="sm" justify="space-between">
  <Group align="baseline">
    <Text fz="xl" fw={600} c="var(--mantine-primary-color-filled)">{name}</Text>
    <Text fz="sm" c="gray.6">{file}</Text>
  </Group>
  <Group>{badges}</Group>
</Group>

4.6 Responsive Behavior

5. Interaction Patterns

5.1 Form Controls

Text search: TextInput with IconSearch left section. Debounced at 250ms via useDebouncedValue. Search state persisted to URL via use-query-params.

State/health filter: StateMultiSelect (custom Combobox) with colored pills. Persisted to URL.

Pagination: Mantine Pagination with hideWithOnePage. Page number in URL (?page=N). Active page clamped to valid range on filter changes.

Settings: Mantine Checkbox and NumberInput inside Fieldset groups in a Popover. Settings written to localStorage via Redux middleware.

Graph time range: RangeInput (TextInput + +/- ActionIcons with stepped values), TimeInput (DateTimePicker + prev/next ActionIcons), ResolutionInput (Select + optional TextInput for custom).

Sorting (FlagsPage only): Client-side sort via UnstyledButton column headers with icon state. Does not persist to URL.

5.2 PromQL Expression Input

Read-only mode (RuleDefinition): Uses CodeMirror directly with editable={false}, only baseTheme + lightTheme + syntaxHighlighting + promqlExtension + lineWrapping β€” no autocomplete, no history.

5.3 Filtering / Search

Client-side fuzzy search: KVSearch from @nexucis/kvsearch used in:

All searches are debounced at 250ms; results are useMemo-ized.

5.5 Data Loading

Pattern: useSuspenseAPIQuery for page-level data (surfaces errors/loading to Suspense/ErrorBoundary above). useAPIQuery for non-blocking fetches (graph data, format query, metric names).

Suspense fallbacks: Skeleton arrays β€” typically Array.from(Array(10/20), ...) generating stacked Skeleton components with fixed heights (40px for pools, 30px for content).

Loading states:

Notifications: useSuspenseQuery re-polls readiness every 1 second for WAL replay. Notifications use SSE (fetchEventSource) with fallback to 10-second polling if SSE returns 204.

Error handling: ErrorBoundary class component wrapping routes and Suspense blocks. Displays Alert color="red" with error message. Resets on route change.

5.6 Infinite Scroll

CustomInfiniteScroll component (using react-infinite-scroll-component) wraps target/alert/rule lists within Accordion panels to prevent rendering all items at once. Used in AlertsPage, RulesPage, ScrapePoolsList, SD lists.

6. Inconsistencies & Issues

6.1 Hardcoded Color Values Bypassing Theme

High severity:

  1. Navigation header background is hardcoded as rgb(65, 73, 81) in both AppShell.Header and AppShell.Navbar. This color does not adapt to light/dark mode β€” the header always appears dark regardless of user theme preference.
  2. Navigation header text is hardcoded as c="#fff" and style={{ color: "white" }} for the logo link. Always white, won't adapt.
  3. Logo text fz={20} uses a bare number. Mantine interprets bare numbers as pixel values, so this may not scale with user font preferences.
  4. SeriesName hover uses #add6ffa0 (hardcoded RGBA hex) and #495057 β€” light-mode specific values that will look wrong in dark mode.
  5. RulesPage accordion borders use inline style with var(--mantine-color-red-4/gray-5/green-4) β€” slightly different shades than Panel.module.css (red-3, gray-3, green-3 in light).
  6. uPlot chart elements (.u-over, .u-select, .u-tooltip border) have hardcoded #ccc and rgba(255, 200, 150, 0.2) that only work well in light mode.
  7. CodeMirror baseTheme contains color: "#999" for completion detail and #e6f3ff for selection match β€” light-mode-only hardcoded values in a supposedly mode-agnostic block.
  8. TreeNode connector line uses an inline style string with light-dark() applied via JavaScript style object rather than CSS β€” may not be processed correctly by all browsers.

6.2 Inconsistent Health Status Borders

AlertsPage uses panelClasses.panelHealthErr/Warn/Ok/Unknown (from Panel.module.css), which applies 5px solid borders with green-3/red-3/orange-3/gray-3 shades.

RulesPage uses inline styles with var(--mantine-color-red-4), var(--mantine-color-gray-5), var(--mantine-color-green-4) β€” different shade numbers (-4 vs -3). Same visual pattern, different implementations.

6.3 Duplicate localStorageKey Between AlertsPage and RulesPage

Both AlertsPage and RulesPage use:

useLocalStorage({ key: "alertsPage.showEmptyGroups", defaultValue: false })
Bug
The RulesPage incorrectly uses the alertsPage.showEmptyGroups key β€” the two pages share the same toggle state.

6.4 Duplicate Page Structure Code (AlertsPage / RulesPage)

AlertsPage and RulesPage share nearly identical structure:

Similarly, TargetsPage and ServiceDiscoveryPage share almost identical filter bar/collapse button structure and pool-selection-with-limit logic. targetPoolDisplayLimit = 20 is defined in both pages independently.

6.5 Missing Accessibility Attributes

  1. Nav links use Button component={NavLink} β€” the parent className doesn't semantically communicate the nav role.
  2. StateMultiSelect uses Combobox.DropdownTarget without role or aria-* attributes on the trigger.
  3. Burger has no accessible label (aria-label not set).
  4. TreeNode clickable boxes have onClick but no role="button" or tabIndex, making them keyboard-inaccessible.
  5. SeriesName labelPair span has onDoubleClick for copy but no keyboard alternative.
  6. uPlot chart has no aria-label or accessible description.
  7. LoadingOverlay has zIndex={1000} and h={570} (hardcoded pixel height).

6.6 Icon Style Inconsistencies

Three separate local icon style objects are defined in different files for the same size:

// QueryPanel.tsx
const iconStyle = { width: "0.9rem", height: "0.9rem" };

// RangeInput.tsx
const iconStyle = { width: "0.9rem", height: "0.9rem" };

// TimeInput.tsx
const iconStyle = { width: "0.9rem", height: "0.9rem" };
The comment in QueryPanel even says // TODO: This is duplicated everywhere, unify it.

Meanwhile styles.ts exports many similar constants: navIconStyle (rem(16) Γ— rem(16)), menuIconStyle (rem(14) Γ— rem(14)), badgeIconStyle (em(17) Γ— em(17)), actionIconStyle (70% Γ— 70%), inputIconStyle (em(16) Γ— em(16)), buttonIconStyle (em(20) Γ— em(20)), infoPageCardTitleIconStyle (em(17.5) Γ— em(17.5)), expandIconStyle (em(16) Γ— em(16)), themeSwitcherIconStyle (rem(20) Γ— rem(20)). Mixed use of rem(), em(), %, and string literals.

6.7 Mantine-Overrides.css Scope

mantine-overrides.css contains a single global override:

.mantine-Badge-label {
  overflow: unset;
  text-overflow: unset;
}

This affects all Badge components throughout the app. The LabelBadges component avoids Mantine's Badge entirely and uses plain spans β€” so this override may be for other Badge usages that need to overflow.

6.8 CSS Module vs Inline Style vs Mantine Props β€” Inconsistent Patterns

ApproachExamples
CSS module classesPanel.module.css, Badge.module.css, TreeNode.module.css
Mantine c= / bg= propsc="gray.6", c="dimmed", c="var(--mantine-primary-color-filled)"
style={} inlinestyle={{ whiteSpace: "nowrap" }}, connector line, TreeNode box border
styles={} propAccordion.Control label padding, Badge label textTransform
CSS variables in propsc="light-dark(var(...), var(...))" in Table.Th

The pattern of passing raw CSS light-dark() strings into Mantine's c prop is particularly unconventional and may break in future Mantine versions.

6.9 DataTable.tsx Inline Flexbox Styles

The histogram table uses inline style objects for flexbox layout:

<Table.Tbody style={{ display: "flex", flexDirection: "column", justifyContent: "space-between" }}>
<Table.Tr style={{ display: "flex", flexDirection: "row", justifyContent: "space-between" }}>

This overrides the default table display model globally. It works but is semantically unusual (flex on <tbody> and <tr>).

In App.tsx:

<Text hiddenFrom="sm" fz={20}>Prometheus</Text>  // visible: xs, hidden: sm+
<Text visibleFrom="md" fz={20}>Prometheus</Text>  // visible: md+

Between breakpoints sm and md, neither text is visible β€” the "Prometheus" label disappears on medium-small windows.

6.11 Commented-Out Code

Several significant pieces of commented-out code exist:

6.12 Missing key Props and TODO Comments

The codebase contains multiple // TODO: Find a stable and definitely unique key. comments in ScrapePoolsList and ServiceDiscoveryPoolsList where list items use array index (key={i}) β€” acknowledged as a potential reconciliation issue.

7. Domain-Specific Patterns

7.1 PromQL Expression Input Architecture

The expression input uses CodeMirror 6 embedded inside a Mantine InputBase component:

<InputBase
  component={CodeMirror}
  className={classes.input}
  basicSetup={false}
  value={expr}
  onChange={setExpr}
  extensions={[
    baseTheme,
    highlightSpecialChars(),
    history(),
    EditorState.allowMultipleSelections.of(true),
    indentOnInput(),
    bracketMatching(),
    closeBrackets(),
    autocompletion(),
    highlightSelectionMatches(),
    EditorView.lineWrapping,
    keymap.of([...closeBracketsKeymap, ...defaultKeymap, ...historyKeymap, ...completionKeymap, ...lintKeymap]),
    placeholder("Enter expression (press Shift+Enter for newlines)"),
    enableSyntaxHighlighting ? syntaxHighlighting(theme === "light" ? promqlHighlighter : darkPromqlHighlighter) : [],
    promqlExtension.asExtension(),
    theme === "light" ? lightTheme : darkTheme,
  ]}
  multiline
/>

Key behaviors: Enter β†’ execute query; Shift+Enter β†’ insert newline; Escape β†’ blur editor. Query history overlay via HistoryCompleteStrategy. Autocomplete and linting are each independently togglable.

7.2 Time Series Graph Rendering

The graph pipeline:

  1. Graph.tsx: Executes /query_range API with effective resolution and time range
  2. Data is held in dataAndRange state β€” old data kept displayed while new query is in-flight (no flash-to-empty)
  3. UPlotChart.tsx: Translates RangeSamples[] to uPlot.AlignedData via getUPlotData(). Calls getUPlotOptions() for theme-aware options.
  4. Width is measured via useElementSize() hook (requires two renders to get valid width)
  5. Colors sourced from colorPool.ts via getSeriesColor(idx, isLight)
  6. Graph height: 570px hardcoded via LoadingOverlay h={570}
  7. Chart wrapper: 1px solid border using theme-aware CSS variable in Graph.module.css

uPlot legend is displayed below the chart with left-aligned text. Custom CSS overrides u-legend to remove default centering.

Range selection: uPlot's built-in select fires onSelectRange(start, end) callback which updates the visualizer state and re-executes the query.

Display modes: GraphDisplayMode.Lines (unstacked) and GraphDisplayMode.Stacked. Heatmap is commented out.

7.3 Metric Display Formatting

SeriesName component renders label sets as metricName{label="value", ...}:

Number display in DataTable: Raw string values from API, no formatting applied.

Duration formatting: Uses custom formatPrometheusDuration() (e.g., "1h30m") throughout RangeInput, RuleDefinition badges.

Timestamp formatting: formatTimestamp(unix, useLocalTime) used in DataTable, StatusPage, TSDBStatusPage, NotificationsIcon. Respects the useLocalTime user setting.

7.4 Label/Badge Display

LabelBadges component is a performance-critical custom implementation. Comment in code explains: "We build our own Mantine-style badges here for performance reasons. We have pages with thousands of labels and care about their performance more than other badges."

Implementation: Plain <span> elements with CSS class .labelBadge that manually replicates Mantine Badge dimensions using var(--mantine-scale) for scaling. Single DOM node vs Mantine's two-node structure.

EndpointLink component parses URL query parameters and renders them as Mantine Badge size="sm" variant="light" color="gray" pills. IPv6 with Zone ID causes URL parse failure β€” handled with synthetic URL construction.

TargetLabels component shows active labels by default, with a collapse toggle to reveal discovered (pre-relabeling) labels in blue styling.

7.5 PromQL AST Tree View

TreeNode is a recursive component that:

  1. Traverses the PromQL AST (from @prometheus-io/codemirror-promql's AST types)
  2. For each node, executes an instant query to get result stats (series count, label cardinalities, examples)
  3. Uses a bottom-up execution order: child nodes report their state to parents; a node only queries when all children report success
  4. Matrix selectors are wrapped in last_over_time() to get cheaper stats
  5. Connector lines between nodes are positioned absolutely using getBoundingClientRect() comparisons in useLayoutEffect
  6. Binary expression nodes render children above and below; other nodes render children below

7.6 Health State System

Three overlapping health state vocabularies are used:

ContextStates
Alert rulesinactive, pending, firing, unknown
Rule healthok, err, unknown
Target healthup, down, unknown
Service discoveryactive, dropped

Each uses the same badge CSS classes but the mapping function is repeated in every page file rather than centralized:

// AlertsPage.tsx
const alertStateToClass = (state) =>
  state === "inactive" ? healthOk : state === "pending" ? healthWarn : ...

// RulesPage.tsx
const healthBadgeClass = (state) =>
  state === "ok" ? healthOk : state === "err" ? healthErr : ...

// ScrapePoolsList.tsx
const healthBadgeClass = (state) =>
  state.toLowerCase() === "up" ? healthOk : state.toLowerCase() === "down" ? healthErr : ...

No shared stateToClass(state: string, vocabulary: 'alert'|'rule'|'target') function exists.

8. Summary of Key Findings

Critical Issues

  1. Header is permanently dark β€” rgb(65, 73, 81) hardcoded, ignores user's light/dark theme preference. The Prometheus logo area never adapts. This is the most visually jarring inconsistency.
  2. localStorage key collision β€” AlertsPage and RulesPage share "alertsPage.showEmptyGroups". This is a functional bug where showing/hiding empty groups in one page affects the other.
  3. TreeNode accessibility β€” Clickable boxes with no keyboard support (onClick only, no role/tabIndex/onKeyDown).

High-Priority Inconsistencies

  1. Dual health-border implementation β€” AlertsPage uses CSS module classes; RulesPage uses inline styles with different color scale steps.
  2. Icon size constants duplicated β€” { width: "0.9rem", height: "0.9rem" } defined locally in 3 files despite acknowledged TODO: duplicated everywhere.
  3. targetPoolDisplayLimit = 20 defined in two files β€” TargetsPage.tsx and ServiceDiscoveryPage.tsx independently.
  4. Duplicate page structure β€” AlertsPage/RulesPage and TargetsPage/ServiceDiscoveryPage share near-identical structure without abstraction.

Design Token Observations

  1. No custom typography β€” No font family, font scale, or heading style is configured in createTheme(). The app fully relies on Mantine defaults.
  2. No custom spacing scale β€” All spacing uses Mantine defaults plus scattered numeric literals.
  3. The color pool is vast but blunt β€” 942 algorithmically-generated colors with no perceptual ordering. First entries are mid-saturation greens/teals which may not be visually distinctive at small chart sizes.
  4. codebox-bg custom color registered but barely used β€” Defined in createTheme() but not referenced in component code.

Positive Patterns

  1. Consistent light-dark() usage in CSS modules β€” badge, panel, accordion, and chart styles all properly handle both modes via light-dark().
  2. Performance-conscious design β€” LabelBadges avoids Mantine Badge overhead, SeriesName falls back to plain text for >1000 series, CustomInfiniteScroll limits DOM nodes, useMemo on filtered/rendered lists.
  3. URL-persistent filter state β€” All major filters (search, state/health filter, pagination, active tab) are persisted in URL via use-query-params, enabling bookmarkable and shareable views.
  4. Suspense + ErrorBoundary composition is consistent throughout β€” all async page data uses useSuspenseAPIQuery and is wrapped in coordinated Suspense/ErrorBoundary pairs that reset on route change.
  5. Settings architecture β€” Redux + localStorage middleware cleanly separates UI settings with good TypeScript typing. Per-setting localStorage keys are exported as named constants.