Skip to content

feat(webapp): Themes + contrast settings update - #4206

Open
kathiekiwi wants to merge 150 commits into
mainfrom
feat/light-theme
Open

feat(webapp): Themes + contrast settings update#4206
kathiekiwi wants to merge 150 commits into
mainfrom
feat/light-theme

Conversation

@kathiekiwi

@kathiekiwi kathiekiwi commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Adds System Preferences, Dark and Light themes, gated by the hasThemeSwitcher feature flag (off by default — dark stays the default theme for everyone).

Old theme is now "Classic"and set as default.
"System preferences" theme has both Light and Dark modes and uses your laptop settings to use a correct one.
It has less color accents (specifically less colored text), and they are the same for both modes, only grayscale values change between them. And Light/Dark themes can be used separately.

New Contrast setting is available for System Preferences, Dark and Light themes - it changes the contrast for the whole app. All new visual Settings live in Account.

Screenshot 2026-07-23 at 22 34 17 Screenshot 2026-07-23 at 22 38 13

Light theme with 0 -> 100 Contrast

Screenshot 2026-07-23 at 22 42 35 Screenshot 2026-07-23 at 22 42 17

Dark theme with 0 -> 100 Contrast

Screenshot 2026-07-23 at 22 43 06 Screenshot 2026-07-23 at 22 42 00

@changeset-bot

changeset-bot Bot commented Jul 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 57769c5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 27 packages
Name Type
@trigger.dev/sdk Patch
@trigger.dev/python Patch
@internal/dashboard-agent Patch
@internal/sdk-compat-tests Patch
@trigger.dev/build Patch
@trigger.dev/core Patch
@trigger.dev/react-hooks Patch
@trigger.dev/redis-worker Patch
@trigger.dev/rsc Patch
@trigger.dev/schema-to-json Patch
@trigger.dev/database Patch
@trigger.dev/otlp-importer Patch
@trigger.dev/rbac Patch
@trigger.dev/sso Patch
trigger.dev Patch
@internal/cache Patch
@internal/clickhouse Patch
@internal/llm-model-catalog Patch
@internal/metrics-pipeline Patch
@internal/redis Patch
@internal/replication Patch
@internal/run-engine Patch
@internal/run-store Patch
@internal/schedule-engine Patch
@internal/testcontainers Patch
@internal/tracing Patch
@internal/tsql Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds feature-gated account theme selection with persisted dark, light, system, classic, and contrast preferences. Extends runtime theme tokens and light-mode styling across the webapp, updates controls, status displays, routes, icons, and debug identifiers, and adds queue-metrics simulation and seeding tooling. Regenerates model pricing and catalog files with formatting-only changes.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is missing the required template sections, including Closes #issue, checklist items, Testing, Changelog, and Screenshots headings. Add the missing template sections with issue reference, checklist, testing steps, a short changelog, and a Screenshots heading.
Docstring Coverage ⚠️ Warning Docstring coverage is 10.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: theme and contrast settings for the web app.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/light-theme

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@samejr samejr added the preview label Jul 9, 2026
@trigger-dot-bot

trigger-dot-bot Bot commented Jul 9, 2026

Copy link
Copy Markdown

Preview Deployment

Status Preview Commit Updated
🟢 Ready feat-light-theme.triggerlabs.dev 57769c5 Jul 30, 09:01 UTC

@pkg-pr-new

pkg-pr-new Bot commented Jul 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@trigger.dev/build

npm i https://pkg.pr.new/@trigger.dev/build@57769c5

trigger.dev

npm i https://pkg.pr.new/trigger.dev@57769c5

@trigger.dev/core

npm i https://pkg.pr.new/@trigger.dev/core@57769c5

@trigger.dev/python

npm i https://pkg.pr.new/@trigger.dev/python@57769c5

@trigger.dev/react-hooks

npm i https://pkg.pr.new/@trigger.dev/react-hooks@57769c5

@trigger.dev/redis-worker

npm i https://pkg.pr.new/@trigger.dev/redis-worker@57769c5

@trigger.dev/rsc

npm i https://pkg.pr.new/@trigger.dev/rsc@57769c5

@trigger.dev/schema-to-json

npm i https://pkg.pr.new/@trigger.dev/schema-to-json@57769c5

@trigger.dev/sdk

npm i https://pkg.pr.new/@trigger.dev/sdk@57769c5

commit: 57769c5

coderabbitai[bot]

This comment was marked as resolved.

@kathiekiwi
kathiekiwi marked this pull request as ready for review July 11, 2026 22:04
devin-ai-integration[bot]

This comment was marked as resolved.

Workspace packages resolve to TS source via the @triggerdotdev/source
condition; server.ts keeps the Express/cluster/ws wiring with vite
middleware in dev and the ESM server bundle in prod. Fixes surfaced by
the stricter ESM pipeline: destructured route exports, server-only
imports in routes/components, CJS interop (redlock, cuid, regression),
browser node-global shims, font asset handling, and a websockets TDZ.
Restores the unit-testable export (removed to satisfy the Vite route
export rules) by extracting the helper out of the route file.
createActionApiRoute's loader handles CORS OPTIONS preflight; the
destructured exports never surfaced it (pre-existing), do it now.
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

- semantic token overrides for light, trigger.light editor/code palettes
- Interface theme dropdown on /account, gated by hasThemeSwitcher flag
- unify light surfaces: buttons, inputs, radios, checkboxes, tabs, clipboard fields
- fix checkbox :read-only override and useThemeColor hydration mismatch
- resolve Firefox panel animation check from request UA
- re-resolve theme colors when data-theme changes
- narrow jsonb_set write for theme preference
- opaque amber for reasoning text in light mode
- keep dark-theme switch/radio thumb colors, white only in light
- pick avatar letter color by background luminance
- seed required preference fields when jsonb column is null
devin-ai-integration[bot]

This comment was marked as resolved.

…eference

React skips the data-theme write when its virtual DOM already matched the
SSR fallback while the inline script had flipped the real attribute; the
sync hook now writes pinned themes explicitly.
@kathiekiwi kathiekiwi changed the title feat(webapp): light theme feat(webapp): Themes + contrast settings update Jul 23, 2026
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

🧭 Helm Chart Prerelease Published

Version: 4.5.7-pr4206.33a5415

Install:

helm upgrade --install trigger \
  oci://ghcr.io/triggerdotdev/charts/trigger \
  --version "4.5.7-pr4206.33a5415"

⚠️ This is a prerelease for testing. Do not use in production.

Login, invites and other anonymous pages resolve the System preference
when the theme switcher flag is on; Classic stays the flag-off default.
The invites heading goes monochrome in System themes.
coderabbitai[bot]

This comment was marked as resolved.

The new-org background is built from dark-dashboard screenshots (plus a
hardcoded near-black fill); the light theme falls back to a flat surface.
Light variants generated from the dark screenshots by inverting lightness
(hues survive, so the accent smudges stay true); optimized progressive
JPEGs at a fraction of the original size.
Error boundary backdrop, prompt override editor pane, AI filter error
popover and chart-zoom tooltip get light: styling; standalone white
spinners go blue; bulk-action onboarding, blank-state and queues-upgrade
screenshots ship light variants (lightness-inverted, optimized).
kathiekiwi and others added 4 commits July 23, 2026 21:59
# Conflicts:
#	apps/webapp/app/routes/account._index/route.tsx
**Stacked on #4131** — targets `feat/queue-metrics-and-health`, not
`main`. GitHub will auto-retarget this to `main` once #4131 merges.
(Targeting `main` now would wrongly include all of #4131's own commits.)

UI polish for the Queue metrics & health pages (TRI-12068). 49 commits,
all under `apps/webapp/app/` — no engine/ClickHouse/package changes.

**Highlights**
- **Layout** — consolidated queue-detail header into one filter bar;
shared `MetricsLayout` spacing (2.5 scale); paused-queue banner
mirroring the environment-paused banner.
- **Charts** — inline legends under titles; threshold gradient coloring
(Env saturation orange only >100%; Concurrency orange only at/over the
limit); fullscreen legend spacing; result caching so tab switches don't
reload; brighter hover cursor + full x-axis labels.
- **Tables** — whole-row click via stretched link; custom Queues icon;
removed column sorting; Limit-cell spacing + hover-bright; "Limited by"
tooltip sizing.
- **Controls** — Pause moved into the filter bar (orange label +
accurate tooltip copy); restyled override-concurrency modal.
- **Copy** — plain-language pass; inline colour swatches; Throttled
reads "% of current period".

Also consolidated the three queue-metrics `.server-changes` notes into
one.

**Known follow-up (not in this PR):** the Concurrency-keys table is
capped at 50 keys (union of live Redis backlog + ClickHouse history, no
single cursor across both) and truncates silently — fine for most
queues, a real limit for high-cardinality per-tenant keys.

Verified locally against the `queue-metrics-demo` seed; `pnpm typecheck
--filter webapp` passes.
Missing or legacy preferences resolve to the pinned Dark theme (not
system-resolved, so nobody is surprised by light mode) and the default
contrast is 50. Classic remains the flag-off look.
devin-ai-integration[bot]

This comment was marked as resolved.

…d-health

# Conflicts:
#	apps/webapp/app/v3/services/worker/workerGroupTokenService.server.ts
# Conflicts:
#	apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx
#	apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues_.$queueParam/route.tsx
Base automatically changed from feat/queue-metrics-and-health to main July 29, 2026 15:45
# Conflicts:
#	.server-changes/agent-detail-metrics-layout.md
#	.server-changes/paginate-concurrency-keys-table.md
#	.server-changes/queue-metrics-dashboard.md
#	apps/webapp/app/components/primitives/UsageSparkline.tsx
#	apps/webapp/app/components/query/QueryEditor.tsx
#	apps/webapp/app/components/queues/QueueControls.tsx
#	apps/webapp/app/components/queues/QueueMetricCards.tsx
#	apps/webapp/app/env.server.ts
#	apps/webapp/app/hooks/useMetricResourceQuery.ts
#	apps/webapp/app/presenters/v3/QueueListPresenter.server.ts
#	apps/webapp/app/presenters/v3/QueueMetricsPresenter.server.ts
#	apps/webapp/app/presenters/v3/RunQueueMetricsPresenter.server.ts
#	apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/ExamplesContent.tsx
#	apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/TableSchemaContent.tsx
#	apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx
#	apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues_.$queueParam/route.tsx
#	apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.tasks.standard.$taskParam/route.tsx
#	apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.usage/route.tsx
#	apps/webapp/app/routes/api.v1.query.schema.ts
#	apps/webapp/app/routes/resources.metric.tsx
#	apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.github.tsx
#	apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query.ai-generate.tsx
#	apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx
#	apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
#	apps/webapp/app/routes/resources.queues.concurrency-keys.ts
#	apps/webapp/app/services/queryService.server.ts
#	apps/webapp/app/tailwind.css
#	apps/webapp/app/v3/querySchemas.ts
#	apps/webapp/app/v3/queueMetrics.server.ts
#	internal-packages/clickhouse/src/client/tsql.ts
#	internal-packages/run-engine/src/engine/systems/enqueueSystem.ts
#	internal-packages/tsql/src/query/schema.ts

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 new potential issues.

Open in Devin Review

Comment on lines +612 to 613
LeadingIcon={isConnectRepositoryLoading ? Spinner : undefined}
leadingIconClassName="text-white"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Loading spinners become nearly invisible on solid coloured buttons

The white loading spinner used on solid indigo/red buttons is swapped for the default blue one (Spinner at apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.github.tsx:612), so the spinner is now blue-on-blue and users can barely tell an action is in progress.

Impact: While connecting a GitHub repo or completing Vercel onboarding the button looks unresponsive because the progress indicator all but disappears against the button colour.

Why the colour disappears: Spinner ignores className colours and defaults to blue

Spinner (apps/webapp/app/components/primitives/Spinner.tsx:8-34) paints its stroke from the color prop (default "blue"rgba(59,130,246)) and ignores any text-* class, so leadingIconClassName="text-white" at apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.github.tsx:613 has no effect. The previous SpinnerWhite wrapper passed color="white", which is why a dedicated ButtonSpinner (white) also exists at apps/webapp/app/components/primitives/Spinner.tsx:64-74.

The same swap was applied to other solid primary/medium buttons: apps/webapp/app/components/integrations/VercelOnboardingModal.tsx:862, :933, :1115, :1171. Standalone spinners on page backgrounds (e.g. Spinner color="blue" in loading rows) are fine; only the ones rendered as LeadingIcon on a filled indigo/danger button lose contrast.

Prompt for agents
Several solid-background buttons (primary/danger variants) had their loading indicator changed from SpinnerWhite to the default Spinner, which renders blue and therefore has almost no contrast against the indigo/red button fill. Spinner derives its stroke colour purely from its `color` prop and ignores className, so the accompanying `leadingIconClassName="text-white"` does nothing. Affected call sites: apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.github.tsx:612 and :1033, and apps/webapp/app/components/integrations/VercelOnboardingModal.tsx:862, :933, :1115, :1171. Consider passing a white spinner (or the existing ButtonSpinner) for buttons whose background is a saturated fill, while keeping the blue spinner for spinners on page/dialog backgrounds.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +345 to +357
onValueChange={(values) => {
// Live preview before the preference persists
document.documentElement.style.setProperty(
"--theme-contrast",
String((values[0] ?? 0) / 100)
);
}}
onValueCommit={(values) =>
contrastFetcher.submit(
{ action: "update-contrast", contrast: String(values[0] ?? 0) },
{ method: "post" }
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Contrast slider preview sticks even when the setting is not actually saved

Dragging the contrast slider writes the new value straight onto the page (document.documentElement.style.setProperty at apps/webapp/app/routes/account._index/route.tsx:347-350) before the save is confirmed, so when the save is skipped or fails the page keeps showing a contrast level that was never stored.

Impact: A user (for example an admin viewing another account) can appear to change the interface contrast, then find it silently reverted on the next page load.

Mechanism: the preview is never rolled back because the server value is unchanged

The committed value is posted to the account action, which calls updateContrastPreference (apps/webapp/app/services/dashboardPreferences.server.ts:211-239). That function returns early without writing when user.isImpersonating, and the action also returns a 404 JSON body when the hasThemeSwitcher flag is off (apps/webapp/app/routes/account._index/route.tsx:144-153) — in both cases the response is not an error the UI surfaces.

Afterwards the root loader revalidates and re-renders <html style={{ "--theme-contrast": themeContrast / 100 }}> (apps/webapp/app/root.tsx:179) with the same value as before, so React sees no change to that style entry and does not rewrite the DOM property — the imperatively-set preview value survives. The slider itself is uncontrolled (defaultValue={[contrast]}), so it also stays at the dragged position. Reverting the preview on a non-success response (or driving the value from loader data instead of an imperative write) would keep the UI honest.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +19 to +28
className: "hidden lg:block light:lg:hidden",
},
{
images: {
menuTop: blurredDashboardBackgroundMenuTopLight,
menuBottom: blurredDashboardBackgroundMenuBottomLight,
table: blurredDashboardBackgroundTableLight,
},
tableFill: "#f4f5f7",
className: "hidden light:lg:block",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Light/dark backdrop visibility relies on class-order, not specificity

The two backdrop layers are distinguished only by "hidden lg:block light:lg:hidden" vs "hidden light:lg:block" (apps/webapp/app/components/BackgroundWrapper.tsx:19,28). The light custom variant is defined with :where(...) (apps/webapp/app/tailwind.css:433), which contributes zero specificity, so light:lg:hidden and lg:block are equally specific and the winner depends purely on the order Tailwind emits the two utilities. If lg:block sorts last, the dark backdrop stays visible on top of the light one in the Light theme. Worth a quick visual check of /orgs/new and /invites at lg width in Light theme.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +142 to +146
// NB: don't use the `read-only:` variant here — checkboxes always
// match :read-only, so it would override the checked style.
"rounded-sm border border-border-bright bg-transparent transition checked:bg-indigo-500! group-hover:bg-background-deep checked:group-hover:bg-indigo-500 group-focus:ring-1 focus:ring-indigo-500 focus:ring-offset-0 focus:ring-offset-transparent focus-visible:outline-hidden focus-visible:ring-indigo-500",
(isDisabled || props.readOnly) &&
"bg-background-raised! checked:bg-background-raised! checked:group-hover:bg-background-raised! group-hover:bg-background-raised!"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Read-only/disabled checkbox styling was restructured but keeps prior behaviour

The read-only:/disabled: Tailwind variants were replaced by conditional class strings driven by props (apps/webapp/app/components/primitives/Checkbox.tsx:142-146 and 196-202). Behaviour is equivalent to before — a read-only checked checkbox still renders with the raised background rather than the indigo fill, because checked:bg-background-raised! is applied for the read-only/disabled case. If the intent of the comment ("it would override the checked style") was to make read-only checked boxes actually look checked, that goal is not met.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/webapp/app/components/queues/QueueControls.tsx (1)

184-193: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Scope the auto-close effect to this form's own submission.

navigation.state is "idle" on mount and returns to "idle"/"loading" after any navigation, so this effect force-closes the dialog whenever an unrelated navigation settles — not just after a queue-override submit. Since Form onSubmit (Line 293) already closes optimistically, gate the effect on this form's submission completing instead.

🐛 Proposed fix
+  const isLoading = Boolean(
+    navigation.formData?.get("action") === "queue-override" ||
+      navigation.formData?.get("action") === "queue-remove-override"
+  );
+  const wasLoading = useRef(false);
   useEffect(() => {
-    if (navigation.state === "loading" || navigation.state === "idle") {
+    if (wasLoading.current && !isLoading) {
       setIsOpen(false);
     }
-  }, [navigation.state]);
-
-  const isLoading = Boolean(
-    navigation.formData?.get("action") === "queue-override" ||
-    navigation.formData?.get("action") === "queue-remove-override"
-  );
+    wasLoading.current = isLoading;
+  }, [isLoading]);

Add useRef to the react import.

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues_.$queueParam/route.tsx (1)

629-632: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Potential TRQL injection via manually-escaped, user-controlled strings.

trqlString only doubles single quotes before interpolating raw text into TRQL query strings. KeyDrilldown's pin (Line 955) is built directly from value("key") — a raw URL search param a user can set to anything — and is spliced into three WHERE clauses (Lines 969, 983, 992) sent via useQueueMetric. GroupedKeySeries's inList (Line 704) similarly interpolates concurrency_key values that originate from the free-form concurrencyKey string set in task code, which can be attacker-influenced in multi-tenant setups.

Hand-rolled quote-doubling is a fragile substitute for parameterized queries; whether this is exploitable depends on how the backend resources.metric endpoint parses/validates TRQL text, which isn't visible in this diff.

#!/bin/bash
# Description: Look for how TRQL query text is parsed/validated server-side, and whether
# trqlString-style escaping is an established, vetted pattern elsewhere in the codebase.

rg -n "trqlString" apps/webapp/app -C3
rg -n "resources\.metric|resources/metric" apps/webapp/app -C5
fd -g '*QueueMetricCards*' apps/webapp/app
fd -g '*useMetricResourceQuery*' apps/webapp/app
rg -n "parseTrql|TRQL|executeQuery|clickhouse" apps/webapp/app/services apps/webapp/app/v3 -i -C3

Also applies to: 704-704, 955-999

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/TableSchemaContent.tsx (1)

47-58: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Guard against an empty schemas array to avoid a crash.

schemas[0].name (initial state) and the ?? schemas[0] fallback both assume listableQuerySchemas(...) never returns an empty array. If the feature-flag-driven filtering (includeQueueMetrics) ever removes every schema, this throws at render (Cannot read properties of undefined).

🛡️ Proposed guard
+  if (schemas.length === 0) {
+    return null;
+  }
   const [selectedTable, setSelectedTable] = useState(schemas[0].name);
   const table = schemas.find((s) => s.name === selectedTable) ?? schemas[0];
🧹 Nitpick comments (7)
apps/webapp/seed-queue-metrics.mts (3)

581-597: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the ch.writer internals cast into one helper.

The as unknown as { client: { command } } escape hatch is duplicated verbatim here and at Lines 946-948. If ClickHouse renames writer or its client, both sites break silently at runtime. A single rawCommand(ch) helper keeps the coupling in one place.

Also worth verifying that lightweight DELETE works against the AggregatingMergeTree targets (queue_metrics_v1, queue_metrics_5m_v1, env_metrics_v1, queue_metrics_ck_v1) on the ClickHouse version pinned for local dev — --reset silently depends on it.

♻️ Proposed helper extraction
+function rawCommand(ch: ClickHouse) {
+  return (
+    ch.writer as unknown as { client: { command: (a: { query: string }) => Promise<unknown> } }
+  ).client;
+}
+
 async function resetEnv(ch: ClickHouse, environmentId: string) {
-  const raw = (
-    ch.writer as unknown as { client: { command: (a: { query: string }) => Promise<unknown> } }
-  ).client;
+  const raw = rawCommand(ch);

661-670: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse CK_TENANT_COUNT instead of re-hardcoding 12.

The comment at Line 328 states the CK simulation's tenant set must match stageRedisUsage, but this local tenants = 12 will silently drift from CK_TENANT_COUNT if that constant is ever tuned — the CH per-key rows and the staged Redis keys would then describe different tenant sets. Same applies to the ckTenantName naming duplicated on Line 666.

♻️ Proposed fix
       if (profile.ck) {
         const now = Date.now();
-        const tenants = 12;
         let totalCkQueued = 0;
-        for (let t = 1; t <= tenants; t++) {
-          const tenant = `tenant-${String(t).padStart(2, "0")}`;
+        for (let t = 0; t < CK_TENANT_COUNT; t++) {
+          const tenant = ckTenantName(t);
           const member = `${logicalBase}${profile.name}:ck:${tenant}`;
-          const hog = t === 1;
+          const hog = t === 0;

923-942: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Flush during the backfill loop rather than buffering every row.

All backfill rows are accumulated in one array before the first insert. For the documented --window 1d / --scenario many-queues combination this is on the order of tens of millions of row objects held in memory simultaneously (8640 buckets × 61 queues × ~30 rows for the busy queues), so the script can OOM before it ever writes. insertBatched already chunks the write side; flushing every N buckets keeps peak memory flat.

♻️ Proposed streaming flush
   const counters = newCounterState(scenario.queues.length);
-  const rows: QueueMetricsRawV1Input[] = [];
+  let pending: QueueMetricsRawV1Input[] = [];
+  let inserted = 0;
   for (let b = 0; b < totalBuckets; b++) {
     const bucketEpochSec = startBucket + b * bucketSec;
     const eventTime = formatChDateTime(new Date(bucketEpochSec * 1000));
-    rows.push(
+    pending.push(
       ...simulateBucket(
         scenario,
         b,
         bucketSec,
         eventTime,
         bucketEpochSec,
         ids,
         backlog,
         counters,
         rng
       )
     );
+    if (pending.length >= 100_000) {
+      await insertBatched(ch, pending, `${nonce}:backfill:${b}`);
+      inserted += pending.length;
+      pending = [];
+    }
   }
-  await insertBatched(ch, rows, nonce);
-  console.log(`Inserted ${rows.length} raw rows.`);
+  if (pending.length > 0) {
+    await insertBatched(ch, pending, `${nonce}:backfill:final`);
+    inserted += pending.length;
+  }
+  console.log(`Inserted ${inserted} raw rows.`);
apps/webapp/app/routes/account._index/route.tsx (1)

345-351: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Stale inline --theme-contrast after an abandoned drag.

The preview writes directly to documentElement.style, which outranks the value React sets on <html> in root.tsx. If a drag is interrupted without onValueCommit (pointer cancel, page kept open), the previewed contrast sticks for the session even though nothing persisted. Clearing the property once the fetcher settles would keep the DOM in sync with the loader value.

apps/webapp/test/themePreference.test.ts (1)

5-43: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add coverage for normalizeThemeContrast.

The contrast normalizer (clamping, rounding, string coercion, non-finite fallback) is new persisted-preference logic with no tests here.

apps/webapp/app/components/BackgroundWrapper.tsx (1)

32-69: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Nit: key off a semantic field rather than tableFill.

key={tableFill} works only because the two fills happen to differ; a theme: "dark" | "light" field on each backdrop entry would be self-documenting and also lets the class strings read against a named mode.

apps/webapp/app/components/query/QueryEditor.tsx (1)

251-251: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer a semantic token over bg-white.

The rest of the theming work re-maps semantic tokens (--color-background-*) per theme; hardcoding bg-white for light and overriding with dark:bg-background-deep splits this one surface out of that system and will drift if the light background changes (or under the contrast control).


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5bf19bf3-838f-4d84-8d57-2e7b6c38b5b3

📥 Commits

Reviewing files that changed from the base of the PR and between 8b64da9 and 57769c5.

⛔ Files ignored due to path filters (3)
  • apps/webapp/app/assets/images/blurred-dashboard-background-menu-bottom-light.jpg is excluded by !**/*.jpg
  • apps/webapp/app/assets/images/blurred-dashboard-background-menu-top-light.jpg is excluded by !**/*.jpg
  • apps/webapp/app/assets/images/blurred-dashboard-background-table-light.jpg is excluded by !**/*.jpg
📒 Files selected for processing (77)
  • .server-changes/light-theme.md
  • apps/webapp/app/assets/icons/MachineIcon.tsx
  • apps/webapp/app/components/BackgroundWrapper.tsx
  • apps/webapp/app/components/BlankStatePanels.tsx
  • apps/webapp/app/components/BulkActionFilterSummary.tsx
  • apps/webapp/app/components/DevPresence.tsx
  • apps/webapp/app/components/ErrorDisplay.tsx
  • apps/webapp/app/components/FeatureBadges.tsx
  • apps/webapp/app/components/LoginPageLayout.tsx
  • apps/webapp/app/components/billing/AnimatedOrgBannerBar.tsx
  • apps/webapp/app/components/billing/OrgBanner.tsx
  • apps/webapp/app/components/code/TSQLEditor.tsx
  • apps/webapp/app/components/environments/EnvironmentLabel.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
  • apps/webapp/app/components/errors/ErrorStatusBadge.tsx
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/components/navigation/SideMenuItem.tsx
  • apps/webapp/app/components/navigation/TreeConnectors.tsx
  • apps/webapp/app/components/primitives/Avatar.tsx
  • apps/webapp/app/components/primitives/Badge.tsx
  • apps/webapp/app/components/primitives/BreadcrumbIcon.tsx
  • apps/webapp/app/components/primitives/Buttons.tsx
  • apps/webapp/app/components/primitives/ClientTabs.tsx
  • apps/webapp/app/components/primitives/ClipboardField.tsx
  • apps/webapp/app/components/primitives/DateField.tsx
  • apps/webapp/app/components/primitives/DateTime.tsx
  • apps/webapp/app/components/primitives/InputNumberStepper.tsx
  • apps/webapp/app/components/primitives/SegmentedControl.tsx
  • apps/webapp/app/components/primitives/Select.tsx
  • apps/webapp/app/components/primitives/Slider.tsx
  • apps/webapp/app/components/primitives/UsageSparkline.tsx
  • apps/webapp/app/components/primitives/charts/Chart.tsx
  • apps/webapp/app/components/primitives/charts/ChartZoom.tsx
  • apps/webapp/app/components/query/QueryEditor.tsx
  • apps/webapp/app/components/queues/QueueControls.tsx
  • apps/webapp/app/components/queues/QueueMetricCards.tsx
  • apps/webapp/app/components/runs/v3/AIFilterInput.tsx
  • apps/webapp/app/components/runs/v3/BatchStatus.tsx
  • apps/webapp/app/components/runs/v3/BulkAction.tsx
  • apps/webapp/app/components/runs/v3/DeploymentStatus.tsx
  • apps/webapp/app/components/runs/v3/RunFilters.tsx
  • apps/webapp/app/components/runs/v3/RunTag.tsx
  • apps/webapp/app/components/runs/v3/SpanTitle.tsx
  • apps/webapp/app/components/runs/v3/TaskRunAttemptStatus.tsx
  • apps/webapp/app/components/runs/v3/TaskRunStatus.tsx
  • apps/webapp/app/components/runs/v3/WaitpointStatus.tsx
  • apps/webapp/app/components/sessions/v1/SessionStatus.tsx
  • apps/webapp/app/hooks/useSystemThemeSync.ts
  • apps/webapp/app/root.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.alerts/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.limits/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.models._index/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.prompts.$promptSlug/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/TableSchemaContent.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues_.$queueParam/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings._index/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.private-connections._index/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.sso/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.usage/route.tsx
  • apps/webapp/app/routes/_app.orgs.new/route.tsx
  • apps/webapp/app/routes/account._index/route.tsx
  • apps/webapp/app/routes/invites.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.github.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/services/dashboardPreferences.server.ts
  • apps/webapp/app/tailwind.css
  • apps/webapp/app/utils/themePreference.ts
  • apps/webapp/app/v3/featureFlags.ts
  • apps/webapp/package.json
  • apps/webapp/seed-queue-metrics.mts
  • apps/webapp/test/themePreference.test.ts
  • internal-packages/llm-model-catalog/src/defaultPrices.ts
  • internal-packages/llm-model-catalog/src/modelCatalog.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/webapp/app/components/billing/AnimatedOrgBannerBar.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/components/primitives/ClientTabs.tsx
  • apps/webapp/app/components/runs/v3/RunTag.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.limits/route.tsx

type: feature
---

Adds an opt-in Interface theme setting on the account page behind a feature flag. Choose Dark (the new default), System preference (follows your OS), Light, or the original Classic look, and fine-tune the new themes with a contrast slider. System preference switches automatically when your OS appearance changes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep the release note user-facing.

Remove “behind a feature flag”; this file is published verbatim, so it should describe the available theme behavior rather than an internal rollout mechanism.

Proposed wording
-Adds an opt-in Interface theme setting on the account page behind a feature flag. Choose Dark (the new default), System preference (follows your OS), Light, or the original Classic look, and fine-tune the new themes with a contrast slider. System preference switches automatically when your OS appearance changes.
+Adds Interface theme settings on the account page. Choose Dark, System preference, Light, or Classic, adjust contrast, and have System preference follow your OS appearance automatically.

Based on learnings, .server-changes/*.md entries are published verbatim as user release notes and should avoid implementation-oriented details such as feature flags.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Adds an opt-in Interface theme setting on the account page behind a feature flag. Choose Dark (the new default), System preference (follows your OS), Light, or the original Classic look, and fine-tune the new themes with a contrast slider. System preference switches automatically when your OS appearance changes.
Adds Interface theme settings on the account page. Choose Dark, System preference, Light, or Classic, adjust contrast, and have System preference follow your OS appearance automatically.

Source: Learnings

Comment thread apps/webapp/app/root.tsx
Comment on lines +173 to +186
<html
lang="en"
className="h-full"
data-theme={resolvedTheme}
data-theme-preference={themePreference}
// Contrast overlay input for the System themes; Classic never reads it
style={{ "--theme-contrast": themeContrast / 100 } as CSSProperties}
>
<head>
<script
dangerouslySetInnerHTML={{
__html: `try{if(document.documentElement.getAttribute("data-theme-preference")==="system"){document.documentElement.setAttribute("data-theme",matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light")}}catch(e){}`,
}}
/>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

React 18 hydration mismatch html data attribute suppressHydrationWarning theme script

💡 Result:

When implementing a theme script that sets a data attribute (e.g., data-theme) on the <html> or <body> element, a hydration mismatch often occurs because the server renders a default value, while the client script updates the DOM before React hydrates [1][2][3]. React detects this discrepancy between the server-rendered HTML and the client-side DOM and triggers a warning [1][2]. To resolve this, you should add the suppressHydrationWarning attribute to the element being modified (usually the <html> tag) [4][5][1]. Key Technical Details: - Purpose: The suppressHydrationWarning attribute tells React to ignore differences in text content or attributes for that specific element during the hydration process [6][7][4]. It allows the DOM value (which your script has set) to persist, preventing React from overwriting it or triggering a mismatch error [1]. - Scope: This attribute only applies one level deep [6][7][5]. It will not silence hydration warnings for child components, which is a key security and consistency feature designed to keep the escape hatch narrowly scoped [6][8]. - Best Practice: The recommended pattern for handling theme-based attributes is to use a blocking inline script in the <head> to read the user's preference (e.g., from localStorage) and set the attribute before the browser paints [1][2][3]. This prevents a "flash of unstyled content" (FOUC) and, when paired with suppressHydrationWarning on the <html> element, provides a robust, flicker-free experience [1][9]. Example Implementation: <script dangerouslySetInnerHTML={{ html: (function(){try{var t=localStorage.getItem("theme");if(t)document.documentElement.setAttribute("data-theme",t)}catch(e){}})(), }} /> {children} This approach ensures the server-rendered HTML and client-side DOM reach a consensus before React hydration begins [1][2]. If you encounter mismatches elsewhere, verify that you are not accessing client-only APIs (like window or localStorage) during the component's render phase [7][10]. Rely on useEffect or next/dynamic with ssr: false for client-only logic that does not require blocking scripts [7][11][12].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate root.tsx and package versions =="
git ls-files | rg '(^|/)root\.tsx$|package\.json$|package-lock\.json$|pnpm-lock\.yaml$|yarn\.lock$' | sed -n '1,120p'

echo
echo "== root.tsx relevant section =="
if [ -f apps/webapp/app/root.tsx ]; then
  nl -ba apps/webapp/app/root.tsx | sed -n '130,210p'
fi

echo
echo "== theme preference helpers in root or related files =="
rg -n "themePreference|resolvedTheme|themeContrast|data-theme|suppressHydrationWarning|prefers-color-scheme" apps/webapp app . --glob '*.{ts,tsx}' | sed -n '1,200p'

Repository: triggerdotdev/trigger.dev

Length of output: 1939


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate repository files =="
if [ -f apps/webapp/app/root.tsx ]; then
  echo "apps/webapp/app/root.tsx exists"
  wc -l apps/webapp/app/root.tsx
else
  echo "apps/webapp/app/root.tsx missing"
  git ls-files | rg '(^|/)root\.tsx$|package\.json$' | sed -n '1,120p'
fi

echo
echo "== root.tsx relevant section =="
if [ -f apps/webapp/app/root.tsx ]; then
  nl -ba apps/webapp/app/root.tsx | sed -n '140,200p'
fi

echo
echo "== theme-related code references =="
rg -n "themePreference|resolvedTheme|themeContrast|data-theme|suppressHydrationWarning|prefers-color-scheme|matchMedia|localStorage|sessionStorage" apps/webapp app . --glob '*.{ts,tsx}' --glob '!node_modules' 2>/dev/null | sed -n '1,240p'

echo
echo "== package react versions =="
for f in package.json apps/webapp/package.json packages/*/package.json; do
  [ -f "$f" ] || continue
  echo "--- $f"
  node -e 'const p=require("./'$f'"); console.log(JSON.stringify({deps:p.dependencies&&p.dependencies.react, devDeps:p.devDependencies&&p.devDependencies.react}, null, 2).replace(/\\n/g,"\n"))' 2>/dev/null || sed -n '/react/p' "$f"
done

Repository: triggerdotdev/trigger.dev

Length of output: 1939


Add suppressHydrationWarning to <html>.

When themePreference is system, the blocking script can change data-theme on the <html> element before React hydrates, producing a React hydration attribute mismatch for the server-rendered dark value. Scope the hydration escape hatch to the only element React does not need to match.

🩹 Proposed tweak
       <html
         lang="en"
         className="h-full"
+        suppressHydrationWarning
         data-theme={resolvedTheme}
         data-theme-preference={themePreference}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<html
lang="en"
className="h-full"
data-theme={resolvedTheme}
data-theme-preference={themePreference}
// Contrast overlay input for the System themes; Classic never reads it
style={{ "--theme-contrast": themeContrast / 100 } as CSSProperties}
>
<head>
<script
dangerouslySetInnerHTML={{
__html: `try{if(document.documentElement.getAttribute("data-theme-preference")==="system"){document.documentElement.setAttribute("data-theme",matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light")}}catch(e){}`,
}}
/>
<html
lang="en"
className="h-full"
suppressHydrationWarning
data-theme={resolvedTheme}
data-theme-preference={themePreference}
// Contrast overlay input for the System themes; Classic never reads it
style={{ "--theme-contrast": themeContrast / 100 } as CSSProperties}
>
<head>
<script
dangerouslySetInnerHTML={{
__html: `try{if(document.documentElement.getAttribute("data-theme-preference")==="system"){document.documentElement.setAttribute("data-theme",matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light")}}catch(e){}`,
}}
/>
🧰 Tools
🪛 ast-grep (0.45.0)

[warning] 182-182: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation

(react-unsafe-html-injection)

Comment on lines +336 to +358
<div className="mt-4 flex w-full items-center justify-between gap-4">
<Label>Contrast</Label>
<Slider
variant="settings"
className="w-44"
min={0}
max={100}
step={5}
defaultValue={[contrast]}
onValueChange={(values) => {
// Live preview before the preference persists
document.documentElement.style.setProperty(
"--theme-contrast",
String((values[0] ?? 0) / 100)
);
}}
onValueCommit={(values) =>
contrastFetcher.submit(
{ action: "update-contrast", contrast: String(values[0] ?? 0) },
{ method: "post" }
)
}
/>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Associate the "Contrast" label with the slider.

Label here renders a bare <label> with no htmlFor, and the Radix thumb is the role="slider" element, so assistive tech announces an unnamed slider. Same applies to the theme Select above. An aria-label (or aria-labelledby pointing at the label) fixes it.

🩹 Proposed fix
                   <Label>Contrast</Label>
                   <Slider
                     variant="settings"
                     className="w-44"
+                    aria-label="Contrast"
                     min={0}
                     max={100}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="mt-4 flex w-full items-center justify-between gap-4">
<Label>Contrast</Label>
<Slider
variant="settings"
className="w-44"
min={0}
max={100}
step={5}
defaultValue={[contrast]}
onValueChange={(values) => {
// Live preview before the preference persists
document.documentElement.style.setProperty(
"--theme-contrast",
String((values[0] ?? 0) / 100)
);
}}
onValueCommit={(values) =>
contrastFetcher.submit(
{ action: "update-contrast", contrast: String(values[0] ?? 0) },
{ method: "post" }
)
}
/>
<div className="mt-4 flex w-full items-center justify-between gap-4">
<Label>Contrast</Label>
<Slider
variant="settings"
className="w-44"
aria-label="Contrast"
min={0}
max={100}
step={5}
defaultValue={[contrast]}
onValueChange={(values) => {
// Live preview before the preference persists
document.documentElement.style.setProperty(
"--theme-contrast",
String((values[0] ?? 0) / 100)
);
}}
onValueCommit={(values) =>
contrastFetcher.submit(
{ action: "update-contrast", contrast: String(values[0] ?? 0) },
{ method: "post" }
)
}
/>

<FormTitle
LeadingIcon={<EnvelopeIcon className="size-6 text-cyan-500" />}
className="mb-0 text-sky-500"
className="mb-0 text-sky-500 system:text-text-bright"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Apply the System-theme color to the envelope icon.

FormTitle.className styles the outer wrapper, but the icon at Line 143 explicitly sets text-cyan-500, so system:text-text-bright here cannot change its color. The invitation icon will remain cyan in System themes instead of following the grayscale treatment.

Proposed fix
-              LeadingIcon={<EnvelopeIcon className="size-6 text-cyan-500" />}
+              LeadingIcon={
+                <EnvelopeIcon className="size-6 text-cyan-500 system:text-text-bright" />
+              }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
className="mb-0 text-sky-500 system:text-text-bright"
LeadingIcon={
<EnvelopeIcon className="size-6 text-cyan-500 system:text-text-bright" />
}
className="mb-0 text-sky-500 system:text-text-bright"

Comment on lines +612 to 613
LeadingIcon={isConnectRepositoryLoading ? Spinner : undefined}
leadingIconClassName="text-white"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep the connect-button spinner white.

Spinner defaults to blue and sets its SVG strokes explicitly, so leadingIconClassName="text-white" has no effect. The primary button will now display a blue loading indicator instead of the intended white one. Use Spinner with color="white" via a wrapper component, or retain SpinnerWhite.

Comment on lines +300 to +303
: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);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Lowercase currentcolor to clear Stylelint.

value-keyword-case flags this as an error, so it will fail lint.

🩹 Proposed fix
   box-shadow: inset 0 0 0 1px
-    color-mix(in srgb, currentColor calc(var(--theme-contrast, 0) * 70%), transparent);
+    color-mix(in srgb, currentcolor calc(var(--theme-contrast, 0) * 70%), transparent);

As per static analysis hint (Stylelint value-keyword-case at line 302).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
: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);
}
: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);
}
🧰 Tools
🪛 Stylelint (17.14.1)

[error] 302-302: Expected "currentColor" to be "currentcolor" (value-keyword-case)

(value-keyword-case)

Source: Linters/SAST tools

@@ -0,0 +1,43 @@
import { describe, expect, it } from "vitest";
import { getDashboardPreferences } from "~/services/dashboardPreferences.server";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Test pulls in env.server.ts transitively.

~/services/dashboardPreferences.server imports prisma/$transaction from ~/db.server, which loads app/env.server.ts, so this test depends on a validated server env at import time. Consider moving the DashboardPreferences schema (or a parseDashboardPreferences helper) into a non-.server module so the schema can be tested without the server graph.

As per coding guidelines: "Test files must not import app/env.server.ts; pass configuration as options instead."

#!/bin/bash
# Confirm the transitive env.server import chain from the service under test
rg -n "^import|from \"" apps/webapp/app/services/dashboardPreferences.server.ts | head -30
fd -t f 'db.server.ts' apps/webapp/app --exec rg -n 'env.server|from "~/env' {}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants