@checkstack/integration-frontend 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,144 @@
1
1
  # @checkstack/integration-frontend
2
2
 
3
+ ## 0.4.5
4
+
5
+ ### Patch Changes
6
+
7
+ - f23f3c9: Retrofit the highest-traffic configuration list tables
8
+ (`HealthCheckList`, `SloConfigPage`, and the integration
9
+ `DeliveryLogsPage`) onto the `ResponsiveTable` + `MobileCardList`
10
+ primitives from `@checkstack/ui`. On `sm` and up each page still
11
+ renders the unchanged 5- to 7-column table; below that breakpoint a
12
+ sibling stacked-card layout surfaces the same data with the resource
13
+ name + status badge at the top, secondary columns in a muted line, and
14
+ the existing action buttons in a right-aligned footer. The
15
+ `HealthCheckListSkeleton` placeholder mirrors both branches so the page
16
+ no longer jumps when data resolves. No business logic, column order,
17
+ or query inputs changed.
18
+ - f23f3c9: Sweep every paginated `*-common` contract onto the canonical
19
+ `PaginationInput` / `PaginatedResult` from `@checkstack/common` and
20
+ remove the now-unused legacy exports.
21
+
22
+ **BREAKING CHANGE** - `@checkstack/common` drops the deprecated
23
+ `PaginationInputSchema`, `paginatedOutput`, and `PaginatedResponse`
24
+ symbols. Callers must consume `PaginationInput` (input) and
25
+ `PaginatedResult(itemSchema)` (output) instead. The canonical input is
26
+ `{ limit (1-100, default 20), offset (>= 0, default 0) }`; the
27
+ canonical output envelope is
28
+ `{ items, total, limit, offset }`.
29
+
30
+ **BREAKING CHANGE** - `@checkstack/notification-common` migrates
31
+ `getNotifications` off the legacy `PaginationInputSchema`
32
+ (`{ limit, offset, unreadOnly }` with output `{ notifications, total }`)
33
+ onto `ListNotificationsInputSchema =
34
+ PaginationInput.extend({ unreadOnly })` and
35
+ `PaginatedResult(NotificationSchema)`. The output key changes from
36
+ `notifications` to `items`, and `limit` / `offset` are now echoed on
37
+ the response. The `PaginationInput` type alias previously exported
38
+ from `notification-common` is removed - use `ListNotificationsInput`
39
+ or the canonical `PaginationInput` from `@checkstack/common`.
40
+
41
+ **BREAKING CHANGE** - `@checkstack/integration-common` migrates
42
+ `listSubscriptions` (inline `{ page, pageSize, ... }` -> output
43
+ `{ subscriptions, total }`) and `getDeliveryLogs` (via
44
+ `DeliveryLogQueryInputSchema` `{ subscriptionId?, eventType?, status?,
45
+ page, pageSize }` -> output `{ logs, total }`) onto the canonical
46
+ `PaginationInput.extend({...})` input and
47
+ `PaginatedResult(itemSchema)` output. External callers must switch
48
+ from `{ page, pageSize }` to `{ limit, offset }` and read response
49
+ items from `data.items` (no more `data.subscriptions` / `data.logs`).
50
+
51
+ The matching `*-backend` handlers were updated to consume the new
52
+ input shape (`offset` arithmetic in lieu of `(page - 1) * pageSize`)
53
+ and to echo `limit` / `offset` on the response. The `*-frontend` call
54
+ sites in `NotificationsPage`, `NotificationBell`, `IntegrationsPage`,
55
+ and `DeliveryLogsPage` were updated to send the new input shape and
56
+ read `data.items`.
57
+
58
+ - f23f3c9: Gate decorative motion and blur effects behind
59
+ `usePerformance().isLowPower` on a focused set of high-traffic plugin
60
+ pages (Dashboard, Dependency map, System node, Notification bell,
61
+ Announcement banner / cards, Anomaly field overrides editor, SLO
62
+ attribution chart, Catalog droppable group). Hover scales, backdrop
63
+ blurs, `animate-pulse`/`animate-ping` accents, and entry transitions
64
+ now drop to static states on low-power devices; functional UX
65
+ transitions (Drawer/Dialog open-close, colour transitions) are left
66
+ alone.
67
+
68
+ Standardise the post-mutation error-toast voice on plugin pages by
69
+ migrating multi-clause `toast.error(extractErrorMessage(error, "Failed
70
+ to X"))` call sites onto the `toastError(toast, "Failed to X", error)`
71
+ helper from `@checkstack/ui`. The helper applies the canonical
72
+ `"action: message"` prefix and 100-character truncation in one place,
73
+ and the now-orphaned `extractErrorMessage` imports are dropped from
74
+ the affected files. No business logic or component APIs changed.
75
+
76
+ - Updated dependencies [f23f3c9]
77
+ - Updated dependencies [f23f3c9]
78
+ - Updated dependencies [f23f3c9]
79
+ - Updated dependencies [f23f3c9]
80
+ - @checkstack/common@0.11.0
81
+ - @checkstack/frontend-api@0.5.2
82
+ - @checkstack/integration-common@0.5.0
83
+ - @checkstack/ui@1.10.0
84
+ - @checkstack/tips-frontend@0.2.5
85
+ - @checkstack/signal-frontend@0.1.4
86
+
87
+ ## 0.4.4
88
+
89
+ ### Patch Changes
90
+
91
+ - a06b899: Render integration provider `setupGuide` content as markdown instead of plain text. The `ProviderDocumentation` panel was wrapping `setupGuide` in a `whitespace-pre-wrap` div, so markdown syntax (headings, links, lists, bold) shipped by providers (e.g. Jira) showed up raw in the subscription dialog. Now uses `MarkdownBlock` from `@checkstack/ui` so the same formatting providers author renders correctly.
92
+ - a06b899: Overhaul shell + inline-script health checks with real shell semantics, real ESM execution, and upstream Node/Bun IntelliSense.
93
+
94
+ **BREAKING CHANGES**
95
+
96
+ - **Shell collector** — the `Execute Script` collector now takes a single `script` string instead of `{ command, args }`. Existing configs are auto-migrated to v2: `command` + `args` are joined with POSIX single-quote escaping into the new `script` field, so behaviour is preserved. Custom UIs that hard-coded `command`/`args` field names need to switch to `script`.
97
+ - **Inline collector** — scripts are now executed as real ES modules in a Bun subprocess (was: `new Function()` inside a Web Worker). The legacy `return X;` style still works (it's auto-wrapped in an async IIFE), but mixed scripts that `import` _and_ `return` at the top level need to use `export default` for their result.
98
+
99
+ **FIXES**
100
+
101
+ - Shell scripts containing pipes, redirects, `awk`, command substitution, conditionals etc. no longer fail with `ENOENT`. The collector now runs through `sh -c <script>` instead of passing the full expression as `Bun.spawn`'s argv[0]. This was the original `awk … failed with ENOENT` regression.
102
+ - Inline scripts can now `import { loadavg } from "node:os"` (and any other `node:*` or `bun` import). They could not before, because the executor wrapped user code inside `new Function(...)` and ran it in a Web Worker that had no Node module access; the wrapper also made top-level `import` syntactically invalid (`Unexpected token '{'`).
103
+ - Healthcheck editor fields no longer reset while you're editing. The page was re-running its form-state init `useEffect` on every refetch of the configuration query — and that query is invalidated on every realtime `HEALTH_CHECK_RUN_COMPLETED` signal across the platform, so in-progress edits got wiped within seconds. Replaced the naive `useEffect([existingConfig])` with a new `useInitOnceForKey` hook from `@checkstack/ui` that initialises the form only on first load per healthcheck id and ignores background refetches. The hook's decision logic is a pure function (`shouldInitForKey`) and is unit-tested in `useInitOnceForKey.test.ts`.
104
+ - Switching between healthcheck collectors no longer mis-applies the previous collector's tokenizer / language service to the new editor. `MultiTypeEditorField` was reusing the same React instance across collector switches (same `key="script"` in both `DynamicForm` renders) and `selectedType` was initialised from `useState` only once on first mount. After a shell→typescript switch the new collector's TS content rendered through the shell branch (no TS highlighting, no IntelliSense); the reverse direction tokenised shell content through TS and surfaced nonsense errors like `2304 "Cannot find name 'and'"` on shell comments. Now a `useEffect` re-derives `selectedType` whenever `editorTypes` changes to a set that doesn't contain the current selection.
105
+ - Monaco workers are now bundled locally via Vite `?worker` imports and wired up through `MonacoEnvironment.getWorker` in a new `monacoWorkers.ts` module. The default `@monaco-editor/loader` CDN path silently failed CORS on worker scripts in some browsers, leaving Monaco's TS service with only the generic `editorWorkerService` — which is enough for tokenizer-only languages like shell but breaks TypeScript's semantic features entirely. Same module configures the TS service singleton (compiler options, eager-model-sync, diagnostics-options-ignore-1108) at module load instead of inside per-editor `onMount`, so the service starts pre-configured regardless of which language opens first. Migrated from the deprecated `monaco.languages.typescript.*` path to `monaco.typescript.*` (the old path is marked `{ deprecated: true }` in monaco-editor 0.55).
106
+ - `defineIntegration` / `defineHealthCheck` callback parameters are now typed against the schema. Previously the virtual module declared them as `(ctx: unknown) => …`, so writing `defineIntegration(async (context) => { console.log(context.event.eventId) })` produced `'context' is of type 'unknown'. (18046)`. The result type and the shared `IntegrationScriptContext` / `HealthCheckScriptContext` interfaces are now generated together in `scriptContext.ts`, so both the function-arg form and the ambient `declare const context` reference the same schema-typed shape.
107
+ - The shell starter template no longer uses Linux-only `/proc/loadavg` (which fails on macOS satellites with `awk: can't open file /proc/loadavg`). It now reads the 1-minute load average via `uptime` and parses both the Linux (`load average: 0.00, 0.01, 0.05`) and macOS (`load averages: 0.45 0.55 0.65`) output formats with a portable `sed`/`awk`/`tr` pipeline.
108
+ - Starter-template seeding is now self-healing. `DynamicForm`'s schema-defaults `useEffect` fires AFTER child seed effects in React's child-before-parent order, so the previous one-shot seed got clobbered back to `""` by the defaults call on first mount and never re-fired. Replaced the `[]`-deps effect with a two-effect pattern: an observer that latches `hasSeededRef = true` the first time `value` is observed non-empty, and a seed effect that keeps re-installing the starter while the latch is open. Once the seed sticks the latch closes; subsequent edits and realtime refetches don't re-trigger.
109
+
110
+ **NEW**
111
+
112
+ - The Monaco editor for inline scripts now mounts the real upstream `@types/node` + `bun-types` declarations as a virtual filesystem (lazy-loaded as its own JS chunk), so IntelliSense covers the full Node/Bun stdlib, the `Bun` global, `process.env`, `Buffer`, etc. DOM types are deliberately excluded so suggestions stay focused on the backend surface. `context.config` is typed from the collector's own JSON Schema.
113
+ - New `healthcheckScriptContext` / `integrationScriptContext` helpers (exported from `@checkstack/ui`) build a complete editor bundle in one call: TS declarations (`context.config` / `context.event.payload` + the virtual `@checkstack/healthcheck` / `@checkstack/integration` result-type modules), starter templates per language, and the shell env-var list (with platform-injected `EVENT_ID` / `DELIVERY_ID` / `PAYLOAD_*` for integrations). Both call sites — `CollectorSection.tsx` and `CreateSubscriptionDialog.tsx` — were rewired to use them, fixing a long-standing wiring gap where IntelliSense for injected values silently never reached the editor.
114
+ - Inline scripts can now `import { defineHealthCheck } from "@checkstack/healthcheck"` (or `defineIntegration` for integrations) for a typed return-shape assertion. The editor catches `{ success: "yes" }` as a type error against `HealthCheckScriptResult`. The runtime is just an identity function — the collector rewrites the import to a sibling helper file in the temp dir before executing.
115
+ - Shell editors now autocomplete env-vars after `$` and `${`. The completion list is supplied by `healthcheckScriptContext` (safe-vars whitelist) and `integrationScriptContext` (whitelist + `EVENT_ID` etc. + `PAYLOAD_*` flattened from the event's payload schema). The matcher is pure and unit-tested in `shellEnvVarMatcher.test.ts` so regex regressions are caught locally.
116
+ - Empty editor fields are now seeded with a working starter template per language (inline TS uses `defineHealthCheck`, inline shell does the `awk` load-average check, integration TS shows `defineIntegration` with `context.event`, integration shell lists the `$EVENT_ID` / `$PAYLOAD_*` env vars). Users see a runnable example instead of a blank canvas; once they edit, we leave their content alone.
117
+ - Hardened concurrency + cleanup model documented and tested: each invocation gets its own `mkdtemp` directory + UUID result marker; the `finally` block clears the timeout handle, kills any surviving subprocess (idempotent), and removes the temp directory on success, throw, _and_ timeout. New `concurrency.test.ts` proves 20 parallel inline scripts don't cross wires and that the temp-dir count returns to baseline after throws and timeouts.
118
+
119
+ **TESTING**
120
+
121
+ Tight unit tests added so changes to the editor surface don't need smoke testing:
122
+
123
+ - `scriptContext.test.ts` — 18 tests covering the generated type declarations (including explicit regression guards for `defineIntegration` / `defineHealthCheck` callback params being typed against the shared context interface rather than `unknown`), starter templates (including a guard that the shell starter doesn't depend on Linux-only `/proc/loadavg`), shell env-vars for both healthcheck + integration flavours, plus the schema-flattening utility.
124
+ - `shellEnvVarMatcher.test.ts` — 12 tests covering the bare `$` / braced `${` / partial-name / case-insensitive matching logic that powers Monaco's shell completion.
125
+ - `inline-script-normaliser.test.ts` — 13 tests covering the legacy `return X;` → IIFE wrap path, the ESM-passthrough path, and the `@checkstack/healthcheck` import rewriter.
126
+ - `inline-script-collector.test.ts` — 18 tests including ones that actually execute a script importing `defineHealthCheck` (named-import form) AND using the global `defineHealthCheck` (no import) to prove both code paths resolve at runtime.
127
+ - `concurrency.test.ts` — 4 tests proving 20 parallel runs don't collide and that the temp-dir count returns to baseline after success, throw, and timeout.
128
+ - `useInitOnceForKey.test.ts` — 10 tests proving the healthcheck-editor form state isn't reset when react-query refetches in the background (the original "fields reset while I'm typing" regression).
129
+ - `starterTemplateSelector.test.ts` — 7 tests for the pure decision function powering empty-field seeding.
130
+ - `security.test.ts` — added an integration test that actually executes the portable load-average pipeline through `Bun.spawn` on the current OS, catching `/proc/loadavg`-style regressions at CI time on macOS runners.
131
+
132
+ **SECURITY**
133
+
134
+ - Same env-var whitelist as before (`PATH`, `HOME`, `USER`, `LANG`, `LC_ALL`, `LC_CTYPE`, `TZ`, `TMPDIR`, `HOSTNAME`, `SHELL`). Backend secrets in the satellite process's environment remain invisible to user scripts.
135
+
136
+ See `docs/src/content/docs/backend/script-healthchecks.md` for the full user-facing guide.
137
+
138
+ - Updated dependencies [a06b899]
139
+ - @checkstack/ui@1.9.0
140
+ - @checkstack/tips-frontend@0.2.4
141
+
3
142
  ## 0.4.3
4
143
 
5
144
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/integration-frontend",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "license": "Elastic-2.0",
5
5
  "type": "module",
6
6
  "main": "src/index.tsx",
@@ -17,8 +17,8 @@
17
17
  "@checkstack/frontend-api": "0.5.1",
18
18
  "@checkstack/integration-common": "0.4.0",
19
19
  "@checkstack/signal-frontend": "0.1.3",
20
- "@checkstack/tips-frontend": "0.2.2",
21
- "@checkstack/ui": "1.8.2",
20
+ "@checkstack/tips-frontend": "0.2.4",
21
+ "@checkstack/ui": "1.9.0",
22
22
  "lucide-react": "^0.344.0",
23
23
  "react": "^18.2.0",
24
24
  "react-router-dom": "^6.22.0"
@@ -13,6 +13,8 @@ import {
13
13
  Textarea,
14
14
  DynamicForm,
15
15
  DynamicIcon,
16
+ integrationScriptContext,
17
+ type JsonSchemaProperty,
16
18
  useToast,
17
19
  Select,
18
20
  SelectContent,
@@ -21,10 +23,11 @@ import {
21
23
  SelectValue,
22
24
  Label,
23
25
  ConfirmationModal,
26
+ toastError,
24
27
  type LucideIconName,
25
28
  } from "@checkstack/ui";
26
29
  import { usePluginClient } from "@checkstack/frontend-api";
27
- import { resolveRoute, extractErrorMessage } from "@checkstack/common";
30
+ import { resolveRoute } from "@checkstack/common";
28
31
  import {
29
32
  IntegrationApi,
30
33
  integrationRoutes,
@@ -103,6 +106,21 @@ export const SubscriptionDialog = ({
103
106
  const payloadProperties: PayloadProperty[] =
104
107
  payloadSchemaData?.availableProperties ?? [];
105
108
 
109
+ // Build the editor IntelliSense + starter-template + shell-env-var
110
+ // bundle for this event. When the payload schema isn't available yet,
111
+ // we still get the result-shape virtual module and the platform-injected
112
+ // env vars (EVENT_ID, DELIVERY_ID, ...) — just no per-payload-field
113
+ // PAYLOAD_* hints. Recomputes only when the schema actually changes.
114
+ const editorScriptContext = useMemo(
115
+ () =>
116
+ integrationScriptContext({
117
+ eventPayloadSchema: payloadSchemaData?.payloadSchema as
118
+ | JsonSchemaProperty
119
+ | undefined,
120
+ }),
121
+ [payloadSchemaData?.payloadSchema],
122
+ );
123
+
106
124
  // Mutations
107
125
  const createMutation = client.createSubscription.useMutation({
108
126
  onSuccess: (result) => {
@@ -111,7 +129,7 @@ export const SubscriptionDialog = ({
111
129
  setSaving(false);
112
130
  },
113
131
  onError: (error) => {
114
- toast.error(extractErrorMessage(error, "Failed to create subscription"));
132
+ toastError(toast, "Failed to create subscription", error);
115
133
  setSaving(false);
116
134
  },
117
135
  });
@@ -124,7 +142,7 @@ export const SubscriptionDialog = ({
124
142
  setSaving(false);
125
143
  },
126
144
  onError: (error) => {
127
- toast.error(extractErrorMessage(error, "Failed to update subscription"));
145
+ toastError(toast, "Failed to update subscription", error);
128
146
  setSaving(false);
129
147
  },
130
148
  });
@@ -136,7 +154,7 @@ export const SubscriptionDialog = ({
136
154
  onOpenChange(false);
137
155
  },
138
156
  onError: (error) => {
139
- toast.error(extractErrorMessage(error, "Failed to delete subscription"));
157
+ toastError(toast, "Failed to delete subscription", error);
140
158
  },
141
159
  });
142
160
 
@@ -537,6 +555,11 @@ export const SubscriptionDialog = ({
537
555
  onValidChange={setProviderConfigValid}
538
556
  optionsResolvers={optionsResolvers}
539
557
  templateProperties={payloadProperties}
558
+ typeDefinitions={editorScriptContext.typeDefinitions}
559
+ shellEnvVars={editorScriptContext.shellEnvVars}
560
+ starterTemplates={
561
+ editorScriptContext.starterTemplates
562
+ }
540
563
  />
541
564
  </div>
542
565
  </div>
@@ -3,6 +3,7 @@ import {
3
3
  Card,
4
4
  Button,
5
5
  Badge,
6
+ MarkdownBlock,
6
7
  Table,
7
8
  TableBody,
8
9
  TableCell,
@@ -70,8 +71,10 @@ export const ProviderDocumentation = ({
70
71
  {documentation.setupGuide && (
71
72
  <div>
72
73
  <h4 className="text-sm font-medium mb-2">Setup Guide</h4>
73
- <div className="bg-muted/50 p-3 rounded-md text-sm whitespace-pre-wrap">
74
- {documentation.setupGuide}
74
+ <div className="bg-muted/50 p-3 rounded-md">
75
+ <MarkdownBlock size="sm">
76
+ {documentation.setupGuide}
77
+ </MarkdownBlock>
75
78
  </div>
76
79
  </div>
77
80
  )}
@@ -24,6 +24,8 @@ import {
24
24
  usePagination,
25
25
  usePaginationSync,
26
26
  BackLink,
27
+ ResponsiveTable,
28
+ MobileCardList,
27
29
  } from "@checkstack/ui";
28
30
  import { usePluginClient } from "@checkstack/frontend-api";
29
31
  import { resolveRoute } from "@checkstack/common";
@@ -70,17 +72,16 @@ export const DeliveryLogsPage = () => {
70
72
  const pagination = usePagination({ defaultLimit: 20 });
71
73
 
72
74
  // Fetch data with useQuery
73
- const page = Math.floor(pagination.offset / pagination.limit) + 1;
74
75
  const { data, isLoading, refetch } =
75
76
  integrationClient.getDeliveryLogs.useQuery({
76
- page,
77
- pageSize: pagination.limit,
77
+ limit: pagination.limit,
78
+ offset: pagination.offset,
78
79
  });
79
80
 
80
81
  // Sync total from response
81
82
  usePaginationSync(pagination, data?.total);
82
83
 
83
- const logs = data?.logs ?? [];
84
+ const logs = data?.items ?? [];
84
85
 
85
86
  // Retry mutation
86
87
  const retryMutation = integrationClient.retryDelivery.useMutation({
@@ -132,81 +133,141 @@ export const DeliveryLogsPage = () => {
132
133
  </div>
133
134
  </Card>
134
135
  ) : (
135
- <Card>
136
- <Table>
137
- <TableHeader>
138
- <TableRow>
139
- <TableHead>Status</TableHead>
140
- <TableHead>Subscription</TableHead>
141
- <TableHead>Event</TableHead>
142
- <TableHead>Attempts</TableHead>
143
- <TableHead>Created</TableHead>
144
- <TableHead>Error</TableHead>
145
- <TableHead></TableHead>
146
- </TableRow>
147
- </TableHeader>
148
- <TableBody>
149
- {logs.map((log: DeliveryLog) => {
150
- const config = statusConfig[log.status];
151
- return (
152
- <TableRow key={log.id}>
153
- <TableCell>
154
- <Badge
155
- variant={config.variant}
156
- className="flex items-center gap-1 w-fit"
157
- >
158
- {config.icon}
159
- {log.status}
160
- </Badge>
161
- </TableCell>
162
- <TableCell>
163
- <div className="font-medium">
164
- {log.subscriptionName ?? "Unknown"}
165
- </div>
166
- </TableCell>
167
- <TableCell>
168
- <div className="text-sm font-mono">
169
- {log.eventType}
170
- </div>
171
- </TableCell>
172
- <TableCell>{log.attempts}</TableCell>
173
- <TableCell>
174
- <div className="text-sm text-muted-foreground">
175
- {new Date(log.createdAt).toLocaleString()}
176
- </div>
177
- </TableCell>
178
- <TableCell>
179
- {log.errorMessage ? (
180
- <div
181
- className="text-sm text-destructive max-w-[200px] truncate"
182
- title={log.errorMessage}
183
- >
184
- {log.errorMessage}
185
- </div>
186
- ) : undefined}
187
- </TableCell>
188
- <TableCell>
189
- {log.status === "failed" && (
190
- <Button
191
- variant="ghost"
192
- size="sm"
193
- onClick={() => handleRetry(log.id)}
194
- disabled={retrying === log.id}
195
- >
196
- <RefreshCw
197
- className={`h-4 w-4 mr-1 ${
198
- retrying === log.id ? "animate-spin" : ""
199
- }`}
200
- />
201
- Retry
202
- </Button>
203
- )}
204
- </TableCell>
136
+ <>
137
+ <ResponsiveTable>
138
+ <Card>
139
+ <Table>
140
+ <TableHeader>
141
+ <TableRow>
142
+ <TableHead>Status</TableHead>
143
+ <TableHead>Subscription</TableHead>
144
+ <TableHead>Event</TableHead>
145
+ <TableHead>Attempts</TableHead>
146
+ <TableHead>Created</TableHead>
147
+ <TableHead>Error</TableHead>
148
+ <TableHead></TableHead>
205
149
  </TableRow>
206
- );
207
- })}
208
- </TableBody>
209
- </Table>
150
+ </TableHeader>
151
+ <TableBody>
152
+ {logs.map((log: DeliveryLog) => {
153
+ const config = statusConfig[log.status];
154
+ return (
155
+ <TableRow key={log.id}>
156
+ <TableCell>
157
+ <Badge
158
+ variant={config.variant}
159
+ className="flex items-center gap-1 w-fit"
160
+ >
161
+ {config.icon}
162
+ {log.status}
163
+ </Badge>
164
+ </TableCell>
165
+ <TableCell>
166
+ <div className="font-medium">
167
+ {log.subscriptionName ?? "Unknown"}
168
+ </div>
169
+ </TableCell>
170
+ <TableCell>
171
+ <div className="text-sm font-mono">
172
+ {log.eventType}
173
+ </div>
174
+ </TableCell>
175
+ <TableCell>{log.attempts}</TableCell>
176
+ <TableCell>
177
+ <div className="text-sm text-muted-foreground">
178
+ {new Date(log.createdAt).toLocaleString()}
179
+ </div>
180
+ </TableCell>
181
+ <TableCell>
182
+ {log.errorMessage ? (
183
+ <div
184
+ className="text-sm text-destructive max-w-[200px] truncate"
185
+ title={log.errorMessage}
186
+ >
187
+ {log.errorMessage}
188
+ </div>
189
+ ) : undefined}
190
+ </TableCell>
191
+ <TableCell>
192
+ {log.status === "failed" && (
193
+ <Button
194
+ variant="ghost"
195
+ size="sm"
196
+ onClick={() => handleRetry(log.id)}
197
+ disabled={retrying === log.id}
198
+ >
199
+ <RefreshCw
200
+ className={`h-4 w-4 mr-1 ${
201
+ retrying === log.id ? "animate-spin" : ""
202
+ }`}
203
+ />
204
+ Retry
205
+ </Button>
206
+ )}
207
+ </TableCell>
208
+ </TableRow>
209
+ );
210
+ })}
211
+ </TableBody>
212
+ </Table>
213
+ </Card>
214
+ </ResponsiveTable>
215
+
216
+ <MobileCardList>
217
+ {logs.map((log: DeliveryLog) => {
218
+ const config = statusConfig[log.status];
219
+ return (
220
+ <Card key={log.id} className="p-3">
221
+ <div className="flex items-start justify-between gap-2">
222
+ <span className="font-medium truncate">
223
+ {log.subscriptionName ?? "Unknown"}
224
+ </span>
225
+ <Badge
226
+ variant={config.variant}
227
+ className="flex items-center gap-1 w-fit shrink-0"
228
+ >
229
+ {config.icon}
230
+ {log.status}
231
+ </Badge>
232
+ </div>
233
+ <div className="mt-1 text-xs text-muted-foreground font-mono break-all">
234
+ {log.eventType}
235
+ </div>
236
+ <div className="mt-1 text-xs text-muted-foreground">
237
+ {log.attempts} attempt
238
+ {log.attempts === 1 ? "" : "s"} &middot;{" "}
239
+ {new Date(log.createdAt).toLocaleString()}
240
+ </div>
241
+ {log.errorMessage && (
242
+ <div
243
+ className="mt-2 text-xs text-destructive line-clamp-2"
244
+ title={log.errorMessage}
245
+ >
246
+ {log.errorMessage}
247
+ </div>
248
+ )}
249
+ {log.status === "failed" && (
250
+ <div className="mt-3 flex justify-end">
251
+ <Button
252
+ variant="ghost"
253
+ size="sm"
254
+ onClick={() => handleRetry(log.id)}
255
+ disabled={retrying === log.id}
256
+ >
257
+ <RefreshCw
258
+ className={`h-4 w-4 mr-1 ${
259
+ retrying === log.id ? "animate-spin" : ""
260
+ }`}
261
+ />
262
+ Retry
263
+ </Button>
264
+ </div>
265
+ )}
266
+ </Card>
267
+ );
268
+ })}
269
+ </MobileCardList>
270
+
210
271
  {pagination.totalPages > 1 && (
211
272
  <div className="p-4 border-t flex justify-center gap-2">
212
273
  <Button
@@ -230,7 +291,7 @@ export const DeliveryLogsPage = () => {
230
291
  </Button>
231
292
  </div>
232
293
  )}
233
- </Card>
294
+ </>
234
295
  )}
235
296
  </section>
236
297
  </div>
@@ -51,7 +51,7 @@ export const IntegrationsPage = () => {
51
51
  data: subscriptionsData,
52
52
  isLoading: subsLoading,
53
53
  refetch: refetchSubs,
54
- } = client.listSubscriptions.useQuery({ page: 1, pageSize: 100 });
54
+ } = client.listSubscriptions.useQuery({ limit: 100, offset: 0 });
55
55
 
56
56
  const { data: providers = [], isLoading: providersLoading } =
57
57
  client.listProviders.useQuery({});
@@ -74,7 +74,7 @@ export const IntegrationsPage = () => {
74
74
  },
75
75
  });
76
76
 
77
- const subscriptions = subscriptionsData?.subscriptions ?? [];
77
+ const subscriptions = subscriptionsData?.items ?? [];
78
78
  const loading = subsLoading || providersLoading || statsLoading;
79
79
 
80
80
  // Handle ?action=create URL parameter (from command palette)
@@ -42,10 +42,11 @@ import {
42
42
  useToast,
43
43
  ConfirmationModal,
44
44
  BackLink,
45
+ toastError,
45
46
  type LucideIconName,
46
47
  } from "@checkstack/ui";
47
48
  import { usePluginClient } from "@checkstack/frontend-api";
48
- import { resolveRoute, extractErrorMessage } from "@checkstack/common";
49
+ import { resolveRoute } from "@checkstack/common";
49
50
  import {
50
51
  IntegrationApi,
51
52
  integrationRoutes,
@@ -108,7 +109,7 @@ export const ProviderConnectionsPage = () => {
108
109
  setSaving(false);
109
110
  },
110
111
  onError: (error) => {
111
- toast.error(extractErrorMessage(error, "Failed to create connection"));
112
+ toastError(toast, "Failed to create connection", error);
112
113
  setSaving(false);
113
114
  },
114
115
  });
@@ -122,7 +123,7 @@ export const ProviderConnectionsPage = () => {
122
123
  setSaving(false);
123
124
  },
124
125
  onError: (error) => {
125
- toast.error(extractErrorMessage(error, "Failed to update connection"));
126
+ toastError(toast, "Failed to update connection", error);
126
127
  setSaving(false);
127
128
  },
128
129
  });
@@ -135,7 +136,7 @@ export const ProviderConnectionsPage = () => {
135
136
  toast.success("Connection deleted");
136
137
  },
137
138
  onError: (error) => {
138
- toast.error(extractErrorMessage(error, "Failed to delete connection"));
139
+ toastError(toast, "Failed to delete connection", error);
139
140
  },
140
141
  });
141
142
 
@@ -157,7 +158,7 @@ export const ProviderConnectionsPage = () => {
157
158
  ...prev,
158
159
  [variables.connectionId]: { success: false, message: "Test failed" },
159
160
  }));
160
- toast.error(extractErrorMessage(error, "Connection test failed"));
161
+ toastError(toast, "Connection test failed", error);
161
162
  setTestingId(undefined);
162
163
  },
163
164
  });