green-400 measures 1.78:1 on white, so an added line in the flag diff was barely
there on the light themes. Under Stronger colors it drops to green-700, the first
stop clearing the 4.5:1 that body text needs - green-600 only reaches 3.22:1,
which is a fill's floor, not text's.
Admin-only, and the +/- prefix still carries the meaning either way.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Queue health, log levels, the Badge primitive and the Trigger Agent tones were all
soft tints - the accent at 10% behind the accent as text - and none of them moved
when the preference went on. A tint that low is carrying no contrast of its own,
so the label was doing all the work at whatever ratio the accent happened to have.
They now do what the Resolved error badge already did: fill with the accent and
put white on it, so the chip reads as a solid block rather than a wash. Off, they
keep the tints.
Same treatment across all four, so a chip doesn't change character depending on
which surface it came from. Idle, DEBUG and the neutral tone take charcoal-500,
which is the neutral the rest of the set is drawn against.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Queued, Delayed, Waiting, Canceled and Expired are pale greys picked to sit
quietly on a white plot, which put all five between 1.32:1 and 1.66:1 - well
under the 3:1 a chart series needs. With Stronger colors on they now take
charcoal-400, the first stop on the ramp that clears it at 3.36:1. charcoal-300
is still 1.98:1, so there was no gentler step available.
All five land on the same stop. That sounds worse than it is: they were already
within 1.05:1 of each other, so there was nothing distinguishable between them to
lose. Telling them apart is a separate question, and it can't be answered without
pushing four of them darker than the floor requires.
The dark themes keep their greys, which already clear 3:1 against a dark plot.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Each swatch now carries a tooltip with its token (or raw utility class) and what
that resolves to in the column it's sitting in - the point of the layout being
that one token is five different values across a row, and until now you had to
guess which.
A plain `title` rather than the tooltip primitive: there are five swatches per row
across sixty-odd rows, and mounting a few hundred Radix tooltips to name a
variable isn't a fair trade.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The page imported TaskRunAttemptStatus from @trigger.dev/database as a value, to
derive the attempt-status list. That package wraps the Prisma client, so it can't
load in a browser: the server render resolved it fine, but the route module failed
client-side, which took the whole page's JavaScript with it.
Three symptoms, one cause. Clicking Colors in the storybook sidebar fell back to a
full document load instead of navigating. The measured ratios stayed blank,
because they're read in an effect that never ran. And the theme switcher in the
storybook layout was dead on that page, since a failed route module leaves the
tree unhydrated.
The enum's six members are written out under a type-only import instead, which is
erased at build time. Every other storybook page already imports this package as
types only.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This reverts commit 90f04957a0e60c0e4d7a51e2c0b2c1e3f4d8b7a1.
#4654's unused-code cleanup stands as written. Every file it touched is now
byte-identical to main again: the two modules it deleted stay deleted, the five
exports it removed stay removed, and the three it merely unexported
(AlertAction, SheetDescription, SheetFooter) are private again too - that last
part went beyond the reverted commit, since those re-exports came in with the
merge itself.
The storybook stories for all of them are gone with them. This branch keeps less
primitive coverage than it had, which is the cost of not carrying components the
product doesn't call.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#4654's unused-code pass removed these, correctly by its own measure: each had
no importer at all, and the storybook pages that document them exist only on this
branch, so the pass couldn't see them.
Restored deliberately, because a design-system storybook that silently omits
primitives is worse than carrying a few components the product doesn't currently
call. Two whole modules come back (AnimatingArrow, PrettyDuration) plus five
exports whose surrounding components stayed (SmartDateTime, LiveCountUp,
InputOTPSeparator, PopoverSideMenuTrigger, TableCellChevron), and their stories
along with them.
Every one carries a comment saying why it's kept and pointing here, so the next
unused-code pass doesn't quietly take them out again - knip is satisfied either
way, since a storybook route is an entry point and anything it imports counts as
used.
LabelValueStack is the exception and stays deleted: its only historical consumer
was a test, not a page.
Collateral from reverting those hunks wholesale - useLocalTimeZone,
useUserTimeZone and the PopoverArrowTriggerVariant type - is kept but no longer
exported, since nothing outside their own files uses them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Eight conflicts, resolved as follows.
themePreference.ts: kept this branch's contrast default of 0 and its two new
preference normalizers, but left the constant unexported as main had it - nothing
imports it, and main's new unused-code gate would reject the export.
user.server.ts: kept the three per-field profile writes; dropped
grantUserCloudAccess, which main deleted as dead code and which has no callers on
either side.
tailwind.css: kept this branch's ramp-travel contrast ramps and added main's new
radio-card tokens on top - surface-hover-subtle on the dark ramp, mirroring
background-hover as it does at contrast 0, and border-selected on the light ramp,
a stop steeper than border-bright so the selected card stays the loudest edge.
SideMenu.tsx: kept the extracted buildSideMenuSections call over main's inline
definition, after checking the two section lists are item-for-item identical.
Main's Webhooks item and Upgrade-to-v4 link live outside the sections array and
merged untouched.
dashboardPreferences.server.ts, storybook.popover, private-connections: took the
union where both sides added imports, and this branch's richer popover story.
LabelValueStack: accepted main's deletion. This branch had added a storybook
section for it, which is gone too - the component was unused in the app, which is
why main removed it.
Main's unused-code pass also deleted exports that only this branch's storybook
pages consumed. Where the code still exists it is re-exported (AlertAction,
SheetDescription, SheetFooter); where main removed a small list, it now lives in
the page that needs it (log levels, attempt statuses); and where the component
itself is gone, the story went with it - AnimatingArrow, PrettyDuration,
SmartDateTime, LiveCountUp, InputOTPSeparator, PopoverSideMenuTrigger,
TableCellChevron.
That same gate flagged exports of this branch's own that nothing imports. They're
now module-local, and AppearanceIcon - added by the first commit here and
superseded by MonitorIcon - is deleted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The measured tables sit inside an Audit, which renders its children once per
theme - and the table was building the five themes a second time internally. The
result was twenty-five mode blocks per table, each one a full Token / Fill /
Ratio / Verdict set, which made the section unreadable.
The table is back to one mode: token details, then Fill, Ratio and Verdict. The
themes come only from the Audit columns around it. That works without the table
knowing anything about themes, because each column is already a `data-theme`
context and the ratios are measured off the DOM, so the same table reports that
column's answer.
Audit takes a column width now, since a block holding four sub-columns needs more
room than one holding a single sample.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The initial was always light, which is fine on the darker tiles in the set and
poor on the pale ones - and the pale tiles are the ones the light themes lean on.
It now measures the tile's own colour and picks whichever of the two letter
colours has the better ratio against it, using WCAG relative luminance so the
comparison matches what a contrast checker would report rather than a
gamma-encoded average.
Recovered from an interrupted session.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Debug was missing from the filter list entirely, so there was no way to filter
for it. The dropdown also hand-rolled its own coloured labels, which meant two
places deciding what a level looks like; it now renders the same LogLevel chip
the log rows use, with the label alongside for screen readers, so the two can't
drift.
The level type comes from the presenter rather than being redeclared, which is
what turned up the missing entry.
Recovered from an interrupted session.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Summary
Memoize shared context values so provider renders do not unnecessarily
rerender every consumer. Oxlint now enforces this pattern for the rest
of the dashboard.
Base: [#4677](https://github.com/triggerdotdev/trigger.dev/pull/4677)
## Summary
Enable lint rules that prefer direct iteration and concise function
callback types.
The existing code now uses direct iteration where no index is needed,
and callback contracts use function types consistently.
Base: [#4675](https://github.com/triggerdotdev/trigger.dev/pull/4675)
## Summary
Enable JSX cleanup rules for shorthand fragments and self-closing
components.
The existing JSX is automatically simplified, and future components will
follow the same concise form.
Base: [#4673](https://github.com/triggerdotdev/trigger.dev/pull/4673)
## Summary
Enable small cleanup rules for redundant boolean expressions, object
ownership checks, assignments, and object construction.
The existing call sites now use the simpler equivalent forms, keeping
future code consistent without changing behavior.
Base: [#4672](https://github.com/triggerdotdev/trigger.dev/pull/4672)
## Summary
Enable additional lint rules that catch unsafe optional-chain
assertions, inherited-property iteration, anonymous symbols, and unsafe
external links.
The existing violations now use explicit values and own-property checks,
so the rules can prevent those patterns from returning.
Adds an optional priority class for run pods.
```
KUBERNETES_RUN_POD_PRIORITY_CLASS_NAME
```
When set, the value is applied as `priorityClassName` on the run pod
spec. When unset, pods are created exactly as before.
Off by default, and inert unless set. It sits beside the existing
`KUBERNETES_SCHEDULER_NAME` option and follows the same conditional
shape:
```ts
...(env.KUBERNETES_RUN_POD_PRIORITY_CLASS_NAME
? { priorityClassName: env.KUBERNETES_RUN_POD_PRIORITY_CLASS_NAME }
: {}),
```
## Verification
`typecheck --filter supervisor`, `format` and `lint` clean. No changeset
or `.server-changes/` note: off by default, no user-visible behaviour
change.
<!-- ccr-slack-attribution -->
_Requested by **Matt Aitken** · [Slack
thread](https://triggerdotdev.slack.com/archives/C0BKB98B84W/p1787045331358929)_
Copy-only reword of the banner shown to org admins who have not set a
billing limit yet.
**Before** — the banner read "Protect your organization from unexpected
usage spikes." with a button labelled "Configure billing limit".
**After** — it reads "Add a billing limit to your account to prevent
overspending" with a button labelled "Billing limit settings".
The new wording names the action up front and matches the destination it
sends you to, so the banner reads as a settings link rather than a
one-off setup step.
## ✅ Checklist
- [x] I have followed every step in the [contributing
guide](https://github.com/triggerdotdev/trigger.dev/blob/main/CONTRIBUTING.md)
- [x] The PR title follows the convention.
- [x] I ran and tested the code works
---
## Testing
Formatting and linting pass (`oxfmt --check`, `oxlint`). No tests or
snapshots assert this copy. The change is two string literals in one
component, with no behaviour attached.
---
## Changelog
Reworded the billing-limit banner for organizations without a limit
configured, and relabelled its button to "Billing limit settings".
---
## How
Both strings live in `NoLimitConfiguredBanner` in
`apps/webapp/app/components/billing/OrgBanner.tsx`: the heading is the
`canManageBillingLimits` branch of the banner's children, and the label
is the `<span>` inside the `LinkButton`. Only those two literals
changed. The button still points at `v3BillingLimitsPath(organization)`
(`/orgs/{slug}/settings/billing-limits`), so routing, permissions and
the non-admin variant of the message are untouched.
Co-authored-by: Claude <noreply@anthropic.com>
Adds KUBERNETES_RUNNER_SECURITY_CONTEXT (off | baseline | restricted), selecting how constrained the run container is.
baseline drops the capability bounding set and blocks privilege escalation. restricted additionally pins the container to a non-root uid, chosen by runtime so bun images get their own.
Default is off, so this is inert on merge.
Adds two optional env vars that rewrite the registry host of run pod images at pod creation, so a supervisor can pull from a registry in its own region. Off by default and inert unless both are set. Exact host-prefix matching, so look-alike hosts pass through untouched.
Replaces the hardcoded runner seccomp profile path with KUBERNETES_RUNNER_SECCOMP_PROFILE_PATH, and the node-24-only condition with KUBERNETES_RUNNER_SECCOMP_PROFILE_RUNTIMES (none | node-24-plus | all).
Both defaults reproduce current behaviour, so this is inert on merge. Widening the scope or turning attachment off becomes a config change rather than a deploy.
The paragraph carried py-4, inherited from the form it replaces. A form needs
that room below it before the footer; a line of explanatory text with no footer
under it doesn't, and the modal read bottom-heavy. pt-2 keeps it clear of the
title and lets the dialog's own padding close the gap.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The supervisor now supports routing an organization's runs to specific
nodes. `KUBERNETES_ORG_PLACEMENT_OVERRIDES` takes JSON keyed by the
internal org ID, adding node selector entries and tolerations to that
org's run pods, e.g. to route an org onto a dedicated, tainted node
pool:
```json
{"<orgId>": {"nodeSelector": {"pool": "dedicated"}, "tolerations": "dedicated=runs:NoSchedule"}}
```
The node selector merges over the defaults (the override wins on key
collision, with a warning logged). Tolerations append to the existing
runner and scheduled-run sets. Overrides are validated at startup
similar to `KUBERNETES_RUNNER_TOLERATIONS`.
Exposed in the Helm chart as
`supervisor.config.kubernetes.orgPlacementOverrides`, where tolerations
can also be given as a list.
This reverts commit 81bacb12e2.
The copy is signed off, so the modal goes back to branching on the real
isSsoManaged value.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"Signs you in through its own identity provider" led with the jargon. Single
sign-on is the term someone will have been given by whoever set it up, so the
sentence now opens with that and leaves the identity provider as the place the
address actually lives.
"An admin on your account" was also ambiguous - it's an organization admin, and
"your account" could be read as the reader's own.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## What & why
The dashboard agent can now run its model calls through AWS Bedrock
instead of the direct Anthropic API, chosen by a single env switch. It's
**off by default** (`DASHBOARD_AGENT_MODEL_PROVIDER` unset ⇒
`anthropic`), so merging changes nothing at runtime — the Bedrock path
is a dormant branch until an operator sets the switch and AWS config.
The default Anthropic path is byte-for-byte unchanged.
This also carries a related tenant-isolation hardening for the agent's
delegated token (kept together deliberately — both land the agent on
Bedrock for HIPAA readiness). Refs: TRI-13251, TRI-11032.
## What's inside
**Provider seam** —
`internal-packages/dashboard-agent/src/model-provider.ts`: the registry
now holds both `anthropic` and `bedrock`; `resolveDashboardAgentModel()`
maps the canonical `"anthropic:<id>"` strings the managed prompts carry
to the active provider, and the cache-breakpoint helpers emit the active
provider's shape — Anthropic `cacheControl` vs Bedrock `cachePoint`.
Managed prompt strings stay canonical, so stored prompts don't change
meaning. Unmapped model ids throw rather than shipping a guaranteed-404
profile. All agent, watch, compaction and title callsites route through
the resolver; the `dashboardAgentModelKey` locals override (test mock
injection) is preserved.
**Cache telemetry** — `step-cache.ts`: cache token usage is read from
the active provider (Anthropic reports it on provider metadata; Bedrock
reports the write on metadata and the read via standard usage), so
`gen_ai.usage.cache_*` is populated on both. This also fixes a latent
ordering bug where step attributes could null-overwrite the prompt-cache
read count.
**Webapp callsites** — `dashboardAgentHeadStart.server.ts` and the
head-start route resolve the model and the cache breakpoint through the
same seam, so the warm-up prefix and the following turn share one
provider. The head-start firing gate is provider-aware: on Bedrock it
gates on `AWS_REGION` and lets the SDK resolve credentials (IAM role /
static keys / session token / bearer), so a role-based deploy still
warms; on Anthropic it stays `Boolean(ANTHROPIC_API_KEY)`.
`app/env.server.ts` gains the optional AWS vars and validates
`DASHBOARD_AGENT_MODEL_PROVIDER`. `ANTHROPIC_API_KEY` is untouched and
not required on a Bedrock deploy.
**Tenant-isolation hardening** —
`internal-packages/rbac/src/fallback.ts`: for a **scoped** context, the
OSS `authenticateUserActor` now applies the same membership floor as the
session path — a delegated user-actor token whose user is not a member
of the scoped org/project is denied (403). Unscoped tokens keep their
prior behavior (no tenant claim, no lookup). The user lookup falls back
replica→primary so replication lag can't spuriously 401 a just-joined
member. Members and admins are unaffected. Previously this invariant
held only through per-route discipline; this makes it structural.
## Enabling Bedrock (later, ops)
- Set `DASHBOARD_AGENT_MODEL_PROVIDER=bedrock` **identically** in both
the webapp and the agent task container — the webapp warms the cache
prefix and the task reads it, so a split would silently miss the cache.
- Set `AWS_REGION` and provide credentials the Bedrock SDK can resolve
(IAM role preferred). For v1 this runs **without** an Anthropic API key.
Note: with no Anthropic key set, rollback is "turn the agent off", not
"unset the switch" (unsetting falls back to the Anthropic provider,
which then has no key).
- Two things to confirm before rollout: the Sonnet inference-profile id
is validated against the SDK's own model-id union but still warrants a
live smoke test; and Bedrock prompt caching for Sonnet is a 5-minute
window (not Anthropic's 1h), so input-token cost rises when flipped.
## Testing
Unit tests cover both provider paths: the provider switch and
per-provider cache shapes, a structural regex asserting Bedrock ids are
real inference profiles (not an echo of the table), the split-metadata
cache telemetry, and real-Postgres RBAC tests — member allowed, scoped
non-member denied (org-only and project-only), missing user → 401, admin
non-member exempt, unscoped success. `typecheck --filter webapp` and the
dashboard-agent + rbac suites pass.
Not for merge. Hard-codes isSsoManaged so the identity-provider copy can be read
in the browser without an SSO org to test against. Revert once the copy is
signed off.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Stronger colors page compared one theme against its own high-contrast
treatment, two columns at a time, which meant four passes with the theme switcher
to see whether an accent held up everywhere. Theme tokens sat separately as a
plain swatch gallery of the layer underneath it.
They're now one page, "Colors", under Foundations - the Accessibility group is
gone, and so is the separate Theme tokens page. Every block renders five times:
once per theme, then once under Stronger colors, which follows the switcher so
any theme can sit beside its own high-contrast treatment.
The measured tables keep the Fill / Ratio / Verdict readings and repeat them per
theme, sixteen columns in one flat grid so a sub-column stays under its header
all the way down. Ratios are still measured live off the DOM, which is what makes
this work at all: pinning data-theme on a wrapper is enough to get that theme's
answer, with no table of hard-coded values to fall out of date.
Two things that made it possible: every theme block is written as a plain
attribute selector rather than being anchored to <html>, and the dark: variant is
descendant-capable - so a nested column inherits the whole token set. The theme
groups use display:contents so their three cells stay direct grid items of the
row. As a bonus the --chart-2/--chart-3 override now resolves here too, since it
wants both attributes on one element and the last column restates the theme it
inherited; the note saying it couldn't is gone.
The charts drop to three bars, since each one renders five times across a row now.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two token changes.
The app had no primary color worth the name: --color-primary was an apple green
that four call sites used and nothing else, while the indigo link accent quietly
did the job a primary usually does. --color-primary now points at
--color-text-link rather than carrying its own value, so it's the same hue by
definition and can't drift. The light themes needed no separate entry once it
became an alias.
Preview environments were yellow-400 on the dark themes and yellow-700 on the
light ones - the darker end reading closer to brown - and blue only under
Stronger colors. Preview and its branch labels are now blue in every theme, so
an environment looks like itself at both ends of the `system` setting. Their
per-mode and Stronger colors entries are gone, since the base value now matches
what those blocks were reaching for.
Worth knowing: because the base and high-contrast values now agree, Stronger
colors no longer moves either preview token.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The rounded variant put text-text-bright on a blue-600 fill. That token is
near-black on Light and White, so the label all but disappeared into the fill
while reading correctly on the dark themes.
The fill is the same in every theme, so the label is now plain white in every
theme too - which is what the variant's own system: override was already doing
under Stronger colors.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The dot was a fixed size-2 next to a ring stretched to the container, and
nothing centred either. At the default size those agree, so it looked right -
but any call site that resized the whole thing got a small dot sitting in the
corner of an oversized halo, flex-start in both axes.
The dot now fills the container, so a size on the wrapper scales the dot and the
ring together, and both are centred.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Summary
When an AI SDK tool call failed inside a run, the span showed up under
the "Errors only" filter but the span inspector gave no hint of what
went wrong. The exception was recorded on the span all along; the
`ai.toolCall` and `ai.embed` inspector views just never rendered span
events. Failed tool call and embedding spans now show the standard error
block (message plus stack trace) below the Input section.
## Root cause
Generic spans render exception span events via the `SpanEvents`
component, but the AI-specific span entities replace the whole panel
with their own layout and dropped the events entirely. The span's events
are now passed into `AIToolCallSpanDetails` and `AIEmbedSpanDetails` and
rendered with the same `SpanEvents` component the generic view uses.
Errored generation spans (`ai.generateText` and friends) use a tabbed
view and still don't surface errors; that needs its own design pass and
is left for a follow-up.
## Summary
`nanoid` was pinned at exactly `3.3.8` in five manifests. Two of those
five never imported it: in `internal-packages/schedule-engine` and
`internal-packages/webhook-engine` the only occurrence of the string
`nanoid` in the entire package was the `package.json` line itself. Both
are removed rather than bumped.
The three that genuinely use it move to `3.3.18`, a version already
present in the tree via `postcss`, so this pulls in nothing new.
| Package | Uses it | Change |
| --- | --- | --- |
| `internal-packages/schedule-engine` | no | removed |
| `internal-packages/webhook-engine` | no | removed |
| `apps/webapp` | yes | `3.3.8` to `3.3.18` |
| `packages/core` | yes | `3.3.8` to `3.3.18` |
| `internal-packages/run-engine` | yes | `3.3.8` to `3.3.18` |
| `packages/redis-worker` | yes | `^5.0.7` to `^5.1.16` |
`redis-worker` is on the 5.x line and is included because its declared
range already permitted a newer release; the lockfile had simply not
re-resolved, leaving it on `5.1.2`.
The unused declarations were found with `pnpm run knip:deps`, which the
repo already ships.
`pnpm run typecheck` passes across all 57 workspaces.
## Summary
`socket.io` was pinned at exactly `4.7.4` in three manifests
(`apps/webapp`, `apps/supervisor`, `packages/core`). That pin capped
`engine.io` at 6.5.4, because 4.7.4 declares `engine.io: ~6.5.2`.
Moving all three pins to `4.8.3` lifts that cap: 4.8.3 declares
`engine.io: ~6.6.0`. The webapp's direct `engine.io` devDependency moves
from `^6.5.4` to `^6.6.7` to match.
These are direct dependencies, so they are bumped in place rather than
forced with an override.
## Result
The tree previously carried two `engine.io` copies. It now carries one:
```
engine.io@6.6.8
└─┬ socket.io@4.8.3
├── @trigger.dev/core (dependencies)
├─┬ react-email
│ └── emails (devDependencies)
├── supervisor (dependencies)
└── webapp (dependencies)
```
`react-email` was already resolving `socket.io@4.8.3` in this same tree,
so that combination was already running here before this change.
## Servers move, clients do not
This bumps `socket.io` (the server) only. `socket.io-client` stays at
`4.7.5` in `packages/core` and `packages/cli-v3`, deliberately: the fix
is server-side, and clients ship inside user deployments, so leaving
them alone keeps the blast radius small. That means a 4.8.3 server will
be talking to 4.7.5 clients indefinitely, which is worth being explicit
about.
That pairing is safe because neither wire protocol changed. Both
versions report the same protocol numbers:
| | 4.7.4 | 4.8.3 |
| --- | --- | --- |
| Socket.IO protocol (`socket.io-parser`) | 5 | 5 |
| Engine.IO protocol (`engine.io-parser`) | 4 | 4 |
The version bump moves `socket.io-parser` 4.2.6 to 4.2.7 and `engine.io`
6.5.4 to 6.6.8, but the protocol constants each exports are unchanged.
The 4.8.0 changes are additive on the client (custom transport
implementations, a `tryAllTransports` option) and bug fixes on the
server.
Verified rather than assumed, with a cross-version matrix covering both
transports and both directions:
```
PASS server 4.8.3 <- client 4.7.5 websocket / polling
PASS server 4.8.3 <- client 4.8.3 websocket / polling
PASS server 4.7.4 <- client 4.7.5 websocket / polling
PASS server 4.7.4 <- client 4.8.3 websocket / polling
```
Each case exercised connect, a server-initiated emit, `emitWithAck`,
room join, room broadcast, and a binary payload. Compatibility holds in
both directions, so there is no upgrade-ordering requirement between
server and client.
`pnpm run typecheck` passes across all 57 workspaces.
Stacked on #4634.
## Summary
`npm-run-all` has had no release since 4.1.5 in 2018, and pnpm now
covers the one thing we used it for. The webapp's `build` script was its
only consumer anywhere in the repo, so the dependency goes away
entirely.
`run-s build:**` becomes an explicit chain:
```
pnpm run build:remix && pnpm run build:server && pnpm run build:otlpworker && pnpm run build:sentry && pnpm run upload:sourcemaps
```
## Why this shape
I compared both forms side by side against the real `run-s` before
swapping:
| Behaviour | `run-s build:**` | explicit chain |
| --- | --- | --- |
| Scripts selected | remix, server, otlpworker, sentry | identical |
| Order | declaration order | identical |
| `upload:sourcemaps` matched by the glob | no | no |
| Second script fails | aborts, third never runs | identical |
| Exit code on failure | `1` | `1` |
`pnpm run --sequential "/^build:/"` was the closer-looking option, but
it keeps running scripts after one fails, so it is not a faithful
replacement.
The one thing given up is that `build:**` automatically picked up any
new `build:*` script, where the chain has to be edited. With four
entries that felt like the better trade.
`pnpm run build --filter webapp` passes end to end locally, all five
steps in order.
## Summary
Following a link straight into an organization's settings (for example
the usage limit link in a billing email) and then clicking "Back to app"
took you to `/`, which resolves to whichever organization you last had
selected, not the one whose settings you were looking at. The button now
links to the organization in the URL, so you land back in the org you
came from.
The org index route already redirects to the best project in that org,
so the destination is unchanged apart from being the right org.
Account settings still links to `/`, since that page is not org scoped
and has no org to return to.
## 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.
The charts section audited series colors as legend swatches only, which shows
the accents side by side but not at the size or the adjacency a real chart puts
them at.
Two examples with static data, sharing one series list so the same four accents
can be compared as fills and as strokes. The bars are stacked so the fills meet
edge to edge with nothing between them - the hardest case for telling two
accents apart, and the one a legend can't help with once you're reading a single
column. The lines are the same colors at 2px, where a series carries a fraction
of a bar's area and can drop under the 3:1 floor a fill clears comfortably.
The series are a deliberate mix rather than a best case: the queue accent moves
on all four themes, success and warning only on the dark ones, and error never
moves at all.
Animation is off. Each chart renders twice on this page, and one growing out of
the axis on every theme switch makes the two columns hard to compare mid-flight.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The page was built when the preference swapped icons as well as colors, and was
named and organised around that. The preference now only moves colors, so the
page name, the column header, the intro and the callout all said something the
feature no longer does.
Renamed throughout, including the route, so the URL matches the sidebar.
Section 2 was nine entries on glyphs shared between states. Two of them survive:
the environment labels and the three run statuses on the stack icon, where the
label drops to the surrounding text color and the icon carries a tint that
genuinely moves. The other seven argued purely from shape, or from a color the
preference never touches, so they're gone along with the constants and imports
they needed. What's left is retitled "Icon and label pairs", where the icon is
context for the label rather than the thing under audit.
The intro also now says which accents move where: several only shift on the dark
themes, because Light and White already darken them to the high-contrast value.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sibling sections sat 48px apart, and the profile page - which hand-rolls its
section headers rather than using SettingsSection - used 32px, so Profile,
Appearance and Interface ran together more than the pages built from the layout
components did.
Both now take 52px from one place, so the profile page gains the most and every
settings page lands on the same rhythm.
The value is written out twice, as a bare class for callers and inside
SettingsSection's own variant. That's deliberate: Tailwind only generates
classes it can see as complete literals, so composing
`[&:not(:first-child)]:${gap}` from the constant would emit no CSS at all and
silently collapse the gap to nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The hover chip beside the switch was tinted with the warning accent, which does
nothing on Light and White. Those themes already darken success and warning for
white, and they land on the very same values the high-contrast set uses - so the
preference genuinely has no effect on that token there, and the preview sat
still while claiming to demonstrate the change.
It now borrows the Resolved error-status treatment: a tinted green chip that
fills solid with white text under the preference. That swap is driven by the
preference itself rather than by an accent value, so it reads in all four themes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The medium track is h-4 with a 2px transparent border, so its content box is
12px - but the thumb was 14px. It overflowed by a pixel top and bottom, leaving
1px of track above and below the handle against 2px at whichever end it had
travelled to. On the profile page's toggles that read as a handle pressed up
against the rails.
The thumb is now 12px travelling 16px, so the gap is an even 2px on all four
sides in both positions. That's the rule `large` already follows: thumb equal to
the content box, travel equal to the content width minus the thumb.
`small` has the same mismatch and is left alone here - it's used across the app
rather than on this page, and the fix is noted in place for whenever someone
wants it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>