@agent-native/core 0.91.2 → 0.92.0

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.
Files changed (216) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +17 -0
  3. package/corpus/core/docs/content/faq.mdx +17 -0
  4. package/corpus/core/docs/content/notifications.mdx +12 -6
  5. package/corpus/core/package.json +1 -1
  6. package/corpus/core/src/agent/production-agent.ts +140 -46
  7. package/corpus/core/src/agent/tool-search.ts +71 -4
  8. package/corpus/core/src/cli/index.ts +20 -0
  9. package/corpus/core/src/cli/upgrade.ts +798 -0
  10. package/corpus/core/src/client/AssistantChat.tsx +1 -1
  11. package/corpus/core/src/client/HighlightedCodeBlock.tsx +20 -3
  12. package/corpus/core/src/client/agent-chat-adapter.ts +31 -6
  13. package/corpus/core/src/client/chat/message-components.tsx +85 -8
  14. package/corpus/core/src/client/chat/tool-call-display.tsx +355 -144
  15. package/corpus/core/src/client/code-agent-chat-adapter.ts +2 -9
  16. package/corpus/core/src/client/session-replay.ts +3 -97
  17. package/corpus/core/src/client/settings/SettingsPanel.tsx +533 -389
  18. package/corpus/core/src/client/settings/SettingsSection.tsx +154 -17
  19. package/corpus/core/src/client/sse-event-processor.ts +29 -2
  20. package/corpus/core/src/extensions/actions.ts +113 -5
  21. package/corpus/core/src/notifications/channels.ts +88 -25
  22. package/corpus/core/src/notifications/registry.ts +17 -3
  23. package/corpus/core/src/notifications/types.ts +4 -1
  24. package/corpus/core/src/server/agent-chat-plugin.ts +7 -3
  25. package/corpus/core/src/server/request-context.ts +7 -0
  26. package/corpus/core/src/styles/agent-conversation.css +1 -1
  27. package/corpus/core/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +20 -2
  28. package/corpus/core/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  29. package/corpus/core/src/templates/default/AGENTS.md +10 -3
  30. package/corpus/core/src/templates/default/package.json +2 -1
  31. package/corpus/core/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  32. package/corpus/core/src/templates/headless/AGENTS.md +9 -3
  33. package/corpus/core/src/templates/headless/package.json +1 -0
  34. package/corpus/core/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +20 -2
  35. package/corpus/core/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  36. package/corpus/core/src/templates/workspace-root/AGENTS.md +10 -4
  37. package/corpus/core/src/templates/workspace-root/package.json +1 -0
  38. package/corpus/templates/analytics/.agents/skills/self-modifying-code/SKILL.md +20 -2
  39. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +5 -6
  40. package/corpus/templates/analytics/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  41. package/corpus/templates/analytics/AGENTS.md +7 -5
  42. package/corpus/templates/analytics/actions/save-monitor.ts +14 -0
  43. package/corpus/templates/analytics/app/components/dashboard/DataTable.tsx +41 -6
  44. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +122 -11
  45. package/corpus/templates/analytics/app/pages/monitoring/MonitoringPage.tsx +8 -2
  46. package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorFormPage.tsx +151 -32
  47. package/corpus/templates/analytics/app/pages/monitoring/uptime/i18n.ts +17 -2
  48. package/corpus/templates/analytics/app/pages/monitoring/uptime/types.ts +4 -0
  49. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +54 -190
  50. package/corpus/templates/analytics/changelog/2026-07-08-dashboard-tables-keep-their-size-while-loading.md +6 -0
  51. package/corpus/templates/analytics/changelog/2026-07-08-monitoring-uptime-and-errors-tabs-now-clearly-show-which-view-is-active.md +6 -0
  52. package/corpus/templates/analytics/changelog/2026-07-08-session-replay-uses-stock-rrweb-sizing-again-so-recorded.md +6 -0
  53. package/corpus/templates/analytics/changelog/2026-07-08-sidebar-dashboard-menus-now-open-above-the-dashboard-canvas.md +6 -0
  54. package/corpus/templates/analytics/changelog/2026-07-08-uptime-alerting-clarifies-in-app-inbox-vs-email-and-lets-you-paste-slack-or-webhook-urls-per-monitor.md +6 -0
  55. package/corpus/templates/analytics/docs/uptime-monitoring.md +13 -11
  56. package/corpus/templates/analytics/server/db/schema-monitoring.ts +4 -0
  57. package/corpus/templates/analytics/server/lib/uptime-monitors.ts +60 -12
  58. package/corpus/templates/analytics/server/plugins/db.ts +10 -0
  59. package/corpus/templates/assets/.agents/skills/self-modifying-code/SKILL.md +20 -2
  60. package/corpus/templates/assets/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  61. package/corpus/templates/brain/.agents/skills/self-modifying-code/SKILL.md +20 -2
  62. package/corpus/templates/brain/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  63. package/corpus/templates/calendar/.agents/skills/self-modifying-code/SKILL.md +20 -2
  64. package/corpus/templates/calendar/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  65. package/corpus/templates/chat/.agents/skills/self-modifying-code/SKILL.md +20 -2
  66. package/corpus/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  67. package/corpus/templates/clips/.agents/skills/self-modifying-code/SKILL.md +20 -2
  68. package/corpus/templates/clips/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  69. package/corpus/templates/clips/changelog/2026-07-08-desktop-meeting-notes-stay-active-from-tray-and-shortcut-controls.md +6 -0
  70. package/corpus/templates/clips/changelog/2026-07-08-desktop-meeting-popovers-can-start-notes-and-join-the-meetin.md +6 -0
  71. package/corpus/templates/clips/changelog/2026-07-08-meeting-notes-keep-their-recording-state-when-the-desktop.md +6 -0
  72. package/corpus/templates/clips/changelog/2026-07-08-meeting-reminder-dropdowns-now-expand-fully-instead-of-being.md +6 -0
  73. package/corpus/templates/clips/desktop/src/app.tsx +123 -3
  74. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +15 -1
  75. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +23 -0
  76. package/corpus/templates/clips/desktop/src/styles.css +23 -1
  77. package/corpus/templates/clips/desktop/src-tauri/src/adhoc_meetings_watcher.rs +1 -3
  78. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +40 -6
  79. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +4 -2
  80. package/corpus/templates/clips/desktop/src-tauri/src/meetings_watcher.rs +3 -5
  81. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +23 -19
  82. package/corpus/templates/clips/desktop/src-tauri/src/shortcuts.rs +3 -3
  83. package/corpus/templates/clips/desktop/src-tauri/src/silence_detector.rs +0 -1
  84. package/corpus/templates/clips/desktop/src-tauri/src/state.rs +5 -0
  85. package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +18 -6
  86. package/corpus/templates/content/.agents/skills/self-modifying-code/SKILL.md +20 -2
  87. package/corpus/templates/content/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  88. package/corpus/templates/content/actions/_batch-utils.ts +8 -0
  89. package/corpus/templates/content/actions/_builder-cms-read-client.ts +7 -5
  90. package/corpus/templates/content/actions/_database-source-utils.ts +776 -319
  91. package/corpus/templates/content/actions/_database-utils.ts +6 -1
  92. package/corpus/templates/content/actions/_property-utils.ts +168 -0
  93. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +19 -12
  94. package/corpus/templates/content/actions/bind-content-database-source-field.ts +19 -12
  95. package/corpus/templates/content/actions/delete-document.ts +9 -13
  96. package/corpus/templates/content/actions/refresh-content-database-source.ts +5 -5
  97. package/corpus/templates/content/app/components/editor/body-hydration.ts +35 -11
  98. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +47 -5
  99. package/corpus/templates/content/app/hooks/use-documents.ts +8 -2
  100. package/corpus/templates/content/changelog/2026-07-08-builder-source-sync-now-completes-reliably-on-hosted-databases.md +6 -0
  101. package/corpus/templates/content/changelog/2026-07-08-fixed-builder-database-sync-so-large-sources-continue-past-t.md +6 -0
  102. package/corpus/templates/content/server/plugins/db.ts +19 -0
  103. package/corpus/templates/design/.agents/skills/self-modifying-code/SKILL.md +20 -2
  104. package/corpus/templates/design/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  105. package/corpus/templates/dispatch/.agents/skills/self-modifying-code/SKILL.md +20 -2
  106. package/corpus/templates/dispatch/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  107. package/corpus/templates/forms/.agents/skills/self-modifying-code/SKILL.md +20 -2
  108. package/corpus/templates/forms/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  109. package/corpus/templates/macros/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  110. package/corpus/templates/mail/.agents/skills/self-modifying-code/SKILL.md +20 -2
  111. package/corpus/templates/mail/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  112. package/corpus/templates/plan/.agents/skills/self-modifying-code/SKILL.md +20 -2
  113. package/corpus/templates/plan/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  114. package/corpus/templates/slides/.agents/skills/self-modifying-code/SKILL.md +20 -2
  115. package/corpus/templates/slides/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  116. package/corpus/templates/videos/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  117. package/corpus/templates/voice/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  118. package/dist/agent/production-agent.d.ts +5 -1
  119. package/dist/agent/production-agent.d.ts.map +1 -1
  120. package/dist/agent/production-agent.js +112 -42
  121. package/dist/agent/production-agent.js.map +1 -1
  122. package/dist/agent/tool-search.d.ts +2 -0
  123. package/dist/agent/tool-search.d.ts.map +1 -1
  124. package/dist/agent/tool-search.js +60 -4
  125. package/dist/agent/tool-search.js.map +1 -1
  126. package/dist/cli/index.js +19 -0
  127. package/dist/cli/index.js.map +1 -1
  128. package/dist/cli/upgrade.d.ts +100 -0
  129. package/dist/cli/upgrade.d.ts.map +1 -0
  130. package/dist/cli/upgrade.js +678 -0
  131. package/dist/cli/upgrade.js.map +1 -0
  132. package/dist/client/AssistantChat.js +1 -1
  133. package/dist/client/AssistantChat.js.map +1 -1
  134. package/dist/client/HighlightedCodeBlock.d.ts +2 -0
  135. package/dist/client/HighlightedCodeBlock.d.ts.map +1 -1
  136. package/dist/client/HighlightedCodeBlock.js +10 -1
  137. package/dist/client/HighlightedCodeBlock.js.map +1 -1
  138. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  139. package/dist/client/agent-chat-adapter.js +32 -4
  140. package/dist/client/agent-chat-adapter.js.map +1 -1
  141. package/dist/client/chat/message-components.d.ts.map +1 -1
  142. package/dist/client/chat/message-components.js +61 -8
  143. package/dist/client/chat/message-components.js.map +1 -1
  144. package/dist/client/chat/tool-call-display.d.ts +12 -0
  145. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  146. package/dist/client/chat/tool-call-display.js +136 -42
  147. package/dist/client/chat/tool-call-display.js.map +1 -1
  148. package/dist/client/code-agent-chat-adapter.js +2 -9
  149. package/dist/client/code-agent-chat-adapter.js.map +1 -1
  150. package/dist/client/session-replay.d.ts +0 -8
  151. package/dist/client/session-replay.d.ts.map +1 -1
  152. package/dist/client/session-replay.js +3 -80
  153. package/dist/client/session-replay.js.map +1 -1
  154. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  155. package/dist/client/settings/SettingsPanel.js +52 -19
  156. package/dist/client/settings/SettingsPanel.js.map +1 -1
  157. package/dist/client/settings/SettingsSection.d.ts +19 -4
  158. package/dist/client/settings/SettingsSection.d.ts.map +1 -1
  159. package/dist/client/settings/SettingsSection.js +33 -4
  160. package/dist/client/settings/SettingsSection.js.map +1 -1
  161. package/dist/client/sse-event-processor.d.ts +9 -0
  162. package/dist/client/sse-event-processor.d.ts.map +1 -1
  163. package/dist/client/sse-event-processor.js +20 -2
  164. package/dist/client/sse-event-processor.js.map +1 -1
  165. package/dist/collab/routes.d.ts +1 -1
  166. package/dist/extensions/actions.d.ts.map +1 -1
  167. package/dist/extensions/actions.js +85 -5
  168. package/dist/extensions/actions.js.map +1 -1
  169. package/dist/notifications/channels.d.ts +8 -3
  170. package/dist/notifications/channels.d.ts.map +1 -1
  171. package/dist/notifications/channels.js +61 -22
  172. package/dist/notifications/channels.js.map +1 -1
  173. package/dist/notifications/registry.d.ts.map +1 -1
  174. package/dist/notifications/registry.js +14 -3
  175. package/dist/notifications/registry.js.map +1 -1
  176. package/dist/notifications/routes.d.ts +1 -1
  177. package/dist/notifications/types.d.ts +4 -1
  178. package/dist/notifications/types.d.ts.map +1 -1
  179. package/dist/notifications/types.js.map +1 -1
  180. package/dist/observability/routes.d.ts +1 -1
  181. package/dist/resources/handlers.d.ts +1 -1
  182. package/dist/server/agent-chat-plugin.d.ts +5 -1
  183. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  184. package/dist/server/agent-chat-plugin.js +2 -2
  185. package/dist/server/agent-chat-plugin.js.map +1 -1
  186. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  187. package/dist/server/request-context.d.ts +7 -0
  188. package/dist/server/request-context.d.ts.map +1 -1
  189. package/dist/server/request-context.js.map +1 -1
  190. package/dist/styles/agent-conversation.css +1 -1
  191. package/dist/templates/default/.agents/skills/self-modifying-code/SKILL.md +20 -2
  192. package/dist/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  193. package/dist/templates/default/AGENTS.md +10 -3
  194. package/dist/templates/default/package.json +2 -1
  195. package/dist/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  196. package/dist/templates/headless/AGENTS.md +9 -3
  197. package/dist/templates/headless/package.json +1 -0
  198. package/dist/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +20 -2
  199. package/dist/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  200. package/dist/templates/workspace-root/AGENTS.md +10 -4
  201. package/dist/templates/workspace-root/package.json +1 -0
  202. package/docs/content/faq.mdx +17 -0
  203. package/docs/content/notifications.mdx +12 -6
  204. package/package.json +2 -2
  205. package/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +20 -2
  206. package/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  207. package/src/templates/default/AGENTS.md +10 -3
  208. package/src/templates/default/package.json +2 -1
  209. package/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  210. package/src/templates/headless/AGENTS.md +9 -3
  211. package/src/templates/headless/package.json +1 -0
  212. package/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +20 -2
  213. package/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  214. package/src/templates/workspace-root/AGENTS.md +10 -4
  215. package/src/templates/workspace-root/package.json +1 -0
  216. package/corpus/templates/analytics/changelog/2026-07-08-session-replay-clamps-ultra-wide-viewports-while-keeping-the.md +0 -6
@@ -0,0 +1,87 @@
1
+ ---
2
+ name: upgrade-agent-native
3
+ description: >-
4
+ Bring an older Agent Native app or workspace current. Use when updating
5
+ @agent-native/core, fixing a broken upgrade, or when tempted to patch or
6
+ override core/dispatch packages to make an old branch run.
7
+ scope: dev
8
+ metadata:
9
+ internal: true
10
+ ---
11
+
12
+ # Upgrade Agent Native
13
+
14
+ ## Rule
15
+
16
+ When an older Agent Native app/branch needs to run on current packages, use
17
+ `agent-native upgrade`. Never "fix" upgrade breakage with
18
+ `pnpm.overrides`, `patchedDependencies`, `resolutions`, local patches, or
19
+ edits under `node_modules/@agent-native/*` — especially not against
20
+ `@agent-native/core` or `@agent-native/dispatch`.
21
+
22
+ ## Why
23
+
24
+ Agents often respond to a failed core bump by inventing framework patches and
25
+ dispatch behavior overrides. That hides the real app-level break, drifts from
26
+ upstream, and makes the next upgrade worse. The supported path is bump →
27
+ install → refresh scaffold skills → verify, then fix **app** code only.
28
+
29
+ ## How
30
+
31
+ 1. **Doctor first (optional but recommended)**
32
+
33
+ ```bash
34
+ npx @agent-native/core@latest upgrade check
35
+ ```
36
+
37
+ This reports framework overrides/patches and pending `@agent-native/*`
38
+ bumps. If overrides/patches are present, remove them before continuing.
39
+
40
+ 2. **Run the upgrade**
41
+
42
+ ```bash
43
+ npx @agent-native/core@latest upgrade
44
+ ```
45
+
46
+ Or from an already-installed CLI: `pnpm exec agent-native upgrade` /
47
+ `agent-native upgrade`.
48
+
49
+ What it does:
50
+
51
+ - Blocks (unless `--force`) when `@agent-native/*` overrides/patches exist
52
+ - Rewrites non-local `@agent-native/*` dependency pins to `latest`
53
+ - Runs the package manager install
54
+ - Runs `skills update scaffold --project`
55
+ - Runs `typecheck` when the project has that script
56
+
57
+ 3. **If upgrade or typecheck fails**
58
+
59
+ - Read the concrete error
60
+ - Fix **app** source, actions, config, or env — not framework packages
61
+ - Re-run `agent-native upgrade` or `pnpm typecheck`
62
+ - Stop and ask the user if you cannot fix the app-level error
63
+
64
+ 4. **Dry-run / partial runs**
65
+
66
+ ```bash
67
+ agent-native upgrade --dry-run
68
+ agent-native upgrade --skip-verify
69
+ agent-native upgrade --skip-install # package.json bumps only
70
+ ```
71
+
72
+ ## Don't
73
+
74
+ - Don't add `pnpm.overrides`, `overrides`, `resolutions`, or
75
+ `patchedDependencies` for any `@agent-native/*` package
76
+ - Don't edit `node_modules/@agent-native/core` or
77
+ `node_modules/@agent-native/dispatch`
78
+ - Don't invent local "dispatch behavior" shims to paper over version skew
79
+ - Don't keep iterating with more framework patches after a failed install
80
+ - Don't skip `skills update scaffold --project` after a core bump (the
81
+ upgrade command does this for you)
82
+
83
+ ## Related Skills
84
+
85
+ - **self-modifying-code** — Tier 4: framework packages are off limits
86
+ - **agent-native-docs** — version-matched docs after the bump
87
+ - **portability** — keep app code provider-agnostic across upgrades
@@ -140,11 +140,13 @@ details live in `.agents/skills/`.
140
140
  `thresholdMode: "distinct_count"` counts unique values from `distinctBy`.
141
141
  Alert notifications use the shared notification channel registry, so
142
142
  `channels` can include `inbox`, `email`, `slack`, `webhook`, or any custom
143
- registered channel. Configure Slack with `NOTIFICATIONS_SLACK_WEBHOOK_URL`
144
- and optional `NOTIFICATIONS_SLACK_WEBHOOK_AUTH`; configure email with
145
- `NOTIFICATIONS_EMAIL_CHANNEL=1`, existing `RESEND_API_KEY` or
146
- `SENDGRID_API_KEY` plus `EMAIL_FROM`, and pass per-rule `emailRecipients` or
147
- the fallback `NOTIFICATIONS_EMAIL_RECIPIENTS`.
143
+ registered channel. Slack/webhook prefer delivery-only
144
+ `metadata.delivery.slackWebhookUrl` / `metadata.delivery.webhookUrl` (uptime
145
+ monitors store these on the monitor row), then fall back to
146
+ `NOTIFICATIONS_SLACK_WEBHOOK_URL` / `NOTIFICATIONS_WEBHOOK_URL`. Configure
147
+ optional `NOTIFICATIONS_SLACK_WEBHOOK_AUTH`; configure email with existing
148
+ `RESEND_API_KEY` or `SENDGRID_API_KEY` plus `EMAIL_FROM`, and pass per-rule
149
+ `emailRecipients` or the fallback `NOTIFICATIONS_EMAIL_RECIPIENTS`.
148
150
  Netlify builds emit an alert cron trigger plus background worker from
149
151
  `scripts/emit-netlify-dashboard-report-cron.ts` every five minutes; long-lived
150
152
  runtimes use the in-process scheduler unless `ANALYTICS_ALERT_JOBS=0` is set.
@@ -123,6 +123,20 @@ export default defineAction({
123
123
  .array(z.string().email())
124
124
  .optional()
125
125
  .describe("Email recipients used by the email channel."),
126
+ slackWebhookUrl: z
127
+ .string()
128
+ .nullable()
129
+ .optional()
130
+ .describe(
131
+ "Optional Slack incoming webhook URL for this monitor. Overrides NOTIFICATIONS_SLACK_WEBHOOK_URL when set.",
132
+ ),
133
+ webhookUrl: z
134
+ .string()
135
+ .nullable()
136
+ .optional()
137
+ .describe(
138
+ "Optional generic webhook URL for this monitor. Overrides NOTIFICATIONS_WEBHOOK_URL when set.",
139
+ ),
126
140
  cooldownMinutes: z
127
141
  .number()
128
142
  .int()
@@ -27,6 +27,8 @@ import {
27
27
  import { cn } from "@/lib/utils";
28
28
 
29
29
  const PAGE_SIZE_OPTIONS = [10, 25, 50, 100];
30
+ const TABLE_MIN_HEIGHT_CLASS = "min-h-[386px]";
31
+ const TABLE_SKELETON_ROWS = 10;
30
32
 
31
33
  interface DataTableProps {
32
34
  title?: string;
@@ -109,11 +111,7 @@ export function DataTable({
109
111
  const content = (
110
112
  <>
111
113
  {isLoading ? (
112
- <div className="space-y-2">
113
- {Array.from({ length: 5 }).map((_, i) => (
114
- <Skeleton key={i} className="h-8 w-full" />
115
- ))}
116
- </div>
114
+ <DataTableLoadingSkeleton />
117
115
  ) : error ? (
118
116
  <p className="text-sm text-red-400 py-4">{error}</p>
119
117
  ) : data.length === 0 ? (
@@ -121,7 +119,7 @@ export function DataTable({
121
119
  {t("common.noData")}
122
120
  </p>
123
121
  ) : (
124
- <div className="overflow-x-auto">
122
+ <div className={`overflow-x-auto ${TABLE_MIN_HEIGHT_CLASS}`}>
125
123
  <Table>
126
124
  <TableHeader>
127
125
  <TableRow>
@@ -227,3 +225,40 @@ export function DataTable({
227
225
  </Card>
228
226
  );
229
227
  }
228
+
229
+ function DataTableLoadingSkeleton() {
230
+ const columnWidths = ["w-24", "w-32", "w-20", "w-28"];
231
+
232
+ return (
233
+ <div className={`space-y-1 ${TABLE_MIN_HEIGHT_CLASS}`}>
234
+ <div className="overflow-x-auto">
235
+ <div className="min-w-[480px]">
236
+ <div className="grid h-8 grid-cols-4 items-center border-b border-border px-2">
237
+ {columnWidths.map((width, index) => (
238
+ <Skeleton key={index} className={`h-3 ${width}`} />
239
+ ))}
240
+ </div>
241
+ {Array.from({ length: TABLE_SKELETON_ROWS }).map((_, row) => (
242
+ <div
243
+ key={row}
244
+ className="grid h-8 grid-cols-4 items-center border-b border-border/50 px-2"
245
+ >
246
+ {columnWidths.map((width, col) => (
247
+ <Skeleton
248
+ key={col}
249
+ className={`h-3 ${
250
+ col === 0 ? "w-36" : col === 2 ? "ml-auto w-16" : width
251
+ }`}
252
+ />
253
+ ))}
254
+ </div>
255
+ ))}
256
+ </div>
257
+ </div>
258
+ <div className="flex h-8 items-center justify-between border-t border-border px-2 text-xs">
259
+ <Skeleton className="h-3 w-28" />
260
+ <Skeleton className="h-3 w-24" />
261
+ </div>
262
+ </div>
263
+ );
264
+ }
@@ -113,6 +113,8 @@ const CHART_LEGEND_PROPS = {
113
113
  const PARTIAL_DAY_TIME_ZONE = "America/Los_Angeles";
114
114
  const PARTIAL_DAY_DASH = "3 5";
115
115
  const PARTIAL_DAY_KEY_PREFIX = "__sql_chart_partial_day";
116
+ const TABLE_PANEL_MIN_HEIGHT_CLASS = "min-h-[386px]";
117
+ const TABLE_PANEL_SKELETON_ROWS = 10;
116
118
 
117
119
  function formatYValue(
118
120
  value: number,
@@ -488,7 +490,7 @@ function SeriesLegend({
488
490
  if (!shouldShowLegend(panel, keys.length)) return null;
489
491
 
490
492
  return (
491
- <div className="mt-2 max-h-16 overflow-y-auto overflow-x-hidden pr-1 text-[11px] leading-4 text-muted-foreground">
493
+ <div className="mt-2 min-h-16 max-h-16 overflow-y-auto overflow-x-hidden pr-1 text-[11px] leading-4 text-muted-foreground">
492
494
  <div className="flex flex-wrap gap-x-3 gap-y-1">
493
495
  {keys.map((key, i) => {
494
496
  const hidden = hiddenKeys?.has(key) ?? false;
@@ -586,6 +588,113 @@ function ChartFrame({
586
588
  );
587
589
  }
588
590
 
591
+ function isTableLikeChartType(type: ChartType): boolean {
592
+ return type === "table" || type === "heatmap";
593
+ }
594
+
595
+ function chartTypeReservesLegend(panel: SqlPanel): boolean {
596
+ if (panel.config?.legend === false) return false;
597
+ const chartUsesFrame =
598
+ panel.chartType === "line" ||
599
+ panel.chartType === "area" ||
600
+ panel.chartType === "bar" ||
601
+ panel.chartType === "pie";
602
+ if (!chartUsesFrame) return false;
603
+ return (
604
+ panel.chartType === "line" ||
605
+ panel.chartType === "area" ||
606
+ panel.chartType === "bar" ||
607
+ usesPrometheusPresentation(panel)
608
+ );
609
+ }
610
+
611
+ function TableLoadingSkeleton() {
612
+ const columnWidths = ["w-24", "w-32", "w-20", "w-28"];
613
+
614
+ return (
615
+ <div
616
+ data-dashboard-report-loading="true"
617
+ className={`w-full flex-1 space-y-1 ${TABLE_PANEL_MIN_HEIGHT_CLASS}`}
618
+ >
619
+ <div className="relative overflow-x-auto">
620
+ <div className="min-w-[480px]">
621
+ <div className="grid h-8 grid-cols-4 items-center border-b border-border px-2">
622
+ {columnWidths.map((width, index) => (
623
+ <Skeleton key={index} className={`h-3 ${width}`} />
624
+ ))}
625
+ </div>
626
+ {Array.from({ length: TABLE_PANEL_SKELETON_ROWS }).map((_, row) => (
627
+ <div
628
+ key={row}
629
+ className="grid h-8 grid-cols-4 items-center border-b border-border/50 px-2"
630
+ >
631
+ {columnWidths.map((width, col) => (
632
+ <Skeleton
633
+ key={col}
634
+ className={`h-3 ${
635
+ col === 0 ? "w-36" : col === 2 ? "ml-auto w-16" : width
636
+ }`}
637
+ />
638
+ ))}
639
+ </div>
640
+ ))}
641
+ </div>
642
+ </div>
643
+ <div className="flex h-8 items-center justify-between border-t border-border px-1 text-xs">
644
+ <Skeleton className="h-3 w-28" />
645
+ <Skeleton className="h-3 w-24" />
646
+ </div>
647
+ </div>
648
+ );
649
+ }
650
+
651
+ function SqlChartLoadingSkeleton({ panel }: { panel: SqlPanel }) {
652
+ const fill = useContext(ChartFillHeightContext);
653
+
654
+ if (panel.chartType === "metric") {
655
+ return (
656
+ <Skeleton
657
+ data-dashboard-report-loading="true"
658
+ className="w-full flex-1 min-h-12"
659
+ />
660
+ );
661
+ }
662
+
663
+ if (isTableLikeChartType(panel.chartType)) {
664
+ return <TableLoadingSkeleton />;
665
+ }
666
+
667
+ const reserveLegend = chartTypeReservesLegend(panel);
668
+
669
+ if (!reserveLegend) {
670
+ return (
671
+ <Skeleton
672
+ data-dashboard-report-loading="true"
673
+ className={`w-full flex-1 ${fill ? "h-full min-h-[250px]" : "min-h-[250px]"}`}
674
+ />
675
+ );
676
+ }
677
+
678
+ return (
679
+ <div
680
+ data-dashboard-report-loading="true"
681
+ className={`flex w-full flex-1 flex-col overflow-hidden ${fill ? "h-full" : ""}`}
682
+ >
683
+ <Skeleton
684
+ className={`w-full ${fill ? "h-full min-h-[250px] flex-1" : "h-[250px]"}`}
685
+ />
686
+ <div className="mt-2 grid min-h-16 grid-cols-2 gap-x-3 gap-y-1 overflow-hidden pr-1 sm:grid-cols-3">
687
+ {Array.from({ length: 6 }).map((_, index) => (
688
+ <div key={index} className="flex items-center gap-1.5">
689
+ <Skeleton className="h-2.5 w-3 shrink-0 rounded-sm" />
690
+ <Skeleton className="h-3 w-20 min-w-0" />
691
+ </div>
692
+ ))}
693
+ </div>
694
+ </div>
695
+ );
696
+ }
697
+
589
698
  function ChartTooltip({
590
699
  active,
591
700
  payload,
@@ -962,16 +1071,16 @@ export function SqlChart({
962
1071
  const yFormatter = panel.config?.yFormatter;
963
1072
 
964
1073
  const isMetric = panel.chartType === "metric";
965
- const placeholderMinH = isMetric ? "min-h-12" : "min-h-[250px]";
1074
+ const isTableLike = isTableLikeChartType(panel.chartType);
1075
+ const placeholderMinH = isMetric
1076
+ ? "min-h-12"
1077
+ : isTableLike
1078
+ ? TABLE_PANEL_MIN_HEIGHT_CLASS
1079
+ : "min-h-[250px]";
966
1080
  const placeholderPadY = isMetric ? "py-2" : "py-8";
967
1081
 
968
1082
  if (!loadData || isLoading) {
969
- return (
970
- <Skeleton
971
- data-dashboard-report-loading="true"
972
- className={`w-full flex-1 ${placeholderMinH}`}
973
- />
974
- );
1083
+ return <SqlChartLoadingSkeleton panel={panel} />;
975
1084
  }
976
1085
 
977
1086
  if (error) {
@@ -1366,7 +1475,7 @@ function TableRenderer({
1366
1475
  }, [handleExportCsv, onExportCsvChange]);
1367
1476
 
1368
1477
  return (
1369
- <div className="space-y-1">
1478
+ <div className={`space-y-1 ${TABLE_PANEL_MIN_HEIGHT_CLASS}`}>
1370
1479
  <div className="relative overflow-x-auto">
1371
1480
  <table className="w-full text-sm">
1372
1481
  <thead>
@@ -1989,7 +2098,9 @@ function HeatmapRenderer({
1989
2098
 
1990
2099
  if (rows.length === 0 || !valueKey) {
1991
2100
  return (
1992
- <div className="flex min-h-[250px] items-center justify-center py-8">
2101
+ <div
2102
+ className={`flex items-center justify-center py-8 ${TABLE_PANEL_MIN_HEIGHT_CLASS}`}
2103
+ >
1993
2104
  <p className="text-sm text-muted-foreground text-center">
1994
2105
  {t("common.noData")}
1995
2106
  </p>
@@ -2010,7 +2121,7 @@ function HeatmapRenderer({
2010
2121
  };
2011
2122
 
2012
2123
  return (
2013
- <div className="overflow-x-auto">
2124
+ <div className={`${TABLE_PANEL_MIN_HEIGHT_CLASS} overflow-x-auto`}>
2014
2125
  <table className="w-full border-collapse text-sm">
2015
2126
  <thead>
2016
2127
  <tr className="border-b border-border">
@@ -69,11 +69,17 @@ export default function MonitoringPage() {
69
69
  {inSubView ? null : (
70
70
  <div className="flex items-center justify-between gap-2">
71
71
  <TabsList>
72
- <TabsTrigger value="uptime" className="gap-2">
72
+ <TabsTrigger
73
+ value="uptime"
74
+ className="gap-2 data-[state=active]:bg-primary data-[state=active]:text-primary-foreground data-[state=active]:shadow-sm"
75
+ >
73
76
  <IconHeartbeat className="h-4 w-4" />
74
77
  {t("navigation.monitoringUptime")}
75
78
  </TabsTrigger>
76
- <TabsTrigger value="errors" className="gap-2">
79
+ <TabsTrigger
80
+ value="errors"
81
+ className="gap-2 data-[state=active]:bg-primary data-[state=active]:text-primary-foreground data-[state=active]:shadow-sm"
82
+ >
77
83
  <IconAlertTriangle className="h-4 w-4" />
78
84
  {t("navigation.monitoringErrors")}
79
85
  </TabsTrigger>
@@ -101,6 +101,8 @@ interface MonitorFormValues {
101
101
  severity: MonitorSeverity;
102
102
  channels: Record<(typeof KNOWN_CHANNELS)[number], boolean>;
103
103
  emailRecipients: string;
104
+ slackWebhookUrl: string;
105
+ webhookUrl: string;
104
106
  cooldownMinutes: number;
105
107
  enabled: boolean;
106
108
  }
@@ -160,6 +162,8 @@ function defaultValues(monitor: MonitorSummary | null): MonitorFormValues {
160
162
  webhook: channelSet.has("webhook"),
161
163
  },
162
164
  emailRecipients: (monitor?.emailRecipients ?? []).join(", "),
165
+ slackWebhookUrl: monitor?.slackWebhookUrl ?? "",
166
+ webhookUrl: monitor?.webhookUrl ?? "",
163
167
  cooldownMinutes: monitor?.cooldownMinutes ?? 15,
164
168
  enabled: monitor?.enabled ?? true,
165
169
  };
@@ -206,6 +210,8 @@ function initialSections(monitor: MonitorSummary | null): SectionState {
206
210
  alerting:
207
211
  !isDefaultChannels ||
208
212
  monitor.emailRecipients.length > 0 ||
213
+ Boolean(monitor.slackWebhookUrl) ||
214
+ Boolean(monitor.webhookUrl) ||
209
215
  monitor.severity !== "critical",
210
216
  advanced:
211
217
  monitor.method !== "GET" ||
@@ -362,6 +368,30 @@ export function MonitorFormPage({
362
368
  setError("channels", { message: t.channelRequired });
363
369
  return;
364
370
  }
371
+ if (
372
+ formValues.channels.email &&
373
+ !splitList(formValues.emailRecipients).length
374
+ ) {
375
+ setSections((prev) => ({ ...prev, alerting: true }));
376
+ setError("emailRecipients", { message: t.emailRecipientsRequired });
377
+ return;
378
+ }
379
+ if (formValues.channels.slack) {
380
+ const slackUrl = formValues.slackWebhookUrl.trim();
381
+ if (slackUrl && !isHttpUrl(slackUrl)) {
382
+ setSections((prev) => ({ ...prev, alerting: true }));
383
+ setError("slackWebhookUrl", { message: t.slackWebhookUrlInvalid });
384
+ return;
385
+ }
386
+ }
387
+ if (formValues.channels.webhook) {
388
+ const hookUrl = formValues.webhookUrl.trim();
389
+ if (hookUrl && !isHttpUrl(hookUrl)) {
390
+ setSections((prev) => ({ ...prev, alerting: true }));
391
+ setError("webhookUrl", { message: t.webhookUrlInvalid });
392
+ return;
393
+ }
394
+ }
365
395
 
366
396
  const assertions = formValues.assertions
367
397
  .map((assertion) => {
@@ -399,7 +429,15 @@ export function MonitorFormPage({
399
429
  followRedirects: formValues.followRedirects,
400
430
  severity: formValues.severity,
401
431
  channels,
402
- emailRecipients: splitList(formValues.emailRecipients),
432
+ emailRecipients: formValues.channels.email
433
+ ? splitList(formValues.emailRecipients)
434
+ : [],
435
+ slackWebhookUrl: formValues.channels.slack
436
+ ? formValues.slackWebhookUrl.trim()
437
+ : null,
438
+ webhookUrl: formValues.channels.webhook
439
+ ? formValues.webhookUrl.trim()
440
+ : null,
403
441
  cooldownMinutes: formValues.cooldownMinutes,
404
442
  enabled: formValues.enabled,
405
443
  };
@@ -736,8 +774,8 @@ export function MonitorFormPage({
736
774
  setSections((prev) => ({ ...prev, alerting: open }))
737
775
  }
738
776
  >
739
- <div className="grid gap-4 sm:grid-cols-2">
740
- <div className="space-y-1.5">
777
+ <div className="space-y-4">
778
+ <div className="space-y-1.5 sm:max-w-xs">
741
779
  <Label htmlFor="monitor-severity">{t.fieldSeverity}</Label>
742
780
  <Controller
743
781
  control={control}
@@ -765,43 +803,108 @@ export function MonitorFormPage({
765
803
  />
766
804
  </div>
767
805
 
768
- <div className="space-y-1.5">
806
+ <div className="space-y-2">
769
807
  <Label>{t.fieldChannels}</Label>
770
808
  <Controller
771
809
  control={control}
772
810
  name="channels"
773
811
  render={({ field }) => (
774
- <div className="grid grid-cols-2 gap-2">
775
- {KNOWN_CHANNELS.map((channel) => (
776
- <label
777
- key={channel}
778
- className="flex items-center gap-2 rounded-md border px-2.5 py-1.5 text-xs"
779
- >
780
- <Checkbox
781
- checked={field.value[channel]}
782
- onCheckedChange={(checked) =>
783
- field.onChange({
784
- ...field.value,
785
- [channel]: checked === true,
786
- })
787
- }
788
- />
789
- {channelLabel(channel, t)}
790
- </label>
791
- ))}
812
+ <div className="space-y-2">
813
+ {KNOWN_CHANNELS.map((channel) => {
814
+ const checked = field.value[channel];
815
+ return (
816
+ <div
817
+ key={channel}
818
+ className="rounded-md border border-border/60"
819
+ >
820
+ <label className="flex cursor-pointer items-start gap-2.5 px-3 py-2.5">
821
+ <Checkbox
822
+ className="mt-0.5"
823
+ checked={checked}
824
+ onCheckedChange={(next) =>
825
+ field.onChange({
826
+ ...field.value,
827
+ [channel]: next === true,
828
+ })
829
+ }
830
+ />
831
+ <span className="min-w-0">
832
+ <span className="block text-sm">
833
+ {channelLabel(channel, t)}
834
+ </span>
835
+ <span className="block text-xs text-muted-foreground">
836
+ {channelHint(channel, t)}
837
+ </span>
838
+ </span>
839
+ </label>
840
+ {channel === "email" && checked ? (
841
+ <div className="space-y-1.5 border-t border-border/60 px-3 py-2.5">
842
+ <Label htmlFor="monitor-emails">
843
+ {t.fieldEmailRecipients}
844
+ </Label>
845
+ <Textarea
846
+ id="monitor-emails"
847
+ className="min-h-[64px]"
848
+ placeholder={t.fieldEmailRecipientsPlaceholder}
849
+ {...register("emailRecipients")}
850
+ />
851
+ {errors.emailRecipients ? (
852
+ <p className="text-xs text-destructive">
853
+ {errors.emailRecipients.message}
854
+ </p>
855
+ ) : null}
856
+ </div>
857
+ ) : null}
858
+ {channel === "slack" && checked ? (
859
+ <div className="space-y-1.5 border-t border-border/60 px-3 py-2.5">
860
+ <Label htmlFor="monitor-slack-url">
861
+ {t.fieldSlackWebhookUrl}
862
+ </Label>
863
+ <Input
864
+ id="monitor-slack-url"
865
+ type="url"
866
+ placeholder={t.fieldSlackWebhookUrlPlaceholder}
867
+ {...register("slackWebhookUrl")}
868
+ />
869
+ {errors.slackWebhookUrl ? (
870
+ <p className="text-xs text-destructive">
871
+ {errors.slackWebhookUrl.message}
872
+ </p>
873
+ ) : (
874
+ <p className="text-xs text-muted-foreground">
875
+ {t.fieldSlackWebhookUrlHint}
876
+ </p>
877
+ )}
878
+ </div>
879
+ ) : null}
880
+ {channel === "webhook" && checked ? (
881
+ <div className="space-y-1.5 border-t border-border/60 px-3 py-2.5">
882
+ <Label htmlFor="monitor-webhook-url">
883
+ {t.fieldWebhookUrl}
884
+ </Label>
885
+ <Input
886
+ id="monitor-webhook-url"
887
+ type="url"
888
+ placeholder={t.fieldWebhookUrlPlaceholder}
889
+ {...register("webhookUrl")}
890
+ />
891
+ {errors.webhookUrl ? (
892
+ <p className="text-xs text-destructive">
893
+ {errors.webhookUrl.message}
894
+ </p>
895
+ ) : (
896
+ <p className="text-xs text-muted-foreground">
897
+ {t.fieldWebhookUrlHint}
898
+ </p>
899
+ )}
900
+ </div>
901
+ ) : null}
902
+ </div>
903
+ );
904
+ })}
792
905
  </div>
793
906
  )}
794
907
  />
795
- </div>
796
-
797
- <div className="space-y-1.5 sm:col-span-2">
798
- <Label htmlFor="monitor-emails">{t.fieldEmailRecipients}</Label>
799
- <Textarea
800
- id="monitor-emails"
801
- className="min-h-[64px]"
802
- placeholder={t.fieldEmailRecipientsPlaceholder}
803
- {...register("emailRecipients")}
804
- />
805
908
  {errors.channels ? (
806
909
  <p className="text-xs text-destructive">
807
910
  {errors.channels.message}
@@ -1024,3 +1127,19 @@ function channelLabel(
1024
1127
  return t.channelWebhook;
1025
1128
  }
1026
1129
  }
1130
+
1131
+ function channelHint(
1132
+ channel: (typeof KNOWN_CHANNELS)[number],
1133
+ t: ReturnType<typeof useUptimeT>,
1134
+ ): string {
1135
+ switch (channel) {
1136
+ case "inbox":
1137
+ return t.channelInboxHint;
1138
+ case "email":
1139
+ return t.channelEmailHint;
1140
+ case "slack":
1141
+ return t.channelSlackHint;
1142
+ case "webhook":
1143
+ return t.channelWebhookHint;
1144
+ }
1145
+ }