Skip to content

feat(webapp): dashboard agent — Watch - #4525

Open
kathiekiwi wants to merge 68 commits into
feat/dashboard-agent-uifrom
feat/dashboard-agent-flows-watch
Open

feat(webapp): dashboard agent — Watch#4525
kathiekiwi wants to merge 68 commits into
feat/dashboard-agent-uifrom
feat/dashboard-agent-flows-watch

Conversation

@kathiekiwi

@kathiekiwi kathiekiwi commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #4529, which is stacked on #4418. Merge those first.

Watch is the agent noticing something later: you ask it to tell you when a condition holds, and it answers when it does — or when it can't any more.

What's inside

  • Watches — condition, cadence and window, evaluated by a cron; one identity per chat so a repeat ask doesn't create a second watch.
  • Delivery — in-chat card, email alert, and the investigation that runs when a watch fires.
  • Submissions — a durable ledger keyed by (chatId, clientRequestId), so a retried submission replays instead of duplicating.
  • Watch token — a dedicated delayed-execution credential, accepted only by the watch endpoints and re-checked against the user's live access on every tick.

How to review

GUIDEBOOK.md — local setup and a walkthrough of all 15 scenarios.

@changeset-bot

changeset-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 3a64fb0

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/core Patch
@trigger.dev/build Patch
trigger.dev Patch
@trigger.dev/python Patch
@trigger.dev/redis-worker Patch
@trigger.dev/schema-to-json Patch
@trigger.dev/sdk Patch
@internal/cache Patch
@internal/clickhouse Patch
@internal/llm-model-catalog Patch
@internal/metrics-pipeline Patch
@trigger.dev/rbac Patch
@internal/redis Patch
@internal/replication Patch
@internal/run-engine Patch
@internal/run-store Patch
@internal/schedule-engine Patch
@trigger.dev/sso Patch
@internal/testcontainers Patch
@internal/tracing Patch
@internal/tsql Patch
@internal/dashboard-agent Patch
@internal/sdk-compat-tests Patch
@trigger.dev/react-hooks Patch
@trigger.dev/rsc Patch
@trigger.dev/database Patch
@trigger.dev/otlp-importer 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 Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1130e232-4855-4f79-ba59-5c6a380abf4a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Added watch creation and configuration for runs, queues, errors, and health reports. Added scheduled checks, lifecycle handling, wake notifications, automatic investigations, unread tracking, and cross-browser activity polling. Added email, Slack, and webhook alert delivery with subscription management. Added dashboard-agent tools, APIs, persistence, worker tasks, scenario tooling, documentation, and extensive unit and integration coverage.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the Watch feature and review guide but omits the required issue, checklist, testing, changelog, and screenshots sections. Complete the repository template by adding the issue reference, checklist, testing steps, changelog entry, and screenshots or an explicit not-applicable note.
Docstring Coverage ⚠️ Warning Docstring coverage is 64.71% 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 clearly identifies the main change: adding the dashboard agent Watch feature.
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/dashboard-agent-flows-watch

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.

@kathiekiwi
kathiekiwi marked this pull request as ready for review August 7, 2026 10:20

@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 potential issues.

View 1 additional finding in Devin Review.

Open in Devin Review

Comment thread apps/webapp/app/components/dashboard-agent/DashboardAgentPanel.tsx Outdated
Comment thread apps/webapp/app/components/dashboard-agent/DashboardAgent.tsx Outdated
Comment thread apps/webapp/app/components/dashboard-agent/DashboardAgent.tsx Outdated

@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: 18

Caution

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

⚠️ Outside diff range comments (1)
apps/webapp/app/components/dashboard-agent/DashboardAgentPanel.tsx (1)

141-168: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

A reload requested after a write can reuse an older in-flight request.

loadHistory returns the in-flight promise when one exists. The callers at Line 417 (submitWatch) and Line 501 (cancelWatch) run right after a server write. If a history fetch started before that write, the caller receives the older response, and the new watch chip or the removal does not appear until the next reload.

Consider queuing a follow-up request when one is already in flight.

♻️ Sketch: chain a fresh request instead of reusing the in-flight one
-  const loadHistory = useCallback(async () => {
-    if (historyInFlight.current) return historyInFlight.current;
-    const request = (async () => {
+  const loadHistory = useCallback(async () => {
+    const previous = historyInFlight.current;
+    const request = (async () => {
+      // Wait out an older request, so a reload after a write never reuses its response.
+      if (previous) await previous;
       try {
🧹 Nitpick comments (22)
apps/webapp/app/v3/commonWorker.server.ts (1)

165-174: 🩺 Stability & Availability | 🔵 Trivial

Note the overlap between the visibility timeout and the cron period.

visibilityTimeoutMs is 5 minutes and the cron period is also 5 minutes. If a sweep exceeds the visibility timeout, the message becomes visible again and a second run can start while the first is still working. maxAttempts: 1 limits retries but does not prevent that re-delivery.

The existing dashboardAgent.maintenance entry uses the same values, so this matches current practice. Confirm that sweepDashboardAgentWatches and rearmDashboardAgentWatchBatches are safe to run concurrently, or raise the visibility timeout above the period.

internal-packages/emails/src/index.tsx (1)

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

Consider using headline in the subject.

The subject interpolates data.identity, which is an internal condition key such as run_finished:run_abc123. The payload also carries headline, the human sentence the panel shows. Reading headline first gives a clearer subject and keeps the email consistent with the in-app wording.

headline is optional, so keep identity as the fallback.

♻️ Proposed subject change
       case "alert-dashboard-agent-watch": {
         return {
-          subject: `[${data.organization}] Watch update: ${data.identity}`,
+          subject: `[${data.organization}] Watch update: ${data.headline ?? data.identity}`,
           component: <AlertDashboardAgentWatchEmail {...data} />,
         };
       }
apps/webapp/app/services/dashboardAgentWatchRunChecks.ts (1)

19-36: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Keep the terminal-status list in sync with ~/v3/taskStatus.

FINAL_STATUSES currently matches FINAL_RUN_STATUSES, but imports should derive from the canonical source when possible. If the module must keep no server-side imports, add a type-level assertion that fails when this list drifts from FINAL_RUN_STATUSES.

apps/webapp/app/services/dashboardAgentWatches.server.ts (1)

930-954: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Hoist the shared TriggerClient if TriggerOptions includes trigger.

TriggerClient is exported from @trigger.dev/sdk, tasks.trigger accepts delay, idempotencyKey, and version, but idempotencyKeyTTL is only accepted by batch trigger options. If the single-task schedule path needs TTL, move it to batch scheduling; otherwise, create one module-level client for each apiOrigin to avoid repeated setup on each watch tick.

Source: Coding guidelines

apps/webapp/app/services/dashboardAgentWatchSweep.server.ts (1)

126-139: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

A rejected authorization promise is cached for the whole sweep.

authorizeOncePerSweep stores the pending promise before it settles. If authorize rejects, for example on a transient database error, every remaining watch in that group reuses the rejected promise. All those rows are then counted as failed in one sweep run instead of being retried independently. The next sweep run recovers them, so the impact is bounded. Consider evicting the entry on rejection.

♻️ Proposed eviction on rejection
     const cached = seen.get(key);
     if (cached) return cached;
-    const pending = authorize(watch);
+    const pending = authorize(watch).catch((error) => {
+      seen.delete(key);
+      throw error;
+    });
     seen.set(key, pending);
     return pending;
apps/webapp/app/services/dashboardAgentWatchToken.server.ts (1)

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

bearerToken only matches the exact scheme Bearer .

The scheme name in an Authorization header is case-insensitive. A header of bearer <token> leaves the prefix in place, so the extracted value fails the token prefix check and the caller receives 401. Match the scheme case-insensitively and allow repeated whitespace.

♻️ Proposed scheme matching
-  const value = raw.replace(/^Bearer /, "").trim();
+  const value = raw.replace(/^Bearer\s+/i, "").trim();
apps/webapp/app/services/dashboardAgentWatchBatch.server.ts (1)

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

The authorization cache key omits environmentId.

authorizeOnce keys on user, organization, and project. The equivalent helper in apps/webapp/app/services/dashboardAgentWatchSweep.server.ts at Line 132 also includes environmentId. The batch is scoped to a single environment by params, so the two keys agree today. Align the keys so a future change to the row loader cannot silently reuse another environment's authorization.

♻️ Proposed key alignment
-    const key = `${watch.userId}:${watch.organizationId}:${watch.projectId}`;
+    const key = `${watch.userId}:${watch.organizationId}:${watch.projectId}:${watch.environmentId}`;
apps/webapp/app/routes/api.v1.dashboard-agent.watches.$watchId.fired.ts (1)

94-99: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

A failing release masks the enqueue error and strands the claim.

If releaseWatchAlertDispatch throws, the rethrow at Line 98 is never reached and the caller sees the release error instead of the enqueue error. The claim also stays held, so no later attempt can send the alert. Swallow and log the release failure, then rethrow the original error.

♻️ Proposed error handling
     try {
       await enqueueWatchFiredAlert(watch, "fired");
     } catch (error) {
-      await releaseWatchAlertDispatch(dashboardAgentDb, { id: watch.id, terminalStatus: "fired" });
+      await releaseWatchAlertDispatch(dashboardAgentDb, {
+        id: watch.id,
+        terminalStatus: "fired",
+      }).catch((releaseError) =>
+        logger.error("Dashboard agent watch alert claim couldn't be released", {
+          watchId,
+          releaseError,
+        })
+      );
       throw error;
     }
internal-packages/dashboard-agent/src/watch-delivery.ts (1)

176-177: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Detect a lost delivery fence when markWatchDelivered returns null.

markWatchDelivered returns Watch | null. A null result means the claimId fence no longer matches, so another deliverer took the claim after the stale window elapsed. The current code ignores that result and continues to notifyFired and notifyInvestigate. Downstream dedup limits the damage, but the lost fence is invisible in logs.

Log the null result so a duplicate-wake incident is diagnosable.

♻️ Proposed change
-  await deps.store.markWatchDelivered({ id: claimed.id, claimId });
+  const marked = await deps.store.markWatchDelivered({ id: claimed.id, claimId });
+  if (!marked) {
+    // The claim was reclaimed after the stale window, so another deliverer may also
+    // append. The action id dedups the wake; record the race for diagnosis.
+    logger.warn("dashboard-agent watch delivery lost its claim after appending", {
+      watchId: claimed.id,
+      claimId,
+    });
+  }
internal-packages/dashboard-agent/src/watch-narration.ts (1)

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

Add an exhaustiveness guard to the category switch.

The switch has no default. If WatchPresentation["category"] gains a member, this function returns undefined at runtime while its declared return type stays string. An exhaustiveness check turns that into a compile error instead. The repository already uses assert-never for this pattern in apps/webapp/app/v3/services/alerts/deliverDashboardAgentWatchAlert.server.ts.

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

98-120: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Consider running the promoted-prompt lookup and the activity read concurrently.

This loader runs on every environment-scoped page load. getPromotedDashboardAgentPrompt and readDashboardAgentWakeActivity are both gated on hasDashboardAgentAccess and are independent, but they are awaited one after the other. That adds two serial round trips to a hot path.

Run them with Promise.all to remove one round trip. Keep the per-read failure isolation so a store outage still lets the dashboard load.

♻️ Proposed concurrent read
-  const promotedDashboardAgentPrompt = hasDashboardAgentAccess
-    ? await getPromotedDashboardAgentPrompt({
-        orgFeatureFlags: (project.organization.featureFlags as Record<string, unknown>) ?? {},
-      })
-    : null;
-
-  // One narrow read per page load, so the wake signal reaches a browser that has never opened
-  // the panel — including one whose watch hasn't fired yet. The poll never asks for this.
-  let dashboardAgentActivity: DashboardAgentWakeActivity = {
-    unreadWakes: 0,
-    hasActiveWatches: false,
-  };
-  if (hasDashboardAgentAccess) {
-    try {
-      dashboardAgentActivity = await readDashboardAgentWakeActivity(dashboardAgentDb, {
-        organizationId: project.organization.id,
-        userId: user.id,
-      });
-    } catch (error) {
-      // The dashboard must load even when the agent's store doesn't answer.
-      logger.error("Failed to read dashboard agent wake activity", { error });
-    }
-  }
+  const NO_ACTIVITY: DashboardAgentWakeActivity = { unreadWakes: 0, hasActiveWatches: false };
+
+  // One narrow read per page load, so the wake signal reaches a browser that has never opened
+  // the panel — including one whose watch hasn't fired yet. The poll never asks for this.
+  const [promotedDashboardAgentPrompt, dashboardAgentActivity] = hasDashboardAgentAccess
+    ? await Promise.all([
+        getPromotedDashboardAgentPrompt({
+          orgFeatureFlags: (project.organization.featureFlags as Record<string, unknown>) ?? {},
+        }),
+        readDashboardAgentWakeActivity(dashboardAgentDb, {
+          organizationId: project.organization.id,
+          userId: user.id,
+        }).catch((error) => {
+          // The dashboard must load even when the agent's store doesn't answer.
+          logger.error("Failed to read dashboard agent wake activity", { error });
+          return NO_ACTIVITY;
+        }),
+      ])
+    : [null, NO_ACTIVITY];
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.alerts.new/route.tsx (1)

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

Extract the alert-type list into one constant.

The literal list is now repeated on line 57 and line 61. This change had to update both branches by hand. The next alert type carries the same risk: if only the array branch is updated, a single-checkbox submission fails validation while a multi-checkbox submission succeeds.

Declare the values once and reuse them in both branches.

♻️ Proposed single source for the alert types
+const AlertTypeEnum = z.enum([
+  "TASK_RUN",
+  "DEPLOYMENT_FAILURE",
+  "DEPLOYMENT_SUCCESS",
+  "DASHBOARD_AGENT_WATCH",
+]);
+
 const FormSchema = z
   .object({
-    alertTypes: z
-      .array(
-        z.enum(["TASK_RUN", "DEPLOYMENT_FAILURE", "DEPLOYMENT_SUCCESS", "DASHBOARD_AGENT_WATCH"])
-      )
-      .min(1)
-      .or(
-        z.enum(["TASK_RUN", "DEPLOYMENT_FAILURE", "DEPLOYMENT_SUCCESS", "DASHBOARD_AGENT_WATCH"])
-      ),
+    alertTypes: z.array(AlertTypeEnum).min(1).or(AlertTypeEnum),
apps/webapp/test/dashboardAgentWatchCardRequestId.test.ts (1)

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

Assert the status of the keyed submit as well.

Line 160 only checks that the response body is not invalid_request. A 500 response with a different error code also passes. Assert the expected status to keep the control case meaningful.

♻️ Proposed change
-      expect(await withKey.json()).not.toMatchObject({ code: "invalid_request" });
+      expect(withKey.status).not.toBe(400);
+      expect(await withKey.json()).not.toMatchObject({ code: "invalid_request" });
internal-packages/dashboard-agent-contracts/src/blocks.test.ts (1)

255-259: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Assert the parsed watch spec, not only the intent kind.

The strict schema could strip or default fields inside intent.spec and this assertion would still pass. Add an assertion on the parsed spec so the round-trip covers the payload.

♻️ Proposed addition
     const strict = viewBlockSchema.parse({ ...body, ...envelope });
     expect(strict.type === "actions" && strict.actions[0].intent.kind).toBe("watch");
+    expect(strict.type === "actions" && strict.actions[0].intent).toMatchObject(watchAction.intent);
apps/webapp/test/dashboardAgentWatchToken.test.ts (1)

12-12: 📐 Maintainability & Code Quality | 🔵 Trivial

Import USER_ACTOR_TOKEN_PREFIX instead of hardcoding "tr_uat_".

@trigger.dev/rbac re-exports USER_ACTOR_TOKEN_PREFIX from @trigger.dev/plugins, so use that constant instead of duplicating the prefix in the watch-token tests and keep the cross-token prefix changes in sync.

[low_effort和low_reward]

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

1473-1481: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Narrow activeWatch on watching.

result.ok still includes the watching: false branch, which does not provide watchId or expiresAt. The callers read those for check/token operations, so add the watching guard before returning.

♻️ Proposed narrowing
   async function activeWatch(seeded: Seeded) {
     const result = await create({ seeded });
     if (!result.ok) throw new Error(`watch not created: ${result.code}`);
+    if (!result.watching) throw new Error("expected an active watch");
     return result;
   }

Source: Coding guidelines

internal-packages/dashboard-agent/src/watch-actions.test.ts (1)

505-523: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the missing-userId fallback.

narrateWatchWake has a branch at watch-actions.ts lines 540-547 that runs when clientData.userId is absent. It logs an error and calls persistMessages with the whole transcript. The comment on lines 532-535 states that a wholesale write can drop host-appended blocks, so this branch trades correctness for delivery on purpose.

No test in this file exercises it. A test that sends WAKE with clientData lacking userId, then asserts calls.appendMessage is empty and calls.persistMessages has one entry, pins that deliberate trade-off in place.

Do you want me to generate this test?

internal-packages/dashboard-agent/src/watch-actions.ts (1)

423-438: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The Haiku wake lane emits no telemetry and no cache metrics.

The Sonnet branch on lines 439-455 ends with ...resolved.toAISDKTelemetry(). The Haiku branch on lines 425-438 does not. conductWatchInvestigation (lines 796-818) and the agent's own run (lines 571-582 in dashboard-agent.ts) both record telemetry and call recordPromptCacheUsage.

The comment on lines 371-375 says Haiku handles the common wake and Sonnet only the consented-investigation wake. So the lane with the most traffic is the one with no observability.

Add telemetry to the Haiku branch so wake narrations appear alongside every other model call.

♻️ Proposed change
           maxOutputTokens: HAIKU_WAKE_MAX_OUTPUT_TOKENS,
+          ...resolved.toAISDKTelemetry(),
         })
apps/webapp/app/components/dashboard-agent/WakeBanner.tsx (1)

113-119: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Share one semantic-icon map. Both files declare an identical SEMANTIC_ICON record that maps WatchSemanticIcon to a Heroicons glyph. A new icon added to the contract must then be added in two places, and the two maps can drift.

  • apps/webapp/app/components/dashboard-agent/WakeBanner.tsx#L113-L119: export this map (or move it to a small shared module next to agent-badges) so it is the single definition.
  • apps/webapp/app/components/dashboard-agent/WatchChips.tsx#L38-L44: delete the local copy and import the shared map. This file already imports wakePresentation from WakeBanner.
apps/webapp/app/components/dashboard-agent/WatchCard.tsx (1)

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

Associate the Field label with its controls.

Field renders the label as a plain span. The pickers built from Choice are bare buttons. A screen reader announces "when it finishes" with no indication that it belongs to "Tell me". The numeric inputs carry aria-label, so only the picker rows are affected.

Add a group role and connect it to the label.

♻️ Proposed change
 function Field({ label, children }: { label: string; children: React.ReactNode }) {
+  const labelId = useId();
   return (
     <div className="flex flex-col gap-1">
-      <span className="text-xxs uppercase tracking-wide text-text-faint">{label}</span>
-      <div className="flex flex-wrap items-center gap-1">{children}</div>
+      <span id={labelId} className="text-xxs uppercase tracking-wide text-text-faint">
+        {label}
+      </span>
+      <div role="group" aria-labelledby={labelId} className="flex flex-wrap items-center gap-1">
+        {children}
+      </div>
     </div>
   );
 }
apps/webapp/app/components/dashboard-agent/watch-card.ts (2)

118-121: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

withWindow clamps but does not snap to an offered option.

The module comment at Line 5 states that "the window is always one of the offered options". withWindow only clamps between WATCH_WINDOW_HOURS_OPTIONS[0] and WATCH_MAX_HOURS. A value such as 7 passes through unchanged and is not an offered option. Today the card only passes values from WATCH_WINDOW_HOURS_OPTIONS, so the mismatch is not visible. WatchCard documents a free-text pre-fill path at Line 145, which could pass an arbitrary number.

Snap to the nearest offered option, in the same way clampCadence does.

♻️ Proposed change
 export function withWindow(draft: WatchDraft, maxHours: number): WatchDraft {
-  const clamped = Math.min(Math.max(maxHours, WATCH_WINDOW_HOURS_OPTIONS[0]), WATCH_MAX_HOURS);
+  const clamped = Math.min(Math.max(maxHours, WATCH_WINDOW_HOURS_OPTIONS[0]!), WATCH_MAX_HOURS);
+  const snapped =
+    WATCH_WINDOW_HOURS_OPTIONS.find((option) => option >= clamped) ??
+    WATCH_WINDOW_HOURS_OPTIONS[WATCH_WINDOW_HOURS_OPTIONS.length - 1]!;
-  return { ...draft, spec: { ...draft.spec, maxHours: clamped } as WatchSpec };
+  return { ...draft, spec: { ...draft.spec, maxHours: snapped } as WatchSpec };
 }

157-181: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

watchDraftError runs a Zod parse on every card render.

WatchCard calls watchDraftError(draft) at Line 164 of apps/webapp/app/components/dashboard-agent/WatchCard.tsx, directly in the render body. Each keystroke in the threshold input reparses the spec through watchSpecSchema.

Memoize the result in the card, keyed on draft.

Based on learnings, this repository treats Zod as a boundary validation tool for API handlers and storage reads/writes, not as inline render-time validation inside React components, to avoid per-render schema-parse overhead.

♻️ Proposed change in `WatchCard.tsx`
-  const localError = watchDraftError(draft);
+  const localError = useMemo(() => watchDraftError(draft), [draft]);

Source: Learnings


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f5885caf-6411-4004-a096-446ba10f9f27

📥 Commits

Reviewing files that changed from the base of the PR and between bb036f9 and 89b7522.

⛔ Files ignored due to path filters (2)
  • apps/webapp/test/__snapshots__/dashboardAgentWatchWording.test.ts.snap is excluded by !**/*.snap
  • internal-packages/dashboard-agent/src/__snapshots__/prompt-prefix.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (140)
  • .server-changes/dashboard-agent.md
  • apps/webapp/app/components/dashboard-agent/DashboardAgent.tsx
  • apps/webapp/app/components/dashboard-agent/DashboardAgentChat.tsx
  • apps/webapp/app/components/dashboard-agent/DashboardAgentDraft.tsx
  • apps/webapp/app/components/dashboard-agent/DashboardAgentHistory.tsx
  • apps/webapp/app/components/dashboard-agent/DashboardAgentMessages.tsx
  • apps/webapp/app/components/dashboard-agent/DashboardAgentPanel.tsx
  • apps/webapp/app/components/dashboard-agent/DashboardAgentSuggestedPrompts.tsx
  • apps/webapp/app/components/dashboard-agent/ReportView.tsx
  • apps/webapp/app/components/dashboard-agent/WakeBanner.tsx
  • apps/webapp/app/components/dashboard-agent/WatchButton.tsx
  • apps/webapp/app/components/dashboard-agent/WatchCard.tsx
  • apps/webapp/app/components/dashboard-agent/WatchChips.tsx
  • apps/webapp/app/components/dashboard-agent/WatchResultBlock.tsx
  • apps/webapp/app/components/dashboard-agent/WatchWakeToast.tsx
  • apps/webapp/app/components/dashboard-agent/chat-layout.test.ts
  • apps/webapp/app/components/dashboard-agent/chat-layout.tsx
  • apps/webapp/app/components/dashboard-agent/dashboardAgentLauncher.tsx
  • apps/webapp/app/components/dashboard-agent/demo/fixtures/page-context.ts
  • apps/webapp/app/components/dashboard-agent/list-row.tsx
  • apps/webapp/app/components/dashboard-agent/message-quota.ts
  • apps/webapp/app/components/dashboard-agent/pending-intents.test.ts
  • apps/webapp/app/components/dashboard-agent/pending-intents.ts
  • apps/webapp/app/components/dashboard-agent/report-sparkline.tsx
  • apps/webapp/app/components/dashboard-agent/suggested-prompts/page-prompts.ts
  • apps/webapp/app/components/dashboard-agent/suggested-prompts/prompt-chips.ts
  • apps/webapp/app/components/dashboard-agent/suggested-prompts/resolver.test.ts
  • apps/webapp/app/components/dashboard-agent/suggested-prompts/resolver.ts
  • apps/webapp/app/components/dashboard-agent/suggested-prompts/signal-prompts.ts
  • apps/webapp/app/components/dashboard-agent/tool-labels.ts
  • apps/webapp/app/components/dashboard-agent/turn-error.test.ts
  • apps/webapp/app/components/dashboard-agent/turn-error.ts
  • apps/webapp/app/components/dashboard-agent/view-actions.test.ts
  • apps/webapp/app/components/dashboard-agent/view-catalog.tsx
  • apps/webapp/app/components/dashboard-agent/wake-banner.test.ts
  • apps/webapp/app/components/dashboard-agent/wake-poll.test.ts
  • apps/webapp/app/components/dashboard-agent/wake-poll.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.ts
  • apps/webapp/app/components/dashboard-agent/watch-card.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-card.ts
  • apps/webapp/app/components/dashboard-agent/watch-chips.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-chips.ts
  • apps/webapp/app/components/dashboard-agent/watch-recommendations.ts
  • apps/webapp/app/components/queues/queue-thresholds.ts
  • apps/webapp/app/presenters/v3/ApiAlertChannelPresenter.server.ts
  • apps/webapp/app/presenters/v3/dashboardAgent/block-text.ts
  • apps/webapp/app/presenters/v3/dashboardAgent/index.ts
  • apps/webapp/app/presenters/v3/dashboardAgent/watch-wording.ts
  • apps/webapp/app/presenters/v3/reports/ReportPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.alerts.new/route.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.errors.$fingerprint/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/route.tsx
  • apps/webapp/app/routes/api.v1.dashboard-agent.alerts.$channelId.ts
  • apps/webapp/app/routes/api.v1.dashboard-agent.alerts.ts
  • apps/webapp/app/routes/api.v1.dashboard-agent.watches.$watchId.check.ts
  • apps/webapp/app/routes/api.v1.dashboard-agent.watches.$watchId.fired.ts
  • apps/webapp/app/routes/api.v1.dashboard-agent.watches.$watchId.investigate.ts
  • apps/webapp/app/routes/api.v1.dashboard-agent.watches.batch-check.ts
  • apps/webapp/app/routes/api.v1.dashboard-agent.watches.ts
  • apps/webapp/app/routes/resources.dashboard-agent.alerts.$channelId.unsubscribe.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboard-agent.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx
  • apps/webapp/app/services/dashboardAgentAlertContext.server.ts
  • apps/webapp/app/services/dashboardAgentAlertUnsubscribeToken.server.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
  • apps/webapp/app/services/dashboardAgentWatchBatch.server.ts
  • apps/webapp/app/services/dashboardAgentWatchCheckBase.ts
  • apps/webapp/app/services/dashboardAgentWatchChecks.server.ts
  • apps/webapp/app/services/dashboardAgentWatchChecks.ts
  • apps/webapp/app/services/dashboardAgentWatchErrorChecks.ts
  • apps/webapp/app/services/dashboardAgentWatchHealthChecks.ts
  • apps/webapp/app/services/dashboardAgentWatchInvestigate.server.ts
  • apps/webapp/app/services/dashboardAgentWatchQueueChecks.ts
  • apps/webapp/app/services/dashboardAgentWatchRunChecks.ts
  • apps/webapp/app/services/dashboardAgentWatchSweep.server.ts
  • apps/webapp/app/services/dashboardAgentWatchToken.server.ts
  • apps/webapp/app/services/dashboardAgentWatches.server.ts
  • apps/webapp/app/v3/alertsWorker.server.ts
  • apps/webapp/app/v3/commonWorker.server.ts
  • apps/webapp/app/v3/services/alerts/deliverAlert.server.ts
  • apps/webapp/app/v3/services/alerts/deliverDashboardAgentWatchAlert.server.ts
  • apps/webapp/package.json
  • apps/webapp/seed-watch-scenarios.mts
  • apps/webapp/test/dashboardAgentBodyCap.test.ts
  • apps/webapp/test/dashboardAgentTranscriptStore.test.ts
  • apps/webapp/test/dashboardAgentWakeActivity.test.ts
  • apps/webapp/test/dashboardAgentWatchAlertFanout.test.ts
  • apps/webapp/test/dashboardAgentWatchAlertOwnerScope.test.ts
  • apps/webapp/test/dashboardAgentWatchBatchFairness.test.ts
  • apps/webapp/test/dashboardAgentWatchBatchRecording.test.ts
  • apps/webapp/test/dashboardAgentWatchCardAtomicity.test.ts
  • apps/webapp/test/dashboardAgentWatchCardRequestId.test.ts
  • apps/webapp/test/dashboardAgentWatchChecks.test.ts
  • apps/webapp/test/dashboardAgentWatchCreationReads.test.ts
  • apps/webapp/test/dashboardAgentWatchInvestigate.test.ts
  • apps/webapp/test/dashboardAgentWatchSweepBoundary.test.ts
  • apps/webapp/test/dashboardAgentWatchTenancy.test.ts
  • apps/webapp/test/dashboardAgentWatchToken.test.ts
  • apps/webapp/test/dashboardAgentWatchWording.test.ts
  • apps/webapp/test/dashboardAgentWatches.test.ts
  • apps/webapp/test/reportHealth.test.ts
  • internal-packages/dashboard-agent-contracts/src/blocks.test.ts
  • internal-packages/dashboard-agent-contracts/src/blocks.ts
  • internal-packages/dashboard-agent-contracts/src/contracts.test.ts
  • internal-packages/dashboard-agent-contracts/src/index.ts
  • internal-packages/dashboard-agent-contracts/src/intent.ts
  • internal-packages/dashboard-agent-contracts/src/watch-wording.ts
  • internal-packages/dashboard-agent/GUIDEBOOK.md
  • internal-packages/dashboard-agent/README.md
  • internal-packages/dashboard-agent/src/agent-runtime.ts
  • internal-packages/dashboard-agent/src/compaction.test.ts
  • internal-packages/dashboard-agent/src/compaction.ts
  • internal-packages/dashboard-agent/src/dashboard-agent.eval.ts
  • internal-packages/dashboard-agent/src/dashboard-agent.test.ts
  • internal-packages/dashboard-agent/src/dashboard-agent.ts
  • internal-packages/dashboard-agent/src/eval-policy.ts
  • internal-packages/dashboard-agent/src/index.ts
  • internal-packages/dashboard-agent/src/step-cache.ts
  • internal-packages/dashboard-agent/src/tool-alerts.ts
  • internal-packages/dashboard-agent/src/tool-investigations.ts
  • internal-packages/dashboard-agent/src/tool-schemas.ts
  • internal-packages/dashboard-agent/src/tools.ts
  • internal-packages/dashboard-agent/src/watch-actions.test.ts
  • internal-packages/dashboard-agent/src/watch-actions.ts
  • internal-packages/dashboard-agent/src/watch-batch.ts
  • internal-packages/dashboard-agent/src/watch-delivery.ts
  • internal-packages/dashboard-agent/src/watch-lifecycle.ts
  • internal-packages/dashboard-agent/src/watch-narration.test.ts
  • internal-packages/dashboard-agent/src/watch-narration.ts
  • internal-packages/dashboard-agent/src/watch-task-adapters.ts
  • internal-packages/dashboard-agent/src/watch-tick.test.ts
  • internal-packages/dashboard-agent/src/watch-tick.ts
  • internal-packages/dashboard-agent/src/watch-tools.ts
  • internal-packages/database/prisma/migrations/20260729120000_add_dashboard_agent_watch_alert_type/migration.sql
  • internal-packages/database/prisma/schema.prisma
  • internal-packages/emails/emails/alert-dashboard-agent-watch.tsx
  • internal-packages/emails/src/index.tsx

Comment thread apps/webapp/app/components/dashboard-agent/DashboardAgentChat.tsx
Comment thread apps/webapp/app/components/dashboard-agent/watch-activity.ts
Comment thread apps/webapp/app/components/dashboard-agent/WatchChips.tsx
Comment thread apps/webapp/app/routes/api.v1.dashboard-agent.alerts.ts Outdated
Comment thread internal-packages/dashboard-agent/src/tool-schemas.ts
Comment thread internal-packages/dashboard-agent/src/watch-actions.ts
Comment thread internal-packages/dashboard-agent/src/watch-actions.ts
Comment thread internal-packages/dashboard-agent/src/watch-lifecycle.ts
Comment thread internal-packages/emails/emails/alert-dashboard-agent-watch.tsx
@kathiekiwi
kathiekiwi changed the base branch from feat/dashboard-agent-flows to feat/dashboard-agent-ui August 7, 2026 12:43
@pkg-pr-new

pkg-pr-new Bot commented Aug 7, 2026

Copy link
Copy Markdown

Open in StackBlitz

@trigger.dev/build

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

trigger.dev

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

@trigger.dev/core

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

@trigger.dev/python

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

@trigger.dev/react-hooks

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

@trigger.dev/redis-worker

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

@trigger.dev/rsc

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

@trigger.dev/schema-to-json

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

@trigger.dev/sdk

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

commit: e7432a8

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Observability map

As of 3a64fb0.

20/100 over 425 measured of 441 entry points (base 19, up 1)

What this PR changed

route base head now failing
/api/v1/dashboard-agent/watches/:watchId/check new 50 error-classification

FIX FIRST

  • /api/v1/projects/:projectRef/envvars (sensitive) - auth-boundary, request-context
  • /auth/sso (sensitive) - auth-boundary, request-context
  • /_app/orgs/:organizationSlug/settings/team (sensitive) - error-classification, auth-scope, request-context

AUDIT 3 of 50 sensitive mutations record an actor. 47 without one.
CONTEXT 22 of 425 entry points name a tenant on a failure path. 325 appear only here, 39 of them sensitive, in the JSON rather than the fix list.

What the score is made of
CHECKS
  error-classification  178 applicable, 102 pass,   0 sole, global without it 12
  auth-boundary          62 applicable,  57 pass,   0 sole, global without it 17
  auth-scope             19 applicable,  17 pass,   0 sole, global without it 20
  request-context       425 applicable,  22 pass, 225 sole, global without it 65
  audit-trail            50 applicable,   3 pass,   0 sole, not in the score

The score and findings here are report-only and never gate the merge. Separately, a required test suite keeps this tool's symbol and route lists in sync with the code they name, and can fail a pull request that renames or removes a symbol they reference, or that adds the first route with a segment they anticipate. Each failure names the list to edit. The rules and their reasons: internal-packages/observability-map/README.md.

@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 3 new potential issues.

Open in Devin Review

Comment thread apps/webapp/app/components/dashboard-agent/DashboardAgentPanel.tsx
Comment thread apps/webapp/app/components/dashboard-agent/ReportView.tsx
Comment thread apps/webapp/app/components/dashboard-agent/DashboardAgentPanel.tsx

@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 thread internal-packages/dashboard-agent/src/tool-alerts.ts
Comment thread internal-packages/dashboard-agent/src/tool-alerts.ts
Comment thread apps/webapp/app/components/dashboard-agent/DashboardAgentPanel.tsx
Comment thread apps/webapp/app/services/dashboardAgentWatchChecks.server.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 3 new potential issues.

Open in Devin Review

Comment thread apps/webapp/app/components/dashboard-agent/DashboardAgentPanel.tsx
Comment thread apps/webapp/app/services/dashboardAgentWatchBatch.server.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 1 new potential issue.

Open in Devin Review

Comment thread apps/webapp/app/routes/api.v1.dashboard-agent.alerts.ts Outdated

@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 1 new potential issue.

Open in Devin Review

Comment thread apps/webapp/app/services/dashboardAgentWatches.server.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 2 new potential issues.

Open in Devin Review

Comment thread apps/webapp/app/components/dashboard-agent/view-actions.test.ts Outdated

@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 1 new potential issue.

Open in Devin Review

Comment thread apps/webapp/app/services/dashboardAgentWatchSweep.server.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 3 new potential issues.

Open in Devin Review

Comment thread apps/webapp/app/components/dashboard-agent/DashboardAgentPanel.tsx
Comment thread apps/webapp/app/components/dashboard-agent/DashboardAgent.tsx
Comment thread apps/webapp/app/components/dashboard-agent/DashboardAgentPanel.tsx Outdated
devin-ai-integration[bot]

This comment was marked as resolved.

@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 1 new potential issue.

Open in Devin Review

Comment thread apps/webapp/app/services/dashboardAgentWatchChecks.server.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 thread apps/webapp/app/services/dashboardAgentWatchChecks.server.ts
devin-ai-integration[bot]

This comment was marked as resolved.

@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.

View 1 additional finding in Devin Review.

Open in Devin Review

Comment thread apps/webapp/app/components/dashboard-agent/DashboardAgentPanel.tsx
Comment thread apps/webapp/app/components/dashboard-agent/DashboardAgentPanel.tsx Outdated
Comment thread apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts
Comment thread apps/webapp/app/components/dashboard-agent/view-catalog.tsx Outdated

@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 1 new potential issue.

View 1 additional finding in Devin Review.

Open in Devin Review

@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 1 new potential issue.

Open in Devin Review

Comment thread internal-packages/dashboard-agent/src/tool-alerts.ts
@kathiekiwi
kathiekiwi force-pushed the feat/dashboard-agent-flows-watch branch from 712396b to e110e90 Compare August 8, 2026 12:05
@kathiekiwi
kathiekiwi force-pushed the feat/dashboard-agent-ui branch from bd4d4a0 to 887f5b6 Compare August 8, 2026 12:05
"Tell me when this run finishes", "ping me if that error comes back". A watch
checks on its own cadence, reports once, and stops within 24 hours. The user
confirms a pre-filled card, so nothing starts behind their back; the answer
lands in the chat, and by email if they asked for it.

Restores the feature this branch's base PR set aside, unchanged: the card and
chips, the wake banner and toast, the unread badge, the checks for runs, queues,
errors and health, the batch scheduler and its backstops, the alert channel and
its email, and the agent's schedule_watch tool with the prompt that governs it.
An investigation card carries its own watch button, and the prompt asked for a watch offer on top of it. The card wins — it is the one with the pre-filled spec — and the prompt now says so too.
create_alert reached for an `alert` envelope the route never sent, so it
always reported undefined, and the 403 branch keyed on `reason` where the
route sends `code` — the "email isn't set up on this instance" wording was
unreachable. The unit fixtures were written against both invented shapes, so
they hid it; they now mirror the route's bodies exactly.

The subscribe path also inlined its deduplication key instead of calling
`watchAlertDeduplicationKey`, the one record of whose channel it is.
Each failed check stored the whole previous `lastResult` under `previous`, so
consecutive failures wrapped one another without bound. The row escapes: an
unverified expiry copies `lastResult` into the wake facts, which the alert and
the webhook body serialise. A failure record is now unwrapped before it is
stored, so `previous` is always the last observation the check really made.
…r_view

The flag was computed inside `ViewBlocks`, which sees the blocks of a single
`render_view` part. A turn that renders the investigation card in one call and
the actions block in another gave each call its own answer, and the duplicate
Watch button came back. It is now computed where every part of the message is
in scope; a card can still add its own offer, never drop the turn's.
The poll deduped fresh wakes against a 50-id localStorage set alone, so inside
the feed's 15-minute window a second browser — or one whose site data was
cleared — toasted wakes the user had already read. The payload's own `unread`
flag now gates the toast as well. A wake landing in an open chat stays unread
until that chat's next read, so it still toasts.
`SimpleTooltip` drops its trigger from the tab order unless `tabbable` is set,
and the chip's label tooltip is the only place its status, cadence and expiry
are written. The cancel tooltip beside it already passed the prop.
The seed script refused a non-local Redis or ClickHouse host outright, but sent
the target's API key to whatever `APP_ORIGIN` named. All three now go through
one guard, which also fixes it for a bracketed IPv6 host — `URL.hostname` hands
back `[::1]`, which the old set membership never matched.
…t read

The column was added nullable and every reader treats NULL as unread, so the
first load after rollout reported every pre-existing chat unread.
… ClickHouse

The chat route now pulls in the watch services, and through them a ClickHouse
client built at import time from an unset env var.
The file the review comment named was missed: it opened a pool per case and never closed one. Adds the sibling files' afterEach and their 30s case timeout.
LEGACY_ASK_AI_SHORTCUT no longer exists, so the import was undefined and the assertion passed vacuously. Uses ASK_AI_SHORTCUT from ask-ai-channels and pins its key and modifiers.
@kathiekiwi
kathiekiwi force-pushed the feat/dashboard-agent-flows-watch branch from c0f0058 to e7432a8 Compare August 8, 2026 14:30
@kathiekiwi
kathiekiwi force-pushed the feat/dashboard-agent-ui branch from 887f5b6 to 17a0f07 Compare August 8, 2026 14:30
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@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: 10

Caution

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

⚠️ Outside diff range comments (1)
internal-packages/dashboard-agent/src/repo-tools.ts (1)

266-278: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Handle ranges that start beyond the end of the file.

If startLine exceeds the file length, from > to and range.content is empty. Line 273 then returns endLine: to, which is before startLine and does not identify a line that was served. Return an explicit out-of-range result, or define empty-range metadata before calculating served.

🧹 Nitpick comments (8)
apps/webapp/test/dashboardAgentWatchQueueName.test.ts (2)

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

Remove the unused chat created in seed.

createFor creates its own chat for every watch. The chat created here is never referenced, so it only adds a write to each test.

♻️ Proposed cleanup
-  await createChat(ctx.agentDb, {
-    id: `chat_${suffix()}`,
-    organizationId: organization.id,
-    userId: user.id,
-  });
-
   return { user, organization, project, environment };

178-203: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Set an explicit timeout on the new container tests. Both new suites call postgresTest without a timeout, while sibling suites such as apps/webapp/test/dashboardAgentTranscriptStore.test.ts pass 30_000. Container start plus migration replay can exceed the default timeout and cause flaky failures.

  • apps/webapp/test/dashboardAgentWatchQueueName.test.ts#L178-L203: pass 30_000 as the final argument to both postgresTest cases.
  • apps/webapp/test/dashboardAgentWatchSweepAlertOnce.test.ts#L167-L209: pass 30_000 as the final argument to the postgresTest case.
apps/webapp/app/components/dashboard-agent/DashboardAgentPanel.tsx (1)

161-190: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Coalescing can return a list that predates the caller's write.

loadHistory returns the in-flight promise to any later caller. submitWatch (Line 433) and cancelWatch (Line 524) call it right after a write. If a history request was already in flight when the write completed, the returned list can be the response to that earlier request, so the new watch chip is missing, or the cancelled one is still present, until the next reload.

Consider marking the in-flight request as stale and starting one more fetch when a write lands during it.

apps/webapp/app/components/dashboard-agent/watch-card-state.test.ts (1)

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

The exact-source assertion is brittle.

Line 97 asserts a source string that includes exact indentation (\n return ...). A formatting change inside claimChatSlot, or a nesting change, fails this test without any behavior change. The bumps count check on Line 96 already carries most of the value.

Consider matching with a whitespace-tolerant regular expression instead of a literal string.

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

105-128: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Two blocking reads are added to every environment-scoped page load.

readDashboardAgentWakeActivity and countChatsWithUnreadWork run on every navigation into an env-scoped route, after the project query and the access check. They are correctly parallel with each other and correctly scoped by organizationId and userId. The added latency lands on a hot path.

Consider adding a timeout or a short per-user cache around these reads, so a slow agent store degrades the dot rather than the page. Note also that the comment on Line 105 says "One narrow read" while the code issues two.

internal-packages/dashboard-agent/src/tool-api.ts (1)

100-108: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

row.type is trusted without a type check.

Line 102 casts row.type to string and uses ??, which only replaces null and undefined. If the route ever returns a non-string or an empty string, that value reaches base.queueType and the "custom" comparison on line 417 silently changes the consumer-task lookup. Validate the value instead.

♻️ Proposed guard
-    queueType: (row.type as string) ?? queueType,
+    queueType: row.type === "task" || row.type === "custom" ? row.type : queueType,
packages/core/src/v3/schemas/api.ts (1)

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

Consider a typed shape for queueConfig.

z.any() gives SDK consumers no type information for a field they must read by name. The only known consumer reads queueConfig.name. A minimal object shape documents the contract and keeps the field permissive.

♻️ Proposed shape
-  queueConfig: z.any().nullish(),
+  queueConfig: z.object({ name: z.string().nullish() }).passthrough().nullish(),

This matches payloadSchema in intent while still accepting absent and null values. Keep z.any() if the queue config shape is expected to change often.

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

27-43: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Remove module mocks from this integration test.

This test uses postgresTest, but it replaces ~/db.server, ~/services/dashboardAgentDb.server, and ~/v3/canAccessDashboardAgent.server with vi.mock(). Pass the real Testcontainers-backed clients through explicit service dependencies instead. This keeps the integration path representative and follows the test rule that dependencies must not be mocked.

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 29dccd6c-57fd-4df3-a507-b8f5cc6e18bc

📥 Commits

Reviewing files that changed from the base of the PR and between 17a0f07 and e7432a8.

⛔ Files ignored due to path filters (2)
  • apps/webapp/test/__snapshots__/dashboardAgentWatchWording.test.ts.snap is excluded by !**/*.snap
  • internal-packages/dashboard-agent/src/__snapshots__/prompt-prefix.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (198)
  • .changeset/quiet-hounds-shave.md
  • .server-changes/dashboard-agent.md
  • apps/webapp/app/components/dashboard-agent/ActionsBlock.tsx
  • apps/webapp/app/components/dashboard-agent/DashboardAgent.tsx
  • apps/webapp/app/components/dashboard-agent/DashboardAgentChat.tsx
  • apps/webapp/app/components/dashboard-agent/DashboardAgentDraft.tsx
  • apps/webapp/app/components/dashboard-agent/DashboardAgentHeader.tsx
  • apps/webapp/app/components/dashboard-agent/DashboardAgentHistory.tsx
  • apps/webapp/app/components/dashboard-agent/DashboardAgentMessages.tsx
  • apps/webapp/app/components/dashboard-agent/DashboardAgentPanel.tsx
  • apps/webapp/app/components/dashboard-agent/DashboardAgentSuggestedPrompts.tsx
  • apps/webapp/app/components/dashboard-agent/ReportView.tsx
  • apps/webapp/app/components/dashboard-agent/WakeBanner.tsx
  • apps/webapp/app/components/dashboard-agent/WatchButton.tsx
  • apps/webapp/app/components/dashboard-agent/WatchCard.tsx
  • apps/webapp/app/components/dashboard-agent/WatchChips.test.ts
  • apps/webapp/app/components/dashboard-agent/WatchChips.tsx
  • apps/webapp/app/components/dashboard-agent/WatchResultBlock.tsx
  • apps/webapp/app/components/dashboard-agent/WatchWakeToast.tsx
  • apps/webapp/app/components/dashboard-agent/agent-shortcuts.test.ts
  • apps/webapp/app/components/dashboard-agent/chat-layout.test.ts
  • apps/webapp/app/components/dashboard-agent/chat-layout.tsx
  • apps/webapp/app/components/dashboard-agent/dashboardAgentLauncher.tsx
  • apps/webapp/app/components/dashboard-agent/demo/fixtures/page-context.ts
  • apps/webapp/app/components/dashboard-agent/explicit-prompt.test.ts
  • apps/webapp/app/components/dashboard-agent/explicit-prompt.ts
  • apps/webapp/app/components/dashboard-agent/list-row.tsx
  • apps/webapp/app/components/dashboard-agent/message-quota.ts
  • apps/webapp/app/components/dashboard-agent/panel-escape.test.ts
  • apps/webapp/app/components/dashboard-agent/panel-escape.ts
  • apps/webapp/app/components/dashboard-agent/pending-intents.test.ts
  • apps/webapp/app/components/dashboard-agent/pending-intents.ts
  • apps/webapp/app/components/dashboard-agent/pending-turn.test.ts
  • apps/webapp/app/components/dashboard-agent/pending-turn.ts
  • apps/webapp/app/components/dashboard-agent/report-sparkline.tsx
  • apps/webapp/app/components/dashboard-agent/suggested-prompts/page-mappers.test.ts
  • apps/webapp/app/components/dashboard-agent/suggested-prompts/page-mappers.ts
  • apps/webapp/app/components/dashboard-agent/suggested-prompts/page-prompts.ts
  • apps/webapp/app/components/dashboard-agent/suggested-prompts/prompt-chips.ts
  • apps/webapp/app/components/dashboard-agent/suggested-prompts/resolver.test.ts
  • apps/webapp/app/components/dashboard-agent/suggested-prompts/resolver.ts
  • apps/webapp/app/components/dashboard-agent/suggested-prompts/signal-prompts.ts
  • apps/webapp/app/components/dashboard-agent/tool-labels.ts
  • apps/webapp/app/components/dashboard-agent/turn-error.test.ts
  • apps/webapp/app/components/dashboard-agent/turn-error.ts
  • apps/webapp/app/components/dashboard-agent/turn-navigation.test.ts
  • apps/webapp/app/components/dashboard-agent/turn-navigation.ts
  • apps/webapp/app/components/dashboard-agent/turn-teardown.test.ts
  • apps/webapp/app/components/dashboard-agent/turn-teardown.ts
  • apps/webapp/app/components/dashboard-agent/unread-counts.test.ts
  • apps/webapp/app/components/dashboard-agent/unread-counts.ts
  • apps/webapp/app/components/dashboard-agent/unread-work.test.ts
  • apps/webapp/app/components/dashboard-agent/view-actions.test.ts
  • apps/webapp/app/components/dashboard-agent/view-actions.ts
  • apps/webapp/app/components/dashboard-agent/view-catalog.tsx
  • apps/webapp/app/components/dashboard-agent/wake-banner.test.ts
  • apps/webapp/app/components/dashboard-agent/wake-poll.test.ts
  • apps/webapp/app/components/dashboard-agent/wake-poll.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.ts
  • apps/webapp/app/components/dashboard-agent/watch-card-state.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-card-state.ts
  • apps/webapp/app/components/dashboard-agent/watch-card.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-card.ts
  • apps/webapp/app/components/dashboard-agent/watch-chips.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-chips.ts
  • apps/webapp/app/components/dashboard-agent/watch-recommendations.ts
  • apps/webapp/app/components/queues/queue-name.ts
  • apps/webapp/app/components/queues/queue-thresholds.ts
  • apps/webapp/app/presenters/v3/ApiAlertChannelPresenter.server.ts
  • apps/webapp/app/presenters/v3/dashboardAgent/block-text.ts
  • apps/webapp/app/presenters/v3/dashboardAgent/index.ts
  • apps/webapp/app/presenters/v3/dashboardAgent/watch-wording.ts
  • apps/webapp/app/presenters/v3/reports/ReportPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.alerts.new/route.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.errors.$fingerprint/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/route.tsx
  • apps/webapp/app/routes/api.v1.dashboard-agent.alerts.$channelId.ts
  • apps/webapp/app/routes/api.v1.dashboard-agent.alerts.ts
  • apps/webapp/app/routes/api.v1.dashboard-agent.watches.$watchId.check.ts
  • apps/webapp/app/routes/api.v1.dashboard-agent.watches.$watchId.fired.ts
  • apps/webapp/app/routes/api.v1.dashboard-agent.watches.$watchId.investigate.ts
  • apps/webapp/app/routes/api.v1.dashboard-agent.watches.batch-check.ts
  • apps/webapp/app/routes/api.v1.dashboard-agent.watches.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts
  • apps/webapp/app/routes/resources.dashboard-agent.alerts.$channelId.unsubscribe.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboard-agent.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx
  • apps/webapp/app/services/dashboardAgent.server.ts
  • apps/webapp/app/services/dashboardAgentAlertContext.server.ts
  • apps/webapp/app/services/dashboardAgentAlertUnsubscribeToken.server.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
  • apps/webapp/app/services/dashboardAgentWatchBatch.server.ts
  • apps/webapp/app/services/dashboardAgentWatchCheckBase.ts
  • apps/webapp/app/services/dashboardAgentWatchChecks.server.ts
  • apps/webapp/app/services/dashboardAgentWatchChecks.ts
  • apps/webapp/app/services/dashboardAgentWatchErrorChecks.ts
  • apps/webapp/app/services/dashboardAgentWatchHealthChecks.ts
  • apps/webapp/app/services/dashboardAgentWatchInvestigate.server.ts
  • apps/webapp/app/services/dashboardAgentWatchQueueChecks.ts
  • apps/webapp/app/services/dashboardAgentWatchRunChecks.ts
  • apps/webapp/app/services/dashboardAgentWatchSweep.server.ts
  • apps/webapp/app/services/dashboardAgentWatchToken.server.ts
  • apps/webapp/app/services/dashboardAgentWatches.server.ts
  • apps/webapp/app/utils/localHostGuard.test.ts
  • apps/webapp/app/utils/localHostGuard.ts
  • apps/webapp/app/v3/alertsWorker.server.ts
  • apps/webapp/app/v3/commonWorker.server.ts
  • apps/webapp/app/v3/queryScope.ts
  • apps/webapp/app/v3/services/alerts/deliverAlert.server.ts
  • apps/webapp/app/v3/services/alerts/deliverDashboardAgentWatchAlert.server.ts
  • apps/webapp/package.json
  • apps/webapp/seed-watch-scenarios.mts
  • apps/webapp/test/dashboardAgentBodyCap.test.ts
  • apps/webapp/test/dashboardAgentCreateChatOrdering.test.ts
  • apps/webapp/test/dashboardAgentInvestigationWinner.test.ts
  • apps/webapp/test/dashboardAgentLastReadBackfill.test.ts
  • apps/webapp/test/dashboardAgentToolScopes.test.ts
  • apps/webapp/test/dashboardAgentTranscriptStore.test.ts
  • apps/webapp/test/dashboardAgentWakeActivity.test.ts
  • apps/webapp/test/dashboardAgentWatchAlertFanout.test.ts
  • apps/webapp/test/dashboardAgentWatchAlertOwnerScope.test.ts
  • apps/webapp/test/dashboardAgentWatchBatchFairness.test.ts
  • apps/webapp/test/dashboardAgentWatchBatchRecording.test.ts
  • apps/webapp/test/dashboardAgentWatchCardAtomicity.test.ts
  • apps/webapp/test/dashboardAgentWatchCardRequestId.test.ts
  • apps/webapp/test/dashboardAgentWatchChecks.test.ts
  • apps/webapp/test/dashboardAgentWatchCreationReads.test.ts
  • apps/webapp/test/dashboardAgentWatchErrorFingerprint.test.ts
  • apps/webapp/test/dashboardAgentWatchInvestigate.test.ts
  • apps/webapp/test/dashboardAgentWatchQueueAge.test.ts
  • apps/webapp/test/dashboardAgentWatchQueueName.test.ts
  • apps/webapp/test/dashboardAgentWatchSweepAlertOnce.test.ts
  • apps/webapp/test/dashboardAgentWatchSweepBoundary.test.ts
  • apps/webapp/test/dashboardAgentWatchTenancy.test.ts
  • apps/webapp/test/dashboardAgentWatchToken.test.ts
  • apps/webapp/test/dashboardAgentWatchWording.test.ts
  • apps/webapp/test/dashboardAgentWatches.test.ts
  • apps/webapp/test/queryScope.test.ts
  • apps/webapp/test/reportCurationTrust.test.ts
  • apps/webapp/test/reportHealth.test.ts
  • internal-packages/dashboard-agent-contracts/src/blocks.test.ts
  • internal-packages/dashboard-agent-contracts/src/blocks.ts
  • internal-packages/dashboard-agent-contracts/src/contracts.test.ts
  • internal-packages/dashboard-agent-contracts/src/evidence.ts
  • internal-packages/dashboard-agent-contracts/src/index.ts
  • internal-packages/dashboard-agent-contracts/src/intent.ts
  • internal-packages/dashboard-agent-contracts/src/page-context.ts
  • internal-packages/dashboard-agent-contracts/src/watch-wording.ts
  • internal-packages/dashboard-agent-contracts/src/watch.test.ts
  • internal-packages/dashboard-agent-contracts/src/watch.ts
  • internal-packages/dashboard-agent-db/drizzle/0002_watches_and_chat_messages.sql
  • internal-packages/dashboard-agent-db/src/ids.ts
  • internal-packages/dashboard-agent-db/src/queries.ts
  • internal-packages/dashboard-agent/GUIDEBOOK.md
  • internal-packages/dashboard-agent/README.md
  • internal-packages/dashboard-agent/package.json
  • internal-packages/dashboard-agent/src/agent-runtime.ts
  • internal-packages/dashboard-agent/src/compaction.test.ts
  • internal-packages/dashboard-agent/src/compaction.ts
  • internal-packages/dashboard-agent/src/dashboard-agent.eval.ts
  • internal-packages/dashboard-agent/src/dashboard-agent.test.ts
  • internal-packages/dashboard-agent/src/dashboard-agent.ts
  • internal-packages/dashboard-agent/src/eval-error-category.test.ts
  • internal-packages/dashboard-agent/src/eval-policy.ts
  • internal-packages/dashboard-agent/src/eval-redaction.test.ts
  • internal-packages/dashboard-agent/src/eval-turn.ts
  • internal-packages/dashboard-agent/src/index.ts
  • internal-packages/dashboard-agent/src/repo-tools.test.ts
  • internal-packages/dashboard-agent/src/repo-tools.ts
  • internal-packages/dashboard-agent/src/step-cache.ts
  • internal-packages/dashboard-agent/src/test-support.ts
  • internal-packages/dashboard-agent/src/tool-alerts.ts
  • internal-packages/dashboard-agent/src/tool-api-client.ts
  • internal-packages/dashboard-agent/src/tool-api.ts
  • internal-packages/dashboard-agent/src/tool-curation.ts
  • internal-packages/dashboard-agent/src/tool-investigations.ts
  • internal-packages/dashboard-agent/src/tool-queue.test.ts
  • internal-packages/dashboard-agent/src/tool-schemas.ts
  • internal-packages/dashboard-agent/src/tools.ts
  • internal-packages/dashboard-agent/src/watch-actions.test.ts
  • internal-packages/dashboard-agent/src/watch-actions.ts
  • internal-packages/dashboard-agent/src/watch-batch.ts
  • internal-packages/dashboard-agent/src/watch-delivery.ts
  • internal-packages/dashboard-agent/src/watch-lifecycle.ts
  • internal-packages/dashboard-agent/src/watch-narration.test.ts
  • internal-packages/dashboard-agent/src/watch-narration.ts
  • internal-packages/dashboard-agent/src/watch-task-adapters.ts
  • internal-packages/dashboard-agent/src/watch-tick.test.ts
  • internal-packages/dashboard-agent/src/watch-tick.ts
  • internal-packages/dashboard-agent/src/watch-tools.ts
  • internal-packages/database/prisma/migrations/20260729120000_add_dashboard_agent_watch_alert_type/migration.sql
  • internal-packages/database/prisma/schema.prisma
  • internal-packages/emails/emails/alert-dashboard-agent-watch.tsx
  • internal-packages/emails/src/index.tsx
  • packages/core/src/v3/schemas/api.ts
🚧 Files skipped from review as they are similar to previous changes (115)
  • apps/webapp/package.json
  • apps/webapp/app/components/dashboard-agent/suggested-prompts/resolver.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.alerts/route.tsx
  • apps/webapp/test/reportHealth.test.ts
  • internal-packages/database/prisma/schema.prisma
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx
  • internal-packages/dashboard-agent/README.md
  • apps/webapp/app/components/dashboard-agent/pending-intents.ts
  • apps/webapp/app/presenters/v3/ApiAlertChannelPresenter.server.ts
  • internal-packages/dashboard-agent/src/dashboard-agent.eval.ts
  • apps/webapp/app/components/dashboard-agent/tool-labels.ts
  • internal-packages/emails/src/index.tsx
  • apps/webapp/app/presenters/v3/dashboardAgent/watch-wording.ts
  • apps/webapp/app/components/queues/queue-thresholds.ts
  • internal-packages/dashboard-agent/src/step-cache.ts
  • apps/webapp/app/components/dashboard-agent/turn-error.test.ts
  • apps/webapp/app/components/dashboard-agent/chat-layout.test.ts
  • internal-packages/dashboard-agent/src/watch-narration.test.ts
  • apps/webapp/app/components/dashboard-agent/wake-banner.test.ts
  • apps/webapp/app/components/dashboard-agent/list-row.tsx
  • apps/webapp/app/services/dashboardAgentWatchHealthChecks.ts
  • apps/webapp/app/components/dashboard-agent/WatchButton.tsx
  • apps/webapp/app/components/dashboard-agent/demo/fixtures/page-context.ts
  • apps/webapp/app/components/dashboard-agent/watch-chips.test.ts
  • internal-packages/dashboard-agent/src/tools.ts
  • apps/webapp/app/components/dashboard-agent/DashboardAgentDraft.tsx
  • apps/webapp/app/v3/services/alerts/deliverAlert.server.ts
  • apps/webapp/app/presenters/v3/reports/ReportPresenter.server.ts
  • internal-packages/dashboard-agent/src/tool-investigations.ts
  • apps/webapp/app/routes/app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues.$queueParam/route.tsx
  • apps/webapp/test/dashboardAgentWatchSweepBoundary.test.ts
  • internal-packages/dashboard-agent-contracts/src/watch-wording.ts
  • apps/webapp/app/routes/api.v1.dashboard-agent.alerts.$channelId.ts
  • apps/webapp/test/dashboardAgentWatchToken.test.ts
  • apps/webapp/app/services/dashboardAgentWatchQueueChecks.ts
  • internal-packages/dashboard-agent-contracts/src/contracts.test.ts
  • apps/webapp/app/components/dashboard-agent/WatchChips.tsx
  • apps/webapp/app/services/dashboardAgentWatchToken.server.ts
  • apps/webapp/test/dashboardAgentWatchWording.test.ts
  • apps/webapp/test/dashboardAgentWakeActivity.test.ts
  • apps/webapp/app/services/dashboardAgentWatchChecks.ts
  • apps/webapp/test/dashboardAgentWatchBatchFairness.test.ts
  • internal-packages/dashboard-agent-contracts/src/intent.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
  • apps/webapp/test/dashboardAgentWatchAlertFanout.test.ts
  • apps/webapp/app/components/dashboard-agent/pending-intents.test.ts
  • internal-packages/dashboard-agent-contracts/src/index.ts
  • apps/webapp/app/components/dashboard-agent/WatchWakeToast.tsx
  • apps/webapp/app/presenters/v3/dashboardAgent/block-text.ts
  • apps/webapp/app/components/dashboard-agent/watch-recommendations.ts
  • apps/webapp/app/routes/api.v1.dashboard-agent.watches.$watchId.investigate.ts
  • apps/webapp/app/components/dashboard-agent/WatchResultBlock.tsx
  • apps/webapp/test/dashboardAgentWatchCreationReads.test.ts
  • apps/webapp/test/dashboardAgentBodyCap.test.ts
  • apps/webapp/app/components/dashboard-agent/report-sparkline.tsx
  • apps/webapp/app/components/dashboard-agent/turn-error.ts
  • apps/webapp/app/components/dashboard-agent/DashboardAgentSuggestedPrompts.tsx
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/routes/api.v1.dashboard-agent.watches.ts
  • apps/webapp/app/components/dashboard-agent/ReportView.tsx
  • apps/webapp/app/routes/api.v1.dashboard-agent.watches.$watchId.check.ts
  • internal-packages/dashboard-agent/src/watch-delivery.ts
  • apps/webapp/app/routes/api.v1.dashboard-agent.watches.batch-check.ts
  • apps/webapp/app/components/dashboard-agent/dashboardAgentLauncher.tsx
  • apps/webapp/app/components/dashboard-agent/message-quota.ts
  • apps/webapp/app/v3/services/alerts/deliverDashboardAgentWatchAlert.server.ts
  • internal-packages/dashboard-agent/src/dashboard-agent.ts
  • apps/webapp/app/v3/commonWorker.server.ts
  • apps/webapp/test/dashboardAgentWatchInvestigate.test.ts
  • internal-packages/dashboard-agent/src/watch-narration.ts
  • apps/webapp/app/presenters/v3/dashboardAgent/index.ts
  • apps/webapp/test/dashboardAgentWatchBatchRecording.test.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
  • internal-packages/dashboard-agent/src/watch-lifecycle.ts
  • apps/webapp/app/components/dashboard-agent/watch-card.ts
  • apps/webapp/app/components/dashboard-agent/suggested-prompts/prompt-chips.ts
  • apps/webapp/app/components/dashboard-agent/WatchCard.tsx
  • apps/webapp/app/services/dashboardAgentWatchInvestigate.server.ts
  • internal-packages/dashboard-agent/src/index.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.alerts.new/route.tsx
  • apps/webapp/app/components/dashboard-agent/WakeBanner.tsx
  • apps/webapp/test/dashboardAgentWatchCardAtomicity.test.ts
  • apps/webapp/app/routes/resources.dashboard-agent.alerts.$channelId.unsubscribe.tsx
  • apps/webapp/app/services/dashboardAgentAlertContext.server.ts
  • apps/webapp/app/services/dashboardAgentWatchErrorChecks.ts
  • internal-packages/dashboard-agent/src/watch-tools.ts
  • apps/webapp/test/dashboardAgentWatchChecks.test.ts
  • apps/webapp/app/routes/api.v1.dashboard-agent.alerts.ts
  • internal-packages/dashboard-agent-contracts/src/blocks.ts
  • internal-packages/dashboard-agent/src/dashboard-agent.test.ts
  • internal-packages/dashboard-agent/src/watch-task-adapters.ts
  • internal-packages/dashboard-agent/src/watch-tick.test.ts
  • apps/webapp/app/services/dashboardAgentWatchSweep.server.ts
  • apps/webapp/seed-watch-scenarios.mts
  • apps/webapp/test/dashboardAgentWatchTenancy.test.ts
  • internal-packages/dashboard-agent/src/watch-batch.ts
  • apps/webapp/test/dashboardAgentWatchCardRequestId.test.ts
  • internal-packages/emails/emails/alert-dashboard-agent-watch.tsx
  • apps/webapp/app/v3/alertsWorker.server.ts
  • internal-packages/dashboard-agent/src/tool-schemas.ts
  • apps/webapp/app/components/dashboard-agent/suggested-prompts/signal-prompts.ts
  • apps/webapp/app/components/dashboard-agent/watch-chips.ts
  • apps/webapp/app/services/dashboardAgentWatches.server.ts
  • internal-packages/dashboard-agent/src/watch-tick.ts
  • apps/webapp/app/services/dashboardAgentWatchChecks.server.ts
  • apps/webapp/app/components/dashboard-agent/suggested-prompts/page-prompts.ts
  • apps/webapp/app/services/dashboardAgentWatchBatch.server.ts
  • apps/webapp/app/routes/api.v1.dashboard-agent.watches.$watchId.fired.ts
  • apps/webapp/app/services/dashboardAgentWatchCheckBase.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboard-agent.ts
  • internal-packages/dashboard-agent/src/tool-alerts.ts
  • apps/webapp/app/services/dashboardAgentWatchRunChecks.ts
  • apps/webapp/app/services/dashboardAgentAlertUnsubscribeToken.server.ts
  • apps/webapp/app/components/dashboard-agent/chat-layout.tsx
  • internal-packages/dashboard-agent/src/watch-actions.ts

Comment on lines +195 to +218
useEffect(() => {
if (!hasAccess || !watching) return;

let cancelled = false;
const load = async () => {
try {
// Bounded, so one stuck request can't hold the poll's in-flight guard.
const res = await fetch(`${actionPath}?unread=1`, {
signal: AbortSignal.timeout(UNREAD_REQUEST_TIMEOUT_MS),
});
if (!res.ok) return;
const data = (await res.json()) as {
unreadWakes?: number;
unreadWork?: number;
wakes?: WatchWake[];
};
if (cancelled) return;
// The wakes list carries read ones too, so only unread ones are subtracted.
const unreadInView = (data.wakes ?? []).filter(
(wake) => wake.unread && wake.chatId === visibleChat.current
).length;
setUnreadWakes(Math.max(0, (data.unreadWakes ?? 0) - unreadInView));
// A chat open in the panel is being read right now, so it isn't unread work.
setUnreadWork(Math.max(0, (data.unreadWork ?? 0) - (open && visibleChat.current ? 1 : 0)));

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

open is captured stale inside the poll effect.

The effect dependency array on Line 248 omits open. The poll callback closes over the open value from the render that started the poll. When the user opens the panel, the effect does not re-run, so open stays false inside load. The unread-work subtraction on Line 218 then never applies while a chat is on screen, and the launcher dot keeps counting the chat the user is reading.

Track open in a ref, as the code already does for visibleChat.

🐛 Proposed fix
   // A wake in the on-screen chat toasts but must not light the dot.
   const visibleChat = useRef<string | null>(null);
+  // Read by the poll, which must not re-subscribe when the panel opens.
+  const openRef = useRef(open);
+  openRef.current = open;
-        setUnreadWork(Math.max(0, (data.unreadWork ?? 0) - (open && visibleChat.current ? 1 : 0)));
+        setUnreadWork(
+          Math.max(0, (data.unreadWork ?? 0) - (openRef.current && visibleChat.current ? 1 : 0))
+        );
📝 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
useEffect(() => {
if (!hasAccess || !watching) return;
let cancelled = false;
const load = async () => {
try {
// Bounded, so one stuck request can't hold the poll's in-flight guard.
const res = await fetch(`${actionPath}?unread=1`, {
signal: AbortSignal.timeout(UNREAD_REQUEST_TIMEOUT_MS),
});
if (!res.ok) return;
const data = (await res.json()) as {
unreadWakes?: number;
unreadWork?: number;
wakes?: WatchWake[];
};
if (cancelled) return;
// The wakes list carries read ones too, so only unread ones are subtracted.
const unreadInView = (data.wakes ?? []).filter(
(wake) => wake.unread && wake.chatId === visibleChat.current
).length;
setUnreadWakes(Math.max(0, (data.unreadWakes ?? 0) - unreadInView));
// A chat open in the panel is being read right now, so it isn't unread work.
setUnreadWork(Math.max(0, (data.unreadWork ?? 0) - (open && visibleChat.current ? 1 : 0)));
// A wake in the on-screen chat toasts but must not light the dot.
const visibleChat = useRef<string | null>(null);
// Read by the poll, which must not re-subscribe when the panel opens.
const openRef = useRef(open);
openRef.current = open;
useEffect(() => {
if (!hasAccess || !watching) return;
let cancelled = false;
const load = async () => {
try {
// Bounded, so one stuck request can't hold the poll's in-flight guard.
const res = await fetch(`${actionPath}?unread=1`, {
signal: AbortSignal.timeout(UNREAD_REQUEST_TIMEOUT_MS),
});
if (!res.ok) return;
const data = (await res.json()) as {
unreadWakes?: number;
unreadWork?: number;
wakes?: WatchWake[];
};
if (cancelled) return;
// The wakes list carries read ones too, so only unread ones are subtracted.
const unreadInView = (data.wakes ?? []).filter(
(wake) => wake.unread && wake.chatId === visibleChat.current
).length;
setUnreadWakes(Math.max(0, (data.unreadWakes ?? 0) - unreadInView));
// A chat open in the panel is being read right now, so it isn't unread work.
setUnreadWork(
Math.max(0, (data.unreadWork ?? 0) - (openRef.current && visibleChat.current ? 1 : 0))
);

Comment on lines +54 to +59
it("marks a dropped navigation handled, so it cannot fire on a later commit", () => {
const effect = chat.slice(chat.indexOf("const pending = pendingNavigateIntents(messages"));
expect(effect.indexOf("pendingNavigateIntents(messages")).toBeLessThan(
effect.indexOf("navigateIntentApplies({")
);
});

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

This test does not check what its name states.

effect starts at the pendingNavigateIntents(messages match, so effect.indexOf("pendingNavigateIntents(messages") is always 0. The assertion then only fails when navigateIntentApplies({ is absent from the remainder. Nothing here asserts that a dropped navigation is marked handled.

Assert the handled marking directly, for example that the slice contains the call that records the intent as handled before the applies branch returns.

Comment on lines +36 to +39
it("ignores the query string, so filtering a page is not leaving it", () => {
// Both sides are pathnames; a filter change never reaches this comparison.
expect(unmountTeardown({ renderedPath: path, livePath: path })).toBe("panel-closed");
});

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

This case repeats the previous assertion and proves nothing about query strings.

Lines 38 and 27 pass identical arguments and expect the same result. No query string reaches unmountTeardown, so the stated behavior stays untested. Either assert that a caller-supplied query string is stripped before the comparison, or delete this case as redundant.

💚 Option: exercise a value that carries a query string
-  it("ignores the query string, so filtering a page is not leaving it", () => {
-    // Both sides are pathnames; a filter change never reaches this comparison.
-    expect(unmountTeardown({ renderedPath: path, livePath: path })).toBe("panel-closed");
-  });
+  it("compares pathnames only, so a filter change is not leaving the page", () => {
+    // A caller that passes a search string must still read as the same page.
+    expect(
+      unmountTeardown({ renderedPath: path, livePath: `${path}?statuses=FAILED` })
+    ).toBe("panel-closed");
+  });

This diff only holds if unmountTeardown normalizes its inputs. The current implementation compares the two strings directly, so confirm the intended contract before applying it.

Comment thread apps/webapp/app/components/dashboard-agent/watch-activity.ts
Comment on lines +5 to +6
export function storedQueueName(queue: { type: string; name: string }): string {
return queue.type === "task" ? `task/${queue.name.replace(/^task\//, "")}` : queue.name;

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

Remove every existing task/ prefix.

task/task/queue still returns task/task/queue. This contradicts the helper contract and produces a different stored queue name for a malformed but reachable input.

Proposed fix
-  return queue.type === "task" ? `task/${queue.name.replace(/^task\//, "")}` : queue.name;
+  return queue.type === "task" ? `task/${queue.name.replace(/^(?:task\/)+/, "")}` : queue.name;
📝 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
export function storedQueueName(queue: { type: string; name: string }): string {
return queue.type === "task" ? `task/${queue.name.replace(/^task\//, "")}` : queue.name;
export function storedQueueName(queue: { type: string; name: string }): string {
return queue.type === "task" ? `task/${queue.name.replace(/^(?:task\/)+/, "")}` : queue.name;

canAccessDashboardAgent: async () => true,
}));

process.env.SESSION_SECRET = "test-session-secret-for-watch-fingerprints";

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

Do not mutate process.env in the test module.

Remove this assignment and use the environment values supplied by apps/webapp/test/setup.ts. Module-level mutation can leak into other tests. The webapp rules also require environment access through env.server.ts rather than direct process.env.

Sources: Coding guidelines, Learnings

Comment thread apps/webapp/test/queryScope.test.ts Outdated
Comment on lines +12 to +15
it("leaves every other bearer credential uncapped", () => {
expect(queryScopeCeilingFor("PRIVATE")).toBe("unbounded");
expect(queryScopeCeilingFor("PUBLIC")).toBe("unbounded");
});

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find which authentication types can reach the query API and how the ceiling is applied.
rg -n 'queryScopeCeilingFor|resolveQueryScope' apps/webapp/app -A5
rg -n '"PUBLIC"' apps/webapp/app/services/apiAuth.server.ts -B3 -A3
fd -t f 'api.v1.*query*' apps/webapp/app/routes | xargs -r rg -n 'authenticate|allowJWT|allowPublicKey|scope'

Repository: triggerdotdev/trigger.dev

Length of output: 4425


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== query route outline =="
ast-grep outline apps/webapp/app/routes/api.v1.query.ts --view expanded || true

echo "== relevant query route =="
cat -n apps/webapp/app/routes/api.v1.query.ts | sed -n '1,90p'

echo "== query schema options =="
cat -n apps/webapp/app/routes/api.v1.query.schema.ts | sed -n '1,90p'

echo "== auth helpers around PUBLIC handling =="
cat -n apps/webapp/app/services/apiAuth.server.ts | sed -n '130,250p'

echo "== all allowPublicKey usages =="
rg -n 'allowPublicKey' apps/webapp/app -A3 -B3

Repository: triggerdotdev/trigger.dev

Length of output: 16000


Block PUBLIC access at this route

PUBLIC keys can reach the query API because api.v1.query.ts allows JWT auth, so PUBLIC_JVT is capped while regular PUBLIC publishable keys remain unbounded and can select organization scope. Disable PUBLIC keys for this route, or cap them with the same scope ceiling used by PUBLIC_JWT.

Comment on lines +28 to +33
**Watch** — "tell me when this run starts", "ping me if this error comes
back", the *Watch recovery* button on a degraded health report. A durable
condition the platform checks on a schedule (no LLM in the checks), which
wakes the chat with the outcome. Fires once, expires within 24h, max 3 per
chat. Five kinds: run start / run finished / backlog drain / error recurrence
/ health recovery.

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

Correct the watch capability list.

The list omits queue threshold, stalled-queue, queue wait-time, and run-failure conditions that Sections 2-5 and 9-12 document. Replace the “Five kinds” sentence with a complete description of the supported watch conditions.

@@ -62,7 +67,8 @@ Write a summary in under 400 words, as notes rather than prose. Keep, in this or
1. What the user is trying to do, in their own terms, and anything they asked to be remembered.
2. Facts already established, with the run ids, queue names, task identifiers, error fingerprints and numbers they rest on. Never restate a number you cannot see.
3. Any investigation that is open: its investigationId, its title and its current outcome.
4. What was asked most recently and what is still unanswered.
4. Any watch that is running or has reported, and what it said.

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

Do not infer that a watch is currently running from the transcript.

A watch can expire or be cancelled without a transcript update. This instruction can preserve an old confirmation as a current running state and cause a later answer to report stale status. Summarize the watch as created or last reported, unless current state is verified from the watch store.

Proposed change
-4. Any watch that is running or has reported, and what it said.
+4. Any watch created or reported in the transcript, and its last recorded result. Do not state that a watch is currently active unless the transcript confirms it.

Comment on lines +399 to +407
// Live state first: metrics are a window, and a window can't say "paused" or show a
// backlog that arrived after it. A queue nobody is running is not the same as a queue
// someone stopped, and the answer has to lead with which one it is.
const live = async (kind: "task" | "custom") => {
const result = await envApiGet(
`/api/v1/queues/${encodeURIComponent(queue)}?type=${kind}`
);
return result?.ok ? (result.data as Record<string, unknown>) : undefined;
};

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 | 🟠 Major | ⚡ Quick win

live discards the failure reason, so an unreachable queue route reads as a missing queue.

live returns undefined for every non-ok response: 401, 403, 429, and 5xx included. withLiveState then reports exists: false on line 99. The model is told the queue does not exist, which is the exact wrong answer the surrounding comments aim to prevent.

Distinguish "the route said 404" from "the read failed" and let withLiveState report the unknown case instead of a negative existence claim.

🐛 Proposed fix: keep the status and only claim non-existence on 404
         const live = async (kind: "task" | "custom") => {
           const result = await envApiGet(
             `/api/v1/queues/${encodeURIComponent(queue)}?type=${kind}`
           );
-          return result?.ok ? (result.data as Record<string, unknown>) : undefined;
+          if (!result) return { state: undefined, missing: false };
+          if (result.ok) return { state: result.data as Record<string, unknown>, missing: false };
+          return { state: undefined, missing: result.status === 404 };
         };

Then thread missing into withLiveState and return exists: false only when missing is true; otherwise omit exists so the answer does not assert either way.

@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.

🧹 Nitpick comments (1)
apps/webapp/app/components/dashboard-agent/watch-activity.test.ts (1)

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

Extract the storage key into a named constant.

Lines 101, 108, and 114 repeat the raw literal "tdev:dashboard-agent:watching". A rename of STORAGE_KEY in watch-activity.ts would leave these tests passing against a key nothing reads. Declare one local constant at the top of the file and use it in every assertion.

♻️ Proposed refactor
-      store.set("tdev:dashboard-agent:watching", JSON.stringify({ org_1: true }));
+      store.set(STORAGE_KEY, JSON.stringify({ org_1: true }));

Add near the top of the file:

const STORAGE_KEY = "tdev:dashboard-agent:watching";

Based on the coding guideline "Use named constants for sentinel or placeholder values instead of scattering raw string literals across comparisons".

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a9b85035-0dce-46a9-a815-d62281afcfa8

📥 Commits

Reviewing files that changed from the base of the PR and between e7432a8 and 4a967b0.

📒 Files selected for processing (5)
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.ts
  • apps/webapp/app/routes/api.v1.dashboard-agent.alerts.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/webapp/app/routes/api.v1.dashboard-agent.alerts.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

**/*.{ts,tsx}: Prefer static imports over dynamic import(); use dynamic imports only for unresolvable circular dependencies, genuine performance code splitting, or conditional runtime loading.
Import Trigger.dev tasks from @trigger.dev/sdk; never use @trigger.dev/sdk/v3 or deprecated client.defineJob.
Add agentcrumbs while writing code using approved namespaces; mark lines with // @Crumbs or blocks with `// `#region` `@crumbs, and strip them before merging.

Files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use vitest for all tests in the Trigger.dev repository

**/*.{test,spec}.{ts,tsx}: Use Vitest exclusively and never mock dependencies; use Testcontainers for integration dependencies.
Place test files next to the source files they test.

Files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: Access environment variables through the env export of env.server.ts instead of directly accessing process.env
Use subpath exports from @trigger.dev/core package instead of importing from the root @trigger.dev/core path

Do not reintroduce the removed v1 execution path; RunEngineVersion.V1 branches may only reject or finalize gracefully so v3 clients receive a clean 4xx, never a 5xx.

Files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
apps/webapp/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Do not import env.server.ts directly or indirectly into test files; instead pass environment-dependent values through options/parameters to make code testable

Files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

For apps, use typecheck for verification and never use build as the correctness check.

Files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
apps/webapp/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)

Test files must not import app/env.server.ts; pass configuration as options instead.

Files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)

apps/webapp/app/**/*.{ts,tsx}: For dashboard changes, visually verify the running Remix app with Chrome DevTools MCP, using snapshots, screenshots, interaction, and console-message checks as appropriate.
Use useCallback and useMemo only for context provider values, expensive derived data used as a dependency, or stable references required by dependency arrays; do not wrap ordinary event handlers or trivial computations.
Use named constants for sentinel or placeholder values instead of scattering raw string literals across comparisons.

Files:

  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
apps/webapp/app/**/*.ts

📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)

apps/webapp/app/**/*.ts: Never use request.signal to detect client disconnects. Use getRequestAbortSignal() from app/services/httpAsyncStorage.server.ts, which is wired to Express response close events.
Access environment variables through the env export from app/env.server.ts; never use process.env directly.
Always use Prisma findFirst instead of findUnique.
Always use the $transaction helper from ~/db.server, never call prisma.$transaction or $replica.$transaction directly. Pass isolation levels as strings, use Serializable for correctness-critical read-then-write invariants, and guard possibly undefined helper results when a definite value is required.

Files:

  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
🧠 Learnings (20)
📚 Learning: 2026-03-22T13:26:12.060Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3244
File: apps/webapp/app/components/code/TextEditor.tsx:81-86
Timestamp: 2026-03-22T13:26:12.060Z
Learning: In the triggerdotdev/trigger.dev codebase, do not flag `navigator.clipboard.writeText(...)` calls for `missing-await`/`unhandled-promise` issues. These clipboard writes are intentionally invoked without `await` and without `catch` handlers across the project; keep that behavior consistent when reviewing TypeScript/TSX files (e.g., usages like in `apps/webapp/app/components/code/TextEditor.tsx`).

Applied to files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
📚 Learning: 2026-03-22T19:24:14.403Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3187
File: apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts:200-204
Timestamp: 2026-03-22T19:24:14.403Z
Learning: In the triggerdotdev/trigger.dev codebase, webhook URLs are not expected to contain embedded credentials/secrets (e.g., fields like `ProjectAlertWebhookProperties` should only hold credential-free webhook endpoints). During code review, if you see logging or inclusion of raw webhook URLs in error messages, do not automatically treat it as a credential-leak/secrets-in-logs issue by default—first verify the URL does not contain embedded credentials (for example, no username/password in the URL, no obvious secret/token query params or fragments). If the URL is credential-free per this project’s conventions, allow the logging.

Applied to files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
📚 Learning: 2026-05-18T08:21:27.694Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3632
File: apps/webapp/sentry.server.ts:4-21
Timestamp: 2026-05-18T08:21:27.694Z
Learning: When handling Prisma error P1001 ("Can't reach database server") in TypeScript, don’t assume a single error shape. Prisma can surface P1001 via two different error classes/fields: `PrismaClientKnownRequestError` exposes it as `err.code === "P1001"` (common during mid-query connection drops), while `PrismaClientInitializationError` exposes it as `err.errorCode === "P1001"` (common on client startup failure). Therefore, predicates should use `err.code === "P1001" || err.errorCode === "P1001"`. Do not flag `err.code === "P1001"` as “unreachable/never matches,” as it is expected in production.

Applied to files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
📚 Learning: 2026-05-18T08:21:27.694Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3632
File: apps/webapp/sentry.server.ts:4-21
Timestamp: 2026-05-18T08:21:27.694Z
Learning: When handling Prisma errors for P1001 ("Can't reach database server"), do not assume it only appears under a single property name. Prisma may surface P1001 via either `PrismaClientKnownRequestError` (`err.code === "P1001"`, e.g., mid-query connection drops) or `PrismaClientInitializationError` (`err.errorCode === "P1001"`, e.g., client startup connection failure). To reliably detect the condition, check `err.code === "P1001" || err.errorCode === "P1001"`, and avoid review rules that would incorrectly flag `err.code === "P1001"` as unreachable/never-matching.

Applied to files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
📚 Learning: 2026-06-13T19:53:13.759Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3937
File: packages/trigger-sdk/skills/realtime-and-frontend/SKILL.md:258-260
Timestamp: 2026-06-13T19:53:13.759Z
Learning: When reviewing code that uses `trigger.dev/react-hooks`’s `useRealtimeRun`, preserve the call signature where the first argument is the full realtime handle object (not `handle.id`). This is intentional to maintain type-safety and is consistent with the official docs; do not suggest changing the first argument from the handle object to `handle.id`.

Applied to files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
📚 Learning: 2026-06-17T17:13:49.929Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3948
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.bulk-actions.$bulkActionParam/route.tsx:48-62
Timestamp: 2026-06-17T17:13:49.929Z
Learning: In triggerdotdev/trigger.dev, within `dashboardLoader`/`dashboardAction` (or similar context resolver code) whenever you resolve an organization ID from an organization slug for RBAC/enterprise authorization scope, always read from the primary Prisma client (`prisma`), not `$replica`. Using `$replica` can hit replica-lag and cause the RBAC lookup/authorization to run without the correct org scope (bypassing intended role enforcement). Implement the slug→org lookup with `prisma.organization.findFirst(...)` (or equivalent primary-client query) and add an inline comment documenting why the primary client is required (replica lag could lead to unscoped RBAC checks).

Applied to files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
📚 Learning: 2026-06-23T13:04:21.413Z
Learnt from: carderne
Repo: triggerdotdev/trigger.dev PR: 4023
File: apps/webapp/app/services/upsertBranch.server.ts:14-18
Timestamp: 2026-06-23T13:04:21.413Z
Learning: In TypeScript, it’s valid to `import { type X }` and then use `typeof X` in a type-only position, e.g. `type Alias = z.infer<typeof X>`. The `type` modifier suppresses the runtime import, but the type checker still has the full exported type so `z.infer<typeof X>` can resolve correctly. In code reviews, don’t flag this as a TypeScript compile error as long as `typeof X` is used in a type context (e.g., with `z.infer`, `type` aliases, generics), not as a runtime value.

Applied to files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
📚 Learning: 2026-05-07T12:25:18.271Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3531
File: apps/webapp/test/sentryTraceContext.server.test.ts:9-47
Timestamp: 2026-05-07T12:25:18.271Z
Learning: In the triggerdotdev/trigger.dev webapp test suite, it is acceptable to leave `createInMemoryTracing()` calls that register a global `NodeTracerProvider` without `afterEach`/`afterAll` teardown. Do not flag this as a test-ordering risk when the code follows the established pattern used across webapp tests (e.g., replication service/benchmark/backfiller tests). This is considered safe because `trace.getActiveSpan()` when called outside a `context.with(...)` block reads `AsyncLocalStorage.getStore()` (undefined when no `run()` scope exists), so it falls back to `ROOT_CONTEXT` with no attached span—regardless of which provider is registered.

Applied to files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
📚 Learning: 2026-05-28T20:02:10.647Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 3772
File: apps/webapp/test/findOrCreateBackgroundWorker.test.ts:1-1
Timestamp: 2026-05-28T20:02:10.647Z
Learning: In the triggerdotdev/trigger.dev monorepo, for the `apps/webapp` package use the established convention of storing Vitest tests (unit, integration, and e2e) under `apps/webapp/test/` rather than colocating them next to source files. Do not flag files located in `apps/webapp/test/` as violating any rule that says to colocate tests with source.

Applied to files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
📚 Learning: 2026-07-30T18:43:56.874Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 4426
File: apps/webapp/test/memberDevEnvironments.server.test.ts:124-125
Timestamp: 2026-07-30T18:43:56.874Z
Learning: In the `apps/webapp` test suite (`apps/webapp/test/**`), respect the established test harness in `apps/webapp/test/setup.ts`: it loads `.env` and provides default values for required environment variables so that transitive imports (e.g., `~/env.server`) work without production-style wiring.

During code review, do not require dependency injection/refactoring solely to avoid this existing import path. Only introduce configuration injection if it delivers production-level value (for example, a more general `createEnvironment` abstraction that improves runtime behavior beyond test setup).

Applied to files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
📚 Learning: 2026-08-08T12:49:17.489Z
Learnt from: kathiekiwi
Repo: triggerdotdev/trigger.dev PR: 4525
File: apps/webapp/test/dashboardAgentWatchAlertOwnerScope.test.ts:101-109
Timestamp: 2026-08-08T12:49:17.489Z
Learning: In apps/webapp/test, every test suite that creates a client with createDashboardAgentDb(connectionUri, ...) must close the DashboardAgentDbClient in afterEach. Although postgresTest drops cloned databases with WITH (FORCE), it does not clean up client-side postgres-js pool sockets or idle timers, so explicitly closing the client prevents resource leaks and test interference.

Applied to files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
📚 Learning: 2026-05-12T21:04:05.815Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3542
File: apps/webapp/app/components/sessions/v1/SessionStatus.tsx:1-3
Timestamp: 2026-05-12T21:04:05.815Z
Learning: In this Remix + TypeScript codebase, do not flag a server/client boundary violation when a file imports only types from a module matching `*.server`.

Specifically, it’s safe to import types using `import type { Foo } from "*.server"` or `import { type Foo } from "*.server"` because TypeScript erases type-only imports at compile time and they emit no JavaScript, so they won’t cross the Remix server/client bundle boundary.

Only raise the boundary concern for value imports (e.g., `import { Foo }` without `type`, or `import Foo`), since those produce JavaScript output.

Applied to files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
📚 Learning: 2026-06-25T18:21:51.905Z
Learnt from: carderne
Repo: triggerdotdev/trigger.dev PR: 4039
File: apps/webapp/app/routes/invite-revoke.tsx:0-0
Timestamp: 2026-06-25T18:21:51.905Z
Learning: During the Zod v4 migration in the triggerdotdev/trigger.dev webapp, ensure any imports from `conform-to/zod` use the Zod-4 subpath: `conform-to/zod/v4` (e.g., `import { parseWithZod } from "conform-to/zod/v4"`). Do not import from the package root `conform-to/zod`, because it is the Zod 3 implementation and may load Zod-3-only symbols (e.g., `ZodBranded`, `ZodEffects`), which can throw at module load (notably with `zod4.4.3`). This should be enforced across `apps/webapp/**/*` where helpers like `parseWithZod` and `conformZodMessage` are used.

Applied to files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
📚 Learning: 2026-07-03T17:10:21.498Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 4148
File: apps/webapp/app/models/orgMember.server.ts:149-168
Timestamp: 2026-07-03T17:10:21.498Z
Learning: In triggerdotdev/trigger.dev, `User.email` (Prisma schema: `internal-packages/database/prisma/schema.prisma`) currently does NOT use `citext` and does NOT have a `lower(email)` functional unique index. Therefore, do not introduce Prisma queries like `where: { email: { equals: <value>, mode: "insensitive" } }` (or any case-insensitive lookup) against `User.email`, because it can force sequential scans of the `users` table under load. During review, ensure email is normalized (e.g., lowercased/trimmed) before both writes and subsequent lookups, and if true case-insensitive behavior/uniqueness is required, implement it via a separate app-wide migration (e.g., switch to `citext` and/or add a functional unique index with backfill) rather than bolting it onto individual feature PRs.

Applied to files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
📚 Learning: 2026-05-18T14:40:02.173Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3658
File: packages/core/src/v3/realtimeStreams/manager.test.ts:1-147
Timestamp: 2026-05-18T14:40:02.173Z
Learning: In the triggerdotdev/trigger.dev repo, the policy “Never mock anything — use testcontainers instead” should only be enforced for integration tests that interact with real external services (e.g., Redis, Postgres) via actual infrastructure. For unit tests that exercise pure in-memory logic (e.g., cache semantics) it is OK to stub collaborators such as `ApiClient` using Vitest (`vi.fn()`) to assert call counts or control behavior. Do not flag `vi.fn()`-based `ApiClient` stubs in unit tests as violations of the testcontainers policy.

Applied to files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
📚 Learning: 2026-06-04T18:16:35.386Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 3836
File: apps/supervisor/src/backpressure/backpressureMonitor.ts:3-5
Timestamp: 2026-06-04T18:16:35.386Z
Learning: When reviewing TypeScript in this repo, apply the rule “prefer type aliases over interfaces” only to data/object shapes and union/intersection type modeling. If an interface is being used as a behavioral contract for collaborators to implement (e.g., method-shape interfaces that define required behavior, such as `BackpressureLogger` / `BackpressureSignalSource` in `apps/supervisor/src/backpressure/backpressureMonitor.ts`), keep it as an `interface` and do not flag it as a type-alias-vs-interface violation.

Applied to files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
📚 Learning: 2026-06-09T17:58:04.699Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 3879
File: apps/webapp/app/models/vercelIntegration.server.ts:619-630
Timestamp: 2026-06-09T17:58:04.699Z
Learning: In this codebase, outbound raw `fetch` calls should typically rely on Node/undici’s default request timeout (about ~300s) rather than adding a per-call `AbortController` + `setTimeout` wrapper inside individual functions (e.g. in files like `apps/webapp/app/models/vercelIntegration.server.ts`). During code review, do not flag the absence of a per-call timeout on a single `fetch` as an issue; if per-call timeouts are needed, they should be implemented via a codebase-wide convention (e.g., a shared fetch wrapper or documented pattern) rather than ad-hoc per-function changes.

Applied to files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
📚 Learning: 2026-06-16T09:19:47.637Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3960
File: apps/webapp/test/prismaInfrastructureErrorCapture.test.ts:0-0
Timestamp: 2026-06-16T09:19:47.637Z
Learning: In this repo’s Vitest setup, `vitest.config.ts` uses `globals: true`, so identifiers like `vi`, `describe`, `it`, and `expect` are available as globals in Vitest test files. During code review, do not flag missing `vi`/`describe`/`it`/`expect` imports as a runtime error or correctness issue when they’re used in `*.test.ts/tsx` or `*.spec.ts/tsx` files. Explicit imports are still preferred for consistency, but they’re not required for runtime behavior.

Applied to files:

  • apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts
  • apps/webapp/app/components/dashboard-agent/watch-activity.test.ts
📚 Learning: 2026-03-26T09:02:07.973Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 3274
File: apps/webapp/app/services/runsReplicationService.server.ts:922-924
Timestamp: 2026-03-26T09:02:07.973Z
Learning: When parsing Trigger.dev task run annotations in server-side services, keep `TaskRun.annotations` strictly conforming to the `RunAnnotations` schema from `trigger.dev/core/v3`. If the code already uses `RunAnnotations.safeParse` (e.g., in a `#parseAnnotations` helper), treat that as intentional/necessary for atomic, schema-accurate annotation handling. Do not recommend relaxing the annotation payload schema or using a permissive “passthrough” parse path, since the annotations are expected to be written atomically in one operation and should not contain partial/legacy payloads that would require a looser parser.

Applied to files:

  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
📚 Learning: 2026-05-05T09:38:02.512Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3523
File: apps/webapp/app/routes/api.v3.batches.ts:178-181
Timestamp: 2026-05-05T09:38:02.512Z
Learning: When reviewing code that catches `ServiceValidationError` in `*.server.ts` files, do not blindly forward `error.status` to HTTP responses, because SVEs may be thrown with non-default statuses (e.g., 400/500) and forwarding them can cause client-visible behavioral regressions (e.g., surfacing 500s to clients). Prefer a safe default response status of `error.status ?? 422`, but only after confirming via the reachable call graph that the caught `ServiceValidationError` instances are expected to carry those non-default statuses; otherwise, normalize to `422` to avoid unexpected client-visible 5xx behavior.

Applied to files:

  • apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts
🔇 Additional comments (3)
apps/webapp/app/services/dashboardAgentWatchAlerts.server.ts (1)

7-11: LGTM!

Also applies to: 184-266, 298-303

apps/webapp/test/dashboardAgentWatchAlertEnvironments.test.ts (2)

72-100: LGTM!

Also applies to: 107-192, 194-293


62-69: 🩺 Stability & Availability

No change needed: alertsWorker.enqueue is already mocked.

apps/webapp/test/setup.ts mocks ~/v3/alertsWorker.server with createWorkerStub(), and enqueue is a vi.fn() used by this suite via the global setup.

…that isn't there

The queue's live row read collapsed every non-ok response into "no live row",
so a 401, a 429 or a 5xx reached the model as exists:false — the queue does not
exist. Only a 404 is evidence of absence now; anything else reports
exists:"unknown" with the status, and the prompt says unknown is never missing.
The wake poll's callback closed over `open` from the render that started it, so
once the panel opened the subtraction never applied. The panel's open state now
goes through a ref, like the visible chat already does, rather than adding
`open` to the effect's deps and restarting the poll on every open and close.
…watch

A watch can expire or be cancelled with nothing written back into the
transcript, so asking the summariser for "any watch that is running" preserved
an old confirmation as current state.
The guard compared a slice against its own start, so it could not fail. Move
the decision into takeNavigateIntent and drive it across two commits instead.
The unmount case repeated its neighbour and no query string can reach
unmountTeardown. Guard the pathname tracking that does decide it.
…token

pk_ is browser-shipped and environment-bound. Nothing routes it to the query
API today, so the cap costs no caller anything and the helper stops promising
the wrong thing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant