@agent-native/core 0.106.0 → 0.106.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 (104) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +12 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/a2a/artifact-response.ts +84 -0
  5. package/corpus/core/src/agent/thread-data-builder.ts +1 -1
  6. package/corpus/core/src/application-state/index.ts +2 -0
  7. package/corpus/core/src/application-state/script-helpers.ts +12 -0
  8. package/corpus/core/src/application-state/store.ts +38 -0
  9. package/corpus/core/src/client/review/ReviewCommentComposer.tsx +10 -3
  10. package/corpus/core/src/integrations/webhook-handler.ts +41 -5
  11. package/corpus/core/src/server/agent-chat-plugin.ts +7 -2
  12. package/corpus/templates/clips/app/components/capture-install-options.tsx +60 -5
  13. package/corpus/templates/clips/app/components/library/library-layout.tsx +9 -1
  14. package/corpus/templates/clips/app/hooks/use-desktop-promo.ts +4 -4
  15. package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -0
  16. package/corpus/templates/clips/app/i18n/de-DE.ts +1 -0
  17. package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
  18. package/corpus/templates/clips/app/i18n/es-ES.ts +1 -0
  19. package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -0
  20. package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -0
  21. package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -0
  22. package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -0
  23. package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -0
  24. package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -0
  25. package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -0
  26. package/corpus/templates/clips/app/lib/capture-install-options.ts +89 -4
  27. package/corpus/templates/clips/app/routes/_app.dictate.tsx +10 -1
  28. package/corpus/templates/clips/app/routes/record.tsx +1 -0
  29. package/corpus/templates/clips/app/routes/share.$shareId.tsx +7 -0
  30. package/corpus/templates/clips/changelog/2026-07-16-the-desktop-app-button-now-shows-open-desktop-app-and-launch.md +6 -0
  31. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +5 -0
  32. package/corpus/templates/clips/desktop/src-tauri/capabilities/default.json +1 -0
  33. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +41 -6
  34. package/corpus/templates/clips/desktop/src-tauri/tauri.conf.json +5 -0
  35. package/corpus/templates/content/.agents/skills/content/SKILL.md +31 -0
  36. package/corpus/templates/content/changelog/2026-07-16-slack-corrections-now-preserve-newer-content-values-unless-y.md +6 -0
  37. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +22 -0
  38. package/corpus/templates/design/AGENTS.md +16 -0
  39. package/corpus/templates/design/actions/cancel-node-rewrite-request.ts +54 -0
  40. package/corpus/templates/design/actions/propose-node-rewrite.ts +290 -0
  41. package/corpus/templates/design/actions/resolve-node-rewrite.ts +257 -0
  42. package/corpus/templates/design/actions/view-screen.ts +98 -2
  43. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +71 -1
  44. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +19 -0
  45. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +33 -0
  46. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +194 -0
  47. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +21 -4
  48. package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +18 -0
  49. package/corpus/templates/design/app/components/design/multi-screen/types.ts +4 -0
  50. package/corpus/templates/design/app/components/visual-editor/NodeRewriteProposal.tsx +638 -0
  51. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +535 -54
  52. package/corpus/templates/design/app/components/visual-editor/index.ts +5 -1
  53. package/corpus/templates/design/app/components/visual-editor/review-canvas-state.ts +21 -3
  54. package/corpus/templates/design/app/i18n/ar-SA.ts +35 -0
  55. package/corpus/templates/design/app/i18n/de-DE.ts +35 -0
  56. package/corpus/templates/design/app/i18n/es-ES.ts +35 -0
  57. package/corpus/templates/design/app/i18n/fr-FR.ts +35 -0
  58. package/corpus/templates/design/app/i18n/hi-IN.ts +35 -0
  59. package/corpus/templates/design/app/i18n/ja-JP.ts +35 -0
  60. package/corpus/templates/design/app/i18n/ko-KR.ts +35 -0
  61. package/corpus/templates/design/app/i18n/pt-BR.ts +35 -0
  62. package/corpus/templates/design/app/i18n/zh-CN.ts +35 -0
  63. package/corpus/templates/design/app/i18n/zh-TW.ts +32 -0
  64. package/corpus/templates/design/app/i18n-data.ts +32 -0
  65. package/corpus/templates/design/app/lib/node-reprompt.ts +110 -0
  66. package/corpus/templates/design/changelog/2026-07-16-design-editing-and-review-are-now-more-predictable.md +6 -0
  67. package/corpus/templates/design/server/lib/reprompt-action-guard.ts +107 -0
  68. package/corpus/templates/design/server/plugins/agent-chat.ts +9 -1
  69. package/corpus/templates/design/shared/code-layer.ts +15 -0
  70. package/corpus/templates/design/shared/node-rewrite.ts +213 -0
  71. package/corpus/templates/design/shared/review-anchor.ts +26 -3
  72. package/dist/a2a/artifact-response.d.ts +9 -0
  73. package/dist/a2a/artifact-response.d.ts.map +1 -1
  74. package/dist/a2a/artifact-response.js +68 -0
  75. package/dist/a2a/artifact-response.js.map +1 -1
  76. package/dist/agent/thread-data-builder.js +1 -1
  77. package/dist/agent/thread-data-builder.js.map +1 -1
  78. package/dist/application-state/index.d.ts +2 -2
  79. package/dist/application-state/index.d.ts.map +1 -1
  80. package/dist/application-state/index.js +2 -2
  81. package/dist/application-state/index.js.map +1 -1
  82. package/dist/application-state/script-helpers.d.ts +1 -0
  83. package/dist/application-state/script-helpers.d.ts.map +1 -1
  84. package/dist/application-state/script-helpers.js +7 -1
  85. package/dist/application-state/script-helpers.js.map +1 -1
  86. package/dist/application-state/store.d.ts +1 -0
  87. package/dist/application-state/store.d.ts.map +1 -1
  88. package/dist/application-state/store.js +28 -0
  89. package/dist/application-state/store.js.map +1 -1
  90. package/dist/client/review/ReviewCommentComposer.d.ts +4 -1
  91. package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -1
  92. package/dist/client/review/ReviewCommentComposer.js +3 -3
  93. package/dist/client/review/ReviewCommentComposer.js.map +1 -1
  94. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  95. package/dist/integrations/webhook-handler.js +24 -5
  96. package/dist/integrations/webhook-handler.js.map +1 -1
  97. package/dist/notifications/routes.d.ts +2 -2
  98. package/dist/resources/handlers.d.ts +1 -1
  99. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  100. package/dist/server/agent-chat-plugin.js +3 -1
  101. package/dist/server/agent-chat-plugin.js.map +1 -1
  102. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  103. package/dist/server/transcribe-voice.d.ts +1 -1
  104. package/package.json +1 -1
@@ -1093,6 +1093,7 @@ Clips में उपयोगकर्ताओं को दिखने व
1093
1093
  desktopTitle: "Desktop app (स्थानीयकृत)",
1094
1094
  desktopDescription:
1095
1095
  "Most seamless for global shortcuts, menu-bar recording, meetings, and repeat captures. (स्थानीयकृत)",
1096
+ openDesktopApp: "Open desktop app (स्थानीयकृत)",
1096
1097
  },
1097
1098
  editableTitle: {
1098
1099
  untitled: "Untitled Clip (स्थानीयकृत)",
@@ -1114,6 +1114,7 @@ Clips のユーザー向けの主な変更はここに記録されます。コ
1114
1114
  desktopTitle: "Desktop app (ローカライズ済み)",
1115
1115
  desktopDescription:
1116
1116
  "Most seamless for global shortcuts, menu-bar recording, meetings, and repeat captures. (ローカライズ済み)",
1117
+ openDesktopApp: "Open desktop app (ローカライズ済み)",
1117
1118
  },
1118
1119
  editableTitle: {
1119
1120
  untitled: "Untitled Clip (ローカライズ済み)",
@@ -1103,6 +1103,7 @@ Clips의 모든 사용자 대상 변경 사항은 여기에 기록됩니다. 명
1103
1103
  desktopTitle: "Desktop app (현지화됨)",
1104
1104
  desktopDescription:
1105
1105
  "Most seamless for global shortcuts, menu-bar recording, meetings, and repeat captures. (현지화됨)",
1106
+ openDesktopApp: "Open desktop app (현지화됨)",
1106
1107
  },
1107
1108
  editableTitle: {
1108
1109
  untitled: "Untitled Clip (현지화됨)",
@@ -1121,6 +1121,7 @@ Todas as mudanças visíveis para usuários do Clips são documentadas aqui. Voc
1121
1121
  desktopTitle: "Desktop app (Localizado)",
1122
1122
  desktopDescription:
1123
1123
  "Most seamless for global shortcuts, menu-bar recording, meetings, and repeat captures. (Localizado)",
1124
+ openDesktopApp: "Open desktop app (Localizado)",
1124
1125
  },
1125
1126
  editableTitle: {
1126
1127
  untitled: "Untitled Clip (Localizado)",
@@ -1064,6 +1064,7 @@ Clips 中所有面向用户的重要更改都会记录在这里。你可以随
1064
1064
  desktopTitle: "Desktop app (已本地化)",
1065
1065
  desktopDescription:
1066
1066
  "Most seamless for global shortcuts, menu-bar recording, meetings, and repeat captures. (已本地化)",
1067
+ openDesktopApp: "Open desktop app (已本地化)",
1067
1068
  },
1068
1069
  editableTitle: {
1069
1070
  untitled: "Untitled Clip (已本地化)",
@@ -1055,6 +1055,7 @@ const messages = {
1055
1055
  "瀏覽器記錄選項已就緒,正在等待 Chrome Web Store URL。",
1056
1056
  desktopTitle: "桌面應用程式",
1057
1057
  desktopDescription: "最適合全域快捷鍵、選單列錄製、會議與重複擷取。",
1058
+ openDesktopApp: "開啟桌面應用程式",
1058
1059
  },
1059
1060
  editableTitle: {
1060
1061
  untitled: "未命名剪輯",
@@ -1,22 +1,107 @@
1
- const DESKTOP_DOWNLOAD_STORAGE_KEY = "clips.desktop-promo.dismissed";
1
+ import { appPath } from "@agent-native/core/client";
2
+
3
+ const DESKTOP_PROMO_DISMISSED_STORAGE_KEY = "clips.desktop-promo.dismissed";
4
+ const DESKTOP_DOWNLOADED_STORAGE_KEY = "clips.desktop-app.downloaded";
5
+
6
+ // Custom scheme the desktop build registers. A web click tries this first and
7
+ // falls back to the download page when nothing handles it.
8
+ const DESKTOP_APP_PROTOCOL_URL = "clips://open";
9
+ const DESKTOP_APP_LAUNCH_FALLBACK_MS = 800;
2
10
 
3
11
  export function hasDownloadedDesktopApp(): boolean {
4
12
  try {
13
+ if (typeof window === "undefined") return false;
14
+ const ls = window.localStorage;
15
+ // Also treat the legacy dismissed flag as "downloaded" — before the flag
16
+ // split, a single key covered both states, so existing users only have it.
5
17
  return (
6
- typeof window !== "undefined" &&
7
- window.localStorage?.getItem(DESKTOP_DOWNLOAD_STORAGE_KEY) === "1"
18
+ ls?.getItem(DESKTOP_DOWNLOADED_STORAGE_KEY) === "1" ||
19
+ ls?.getItem(DESKTOP_PROMO_DISMISSED_STORAGE_KEY) === "1"
8
20
  );
9
21
  } catch {
10
22
  return false;
11
23
  }
12
24
  }
13
25
 
26
+ const downloadedListeners = new Set<() => void>();
27
+
28
+ export function subscribeDownloaded(callback: () => void): () => void {
29
+ downloadedListeners.add(callback);
30
+ return () => {
31
+ downloadedListeners.delete(callback);
32
+ };
33
+ }
34
+
14
35
  export function markDesktopAppDownloaded(): void {
15
36
  try {
16
- window.localStorage?.setItem(DESKTOP_DOWNLOAD_STORAGE_KEY, "1");
37
+ // Downloading (or successfully launching) the app also hides the promo.
38
+ window.localStorage?.setItem(DESKTOP_DOWNLOADED_STORAGE_KEY, "1");
39
+ window.localStorage?.setItem(DESKTOP_PROMO_DISMISSED_STORAGE_KEY, "1");
17
40
  } catch {
18
41
  // Download tracking is best-effort and must not block the installer.
19
42
  }
43
+ downloadedListeners.forEach((fn) => fn());
44
+ }
45
+
46
+ export function hasDismissedDesktopPromo(): boolean {
47
+ try {
48
+ return (
49
+ typeof window !== "undefined" &&
50
+ window.localStorage?.getItem(DESKTOP_PROMO_DISMISSED_STORAGE_KEY) === "1"
51
+ );
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
56
+
57
+ export function markDesktopPromoDismissed(): void {
58
+ try {
59
+ window.localStorage?.setItem(DESKTOP_PROMO_DISMISSED_STORAGE_KEY, "1");
60
+ } catch {
61
+ // Dismiss tracking is best-effort.
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Try to launch the installed desktop app via its custom protocol, falling back
67
+ * to the download page when nothing handles the scheme. Browsers expose no way
68
+ * to query whether the protocol is registered, so we watch for the tab losing
69
+ * focus (the app taking over) within a short window; if that never happens we
70
+ * assume the app is not installed and navigate to the fallback. A successful
71
+ * launch self-heals the stored "downloaded" flag.
72
+ */
73
+ export function attemptOpenDesktopApp(fallbackHref = "/download"): void {
74
+ if (typeof window === "undefined") return;
75
+ const fallbackUrl = appPath(fallbackHref);
76
+
77
+ let launched = false;
78
+ const cleanup = () => {
79
+ window.removeEventListener("blur", onLeave);
80
+ document.removeEventListener("visibilitychange", onVisibility);
81
+ };
82
+ const onLeave = () => {
83
+ if (launched) return;
84
+ launched = true;
85
+ markDesktopAppDownloaded();
86
+ cleanup();
87
+ };
88
+ const onVisibility = () => {
89
+ if (document.hidden) onLeave();
90
+ };
91
+
92
+ window.addEventListener("blur", onLeave);
93
+ document.addEventListener("visibilitychange", onVisibility);
94
+
95
+ window.setTimeout(() => {
96
+ cleanup();
97
+ if (!launched) window.location.href = fallbackUrl;
98
+ }, DESKTOP_APP_LAUNCH_FALLBACK_MS);
99
+
100
+ try {
101
+ window.location.href = DESKTOP_APP_PROTOCOL_URL;
102
+ } catch {
103
+ // Some browsers throw on an unknown scheme; the timeout handles fallback.
104
+ }
20
105
  }
21
106
 
22
107
  function isFalsy(value: unknown): boolean {
@@ -640,7 +640,16 @@ function EmptyState({ isDesktopApp }: { isDesktopApp: boolean }) {
640
640
  {t("dictateRoute.emptyWebDescription")}
641
641
  </p>
642
642
  <div className="mt-5 flex items-center justify-center">
643
- <CaptureInstallButton size="sm" className="gap-1.5">
643
+ <CaptureInstallButton
644
+ size="sm"
645
+ className="gap-1.5"
646
+ downloadedChildren={
647
+ <>
648
+ <IconDeviceDesktop className="h-3.5 w-3.5" />
649
+ {t("captureInstall.openDesktopApp")}
650
+ </>
651
+ }
652
+ >
644
653
  <IconDownload className="h-3.5 w-3.5" />
645
654
  {t("dictateRoute.downloadDesktopApp")}
646
655
  </CaptureInstallButton>
@@ -670,6 +670,7 @@ function DesktopRecorderCallout() {
670
670
  <CaptureInstallButton
671
671
  size="sm"
672
672
  className="mt-4 w-full bg-primary text-primary-foreground hover:bg-primary/90"
673
+ downloadedChildren={t("captureInstall.openDesktopApp")}
673
674
  >
674
675
  {t("recordRoute.downloadDesktopApp")}
675
676
  </CaptureInstallButton>
@@ -11,6 +11,7 @@ import {
11
11
  import {
12
12
  IconAlertTriangle,
13
13
  IconArrowLeft,
14
+ IconDeviceDesktop,
14
15
  IconDownload,
15
16
  IconDots,
16
17
  IconExternalLink,
@@ -1220,6 +1221,12 @@ function PublicAgentEmptyState({
1220
1221
  className="w-full gap-2"
1221
1222
  align="center"
1222
1223
  onClick={() => onCtaClick("download")}
1224
+ downloadedChildren={
1225
+ <>
1226
+ <IconDeviceDesktop className="h-4 w-4" />
1227
+ {t("captureInstall.openDesktopApp")}
1228
+ </>
1229
+ }
1223
1230
  >
1224
1231
  <IconDownload className="h-4 w-4" />
1225
1232
  {downloadLabel}
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-16
4
+ ---
5
+
6
+ The desktop app button now shows "Open desktop app" and launches the installed app instead of re-downloading once you've downloaded it.
@@ -34,6 +34,11 @@ tauri = { version = "2", features = [
34
34
  tauri-plugin-shell = "2"
35
35
  tauri-plugin-global-shortcut = "2"
36
36
  tauri-plugin-single-instance = "2"
37
+ # Registers the `clips://` custom URL scheme so a web "Open desktop app"
38
+ # click can launch/focus the running tray app. macOS registration is baked
39
+ # into Info.plist at build time from `plugins.deep-link` in tauri.conf.json;
40
+ # Windows/Linux register at runtime via `register_all()`.
41
+ tauri-plugin-deep-link = "2"
37
42
  tauri-plugin-updater = "2"
38
43
  tauri-plugin-process = "2"
39
44
  tauri-plugin-autostart = "2.5.1"
@@ -28,6 +28,7 @@
28
28
  "core:window:allow-close",
29
29
  "core:window:allow-start-dragging",
30
30
  "shell:allow-open",
31
+ "deep-link:default",
31
32
  "global-shortcut:allow-register",
32
33
  "global-shortcut:allow-unregister",
33
34
  "global-shortcut:allow-is-registered",
@@ -48,6 +48,15 @@ use util::{
48
48
  // should replace with their real icon.
49
49
  pub(crate) const TRAY_PNG: &[u8] = include_bytes!("../icons/tray.png");
50
50
 
51
+ fn present_popover(app: &tauri::AppHandle) {
52
+ if let Some(window) = app.get_webview_window("popover") {
53
+ set_capture_included(&window);
54
+ configure_overlay_behavior(&window);
55
+ position_popover(app, &window);
56
+ present_interactive_window(&window);
57
+ }
58
+ }
59
+
51
60
  #[cfg_attr(mobile, tauri::mobile_entry_point)]
52
61
  pub fn run() {
53
62
  sentry_report::init();
@@ -57,12 +66,7 @@ pub fn run() {
57
66
  // Second launch just focuses the popover of the already-running
58
67
  // instance. Prevents the "two tray icons" UX where clicks fight
59
68
  // over focus and neither popover shows.
60
- if let Some(window) = app.get_webview_window("popover") {
61
- set_capture_included(&window);
62
- configure_overlay_behavior(&window);
63
- position_popover(app, &window);
64
- present_interactive_window(&window);
65
- }
69
+ present_popover(app);
66
70
  }))
67
71
  .invoke_handler(tauri::generate_handler![
68
72
  // clips commands
@@ -189,6 +193,7 @@ pub fn run() {
189
193
  logfile::open_logs,
190
194
  ])
191
195
  .plugin(tauri_plugin_shell::init())
196
+ .plugin(tauri_plugin_deep_link::init())
192
197
  .plugin(tauri_plugin_process::init())
193
198
  .plugin(tauri_plugin_fs::init())
194
199
  .plugin(
@@ -236,6 +241,36 @@ pub fn run() {
236
241
  err
237
242
  })?;
238
243
 
244
+ // clips:// deep-link handler — a web "Open desktop app" click
245
+ // launches or focuses the running tray popover (same as a second
246
+ // launch). macOS registers the scheme via Info.plist at build time;
247
+ // Windows/Linux register it at runtime below.
248
+ {
249
+ use tauri_plugin_deep_link::DeepLinkExt;
250
+ let dl_handle = app.handle().clone();
251
+ app.deep_link().on_open_url(move |event| {
252
+ let urls: Vec<String> =
253
+ event.urls().iter().map(|u| u.to_string()).collect();
254
+ dlog!("[clips-tray] deep link opened: {:?}", urls);
255
+ present_popover(&dl_handle);
256
+ let _ = dl_handle.emit("clips:deep-link", urls);
257
+ });
258
+ #[cfg(any(windows, target_os = "linux"))]
259
+ if let Err(err) = app.deep_link().register_all() {
260
+ eprintln!("[clips-tray] deep link register_all failed: {err}");
261
+ }
262
+ // On Linux/Windows a cold launch from clips:// arrives as startup
263
+ // arguments rather than an on_open_url event. Consume it here so
264
+ // the popover appears and the browser does not hit its fallback.
265
+ if let Ok(Some(urls)) = app.deep_link().get_current() {
266
+ let url_strings: Vec<String> =
267
+ urls.iter().map(|u| u.to_string()).collect();
268
+ dlog!("[clips-tray] deep link cold start: {:?}", url_strings);
269
+ present_popover(app.handle());
270
+ let _ = app.handle().emit("clips:deep-link", url_strings);
271
+ }
272
+ }
273
+
239
274
  if let Err(err) = notifications::show_meeting_notification_window(app.handle()) {
240
275
  println!("[clips-tray] show meeting notification failed: {err}");
241
276
  }
@@ -39,6 +39,11 @@
39
39
  }
40
40
  },
41
41
  "plugins": {
42
+ "deep-link": {
43
+ "desktop": {
44
+ "schemes": ["clips"]
45
+ }
46
+ },
42
47
  "shell": {
43
48
  "open": "(((mailto:\\w+)|(tel:\\w+)|(https?://\\w+)).+|(zoommtg://([A-Za-z0-9-]+\\.)*zoom\\.us/join\\?.+)|(ms-settings:[A-Za-z0-9._?=&-]+))"
44
49
  },
@@ -137,6 +137,37 @@ intent:
137
137
  explicitly asks for a new artifact. Do not blindly submit another row merely
138
138
  because a new Slack message arrived.
139
139
 
140
+ For a correction to an existing artifact, treat Slack history as identity and
141
+ intent context, not as the current record state. A title captured when the row
142
+ was created is a historical title: it may help locate the stable document ID,
143
+ but it is not authoritative after the row has been renamed in Content. Once the
144
+ stable ID is known, an external Slack or A2A correction must call
145
+ `pull-document` first to flush any open collaborative editor state; fail closed
146
+ if that flush/read cannot complete. Then read the canonical database row from
147
+ Content immediately before building the update. Treat those freshly read
148
+ values as authoritative for every field the correction does not explicitly
149
+ change.
150
+
151
+ Build corrections as sparse patches:
152
+
153
+ - Include only fields the user explicitly asks to change. "Keep," "preserve,"
154
+ "leave as is," and "unchanged" are constraints, not new values; omit those
155
+ fields from the mutation so a newer Content-side value cannot be overwritten
156
+ by stale Slack context.
157
+ - Omission and clearing are different operations. An omitted field keeps its
158
+ live Content value. Clear a field only when the user explicitly asks to
159
+ remove, unset, or clear it, and use the empty representation accepted by the
160
+ current database schema.
161
+ - Never reconstruct a full-row update from the original Slack request. Derive
162
+ the patch from the correction message and the freshly read canonical row,
163
+ while preserving the stable document ID.
164
+ - After the mutation, read the row again and verify that the requested fields
165
+ changed and the mutation did not include omitted fields. Post-write
166
+ verification is not compare-and-swap: it can reveal an unexpected result but
167
+ cannot prevent a concurrent edit between the read and a blind metadata or
168
+ property write. Report an action-provided conflict when one exists; otherwise
169
+ keep the patch minimal and do not claim the write was conflict-safe.
170
+
140
171
  Apply people fields from verified identity and intent, not from convenient
141
172
  guesswork:
142
173
 
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-16
4
+ ---
5
+
6
+ Slack corrections now preserve newer Content values unless you explicitly change or clear them.
@@ -47,6 +47,28 @@ iframe-backed screens on the infinite canvas.
47
47
  and create one screen per URL/path. Shorthand like
48
48
  `localhost:1234/onboarding/1` means `http://localhost:1234/onboarding/1`.
49
49
 
50
+ ## Select And Reprompt
51
+
52
+ When a chat message begins with `[Reprompt selection]`, the selected subtree is
53
+ a hard write boundary. The only mutation path is `propose-node-rewrite` with
54
+ the exact `repromptId`, target, and `baseVersionHash` captured in
55
+ `design-reprompt-pending:<designId>:<fileId>`. Never use `apply-visual-edit`,
56
+ `apply-source-edit`, `write-source`, `write-local-file`, `edit-design`, or any
57
+ other content-writing action for that request. Clarifying questions are allowed,
58
+ but a requested change must remain a proposal.
59
+
60
+ Produce one variant by default. Produce two or three only when the instruction
61
+ asks for options. A retry includes `priorProposalId`; keep the same target and
62
+ base version, incorporate the feedback, and call `propose-node-rewrite` again.
63
+ The UI previews the returned subtree without persisting it.
64
+
65
+ Use `resolve-node-rewrite` for the accept/reject lifecycle. Accept applies the
66
+ chosen variant as one version-checked inline/Yjs content transaction so one
67
+ undo restores the prior structure; reject clears the proposal without changing
68
+ content. For conversational resolution such as “apply the second one,” call
69
+ `view-screen`, read the active `design.reprompt.proposal`, and pass its
70
+ `proposalId` plus the zero-based `variantIndex` to `resolve-node-rewrite`.
71
+
50
72
  ## Review Quality
51
73
 
52
74
  - Treat the running app as the truth. Preserve its component language, tokens,
@@ -16,6 +16,13 @@ patterns live in `.agents/skills/`.
16
16
  - Never hardcode API keys, tokens, webhook URLs, signing secrets, private Builder/internal data, customer data, or credential-looking literals. Use secrets/OAuth/runtime configuration and obvious placeholders in examples.
17
17
  - Use the app actions for designs, files, versions, design systems, variants,
18
18
  export, and sharing. Do not write design rows directly with SQL.
19
+ - A message beginning with `[Reprompt selection]` is preview-only. Call
20
+ `propose-node-rewrite` with its exact `repromptId`, target, and base hash;
21
+ never call `edit-design`, `update-design`, `update-file`, `generate-design`,
22
+ `apply-visual-edit`, or another content writer. Only the frontend-only
23
+ `resolve-node-rewrite` action may persist an explicitly accepted proposal.
24
+ - A message beginning with `[Selection question]` is read-only. Answer about
25
+ the captured element and subtree without calling content-writing actions.
19
26
  - When a user wants an established public system as a starting point, call
20
27
  `create-design-system` with `templateId: material-3`, `carbon-white`, or
21
28
  `primer-light`. These are source-linked, versioned token snapshots with
@@ -268,6 +275,15 @@ patterns live in `.agents/skills/`.
268
275
  generation planning state created by `generate-screens` (canvas region
269
276
  assignments and per-frame instructions consumed by `generate-design` and
270
277
  `view-screen`; not rendered as canvas overlays).
278
+ - `design-reprompt-pending:<designId>:<fileId>` is the client-captured source
279
+ selection, instruction, base hash, and authoritative current request id for
280
+ a scoped regenerate request.
281
+ - `design-reprompt-proposal:<designId>:<fileId>:<repromptId>` is one
282
+ request-specific preview-only subtree proposal. Candidate payloads have a
283
+ 256 KiB aggregate serialized limit. Resolution and cancellation use atomic
284
+ compare-and-set cleanup so an older request cannot erase a newer one.
285
+ `view-screen` lists only proposals paired to the current pending request as
286
+ `pendingCandidateReviews`.
271
287
  - `show-design-questions` opens focused pre-generation questions in the main
272
288
  design canvas (`show-questions` application state).
273
289
  - `guided-questions` may contain a one-click chat choice for the current
@@ -0,0 +1,54 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import {
3
+ compareAndSetAppState,
4
+ readAppState,
5
+ } from "@agent-native/core/application-state";
6
+ import { assertAccess } from "@agent-native/core/sharing";
7
+ import { z } from "zod";
8
+
9
+ import {
10
+ designRepromptPendingStateKey,
11
+ designRepromptProposalStateKey,
12
+ isNodeRewriteProposal,
13
+ isPendingDesignReprompt,
14
+ } from "../shared/node-rewrite.js";
15
+
16
+ export default defineAction({
17
+ agentTool: false,
18
+ description:
19
+ "Cancel one pending node rewrite only when it is still the current request.",
20
+ schema: z.object({
21
+ designId: z.string().min(1),
22
+ fileId: z.string().min(1),
23
+ repromptId: z.string().min(1),
24
+ }),
25
+ run: async ({ designId, fileId, repromptId }) => {
26
+ await assertAccess("design", designId, "editor");
27
+ const pendingKey = designRepromptPendingStateKey(designId, fileId);
28
+ const pending = await readAppState(pendingKey);
29
+ if (
30
+ !isPendingDesignReprompt(pending) ||
31
+ pending.repromptId !== repromptId
32
+ ) {
33
+ return { cancelled: false, superseded: true };
34
+ }
35
+
36
+ const proposalKey = designRepromptProposalStateKey(
37
+ designId,
38
+ fileId,
39
+ repromptId,
40
+ );
41
+ const proposal = await readAppState(proposalKey);
42
+ const [pendingCancelled, proposalCancelled] = await Promise.all([
43
+ compareAndSetAppState(pendingKey, pending, null),
44
+ isNodeRewriteProposal(proposal)
45
+ ? compareAndSetAppState(proposalKey, proposal, null)
46
+ : Promise.resolve(false),
47
+ ]);
48
+ return {
49
+ cancelled: pendingCancelled,
50
+ proposalCancelled,
51
+ superseded: !pendingCancelled,
52
+ };
53
+ },
54
+ });