Files
triggerdotdev--trigger.dev/apps/webapp/app/tailwind.css
T
Eric Allam c0b84595a3 feat(webapp): hosted webhook ingress, delivery pipeline, and dashboard (#4344)
## Summary

The server half of hosted webhooks: the public ingress endpoint,
signature verification, the delivery pipeline (Postgres partitioned
storage + ClickHouse for ordering), the in-app partition manager, the
HTTP API, and the dashboard (Deliveries, Endpoints, and the in-app test
console).

The public SDK and docs half is #4537. That PR carries the user-facing
API (`webhook()`, `chat.event` / `chat.channels`, the
`@trigger.dev/slack` connector) and builds on the shared
`@trigger.dev/core` schemas that ship here.

## Shipping behind a flag

A `WEBHOOK_ENABLED` env var (default off) gates the public ingress route
and the engine worker plus partition cron, so merging and deploying this
changes nothing in production until it is flipped on per environment.
The dashboard is separately gated per org by the `hasWebhooksAccess`
feature flag.

## Note on packages

This PR includes the `@trigger.dev/core` schema additions the server
compiles against, but carries no changeset. Core is not consumed
independently of the SDK, so it is released together with the SDK via
#4537. Keeping its changeset off `main` means no release cut from `main`
publishes it early.
2026-08-16 14:33:42 +01:00

1001 lines
36 KiB
CSS

@import "react-grid-layout/css/styles.css" layer(base);
@import "react-resizable/css/styles.css" layer(base);
@import "tailwindcss";
@import "tw-animate-css";
@import "tailwind-scrollbar-hide/v4";
@plugin "@tailwindcss/forms";
@plugin "@tailwindcss/typography";
@plugin "tailwind-scrollbar";
/* Streamdown ships prebuilt Tailwind classes - scan it for used utilities */
@source "../node_modules/streamdown/dist";
/* Raw palettes and static tokens - theme-independent, never override per theme */
@theme {
--font-sans: "Geist Variable", "Helvetica Neue", Helvetica, Arial, sans-serif;
--font-mono: "Geist Mono Variable", monaco, Consolas, "Lucida Console", monospace;
--text-xxs: 0.65rem;
--text-xxs--line-height: 0.75rem;
--text-xxs--letter-spacing: -0.01em;
--text-xxs--font-weight: 500;
--text-2sm: 0.8125rem;
--text-2sm--line-height: 0.875rem;
--text-2sm--letter-spacing: -0.01em;
--text-2sm--font-weight: 500;
--color-charcoal-100: #e8e9ec;
--color-charcoal-200: #d7d9dd;
--color-charcoal-300: #b5b8c0;
--color-charcoal-400: #878c99;
--color-charcoal-500: #5f6570;
--color-charcoal-550: #4d525b;
--color-charcoal-600: #3b3e45;
--color-charcoal-650: #2c3034;
--color-charcoal-700: #272a2e;
--color-charcoal-750: #212327;
--color-charcoal-775: #1c1e21;
--color-charcoal-800: #1a1b1f;
--color-charcoal-850: #15171a;
--color-charcoal-900: #121317;
--color-charcoal-950: #0d0e12;
--color-charcoal-1000: #0b0c0f;
--color-apple-100: #e4ffc9;
--color-apple-200: #cfffa0;
--color-apple-300: #bfff81;
--color-apple-400: #afff62;
--color-apple-500: #a8ff53;
--color-apple-600: #82d134;
--color-apple-700: #6fb12f;
--color-apple-750: #5e932a;
--color-apple-800: #45711a;
--color-apple-850: #2e4e10;
--color-apple-900: #20370a;
--color-apple-950: #152506;
--color-mint-50: #f0fdf4;
--color-mint-100: #ddfbe6;
--color-mint-200: #bdf5d0;
--color-mint-300: #87eba9;
--color-mint-400: #4fd97e;
--color-mint-500: #28bf5c;
--color-mint-600: #1b9e48;
--color-mint-700: #197c3c;
--color-mint-800: #196233;
--color-mint-900: #16512c;
--color-mint-950: #062d15;
--color-sun-50: #fdfee8;
--color-sun-100: #fdffc2;
--color-sun-200: #ffff89;
--color-sun-300: #fff852;
--color-sun-400: #fdea12;
--color-sun-500: #eccf06;
--color-sun-600: #cca302;
--color-sun-700: #a37505;
--color-sun-800: #865b0d;
--color-sun-900: #724b11;
--color-sun-950: #432705;
--color-lavender-50: #f4f2ff;
--color-lavender-100: #eae8ff;
--color-lavender-200: #d7d4ff;
--color-lavender-300: #bab2ff;
--color-lavender-400: #826dff;
--color-lavender-500: #7655fd;
--color-lavender-600: #6532f5;
--color-lavender-700: #5620e1;
--color-lavender-800: #481abd;
--color-lavender-900: #3d189a;
--color-lavender-950: #230c69;
--text-shadow-custom: 1px 1px 1px rgba(0, 0, 0, 0.5);
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
--animate-tile-scroll: tile-move 0.5s infinite linear;
--animate-tile-scroll-offset: tile-move-offset 0.5s infinite linear;
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
}
@keyframes accordion-up {
from {
height: var(--radix-accordion-content-height);
}
to {
height: 0;
}
}
@keyframes tile-move {
0% {
background-position: 0px;
}
100% {
background-position: 8px;
}
}
@keyframes tile-move-offset {
0% {
background-position: -1px;
}
100% {
background-position: 7px;
}
}
}
/*
Semantic tokens - the themable layer. Utilities compile to var(--color-*),
so a theme overrides these vars in a scoped selector, e.g.
[data-theme="light"] { --color-text-bright: #1a1b1f; }
Defaults below are the original dark theme.
*/
@theme {
/* Text colors */
--color-primary: var(--color-apple-500);
--color-secondary: var(--color-charcoal-650);
--color-tertiary: var(--color-charcoal-700);
--color-text-link: var(--color-lavender-400);
--color-text-faint: var(--color-charcoal-500);
--color-text-dimmed: var(--color-charcoal-400);
--color-text-bright: var(--color-charcoal-200);
/* Surfaces, from deepest to most raised */
--color-background-deep: var(--color-charcoal-900);
--color-background-dimmed: var(--color-charcoal-850);
--color-background-bright: var(--color-charcoal-800);
--color-background-hover: var(--color-charcoal-750);
--color-surface-hover-subtle: var(--color-charcoal-750);
--color-background-raised: var(--color-charcoal-700);
--color-surface-selected: var(--color-charcoal-650);
--color-surface-selected-hover: var(--color-charcoal-600);
--color-surface-control: var(--color-charcoal-600);
--color-surface-control-hover: var(--color-charcoal-550);
--color-surface-control-active: var(--color-charcoal-500);
--color-input-bg: var(--color-charcoal-750);
/* Borders, from subtlest to most visible */
--color-grid-dimmed: var(--color-charcoal-750);
--color-grid-bright: var(--color-charcoal-700);
/* Blend of border-bright over the selected card, so it tracks whatever ramp
border-bright is on - the dark themes need no contrast entry of their own */
--color-border-selected: color-mix(in srgb, var(--color-border-bright) 50%, var(--color-surface-selected));
--color-border-bright: var(--color-charcoal-600);
--color-border-brighter: var(--color-charcoal-550);
--color-border-brightest: var(--color-charcoal-500);
--color-success: var(--color-mint-500);
--color-pending: var(--color-blue-500);
--color-warning: var(--color-amber-500);
--color-error: var(--color-rose-600);
/* Environment colors */
--color-dev: var(--color-pink-500);
--color-staging: var(--color-orange-400);
--color-prod: var(--color-mint-500);
--color-preview: var(--color-yellow-400);
/* Icon colors */
--color-tasks: var(--color-blue-500);
--color-runs: var(--color-indigo-500);
--color-batches: var(--color-pink-500);
--color-schedules: var(--color-yellow-500);
--color-queues: var(--color-purple-500);
--color-query: var(--color-blue-500);
--color-metrics: var(--color-green-500);
--color-customDashboards: var(--color-charcoal-400);
--color-deployments: var(--color-blue-500);
--color-concurrency: var(--color-amber-500);
--color-limits: var(--color-purple-500);
--color-regions: var(--color-green-500);
--color-logs: var(--color-pink-500);
--color-tests: var(--color-pink-500);
--color-apiKeys: var(--color-amber-500);
--color-environmentVariables: var(--color-pink-500);
--color-alerts: var(--color-red-500);
--color-projectSettings: var(--color-blue-500);
--color-orgSettings: var(--color-blue-500);
--color-docs: var(--color-blue-500);
--color-bulkActions: var(--color-emerald-500);
--color-aiPrompts: var(--color-blue-500);
--color-aiMetrics: var(--color-green-500);
--color-errors: var(--color-amber-500);
--color-agents: var(--color-purple-500);
--color-webhooks: var(--color-teal-500);
--color-sessions: var(--color-pink-500);
--color-playgrounds: var(--color-fuchsia-500);
--color-models: var(--color-violet-500);
--color-previewBranches: var(--color-yellow-500);
}
/* Callout variant accents (see components/primitives/Callout.tsx) */
@theme {
--color-callout-warning-text: var(--color-yellow-200);
--color-callout-error-text: var(--color-rose-200);
--color-callout-success-text: var(--color-green-200);
--color-callout-docs: var(--color-blue-400);
--color-callout-docs-text: var(--color-blue-200);
--color-callout-pending: var(--color-blue-400);
--color-callout-pending-bg: var(--color-blue-800);
--color-callout-pending-text: var(--color-blue-300);
--color-callout-pricing: var(--color-indigo-400);
--color-callout-pricing-bg: var(--color-indigo-800);
--color-callout-pricing-text: var(--color-indigo-300);
}
/*
Theme overrides. A theme re-maps ONLY the semantic tokens above, never the raw
palette values. That invariant is what lets a future [data-contrast] overlay
re-map semantic tokens on top of whichever theme is active.
*/
/*
System preference unified accents (dark+light); Classic keeps the original
set. One shared value per accent token, >=3:1 against both dark cards and
white. Accents whose Classic default already clears both modes (blue-500,
indigo-500, pink-500, purple-500, red-500, violet-500, fuchsia-500,
rose-600) are not repeated here. Text-sized tokens (text-link, callout
text) stay per-mode: no color reaches 4.5:1 on both #1a1b1f and #ffffff.
Dark derives everything else (monochrome surfaces etc.) from Classic.
*/
:is([data-theme="dark"], [data-theme="light"]) {
/* Status */
--color-success: var(--color-mint-600);
--color-warning: var(--color-amber-600);
/* Env set: pink dev and green prod as in Classic, preview moves to blue
(yellow's unified mid-tone reads muddy brown). Staging stays warm orange
and is the one per-mode env color - see the light block. */
--color-prod: var(--color-mint-600);
--color-preview: var(--color-blue-500);
/* Icons */
--color-schedules: var(--color-yellow-700);
/* Matches the preview env color */
--color-previewBranches: var(--color-blue-500);
--color-metrics: var(--color-green-600);
--color-regions: var(--color-green-600);
--color-aiMetrics: var(--color-green-600);
--color-bulkActions: var(--color-emerald-600);
--color-concurrency: var(--color-amber-600);
--color-errors: var(--color-amber-600);
--color-apiKeys: var(--color-amber-600);
/* Queue charts read blue here; Classic keeps the queues purple */
--color-queues-chart: var(--color-blue-500);
--color-queues-chart-ref: var(--color-charcoal-500);
/* Callout accents (callout text/bg tints stay per-mode) */
--color-callout-docs: var(--color-blue-500);
--color-callout-pending: var(--color-blue-500);
--color-callout-pricing: var(--color-indigo-500);
/* Amber run statuses: three distinct steps, Classic's dark-to-light order
kept (waiting-for-deploy < pending-version < paused) */
--color-run-waiting-for-deploy: var(--color-amber-700);
--color-run-pending-version: #c76508;
--color-run-paused: var(--color-amber-600);
/* Nudged under 3:1's ceiling on white; stays lighter than system-failure */
--color-run-timed-out: #ed5f74;
}
/* System themes drop decorative icon accents to monochrome; Classic keeps the
colored icons. side-menu-active-icon is set in SideMenuItem for the active
nav item; system-mono-icon marks section-header icons (e.g. the limits page). */
:is([data-theme="dark"], [data-theme="light"]) :is(.side-menu-active-icon, .system-mono-icon) {
color: var(--color-text-bright);
}
/* System themes: status/env labels follow the surrounding text color, only the
icon keeps its tint. Classic colors both. Set in EnvironmentLabel and the
status combo components. */
:is([data-theme="dark"], [data-theme="light"]) .system-mono-label {
color: inherit;
}
/* Tinted status chips respond to the contrast control: a ring in the chip's
own text color fades in as contrast rises, so the soft tint keeps its
footprint but the chip gains definition. Transparent at contrast 0;
Classic never sets the variable. Marker set by the chip components. */
:is([data-theme="dark"], [data-theme="light"]) .contrast-chip {
box-shadow: inset 0 0 0 1px
color-mix(in srgb, currentcolor calc(var(--theme-contrast, 0) * 70%), transparent);
}
/*
Code syntax palette - the trigger-dark highlight theme, shared by the shiki
theme (streamdown) and the prism theme (CodeBlock). Consumed from JS via
var(), so declared static to always be emitted.
*/
@theme static {
--color-code-background: #212327;
--color-code-foreground: #878c99;
--color-code-line-number: #484c54;
--color-code-plain: #d4d4d4;
--color-code-muted: #5f6570;
--color-code-comment: #6f736d;
--color-code-keyword: #e888f8;
--color-code-storage: #8271ed;
--color-code-type: #f770c6;
--color-code-function: #d9f07c;
--color-code-variable: #cccbff;
--color-code-constant: #9c9af2;
--color-code-language: #9b99ff;
--color-code-object-key: #8b89ff;
--color-code-string: #afec73;
--color-code-template-punctuation: #7a78ea;
--color-code-number: #b5cea8;
--color-code-builtin: #e5c07b;
--color-code-attribute: #c39eff;
--color-code-escape: #d7ba7d;
--color-code-regexp: #d16969;
--color-code-regexp-constant: #646695;
--color-code-invalid: #f44747;
--color-code-deleted: #f85149;
--color-code-jsx-text: #d7d9dd;
}
/*
CodeMirror editor palette (see components/code/codeMirrorTheme.ts).
Consumed from JS via var(), so declared static to always be emitted.
*/
@theme static {
--color-editor-background: var(--color-background-deep);
--color-editor-foreground: #abb2bf;
--color-editor-keyword: #c678dd;
--color-editor-name: #9b99ff;
--color-editor-function: #61afef;
--color-editor-constant: #d19a66;
--color-editor-type: #e5c07b;
--color-editor-operator: #56b6c2;
--color-editor-comment: #7d8799;
--color-editor-heading: #e06c75;
--color-editor-string: #afec73;
--color-editor-invalid: #ffffff;
--color-editor-panel-background: #21252b;
--color-editor-highlight-background: rgba(71, 85, 105, 0.2);
--color-editor-tooltip-background: #353a42;
--color-editor-selection: rgb(71 85 105);
--color-editor-cursor: #528bff;
--color-editor-search-match: #72a1ff59;
--color-editor-search-match-outline: #457dff;
--color-editor-search-match-selected: #6199ff2f;
--color-editor-selection-match: #aafe661a;
--color-editor-matching-bracket: rgba(18, 19, 23, 0.9);
--color-editor-matching-bracket-outline: rgba(81, 90, 107, 0.5);
--color-editor-fold-placeholder: #dddddd;
--color-editor-scrollbar-track-active: #131b2b;
--color-editor-scrollbar-thumb: #293649;
--color-editor-scrollbar-thumb-active: #3c4b62;
}
/* Queue chart series colors - queues purple in Classic, overridden to blue in
the System themes; the grey reference series lightens per mode there.
Consumed from JS via var(), so declared static. */
@theme static {
--color-queues-chart: var(--color-queues);
--color-queues-chart-ref: #4d525b;
}
/*
Run-status chart colors. In-between shades are intentional - statuses within
a family (blues, roses, charcoals) are evenly spaced so chart series stay
distinguishable. Referenced from JS via var(--color-run-*), so declared
static to always be emitted.
*/
@theme static {
--color-run-pending: var(--color-charcoal-500);
--color-run-delayed: #6b7580;
--color-run-pending-version: var(--color-warning);
--color-run-waiting-for-deploy: #d97706;
--color-run-executing: var(--color-pending);
--color-run-retrying-after-failure: #2f6fec;
--color-run-dequeued: #4d8ef5;
--color-run-waiting-to-resume: #555d67;
--color-run-paused: #fbbf24;
--color-run-canceled: #78828c;
--color-run-expired: #848d96;
--color-run-interrupted: #d52c4d;
--color-run-completed-successfully: var(--color-success);
--color-run-completed-with-errors: #de405c;
--color-run-system-failure: #e7536c;
--color-run-crashed: #cc193d;
--color-run-timed-out: #f0667b;
}
/*
shadcn/ui tokens used by streamdown's internals. Inline so the vars resolve
where the utility is used - .streamdown-container redefines them locally.
*/
@theme inline {
--color-background: var(--background, #121317);
--color-foreground: var(--foreground, #d7d9dd);
--color-muted: var(--muted, #1c1e21);
--color-muted-foreground: var(--muted-foreground, #878c99);
--color-border: var(--border, #2c3034);
--color-sidebar: var(--sidebar, #15171a);
--color-primary-foreground: var(--primary-foreground, #121317);
}
@custom-variant lg-height (@media (max-height: 750px));
@custom-variant md-height (@media (max-height: 600px));
/* dark: follows the app theme (data-theme on <html>), not the OS preference.
Classic and Dark are both dark-mode themes, so the variant matches both. */
@custom-variant dark (&:where([data-theme="dark"], [data-theme="classic"], [data-theme="dark"] *, [data-theme="classic"] *));
/* system: matches the System preference themes (Dark and Light) but never
Classic - for restyles that must leave Classic untouched. */
@custom-variant system (&:where([data-theme="dark"], [data-theme="light"], [data-theme="dark"] *, [data-theme="light"] *));
/* light: the Light theme only - for values that are fine on every dark theme
but illegible on white. Classic is never matched. */
@custom-variant light (&:where([data-theme="light"], [data-theme="light"] *));
@utility focus-custom {
&:focus-visible {
outline: 1px solid var(--color-text-link);
/* Inset the ring inside the box; at offset 0 the outline sits outside the border edge and gets
clipped by ancestors with overflow hidden/auto (scroll areas, the side menu grid). */
outline-offset: -1px;
border-radius: 5px;
}
}
@utility scrollbar-gutter-stable {
scrollbar-gutter: stable;
}
/* Fade the thumb in only while the pointer is in the scroll area. Uses
::-webkit-scrollbar (no scrollbar-width) so Chrome keeps a classic always-present
bar to reveal, not the auto-hiding overlay; Firefox falls back to standard props.
Thumb color is a registered @property so it can transition. */
@property --sm-sb-thumb {
syntax: "<color>";
initial-value: transparent;
inherits: true;
}
@utility scrollbar-thumb-on-hover {
transition: --sm-sb-thumb 200ms ease;
&::-webkit-scrollbar {
width: 8px;
height: 8px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: var(--sm-sb-thumb);
/* transparent border + padding-box clip = thinner thumb, inset from the channel edges */
border: 2px solid transparent;
border-radius: 9999px;
background-clip: padding-box;
}
@supports (-moz-appearance: none) {
scrollbar-width: thin;
scrollbar-color: var(--sm-sb-thumb) transparent;
}
&:hover {
--sm-sb-thumb: var(--color-surface-control);
}
}
/* Shared stop list for the animated glow utilities below. The gradient itself
is assembled per-element so the animated --gradient-angle resolves there. */
:root {
--glow-gradient-stops: rgb(99 102 241), rgb(245 158 11), rgb(236 72 153), rgb(245 158 11),
rgb(99 102 241);
}
@utility animated-gradient-glow {
position: relative;
overflow: visible;
&::before {
content: "";
position: absolute;
inset: -8px;
z-index: -1;
background: conic-gradient(from var(--gradient-angle), var(--glow-gradient-stops));
border-radius: inherit;
animation: gradient-rotation 3s linear infinite;
pointer-events: none;
filter: blur(0.5rem);
opacity: 0.1;
}
}
@utility animated-gradient-glow-small {
position: relative;
overflow: visible;
&::before {
content: "";
position: absolute;
inset: -1px;
z-index: -1;
background: conic-gradient(from var(--gradient-angle), var(--glow-gradient-stops));
border-radius: inherit;
animation: gradient-rotation 3s linear infinite;
pointer-events: none;
filter: blur(0.2rem);
opacity: 0.3;
}
}
@property --gradient-angle {
syntax: "<angle>";
initial-value: 0deg;
inherits: false;
}
@keyframes gradient-rotation {
0% {
--gradient-angle: 0deg;
}
100% {
--gradient-angle: 360deg;
}
}
@layer base {
/* v4 defaults border color to currentcolor - keep the app-wide default from v3 */
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-grid-bright);
}
body {
@apply bg-background-dimmed text-text-dimmed;
font-feature-settings: "rlig" 1, "calt" 1;
}
/* Text selection styles */
::selection {
@apply bg-text-bright/30 text-text-bright;
}
/* Kept separate from ::selection on purpose: grouping them makes older
Firefox drop the whole rule. */
/* stylelint-disable-next-line selector-no-vendor-prefix */
::-moz-selection {
@apply bg-text-bright/30 text-text-bright;
}
/* Native form controls, scrollbars etc. follow the app theme */
:root {
color-scheme: dark;
}
[data-theme="light"] {
color-scheme: light;
}
/* shadcn charts: https://ui.shadcn.com/docs/components/chart#add-a-grid */
:root {
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
}
/* Classic and Dark share these HSL chart vars - they were the app-wide dark values. */
:is([data-theme="dark"], [data-theme="classic"]) {
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
/* System preference unified chart set (dark+light): Classic's hues, with
chart-2/3 darkened to clear 3:1 on white. Classic keeps the set above. */
:is([data-theme="dark"], [data-theme="light"]) {
--chart-1: 220 70% 50%;
--chart-2: 160 60% 40%;
--chart-3: 30 80% 47%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
/* Override react-grid-layout placeholder color (default is red) */
.react-grid-item.react-grid-placeholder {
background: rgb(99 102 241) !important; /* indigo-500 */
border-radius: 0.375rem !important; /* rounded-md */
}
/* Sidebar reorder grid: subtle placeholder */
.sidebar-reorder-grid .react-grid-item.react-grid-placeholder {
background: var(--color-background-raised) !important;
border-radius: 0.25rem;
opacity: 1 !important;
}
/* Sidebar reorder grid: only animate transform (vertical position), not width/height */
.sidebar-reorder-grid .react-grid-item {
transition: transform 200ms ease !important;
}
/* Override resize handle icon to white */
.react-resizable-handle {
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2IDYiIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiNmZmZmZmYwMCIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSI2cHgiIGhlaWdodD0iNnB4Ij48ZyBvcGFjaXR5PSIwLjMwMiI+PHBhdGggZD0iTSA2IDYgTCAwIDYgTCAwIDQuMiBMIDQgNC4yIEwgNC4yIDQuMiBMIDQuMiAwIEwgNiAwIEwgNiA2IEwgNiA2IFoiIGZpbGw9IiNmZmZmZmYiLz48L2c+PC9zdmc+') !important;
}
.react-resizable-handle::after {
border-bottom-color: rgba(255, 255, 255, 0.4) !important;
border-right-color: rgba(255, 255, 255, 0.4) !important;
border-top-color: rgba(255, 255, 255, 0.4) !important;
border-left-color: rgba(255, 255, 255, 0.4) !important;
}
}
@layer components {
@supports (-webkit-hyphens: none) {
.safari-only {
@apply border-b border-grid-dimmed;
}
}
}
/* Streamdown markdown styling */
.streamdown-container {
/* Streamdown uses shadcn/ui CSS variables - derive them from our semantic
tokens so they follow the theme. They map Tailwind utility classes like
bg-background, bg-primary, etc. that streamdown uses internally for its
link safety modal, code blocks, and other interactive elements. */
--background: var(--color-background-deep);
--foreground: var(--color-text-bright);
--muted: var(--color-charcoal-775);
--muted-foreground: var(--color-text-dimmed);
--border: var(--color-border-bright);
--primary: var(--color-primary);
--primary-foreground: var(--color-background-deep);
--sidebar: var(--color-background-dimmed);
/* Code block styling */
& [data-streamdown="code-block"] {
@apply rounded-sm my-2 border-charcoal-700;
}
& p {
@apply my-1;
}
& h1,
& h2,
& h3,
& h4,
& h5,
& h6 {
@apply font-semibold text-text-bright mt-2 mb-1;
}
& h1 {
@apply text-base;
}
& h2 {
@apply text-sm;
}
& h3,
& h4,
& h5,
& h6 {
@apply text-xs;
}
& ul,
& ol {
@apply ml-4 my-1;
}
& ul {
@apply list-disc;
}
& ol {
@apply list-decimal;
}
& li {
@apply my-0.5;
}
/* Theme-mapped, not raw charcoal: fenced blocks stay dark via shiki. */
& code:not(pre code) {
@apply px-1 py-0.5 rounded-sm text-text-bright font-mono;
background-color: var(--muted);
}
& blockquote {
@apply border-l-2 border-charcoal-600 pl-3 my-2 italic;
}
& a {
@apply text-text-link hover:underline;
}
& strong {
@apply font-semibold text-text-bright;
}
& em {
@apply italic;
}
& hr {
@apply my-2 border-charcoal-600;
}
& table {
@apply w-full my-2 border-collapse;
}
& th,
& td {
@apply border border-grid-bright px-2 py-1 text-left;
}
& th {
@apply font-semibold;
background-color: var(--muted);
}
/* The body scrolls and sizes the code; streamdown ships it at text-sm. */
& [data-streamdown="code-block-body"] {
@apply text-xs leading-relaxed scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600;
}
}
/*
Light theme. Overrides the themable variables only; raw palettes stay put.
Code/editor values come from the trigger.light VS Code theme.
*/
[data-theme="light"] {
--color-secondary: #ffffff;
--color-input-bg: #ffffff;
/* shadcn vars consumed by charts/streamdown (tooltip cursor fill etc.) */
--background: #ffffff;
--foreground: #1a1b1f;
--muted: #eceef1;
--muted-foreground: #5f6570;
--border: #e2e4e9;
--sidebar: #f6f7f8;
--primary-foreground: #ffffff;
/* Text */
--color-primary: var(--color-apple-600);
--color-tertiary: #eef0f3;
--color-text-link: var(--color-lavender-600);
--color-text-faint: var(--color-charcoal-400);
--color-text-dimmed: var(--color-charcoal-500);
--color-text-bright: var(--color-charcoal-800);
/* Surfaces */
--color-background-deep: #f1f2f4;
--color-background-dimmed: #fbfbfc;
--color-background-bright: #ffffff;
--color-background-hover: #f2f3f5;
/* Cards on white need a gentler hover than the app-wide surfaces */
--color-surface-hover-subtle: #f7f8f9;
--color-background-raised: #e9eaee;
--color-surface-control: #dcdee3;
--color-surface-control-hover: #cfd2d9;
--color-surface-control-active: #b8bcc6;
/* Selection sits lighter than the controls here - on white a soft grey is
already enough to read as selected */
--color-surface-selected: #eff0f2;
--color-surface-selected-hover: #e7e9ec;
/* Borders */
--color-grid-dimmed: #eceef1;
--color-grid-bright: #e2e4e9;
--color-border-selected: #e0e2e6;
--color-border-bright: #d2d5db;
--color-border-brighter: #b9bdc7;
--color-border-brightest: #9ba1ad;
/* Status/env/icon accents live in the unified dark+light block above;
dev keeps its Classic pink-500, which passes on white too. */
/* Staging is the one per-mode env color: warm orange has no clean unified
mid-tone, so dark keeps Classic's bright orange-400 and light deepens it */
--color-staging: var(--color-orange-600);
/* Grey chart reference series, light enough not to dominate on white */
--color-queues-chart-ref: #c3c7cf;
/* Neutral run statuses: soft light grays for sparkbars and charts */
--color-run-pending: #ccd0d6;
--color-run-delayed: #d3d6db;
--color-run-waiting-to-resume: #c5c9d0;
--color-run-canceled: #d8dbdf;
--color-run-expired: #dee0e4;
/* Monochrome icon gray - per-mode, not part of the unified accent set */
--color-customDashboards: var(--color-charcoal-500);
/* Callout text/bg - deep tints instead of the dark theme's pastels; text
can't unify across modes (see the unified accents block) */
--color-callout-warning-text: var(--color-yellow-800);
--color-callout-error-text: var(--color-rose-700);
--color-callout-success-text: var(--color-green-800);
--color-callout-docs-text: var(--color-blue-800);
--color-callout-pending-bg: var(--color-blue-100);
--color-callout-pending-text: var(--color-blue-800);
--color-callout-pricing-bg: var(--color-indigo-100);
--color-callout-pricing-text: var(--color-indigo-800);
/* Code syntax - trigger.light */
--color-code-background: #ffffff;
--color-code-foreground: #333333;
--color-code-line-number: #a8a8ad;
--color-code-plain: #2e2e4b;
--color-code-muted: #333333;
--color-code-comment: #767a81;
--color-code-keyword: #b114d3;
--color-code-storage: #b114d3;
--color-code-type: #b114d3;
--color-code-function: #6532f5;
--color-code-variable: #404040;
--color-code-constant: #1e1e1e;
--color-code-language: #b114d3;
--color-code-object-key: #222222;
--color-code-string: #262626;
--color-code-template-punctuation: #0879e2;
--color-code-number: #262626;
--color-code-builtin: #3080e0;
--color-code-attribute: #222222;
--color-code-escape: #222222;
--color-code-regexp: #dc3545;
--color-code-regexp-constant: #5f6570;
--color-code-invalid: #dc3545;
--color-code-deleted: #dc3545;
--color-code-jsx-text: #2e2e4b;
/* CodeMirror - trigger.light */
--color-editor-background: #ffffff;
--color-editor-foreground: #2e2e4b;
--color-editor-keyword: #b114d3;
--color-editor-name: #197c3c;
--color-editor-function: #6532f5;
--color-editor-constant: #3080e0;
--color-editor-type: #2980b9;
--color-editor-operator: #333333;
--color-editor-comment: #767a81;
--color-editor-heading: #2c3e50;
--color-editor-string: #262626;
--color-editor-invalid: #dc3545;
--color-editor-panel-background: #f4f4f6;
--color-editor-highlight-background: #f8f8fa;
--color-editor-tooltip-background: #ffffff;
--color-editor-selection: #d9dce3;
--color-editor-cursor: #2e2e4b;
--color-editor-search-match: #0879e226;
--color-editor-search-match-outline: #0879e2;
--color-editor-search-match-selected: #0879e240;
--color-editor-selection-match: #e8e8ed;
--color-editor-matching-bracket: rgba(240, 241, 244, 0.9);
--color-editor-matching-bracket-outline: rgba(160, 166, 180, 0.5);
--color-editor-fold-placeholder: #555555;
--color-editor-scrollbar-track-active: #e8e9ec;
--color-editor-scrollbar-thumb: #c9ccd4;
--color-editor-scrollbar-thumb-active: #aeb3be;
}
/* Streamdown's muted surface has no semantic token (charcoal-775); theme it here */
[data-theme="light"] .streamdown-container {
--muted: #eceef1;
}
/* The timeline label shadow is a Classic legibility aid; the System themes
drop it entirely */
:is([data-theme="dark"], [data-theme="light"]) .text-shadow-custom {
text-shadow: none;
}
/* Neutral timeline points: invert to a light dot with a gray ring */
[data-theme="light"] .timeline-point.bg-surface-control-active {
border-color: var(--color-surface-control-active);
background-color: var(--color-background-bright);
}
/* Run timeline bars: no fade gradient on light */
[data-theme="light"] .timeline-span {
background-image: none;
}
/* On saturated bars the duration label keeps the dark-theme treatment,
but only when the bar is wide enough to contain the label — on narrow
bars the sticky label overflows onto the page background, where the
default dark-on-light text is correct. */
[data-theme="light"] .timeline-span.bg-success,
[data-theme="light"] .timeline-span.bg-error,
[data-theme="light"] .timeline-span.bg-blue-500 {
container-type: inline-size;
}
@container (min-width: 3.5rem) {
[data-theme="light"] .timeline-span.bg-success .text-shadow-custom,
[data-theme="light"] .timeline-span.bg-error .text-shadow-custom,
[data-theme="light"] .timeline-span.bg-blue-500 .text-shadow-custom {
color: #ffffff;
}
}
/* The table row-hover menu wraps the always-visible "Suggest a region" button
in a ring container; on light that ring doubles up with the button's own
border, so drop it here. */
[data-theme="light"] .suggest-region-cell > div > div {
box-shadow: none;
}
/*
Interface contrast (System themes only). --theme-contrast is 0..1, set on
<html> from the dashboard preference. It pulls the whole monochrome scale
apart: text mixes toward the mode's foreground, backgrounds toward its
depth, borders step up. Accents are untouched. srgb mixing keeps contrast 0
byte-identical to the base values, and Classic never reads the variable.
These blocks must stay below the [data-theme="light"] theme block so they
win the cascade.
*/
[data-theme="dark"] {
/* Text brightens toward white */
--color-text-bright: color-mix(in srgb, var(--color-charcoal-200), #fff calc(var(--theme-contrast, 0) * 100%));
--color-text-dimmed: color-mix(in srgb, var(--color-charcoal-400), #fff calc(var(--theme-contrast, 0) * 60%));
--color-text-faint: color-mix(in srgb, var(--color-charcoal-500), #fff calc(var(--theme-contrast, 0) * 45%));
/* Backgrounds deepen toward black, keeping their relative order */
--color-background-deep: color-mix(in srgb, var(--color-charcoal-900), #000 calc(var(--theme-contrast, 0) * 60%));
--color-background-dimmed: color-mix(in srgb, var(--color-charcoal-850), #000 calc(var(--theme-contrast, 0) * 55%));
--color-background-bright: color-mix(in srgb, var(--color-charcoal-800), #000 calc(var(--theme-contrast, 0) * 45%));
--color-background-hover: color-mix(in srgb, var(--color-charcoal-750), #000 calc(var(--theme-contrast, 0) * 35%));
--color-surface-hover-subtle: color-mix(in srgb, var(--color-charcoal-750), #000 calc(var(--theme-contrast, 0) * 35%));
--color-background-raised: color-mix(in srgb, var(--color-charcoal-700), #000 calc(var(--theme-contrast, 0) * 25%));
--color-input-bg: color-mix(in srgb, var(--color-charcoal-750), #000 calc(var(--theme-contrast, 0) * 35%));
/* Controls and borders step up toward white */
--color-surface-control: color-mix(in srgb, var(--color-charcoal-600), #fff calc(var(--theme-contrast, 0) * 12%));
--color-surface-control-hover: color-mix(in srgb, var(--color-charcoal-550), #fff calc(var(--theme-contrast, 0) * 14%));
--color-surface-control-active: color-mix(in srgb, var(--color-charcoal-500), #fff calc(var(--theme-contrast, 0) * 16%));
--color-grid-dimmed: color-mix(in srgb, var(--color-charcoal-750), #fff calc(var(--theme-contrast, 0) * 18%));
--color-grid-bright: color-mix(in srgb, var(--color-charcoal-700), #fff calc(var(--theme-contrast, 0) * 20%));
--color-border-bright: color-mix(in srgb, var(--color-charcoal-600), #fff calc(var(--theme-contrast, 0) * 24%));
--color-border-brighter: color-mix(in srgb, var(--color-charcoal-550), #fff calc(var(--theme-contrast, 0) * 27%));
--color-border-brightest: color-mix(in srgb, var(--color-charcoal-500), #fff calc(var(--theme-contrast, 0) * 30%));
}
[data-theme="light"] {
/* Text deepens toward black */
--color-text-bright: color-mix(in srgb, #1a1b1f, #000 calc(var(--theme-contrast, 0) * 100%));
--color-text-dimmed: color-mix(in srgb, var(--color-charcoal-500), #000 calc(var(--theme-contrast, 0) * 85%));
--color-text-faint: color-mix(in srgb, var(--color-charcoal-400), #000 calc(var(--theme-contrast, 0) * 70%));
/* On white the foregrounds carry the contrast: cards stay white while the
page-behind surfaces darken a touch so panels separate */
--color-background-deep: color-mix(in srgb, #f1f2f4, #000 calc(var(--theme-contrast, 0) * 10%));
--color-background-hover: color-mix(in srgb, #f2f3f5, #000 calc(var(--theme-contrast, 0) * 8%));
/* Radio card surfaces take no ramp at all on white - their contrast is
carried by border-selected below. Deliberately absent: surface-hover-subtle,
surface-selected, surface-selected-hover. */
--color-background-raised: color-mix(in srgb, #e9eaee, #000 calc(var(--theme-contrast, 0) * 10%));
/* Controls and borders push hard toward black - this is where light-mode
contrast is actually visible */
--color-surface-control: color-mix(in srgb, #dcdee3, #000 calc(var(--theme-contrast, 0) * 25%));
--color-surface-control-hover: color-mix(in srgb, #cfd2d9, #000 calc(var(--theme-contrast, 0) * 28%));
--color-surface-control-active: color-mix(in srgb, #b8bcc6, #000 calc(var(--theme-contrast, 0) * 32%));
--color-grid-dimmed: color-mix(in srgb, #eceef1, #000 calc(var(--theme-contrast, 0) * 28%));
--color-grid-bright: color-mix(in srgb, #e2e4e9, #000 calc(var(--theme-contrast, 0) * 32%));
/* Steeper than border-bright so the selected card stays the loudest edge */
--color-border-selected: color-mix(in srgb, #e0e2e6, #000 calc(var(--theme-contrast, 0) * 45%));
--color-border-bright: color-mix(in srgb, #d2d5db, #000 calc(var(--theme-contrast, 0) * 38%));
--color-border-brighter: color-mix(in srgb, #b9bdc7, #000 calc(var(--theme-contrast, 0) * 42%));
--color-border-brightest: color-mix(in srgb, #9ba1ad, #000 calc(var(--theme-contrast, 0) * 46%));
}
form:has(.unlock-hint-staging-env:hover) [data-unlock-target="staging-env"],
form:has(.unlock-hint-pull-prod:hover) [data-unlock-target="pull-prod"],
form:has(.unlock-hint-pull-stg:hover) [data-unlock-target="pull-stg"],
form:has(.unlock-hint-pull-preview:hover) [data-unlock-target="pull-preview"],
form:has(.unlock-hint-pull-dev:hover) [data-unlock-target="pull-dev"] {
border-radius: 0.25rem;
outline: 2px dashed var(--color-warning);
outline-offset: 2px;
}