@agent-native/core 0.70.0 → 0.70.2

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 (172) hide show
  1. package/README.md +15 -15
  2. package/corpus/README.md +2 -2
  3. package/corpus/core/CHANGELOG.md +67 -0
  4. package/corpus/core/docs/design/durable-agent-runs.md +217 -0
  5. package/corpus/core/package.json +1 -1
  6. package/corpus/core/src/action.ts +89 -0
  7. package/corpus/core/src/agent/model-config.ts +0 -2
  8. package/corpus/core/src/agent/run-loop-with-resume.ts +48 -0
  9. package/corpus/core/src/cli/skills.ts +2 -2
  10. package/corpus/core/src/cli/sync-builder-starter-manifest.ts +275 -0
  11. package/corpus/core/src/client/AgentPanel.tsx +5 -3
  12. package/corpus/core/src/client/AssistantChat.tsx +8 -0
  13. package/corpus/core/src/client/client-surface.ts +41 -0
  14. package/corpus/core/src/client/composer/TiptapComposer.tsx +123 -12
  15. package/corpus/core/src/client/feedback-context.ts +4 -0
  16. package/corpus/core/src/client/index.ts +1 -0
  17. package/corpus/core/src/client/sse-event-processor.ts +7 -1
  18. package/corpus/core/src/mcp/build-server.ts +57 -1
  19. package/corpus/core/src/mcp/embed-app.ts +86 -2
  20. package/corpus/core/src/mcp/oauth-route.ts +109 -13
  21. package/corpus/core/src/server/core-routes-plugin.ts +78 -0
  22. package/corpus/core/src/server/onboarding-html.ts +35 -0
  23. package/corpus/core/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +72 -0
  24. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +22 -0
  25. package/corpus/templates/analytics/AGENTS.md +8 -1
  26. package/corpus/templates/analytics/actions/install-dashboard-template.ts +97 -2
  27. package/corpus/templates/analytics/actions/update-dashboard.ts +36 -7
  28. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +30 -3
  29. package/corpus/templates/analytics/app/components/layout/Header.tsx +0 -1
  30. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +2 -4
  31. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +0 -3
  32. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +32 -2
  33. package/corpus/templates/analytics/changelog/2026-06-23-long-dashboard-and-analysis-names-in-the-sidebar-now-truncat.md +6 -0
  34. package/corpus/templates/analytics/changelog/2026-06-23-open-any-dashboard-chart-full-screen-in-a-modal-from-the-pan.md +6 -0
  35. package/corpus/templates/assets/actions/generate-image.ts +24 -0
  36. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +2 -2
  37. package/corpus/templates/assets/app/global.css +17 -1
  38. package/corpus/templates/assets/app/routes/_index.tsx +94 -14
  39. package/corpus/templates/assets/changelog/2026-06-23-chat-dates-in-the-sidebar-no-longer-wrap-onto-two-lines.md +6 -0
  40. package/corpus/templates/assets/changelog/2026-06-23-clicking-image-video-or-refine-on-the-start-screen-now-drops.md +6 -0
  41. package/corpus/templates/assets/changelog/2026-06-23-pick-your-image-generation-model-right-from-the-chat-model-m.md +6 -0
  42. package/corpus/templates/clips/app/components/library/library-grid.tsx +1 -20
  43. package/corpus/templates/clips/app/components/recorder/countdown-overlay.tsx +72 -48
  44. package/corpus/templates/clips/app/lib/countdown-audio-cue.ts +35 -17
  45. package/corpus/templates/clips/changelog/2026-06-23-removed-select-button-from-library-hover-a-clip-to-select-it.md +6 -0
  46. package/corpus/templates/clips/changelog/2026-06-23-shared-video-links-now-play-for-anyone-without-signing-in.md +6 -0
  47. package/corpus/templates/clips/changelog/2026-06-23-skip-or-cancel-the-recording-countdown-with-buttons-beside-t.md +6 -0
  48. package/corpus/templates/clips/changelog/2026-06-23-the-desktop-camera-bubble-now-looks-sharp-the-instant-it-ope.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-06-23-the-desktop-recording-widget-now-grows-downward-only-when-yo.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-06-23-the-recording-start-sound-is-now-a-softer-more-modern-chime.md +6 -0
  51. package/corpus/templates/clips/chrome-extension/public/manifest.json +17 -8
  52. package/corpus/templates/clips/chrome-extension/src/background.ts +769 -180
  53. package/corpus/templates/clips/chrome-extension/src/content-script.ts +177 -0
  54. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +466 -329
  55. package/corpus/templates/clips/chrome-extension/src/overlay.css +377 -0
  56. package/corpus/templates/clips/chrome-extension/src/overlay.html +12 -0
  57. package/corpus/templates/clips/chrome-extension/src/overlay.ts +330 -0
  58. package/corpus/templates/clips/chrome-extension/src/permission.html +227 -0
  59. package/corpus/templates/clips/chrome-extension/src/permission.ts +105 -0
  60. package/corpus/templates/clips/chrome-extension/src/popup.html +3 -0
  61. package/corpus/templates/clips/chrome-extension/src/popup.ts +50 -2
  62. package/corpus/templates/clips/chrome-extension/src/styles.css +50 -0
  63. package/corpus/templates/clips/chrome-extension/vite.config.ts +3 -0
  64. package/corpus/templates/clips/desktop/src/lib/audio-cue.ts +41 -24
  65. package/corpus/templates/clips/desktop/src/lib/bubble-webrtc.ts +119 -2
  66. package/corpus/templates/clips/desktop/src/overlays/countdown.tsx +21 -2
  67. package/corpus/templates/clips/desktop/src/styles.css +70 -2
  68. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +73 -0
  69. package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +34 -5
  70. package/corpus/templates/content/AGENTS.md +20 -3
  71. package/corpus/templates/content/actions/_database-utils.ts +15 -0
  72. package/corpus/templates/content/actions/_property-utils.ts +371 -4
  73. package/corpus/templates/content/actions/configure-document-property.ts +35 -1
  74. package/corpus/templates/content/actions/create-content-database.ts +5 -1
  75. package/corpus/templates/content/actions/delete-document-property.ts +52 -2
  76. package/corpus/templates/content/actions/duplicate-document-property.ts +34 -17
  77. package/corpus/templates/content/actions/reorder-document-property.ts +79 -0
  78. package/corpus/templates/content/actions/set-document-property.ts +39 -0
  79. package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +806 -0
  80. package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +226 -68
  81. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +59 -30
  82. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +25 -1
  83. package/corpus/templates/content/app/components/editor/blockFieldSaveController.ts +180 -0
  84. package/corpus/templates/content/app/components/editor/blockFieldSaveRegistry.ts +179 -0
  85. package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +244 -0
  86. package/corpus/templates/content/app/components/editor/previewDocumentSaveRegistry.ts +132 -0
  87. package/corpus/templates/content/app/global.css +9 -0
  88. package/corpus/templates/content/app/hooks/use-document-properties.ts +21 -0
  89. package/corpus/templates/content/server/db/schema.ts +30 -0
  90. package/corpus/templates/content/server/plugins/db.ts +87 -0
  91. package/corpus/templates/content/shared/api.ts +7 -0
  92. package/corpus/templates/content/shared/properties.ts +109 -0
  93. package/corpus/templates/forms/.agents/skills/form-responses/SKILL.md +10 -0
  94. package/corpus/templates/forms/actions/export-responses.ts +6 -0
  95. package/corpus/templates/forms/actions/list-responses.ts +2 -0
  96. package/corpus/templates/forms/actions/response-insights.ts +2 -0
  97. package/corpus/templates/forms/app/pages/ResponsesPage.tsx +138 -1
  98. package/corpus/templates/forms/changelog/2026-06-23-response-tables-now-show-the-page-each-submission-came-from-.md +6 -0
  99. package/corpus/templates/forms/changelog/2026-06-23-response-tables-now-show-whether-feedback-came-from-the-web-.md +6 -0
  100. package/corpus/templates/forms/server/db/schema.ts +7 -0
  101. package/corpus/templates/forms/server/handlers/submissions.ts +19 -2
  102. package/corpus/templates/forms/server/lib/integrations.ts +76 -2
  103. package/corpus/templates/forms/server/plugins/db.ts +20 -0
  104. package/corpus/templates/forms/shared/types.ts +12 -0
  105. package/corpus/templates/plan/app/components/plan/wireframe/html-artboard.css +3 -1
  106. package/corpus/templates/plan/changelog/2026-06-23-large-diagrams-in-a-plan-now-scroll-within-their-block-inste.md +6 -0
  107. package/dist/action.js +87 -0
  108. package/dist/action.js.map +1 -1
  109. package/dist/agent/engine/builder-engine.d.ts +1 -1
  110. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  111. package/dist/agent/model-config.d.ts +2 -2
  112. package/dist/agent/model-config.d.ts.map +1 -1
  113. package/dist/agent/model-config.js +0 -2
  114. package/dist/agent/model-config.js.map +1 -1
  115. package/dist/agent/run-loop-with-resume.d.ts +13 -0
  116. package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
  117. package/dist/agent/run-loop-with-resume.js +44 -0
  118. package/dist/agent/run-loop-with-resume.js.map +1 -1
  119. package/dist/cli/skills.js +2 -2
  120. package/dist/cli/skills.js.map +1 -1
  121. package/dist/cli/sync-builder-starter-manifest.d.ts +32 -0
  122. package/dist/cli/sync-builder-starter-manifest.d.ts.map +1 -0
  123. package/dist/cli/sync-builder-starter-manifest.js +179 -0
  124. package/dist/cli/sync-builder-starter-manifest.js.map +1 -0
  125. package/dist/client/AgentPanel.d.ts.map +1 -1
  126. package/dist/client/AgentPanel.js +5 -3
  127. package/dist/client/AgentPanel.js.map +1 -1
  128. package/dist/client/AssistantChat.d.ts +6 -1
  129. package/dist/client/AssistantChat.d.ts.map +1 -1
  130. package/dist/client/AssistantChat.js +2 -2
  131. package/dist/client/AssistantChat.js.map +1 -1
  132. package/dist/client/client-surface.d.ts +17 -0
  133. package/dist/client/client-surface.d.ts.map +1 -0
  134. package/dist/client/client-surface.js +24 -0
  135. package/dist/client/client-surface.js.map +1 -0
  136. package/dist/client/composer/TiptapComposer.d.ts +26 -1
  137. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  138. package/dist/client/composer/TiptapComposer.js +27 -5
  139. package/dist/client/composer/TiptapComposer.js.map +1 -1
  140. package/dist/client/feedback-context.d.ts +3 -0
  141. package/dist/client/feedback-context.d.ts.map +1 -1
  142. package/dist/client/feedback-context.js +2 -0
  143. package/dist/client/feedback-context.js.map +1 -1
  144. package/dist/client/index.d.ts +1 -0
  145. package/dist/client/index.d.ts.map +1 -1
  146. package/dist/client/index.js +1 -0
  147. package/dist/client/index.js.map +1 -1
  148. package/dist/client/sse-event-processor.d.ts.map +1 -1
  149. package/dist/client/sse-event-processor.js +7 -1
  150. package/dist/client/sse-event-processor.js.map +1 -1
  151. package/dist/mcp/build-server.d.ts +21 -0
  152. package/dist/mcp/build-server.d.ts.map +1 -1
  153. package/dist/mcp/build-server.js +43 -1
  154. package/dist/mcp/build-server.js.map +1 -1
  155. package/dist/mcp/embed-app.d.ts.map +1 -1
  156. package/dist/mcp/embed-app.js +86 -2
  157. package/dist/mcp/embed-app.js.map +1 -1
  158. package/dist/mcp/oauth-route.d.ts.map +1 -1
  159. package/dist/mcp/oauth-route.js +95 -12
  160. package/dist/mcp/oauth-route.js.map +1 -1
  161. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  162. package/dist/server/core-routes-plugin.js +63 -0
  163. package/dist/server/core-routes-plugin.js.map +1 -1
  164. package/dist/server/onboarding-html.d.ts.map +1 -1
  165. package/dist/server/onboarding-html.js +35 -0
  166. package/dist/server/onboarding-html.js.map +1 -1
  167. package/dist/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +72 -0
  168. package/docs/design/durable-agent-runs.md +217 -0
  169. package/package.json +1 -1
  170. package/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +72 -0
  171. package/corpus/templates/analytics/app/pages/About.tsx +0 -108
  172. package/corpus/templates/analytics/app/routes/about.tsx +0 -9
@@ -87,7 +87,14 @@ details live in `.agents/skills/`.
87
87
  `id`, category, data sources, panel count, and installed dashboard IDs.
88
88
  - `install-dashboard-template` installs a catalog template into normal
89
89
  SQL-backed dashboards. Required: `templateId`. Optional: `dashboardId`,
90
- `name`, `overwrite`, and `forceNew`.
90
+ `name`, `overwrite`, `forceNew`, and `mergePanels`.
91
+ - To add a template's panels to an existing dashboard, call
92
+ `install-dashboard-template` with `mergePanels: true` and the existing
93
+ `dashboardId`. It appends only the template panels whose id is not already
94
+ present (preserving existing panels and order) in one atomic save and returns
95
+ `{ addedPanelIds, skippedExistingIds, panelCount }`. Prefer this over looping
96
+ `update-dashboard` to add many panels — sequential calls time out on the ~40s
97
+ hosted run budget.
91
98
  - Node Exporter ships as `node-exporter-macos` for Darwin/Homebrew
92
99
  `node_exporter` scrapes and `node-exporter-full` for the Linux-focused
93
100
  Grafana 1860 revision 45 full dashboard converted into native Analytics
@@ -43,7 +43,8 @@ function uniqueConstraintMessage(err: unknown): boolean {
43
43
 
44
44
  export default defineAction({
45
45
  description:
46
- "Install a dashboard template from the Analytics catalog into the user's SQL-backed dashboards. Use list-dashboard-templates first when choosing a template.",
46
+ "Install a dashboard template from the Analytics catalog into the user's SQL-backed dashboards. Use list-dashboard-templates first when choosing a template. " +
47
+ "To ADD a template's panels to an EXISTING dashboard in ONE call (the preferred way to bulk-add panels), pass `mergePanels: true` with the `dashboardId` of the existing dashboard: it appends every template panel whose id is not already present, preserves all existing panels and their order, and saves once. This avoids looping update-dashboard, which times out on the ~40s hosted run budget.",
47
48
  schema: z.object({
48
49
  templateId: z
49
50
  .string()
@@ -52,7 +53,7 @@ export default defineAction({
52
53
  .string()
53
54
  .optional()
54
55
  .describe(
55
- "Optional dashboard id to write. Omit to reuse an existing installed copy or create a unique id.",
56
+ "Optional dashboard id to write. Omit to reuse an existing installed copy or create a unique id. Required when mergePanels is true.",
56
57
  ),
57
58
  name: z
58
59
  .string()
@@ -70,6 +71,12 @@ export default defineAction({
70
71
  .describe(
71
72
  "If true, create another copy even when this template is installed.",
72
73
  ),
74
+ mergePanels: z
75
+ .boolean()
76
+ .optional()
77
+ .describe(
78
+ "If true AND a dashboard already exists at dashboardId, APPEND this template's panels (only the ones whose id is not already present) to the existing dashboard in one atomic save, preserving all existing panels and their order. Returns { addedPanelIds, skippedExistingIds, panelCount }. Non-destructive; does not change overwrite/forceNew behavior.",
79
+ ),
73
80
  }),
74
81
  mcpApp: {
75
82
  compactCatalog: true,
@@ -90,6 +97,94 @@ export default defineAction({
90
97
  if (!entry)
91
98
  throw new Error(`Unknown dashboard template: ${args.templateId}`);
92
99
 
100
+ // Append/merge mode: add this template's panels to an existing dashboard
101
+ // in ONE atomic save instead of looping update-dashboard (which times out
102
+ // on the ~40s hosted run budget). Non-destructive: existing panels and
103
+ // their order are preserved; only template panels with a new id are added.
104
+ if (args.mergePanels) {
105
+ const targetId = args.dashboardId?.trim();
106
+ if (!targetId) {
107
+ throw new Error(
108
+ "mergePanels=true requires dashboardId (the existing dashboard to append the template's panels to).",
109
+ );
110
+ }
111
+ const target = await getDashboard(targetId, ctx);
112
+ if (!target) {
113
+ throw new Error(
114
+ `Dashboard "${targetId}" not found (or you don't have access). mergePanels appends to an existing dashboard — install the template normally first, or omit mergePanels to create a new copy.`,
115
+ );
116
+ }
117
+
118
+ const targetConfig = target.config as Record<string, unknown>;
119
+ const existingPanels = Array.isArray(targetConfig.panels)
120
+ ? (targetConfig.panels as Array<Record<string, unknown>>)
121
+ : [];
122
+ const existingIds = new Set(
123
+ existingPanels
124
+ .map((panel) => (typeof panel?.id === "string" ? panel.id : null))
125
+ .filter((id): id is string => !!id),
126
+ );
127
+
128
+ const seedConfig = cloneDashboardConfig(entry);
129
+ const seedPanels = Array.isArray(seedConfig.panels)
130
+ ? (seedConfig.panels as unknown as Array<Record<string, unknown>>)
131
+ : [];
132
+
133
+ const addedPanelIds: string[] = [];
134
+ const skippedExistingIds: string[] = [];
135
+ const appended: Array<Record<string, unknown>> = [];
136
+ for (const panel of seedPanels) {
137
+ const id = typeof panel?.id === "string" ? panel.id : null;
138
+ if (id && existingIds.has(id)) {
139
+ skippedExistingIds.push(id);
140
+ continue;
141
+ }
142
+ appended.push(panel);
143
+ if (id) {
144
+ addedPanelIds.push(id);
145
+ existingIds.add(id);
146
+ }
147
+ }
148
+
149
+ const mergedConfig: Record<string, unknown> = {
150
+ ...targetConfig,
151
+ panels: [...existingPanels, ...appended],
152
+ };
153
+ const panelCount = (mergedConfig.panels as unknown[]).length;
154
+
155
+ if (appended.length > 0) {
156
+ const saved = await upsertDashboard(
157
+ targetId,
158
+ target.kind,
159
+ mergedConfig,
160
+ ctx,
161
+ );
162
+ await syncToCollab(targetId, mergedConfig);
163
+ targetConfig.name = saved.title;
164
+ }
165
+
166
+ return {
167
+ templateId: entry.id,
168
+ templateName: entry.name,
169
+ dashboardId: targetId,
170
+ name: target.title,
171
+ merged: true,
172
+ addedPanelIds,
173
+ skippedExistingIds,
174
+ panelCount,
175
+ urlPath: `/dashboards/${targetId}`,
176
+ deepLink: buildDeepLink({
177
+ app: "analytics",
178
+ view: "adhoc",
179
+ params: { dashboardId: targetId },
180
+ }),
181
+ message:
182
+ appended.length > 0
183
+ ? `Added ${addedPanelIds.length} panel(s) from "${entry.name}" to "${target.title}"; ${skippedExistingIds.length} already present. Dashboard now has ${panelCount} panel(s).`
184
+ : `No new panels to add from "${entry.name}" — all ${skippedExistingIds.length} template panel id(s) already present. Dashboard has ${panelCount} panel(s).`,
185
+ };
186
+ }
187
+
93
188
  const installed = (await listDashboardCatalog(ctx)).find(
94
189
  (template) => template.id === entry.id,
95
190
  );
@@ -378,6 +378,11 @@ function resolveScope() {
378
378
  return { orgId, email };
379
379
  }
380
380
 
381
+ /** Resulting panel count, used for the proof-of-done return summary. */
382
+ function countPanels(config: Record<string, unknown>): number {
383
+ return Array.isArray(config.panels) ? config.panels.length : 0;
384
+ }
385
+
381
386
  /**
382
387
  * Push a config update through the collab layer so open dashboard editors
383
388
  * receive the change in real time. Seeds the collab state if it doesn't
@@ -407,11 +412,19 @@ async function syncToCollab(
407
412
 
408
413
  export default defineAction({
409
414
  description:
410
- "Edit a SQL dashboard config (scope-aware). Prefer this over raw db-patch on the settings table — " +
415
+ "Edit a SQL dashboard config (scope-aware) atomically in ONE call. Prefer this over raw db-patch on the settings table — " +
411
416
  "it resolves org vs. user scope correctly so the edit lands on the row the UI actually renders. " +
412
- "Use `ops` for structural changes (reorder/insert/remove panels, update field values via JSON Pointer paths). " +
413
- "Use `config` to replace the entire dashboard config. " +
414
- "For a large dashboard, build it up incrementally: save a small dashboard first, then add panels one at a time with `ops` rather than authoring the whole `config` in one call a very large single `config` payload can get cut off mid-stream and stall the turn. " +
417
+ "BATCH ALL EDITS INTO A SINGLE CALL. Never call this action repeatedly in a loop: hosted agent runs have a ~40s budget, and many sequential update-dashboard calls time out mid-way and leave the dashboard in a partial state even though earlier calls looked like they succeeded. Put every change you want to make into one `ops` array (or one `config`). " +
418
+ "`ops` is an array of { op, path, from?, value? } applied in order in a single atomic save. " +
419
+ "`op` is one of: set | replace | remove | insert | move | move-before. " +
420
+ "`path` is a JSON Pointer into the config (e.g. `/panels/3` is the 4th panel, `/panels/3/title` is its title, `/name` is the dashboard name). The special index `-` means the end of an array: `/panels/-` appends. " +
421
+ "`value` is the panel or object to set/insert. `from` is the source JSON Pointer for move / move-before. " +
422
+ "To ADD N panels in one call, pass N entries of { op: 'insert', path: '/panels/-', value: <panel> }. " +
423
+ "To reorder: [{ op: 'move', from: '/panels/2', path: '/panels/0' }]. " +
424
+ "To edit a field: [{ op: 'replace', path: '/panels/0/title', value: 'Events by Day' }]. " +
425
+ "Use `config` to replace the entire dashboard config in one call. Provide `ops` OR `config`, not both. " +
426
+ "To add a shipped catalog template's panels to an existing dashboard, prefer `install-dashboard-template` with `mergePanels: true` — it appends the template's panels in one call without you having to author each panel. " +
427
+ "The result includes `panelCount` (resulting number of panels), `appliedOps` (count), and a `summary` string — use them as proof-of-done and report the new panel count back to the user instead of assuming success. " +
415
428
  "The UI auto-refreshes after this action — do NOT call `refresh-screen`.",
416
429
  schema: z.object({
417
430
  dashboardId: z
@@ -440,8 +453,11 @@ export default defineAction({
440
453
  )
441
454
  .optional()
442
455
  .describe(
443
- "Array of JSON-patch-style ops applied in order (or a JSON string). " +
444
- "Example reorder: [{op:'move', from:'/panels/2', path:'/panels/0'}]",
456
+ "Array of JSON-patch-style ops applied in order in ONE atomic save (or a JSON string). " +
457
+ "Each op is { op, path, from?, value? }. op ∈ set|replace|remove|insert|move|move-before. " +
458
+ "path is a JSON Pointer (e.g. '/panels/3', and '/panels/-' appends to the end). " +
459
+ "Add N panels at once with N {op:'insert', path:'/panels/-', value:<panel>} entries. " +
460
+ "Example reorder: [{op:'move', from:'/panels/2', path:'/panels/0'}].",
445
461
  ),
446
462
  config: z
447
463
  .preprocess(
@@ -482,6 +498,7 @@ export default defineAction({
482
498
  if (sqlError) throw new Error(sqlError);
483
499
  await upsertDashboard(args.dashboardId, "sql", args.config, ctx);
484
500
  await syncToCollab(args.dashboardId, args.config);
501
+ const panelCount = countPanels(args.config);
485
502
  return {
486
503
  id: args.dashboardId,
487
504
  dashboardId: args.dashboardId,
@@ -489,6 +506,11 @@ export default defineAction({
489
506
  typeof args.config.name === "string"
490
507
  ? args.config.name
491
508
  : args.dashboardId,
509
+ // Proof-of-done: lead with verifiable state so the agent/UI can
510
+ // confirm the new shape cheaply instead of assuming success.
511
+ panelCount,
512
+ appliedOps: 0,
513
+ summary: `Replaced dashboard "${args.dashboardId}"; it now has ${panelCount} panel(s).`,
492
514
  config: args.config,
493
515
  urlPath: `/dashboards/${args.dashboardId}`,
494
516
  deepLink: buildDeepLink({
@@ -528,10 +550,16 @@ export default defineAction({
528
550
  );
529
551
  await syncToCollab(args.dashboardId, root as Record<string, unknown>);
530
552
 
553
+ const panelCount = countPanels(root as Record<string, unknown>);
531
554
  return {
532
555
  id: args.dashboardId,
533
556
  dashboardId: args.dashboardId,
534
557
  name: typeof root.name === "string" ? root.name : args.dashboardId,
558
+ // Proof-of-done: lead with verifiable state (op count + resulting panel
559
+ // count) so the agent/UI can confirm success without re-fetching.
560
+ panelCount,
561
+ appliedOps: details.length,
562
+ summary: `Applied ${details.length} op(s); dashboard "${args.dashboardId}" now has ${panelCount} panel(s).`,
535
563
  config: root,
536
564
  urlPath: `/dashboards/${args.dashboardId}`,
537
565
  deepLink: buildDeepLink({
@@ -541,7 +569,8 @@ export default defineAction({
541
569
  }),
542
570
  message:
543
571
  `Dashboard "${args.dashboardId}" updated. ` +
544
- `Applied ${details.length} op(s): ${details.join("; ")}.`,
572
+ `Applied ${details.length} op(s): ${details.join("; ")}. ` +
573
+ `Now ${panelCount} panel(s).`,
545
574
  };
546
575
  },
547
576
  link: ({ result }) => {
@@ -1,5 +1,7 @@
1
1
  import {
2
+ createContext,
2
3
  useCallback,
4
+ useContext,
3
5
  useEffect,
4
6
  useMemo,
5
7
  useState,
@@ -248,6 +250,20 @@ function SeriesLegend({
248
250
  );
249
251
  }
250
252
 
253
+ // When a chart renders inside the full-screen modal it should grow to fill the
254
+ // available space rather than the fixed 250px card height. ChartFrame reads
255
+ // this via context so we avoid threading a prop through every renderer
256
+ // (line/area/bar/pie all share ChartFrame).
257
+ const ChartFillHeightContext = createContext(false);
258
+
259
+ export function ChartFillHeight({ children }: { children: ReactNode }) {
260
+ return (
261
+ <ChartFillHeightContext.Provider value={true}>
262
+ {children}
263
+ </ChartFillHeightContext.Provider>
264
+ );
265
+ }
266
+
251
267
  function ChartFrame({
252
268
  panel,
253
269
  legendKeys,
@@ -259,13 +275,24 @@ function ChartFrame({
259
275
  colors: string[];
260
276
  children: ReactNode;
261
277
  }) {
278
+ const fill = useContext(ChartFillHeightContext);
279
+ const chartHeight = fill ? "h-full min-h-[250px]" : "h-[250px]";
280
+
262
281
  if (!usesPrometheusPresentation(panel)) {
263
- return <div className="h-[250px] w-full overflow-visible">{children}</div>;
282
+ return (
283
+ <div className={`${chartHeight} w-full overflow-visible`}>{children}</div>
284
+ );
264
285
  }
265
286
 
266
287
  return (
267
- <div className="w-full overflow-hidden">
268
- <div className="h-[250px] w-full overflow-visible">{children}</div>
288
+ <div
289
+ className={`flex w-full flex-col overflow-hidden ${fill ? "h-full" : ""}`}
290
+ >
291
+ <div
292
+ className={`${chartHeight} w-full overflow-visible ${fill ? "flex-1" : ""}`}
293
+ >
294
+ {children}
295
+ </div>
269
296
  <SeriesLegend keys={legendKeys} colors={colors} panel={panel} />
270
297
  </div>
271
298
  );
@@ -18,7 +18,6 @@ const pageTitles: Record<string, string> = {
18
18
  "/dashboards/explorer": "Explorer",
19
19
  "/team": "Team",
20
20
  "/settings": "Settings",
21
- "/about": "About",
22
21
  };
23
22
 
24
23
  function resolveTitle(pathname: string): ReactNode {
@@ -16,7 +16,6 @@ import {
16
16
  IconChevronDown,
17
17
  IconSun,
18
18
  IconMoon,
19
- IconInfoCircle,
20
19
  IconTrash,
21
20
  IconDots,
22
21
  IconLoader2,
@@ -146,7 +145,6 @@ import { CSS } from "@dnd-kit/utilities";
146
145
  const bottomItems = [
147
146
  { icon: IconUsers, label: "Team", href: "/team" },
148
147
  { icon: IconSettings, label: "Settings", href: "/settings" },
149
- { icon: IconInfoCircle, label: "About", href: "/about" },
150
148
  ];
151
149
 
152
150
  function getStoredBoolean(key: string, fallback: boolean): boolean {
@@ -1947,7 +1945,7 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
1947
1945
  </Link>
1948
1946
 
1949
1947
  {/* Dashboards section */}
1950
- <div className="group/section space-y-1">
1948
+ <div className="group/section min-w-0 space-y-1">
1951
1949
  <div
1952
1950
  className={cn(
1953
1951
  "flex w-full min-w-0 items-center rounded-lg transition-all hover:text-primary",
@@ -2050,7 +2048,7 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
2050
2048
  </div>
2051
2049
 
2052
2050
  {/* Analyses section */}
2053
- <div className="group/section space-y-1">
2051
+ <div className="group/section min-w-0 space-y-1">
2054
2052
  <div
2055
2053
  className={cn(
2056
2054
  "flex w-full min-w-0 items-center rounded-lg transition-all hover:text-primary",
@@ -63,8 +63,6 @@ export function useNavigationState() {
63
63
  state.view = "catalog";
64
64
  } else if (pathname === "/settings") {
65
65
  state.view = "settings";
66
- } else if (pathname === "/about") {
67
- state.view = "about";
68
66
  }
69
67
 
70
68
  return state;
@@ -84,7 +82,6 @@ export function useNavigationState() {
84
82
  if (cmd.view === "ask") return "/ask";
85
83
  if (cmd.view === "settings") return "/settings";
86
84
  if (cmd.view === "overview") return "/overview";
87
- if (cmd.view === "about") return "/about";
88
85
  return "/";
89
86
  },
90
87
  onNavigate: (_command, path) => {
@@ -5,6 +5,7 @@ import {
5
5
  IconArrowsMaximize,
6
6
  IconArrowsMinimize,
7
7
  IconDotsVertical,
8
+ IconMaximize,
8
9
  IconPencil,
9
10
  IconTrash,
10
11
  IconCode,
@@ -32,9 +33,15 @@ import {
32
33
  AlertDialogHeader,
33
34
  AlertDialogTitle,
34
35
  } from "@/components/ui/alert-dialog";
36
+ import {
37
+ Dialog,
38
+ DialogContent,
39
+ DialogHeader,
40
+ DialogTitle,
41
+ } from "@/components/ui/dialog";
35
42
  import { useSortable } from "@dnd-kit/sortable";
36
43
  import { CSS } from "@dnd-kit/utilities";
37
- import { SqlChart } from "@/components/dashboard/SqlChart";
44
+ import { ChartFillHeight, SqlChart } from "@/components/dashboard/SqlChart";
38
45
  import { ViewSqlPopover } from "./ViewSqlPopover";
39
46
  import type { SqlPanel } from "./types";
40
47
 
@@ -76,6 +83,7 @@ export function SqlChartCard({
76
83
  } = useSortable({ id: panel.id, disabled: !editable });
77
84
 
78
85
  const [confirmOpen, setConfirmOpen] = useState(false);
86
+ const [expanded, setExpanded] = useState(false);
79
87
  const [exportCsv, setExportCsv] = useState<(() => void) | null>(null);
80
88
  const [shouldLoadData, setShouldLoadData] = useState(
81
89
  panel.chartType === "section",
@@ -229,7 +237,9 @@ export function SqlChartCard({
229
237
  );
230
238
  }
231
239
 
232
- const showPanelMenu = editable || panel.chartType === "table";
240
+ // Every non-section panel exposes at least the Full screen view action, so the
241
+ // options menu always renders — including on read-only / shared dashboards.
242
+ const showPanelMenu = true;
233
243
 
234
244
  return (
235
245
  <div
@@ -259,6 +269,13 @@ export function SqlChartCard({
259
269
  <TooltipContent>Panel options</TooltipContent>
260
270
  </Tooltip>
261
271
  <DropdownMenuContent align="end" className="w-44">
272
+ <DropdownMenuItem onSelect={() => setExpanded(true)}>
273
+ <IconMaximize className="h-4 w-4 mr-2" />
274
+ Full screen
275
+ </DropdownMenuItem>
276
+ {editable || panel.chartType === "table" ? (
277
+ <DropdownMenuSeparator />
278
+ ) : null}
262
279
  {panel.chartType === "table" && (
263
280
  <DropdownMenuItem
264
281
  disabled={!exportCsv}
@@ -346,6 +363,19 @@ export function SqlChartCard({
346
363
  </CardContent>
347
364
  </Card>
348
365
 
366
+ <Dialog open={expanded} onOpenChange={setExpanded}>
367
+ <DialogContent className="flex h-[90vh] w-[95vw] max-w-[1400px] flex-col gap-4">
368
+ <DialogHeader className="shrink-0 pr-8 text-left">
369
+ <DialogTitle className="truncate">{panel.title}</DialogTitle>
370
+ </DialogHeader>
371
+ <div className="flex min-h-0 flex-1 flex-col overflow-auto">
372
+ <ChartFillHeight>
373
+ <SqlChart panel={panel} resolvedSql={resolvedSql} loadData />
374
+ </ChartFillHeight>
375
+ </div>
376
+ </DialogContent>
377
+ </Dialog>
378
+
349
379
  {editable ? (
350
380
  <AlertDialog open={confirmOpen} onOpenChange={setConfirmOpen}>
351
381
  <AlertDialogContent>
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-23
4
+ ---
5
+
6
+ Long dashboard and analysis names in the sidebar now truncate with an ellipsis instead of being clipped
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: added
3
+ date: 2026-06-23
4
+ ---
5
+
6
+ Open any dashboard chart full screen in a modal from the panel's three-dots menu
@@ -1,5 +1,6 @@
1
1
  import { defineAction } from "@agent-native/core";
2
2
  import {
3
+ readAppState,
3
4
  writeAppState,
4
5
  deleteAppState,
5
6
  } from "@agent-native/core/application-state";
@@ -55,6 +56,28 @@ function resolveModelForTier(
55
56
  : "gemini-3.1-flash-image";
56
57
  }
57
58
 
59
+ /**
60
+ * The user's default image model, chosen from the composer's model picker and
61
+ * persisted in per-user application state. Used as a fallback when no explicit
62
+ * model, tier, or preset model is supplied. Returns undefined when unset or
63
+ * invalid so the hardcoded default still applies.
64
+ */
65
+ async function readUserDefaultImageModel(): Promise<ImageModel | undefined> {
66
+ try {
67
+ const stored = await readAppState("imageGenerationModel");
68
+ const model = stored?.model;
69
+ if (
70
+ typeof model === "string" &&
71
+ (IMAGE_MODELS as readonly string[]).includes(model)
72
+ ) {
73
+ return model as ImageModel;
74
+ }
75
+ } catch {
76
+ // No request context or read failure — fall back to defaults below.
77
+ }
78
+ return undefined;
79
+ }
80
+
58
81
  export default defineAction({
59
82
  description:
60
83
  "Generate one brand-consistent image from a library. This is synchronous for images and returns the final asset with preview/download/embed URLs. Use generate-image-batch for multiple independent slots; do not poll image runs after this action returns.",
@@ -226,6 +249,7 @@ export default defineAction({
226
249
  const resolvedModel = (args.model ??
227
250
  resolveModelForTier(resolvedTier, category) ??
228
251
  preset?.model ??
252
+ (await readUserDefaultImageModel()) ??
229
253
  "gemini-3.1-flash-image") as (typeof IMAGE_MODELS)[number];
230
254
  const resolvedCategories =
231
255
  args.categories ??
@@ -305,8 +305,8 @@ function AssetsChatsSection() {
305
305
  {threadTitle(thread)}
306
306
  </span>
307
307
  </button>
308
- <div className="relative flex size-7 shrink-0 items-center justify-end pr-1">
309
- <span className="text-[11px] text-muted-foreground/75 transition-opacity group-hover:opacity-0 group-focus-within:opacity-0">
308
+ <div className="relative flex w-auto min-w-7 shrink-0 items-center justify-end pr-1">
309
+ <span className="whitespace-nowrap text-[11px] tabular-nums text-muted-foreground/75 transition-opacity group-hover:opacity-0 group-focus-within:opacity-0">
310
310
  {isActive ? "" : formatThreadAge(threadUpdatedAt(thread))}
311
311
  </span>
312
312
  <DropdownMenu>
@@ -135,7 +135,7 @@
135
135
  gap: 0.5rem;
136
136
  }
137
137
 
138
- .assets-create-chat-pill-row span {
138
+ .assets-create-chat-pill-row button {
139
139
  display: inline-flex;
140
140
  align-items: center;
141
141
  gap: 0.375rem;
@@ -147,6 +147,22 @@
147
147
  color: hsl(var(--muted-foreground));
148
148
  font-size: 0.75rem;
149
149
  font-weight: 500;
150
+ cursor: pointer;
151
+ transition:
152
+ background-color 0.15s ease,
153
+ color 0.15s ease,
154
+ border-color 0.15s ease;
155
+ }
156
+
157
+ .assets-create-chat-pill-row button:hover {
158
+ border-color: hsl(var(--foreground) / 0.25);
159
+ background: hsl(var(--accent));
160
+ color: hsl(var(--foreground));
161
+ }
162
+
163
+ .assets-create-chat-pill-row button:focus-visible {
164
+ outline: 2px solid hsl(var(--ring));
165
+ outline-offset: 2px;
150
166
  }
151
167
 
152
168
  .assets-create-chat-panel