Behavior¶
Field Rendering System¶
Fields use mixed visual treatment โ position, glyph prefix, and typography identify each field. No uniform label/value rows on tiles/nodes.
Position Contract (Matrix Tiles)¶
| Field | Position | Treatment | Class |
|---|---|---|---|
version |
Headline row, left | JetBrains Mono 11px 600, --ink-0 |
.ver |
actor |
Headline row, right | @ prefix, mono 10.5px, --ink-1 |
.fld-actor |
happened_at |
Headline row, far right | Elapsed ("3h ago"), mono 10px, --ink-2 |
.fld-time |
ref |
Mid-row left cluster | โ prefix, mono 10.5px, --ink-1 |
.fld-ref |
sha |
Mid-row left cluster | Plain mono hex, 10.5px, --ink-1 |
.fld-sha |
run_url |
Mid-row right cluster | โ "run" dashed link, indigo (#a8a0ff) | .fld-runurl |
run_number |
Mid-row right cluster | # prefix, mono 10.5px | .fld-runno |
parrent_deployments |
Full-width tail row | โต N parents, mono 10px, --ink-2 |
.fld-parents |
Next badge (nextStatus) |
.ctx-row below attrs |
.ctx-badge: icon + status word + version; hue from next status. Present only when a next deployment exists. Never drives tile colour. |
.ctx-row / .ctx-badge |
| Never-deployed chip | Replaces primary icon in headline | Status chip in context-status hue; tile surface is neutral (s-never-deployed). Present only when slot has no prior effective deployment. |
.status-chip |
Position Contract (Swimlane Nodes)¶
| Field | Grid Position | Treatment |
|---|---|---|
version |
Top row, col 1 (left) | Mono 10.5px 500, --ink-2 (demoted) |
happened_at |
Top row, col 2 (right) | Mono 9px, --ink-2, margin-left: auto |
ref |
Body col 1, row 1 | โ prefix, 9.5px |
sha |
Bottom-left (env row, col 1) | Plain mono hex, 9.5px |
run_url + run_number + actor |
Body col 2 (right cluster) | Same glyphs as Matrix, 9.5px |
environment |
Bottom-right (env row, col 2) | Inter 11px 600, --ink-0, lowercase (PROMOTED) |
Next badge (nextStatus) |
.ctx-row below body |
.ctx-badge: icon + status word + version; hue from next status. Present only when a next deployment exists. Never drives card colour. |
| Never-deployed chip | Replaces primary icon in top row | Status chip in context-status hue; card surface is neutral (s-never-deployed). Present only when node has no prior effective deployment. |
Details Surfaces (Drawer & Inspector)¶
Use explicit label/value rows โ every visible domain-model field rendered regardless of attribute-picker state. happened_at shows both elapsed and absolute UTC.
Bottom-Section Fallback Chain (Split Tiles / Cards)¶
The split-bottom section shows a single identifier from the last-successful (current) deployment. Required on both matrix tiles and swimlane cards (S2โS4 states). The first non-empty field wins:
Interactions¶
| Trigger | Target | Result |
|---|---|---|
| Click tab | Segmented control | Switch Matrix โ Swimlanes โ Feed โ Analytics view. Close popovers. Swap field picker content. Rebuild swimlane layout on switch-to-vis; re-render the Feed log on switch-to-feed; re-sync feed-dock visibility and the topbar dock-toggle's disabled state on every switch (dock hidden + toggle disabled only while Feed is active). |
| Type in filter input | Matrix rows | Hide rows whose component name doesn't include the query (case-insensitive substring). |
| Click failures toggle | Matrix rows | Toggle .is-on. When ON, hide rows with no failed states. |
Hover .ver |
All tiles | Amber-highlight all .ver spans and their parent .slots that share the same version string. |
| Click Matrix tile | Drawer | Open history drawer for that (service, environment) slot. Overlay + slide-in animation. History shows all 8 statuses as distinct entries (pip in status hue); next deployment leads the list. |
| Click Swimlane node | Inspector | Select node (accent ring). Inspector shows effective deployment's fields first, dotted separator, then next group (if present). |
| Click chevron (โบ) | Swimlane lane | Toggle lane collapsed โ expanded. Collapsed shows vector (chain of real vis-cards ending at newest event). State persists to localStorage. |
| Click "Collapse/Expand all" | All swimlane lanes | Flip every lane simultaneously. Button label updates to reflect current state. |
| Click "Auto-scroll to change" toggle | Swimlane behavior | Toggle ON/OFF. When ON + lane off-screen: "Simulate event" scrolls lane into view after event fires โ in both collapsed and expanded states. Persists to localStorage. |
| Click "Simulate event" | Lane tip card (any state) | Advance tip node's status, rebuild via the unified renderer, flash the tip card (svFlash animation) โ collapsed = vector tip card, expanded = newest-event DAG node card. Scroll lane into view if auto-scroll ON and lane off-screen. |
Live effective-status change (SSE, lastEffectiveEvent) |
Matrix tile | One-shot accent flash (tile-flash animation) on the (service, environment, namespace) tile whose slot changed. Effective statuses only (success / in-progress / failure) โ context statuses (pending/queued/waiting/cancelled/rejected) don't flash. Fades ~1.2s, then clears. Mirrors the Swimlanes tip-card flash above (#309); no auto-scroll โ the Matrix is a grid, not a scrollable lane list. (#398) |
| Click feed-dock toggle (topbar) | Feed dock | Toggle dock open/closed. Persist the preference to localStorage. Disabled while the Feed view is active. (#397) |
| Click dock ร / "Open feed โ" | Feed dock | ร closes the dock (updates the persisted preference). "Open feed โ" switches to the Feed view tab. (#397) |
| Type in Feed search input | Feed view log | Debounce, then fire the server-side q param on GET /api/deployments โ searches the full history, not only rendered rows. Resets pagination and re-renders from the first page. (#397) |
| Click group/flat toggle (Feed view or dock) | Feed view + Feed dock | Toggle the shared feedGrouped state; both surfaces re-render immediately from the same state; persists to localStorage. (#397) |
| Click group roll-up row (Feed view or dock) | Feed row | Toggle that group's expand/collapse, revealing/hiding its .feed-group-detail child rows. Clicking the run link does not toggle the row. (#397) |
| Scroll near bottom of Feed log | Feed view log | If more pages remain, append the next page via the cursor / next_cursor pair, with a brief loading tail row; show an end-of-history tail row once next_cursor is null. (#397) |
| Live deployment event (SSE) | Feed dock (if open) + Feed view (if active) | Prepend the new event newest-first โ to a new or existing group by deployment_id โ and play the row flash animation. When a search (q) is active, only re-renders if the new event matches it. (#397) |
| Click icon button | Popover | Toggle popover open/closed. Close any other open popover first. |
| Click field toggle | View content | Toggle field on/off in the active view. Tiles/nodes resize. Swimlanes recompute layout. |
| Click predicate radio | Correlation picker | Single-select. Disable time-window when "explicit parent" is selected. |
| Click theme option | <html data-theme> |
Switch theme. Persist to localStorage('theme'). Rebuild swimlane SVG (baked colors). |
| Click outside popover | All popovers | Close all open popovers. |
| Escape key | Drawer / popovers | Close drawer and/or all popovers. |
| Click overlay / ร button | Drawer | Close history drawer. |
Presets¶
Save¶
- User types a name (max 60 chars) in the name input and clicks Save (or presses Enter).
captureSettings()snapshots the full live UI state into asettingsobject.- Envelope
{ version: 1, name, settings }is appended to thedd:presetsarray inlocalStorage. - The new preset becomes the active preset; badge dot becomes visible.
Apply¶
- User clicks a preset row (or its โ Apply action button).
applySettings(p.settings)writes each captured key back through the same code paths used by direct UI interaction โ no short-circuit: theme flip triggers swimlane rebuild; field changes trigger matrix/swimlane re-render; filter changes callapplySvcFilter(); column changes trigger grid recompute;feedGrouped/feedDockOpenchanges callfeedSetGrouped()/feedSetDockOpen(). View is route-driven โ applyingviewcallssetView()and realigns the route/active tab to match (thewithViewRealignpattern), not just an internal re-render, so a preset can never leave the URL and the visibly active tab out of sync.- The applied preset is marked
.is-active: accent border + tint,aria-current="true", and an "Active" badge on its row. The name is persisted tolocalStorage(dd:presetActive), so the indicator survives page reload. - Unknown/missing fields in the snapshot fall back to current defaults โ no error thrown.
Clone¶
- User clicks โ Clone.
- A deep copy of the envelope is created with name
<original> (copy). - Clone is inserted immediately after the source in the list. The active indicator does NOT transfer to the clone โ the previously active preset (if any) remains active.
Update¶
- User clicks Update on a preset row.
window.confirm()prompt:Update preset "<name>" with the current settings?โ user must confirm.- On confirm:
captureSettings()snapshots the current live UI state; the preset'ssettingsfield is replaced with the snapshot. Name and version remain unchanged.
Rename¶
- User clicks โ Rename.
- The name span is replaced in-place with a text input pre-filled with the current name.
- Commit on Enter or blur; cancel on Escape (restores the list without saving).
- If the renamed preset was the active one, the stored
dd:presetActivekey updates to the new name โ the active indicator follows the rename.
Delete¶
- User clicks โ Delete.
window.confirm()prompt:Delete preset "<name>"?โ user must confirm.- On confirm: the preset is removed from the array,
localStorageis updated. If the deleted preset was the active one, the active indicator is cleared (dd:presetActiveremoved) โ no preset is considered active.
Reset All Settings¶
- User clicks Reset all settings in the Presets popover.
window.confirm()prompt:Reset all settings to defaults?โ user must confirm.- On confirm: every captured setting key is removed from
localStorageand each setting returns to its framework default (theme โdark; field pickers โ all ON; filters โ clear; view โ Matrix (route realigned viawithViewRealign); feed dock โ closed; feed grouping โ ON; etc.). - The active indicator is cleared (
dd:presetActiveremoved) โ no preset is considered active after a reset. The saved preset list is not affected; only the active tracking is cleared.
Export (per-preset)¶
- Produces a single-envelope JSON file:
dd-preset-<slug>.json. - Slug is the name lowercased with non-alphanumeric runs replaced by
-. - File contains the full envelope (
{ version, name, settings }) โ one preset per file.
Import (per-preset)¶
- User clicks โฌ Import; a hidden file input (
accept=".json") opens the OS picker. - File is read as text and JSON-parsed.
- Validation: must be a non-array object with
version === 1, non-empty stringname, and a non-nullsettingsobject. Array inputs (old bulk format) are rejected. - Name-clash: if a preset with the same name already exists, a numeric suffix
(2),(3), โฆ is appended until unique. - Envelope is appended to the list;
localStorageupdated; list re-rendered. - The file input is reset after each import so the same file can be re-imported.
Theme System¶
Implementation¶
- Pre-paint bootstrap: inline
<script>in<head>readslocalStorage.themeand setsdata-themebefore any CSS evaluates โ prevents flash of wrong theme. - Token scoping: dark tokens on
:root/:root[data-theme="dark"]. Light tokens on:root[data-theme="light"]. Auto tokens inside@media (prefers-color-scheme: light) { :root[data-theme="auto"] { ... } }. - Persistence:
localStorage.setItem('theme', value)on every switch. Default =dark. - Rebuild: theme switch invalidates swimlane SVG (status colors baked at build time) โ
buildSwim()must re-run.
Extension Behavior¶
Badge State Mapping¶
The toolbar badge reflects the highest-priority state across all watched deployments:
| Priority | Condition | Badge state |
|---|---|---|
| 1 (highest) | โฅ 1 failure in watch scope |
Coral + failure count |
| 2 | โฅ 1 in-progress in watch scope (no failures) |
Amber + in-flight count |
| 3 | All watched deployments succeeded or no data | Idle (logo only, no overlay) |
- Count reflects the number of deployments in that state, not unique services.
- Watch scope = services ร environments passing the current include/exclude filter.
- Badge clears immediately when Watching switch is turned OFF.
Toast Click-to-Run¶
- Clicking anywhere on a notification toast navigates to
run_urlin a new browser tab. - The "View run #NNN" inline link within the toast body navigates to the same
run_url. - Both paths open the CI/CD run directly โ not the dashboard.
Master Switch Gating¶
When the Watching switch is set to OFF:
- SSE connection is closed (no
LISTEN/NOTIFYconsumption). - Toolbar badge is cleared to idle state.
- No browser notifications are fired.
- Filter controls (services, environments, mode) are visually dimmed and non-interactive.
- State is persisted immediately to extension storage.
Turning the switch back ON re-opens the SSE connection and resumes all activity.
Include/Exclude Watch Filter Semantics¶
The mode control determines how the service and environment checkbox selections are interpreted:
| Mode | Checked item behaviour | Unchecked item behaviour |
|---|---|---|
| Watch all except (default) | Excluded from watch scope | Included in watch scope |
| Watch only | Included in watch scope | Excluded from watch scope |
An empty selection in "Watch only" mode results in an empty watch scope โ no badge updates or notifications.
Server-Side Excludes¶
Distinct from the client watch filter above. Two separate server-side excludes operate at different tiers:
| Dimension | Client watch filter | Fetcher GITHUB_WORKFLOW_EXCLUDE |
API SERVICE_EXCLUDE |
|---|---|---|---|
| Where configured | Per-browser, in the extension | Fetcher container env var | API container env var |
| Identity matched | Per-browser service/environment prefs | owner/repo/workflow โ GitHub-specific 3-segment glob |
Opaque namespace/service identity glob |
| Scope | Badge updates and notifications only | GitHub fetcher poll only โ matching workflows never ingested | Write ingest + all read surfaces (/api/services, /api/matrix, /api/deployments, /api/events/stream, /api/analytics/*) โ excluded services contribute to no analytics aggregate |
| Effect on storage | None โ all events remain in storage | Excluded workflows cost no CI/CD API quota and produce no rows | API write: matching ingest rejected 403. API read: already-stored events hidden (storage unchanged) |
| Precedence | Applied client-side after the API response | Applied before any ingest attempt | Applied server-side before any data reaches the client |
| Provider scope | n/a | GitHub adapter only โ future adapters (Azure DevOps, Jenkins, โฆ) add their own analogous exclude | Provider-agnostic โ applies to all ingested events regardless of source |
GITHUB_WORKFLOW_EXCLUDE uses three clean segments (owner/repo/workflow). GitHub identifiers never contain /, so * matches within each segment. Example: acme/*/canary excludes the canary workflow across all acme repos.
SERVICE_EXCLUDE matches the event's opaque namespace/service identity. A pattern without / matches the service segment across all namespaces; a pattern with / is globbed against the full composite, where * spans /.
Both server-side excludes are invisible to the extension โ the watch filter only sees the services the API exposes. A service excluded by SERVICE_EXCLUDE will not appear in the extension's services list and cannot be added to the watch scope.
Responsive Rules¶
- Content-driven sizing: tiles and node cards size to their currently visible content. Toggling fields off โ elements shrink.
- No truncation:
version(up to 50 chars) always renders fully, single-line. Columns/cards expand to fit. - Horizontal scroll: both Matrix (
.matrix-shell) and Swimlanes (.vis-canvas) scroll horizontally when content exceeds viewport. - Sticky service column:
position: sticky; left: 0on.row-head. Requires opaque background (--glass-strong+ backdrop-filter). - No overlap: deployment elements never overlap in either view. Swimlane per-rank column spacing = max card width in rank + fixed gap.
- Tile vertical packing: content distributes across available height with no large empty middle (
margin-top: autoon attrs pushes them to bottom). - Swimlane recompute: on every attribute toggle, lane heights, rank-column positions, and canvas size recompute via a measurement pass.
- Dense packing: swimlane lanes pack with minimal inter-lane (~8px) and inter-track (~8px) gaps.
- Split-tile containment: bottom content stays within cell bounds; never bleeds into adjacent rows.