@agent-native/core 0.149.1 → 0.149.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 (115) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +2 -0
  3. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +150 -46
  4. package/corpus/templates/analytics/app/global.css +1 -1
  5. package/corpus/templates/analytics/app/i18n-data.ts +37 -0
  6. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +1 -1
  7. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +32 -0
  8. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +2 -0
  9. package/corpus/templates/analytics/changelog/2026-08-10-dashboard-tables-can-be-copied-and-sorted-by-multiple-column.md +6 -0
  10. package/corpus/templates/analytics/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  11. package/corpus/templates/assets/app/global.css +1 -1
  12. package/corpus/templates/assets/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  13. package/corpus/templates/brain/app/global.css +1 -1
  14. package/corpus/templates/brain/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  15. package/corpus/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  16. package/corpus/templates/clips/changelog/2026-08-10-clips-desktop-sign-in-now-starts-with-magic-links-and-google.md +6 -0
  17. package/corpus/templates/clips/chrome-extension/src/overlay.css +1 -0
  18. package/corpus/templates/clips/desktop/src/app.tsx +274 -137
  19. package/corpus/templates/clips/desktop/src/styles.css +35 -0
  20. package/corpus/templates/content/app/global.css +1 -0
  21. package/corpus/templates/crm/app/global.css +1 -1
  22. package/corpus/templates/crm/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  23. package/corpus/templates/dispatch/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  24. package/corpus/templates/factory/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  25. package/corpus/templates/forms/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  26. package/corpus/templates/plan/app/global.css +1 -0
  27. package/corpus/templates/plan/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  28. package/corpus/templates/plan/server/ui-plan-html.ts +4 -1
  29. package/corpus/templates/slides/AGENTS.md +11 -10
  30. package/corpus/templates/slides/actions/add-deck.ts +2 -0
  31. package/corpus/templates/slides/actions/add-slide.ts +15 -1
  32. package/corpus/templates/slides/actions/create-deck.ts +18 -6
  33. package/corpus/templates/slides/actions/get-deck.ts +29 -1
  34. package/corpus/templates/slides/actions/patch-deck.ts +122 -9
  35. package/corpus/templates/slides/actions/save-deck.ts +24 -1
  36. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +134 -618
  37. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +69 -60
  38. package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +1 -1
  39. package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +13 -1
  40. package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +56 -174
  41. package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +239 -116
  42. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +3 -3
  43. package/corpus/templates/slides/app/components/layout/AgentWorkIndicator.tsx +1 -1
  44. package/corpus/templates/slides/app/components/layout/Layout.tsx +34 -35
  45. package/corpus/templates/slides/app/components/layout/layout-route-policy.ts +2 -5
  46. package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +45 -40
  47. package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +63 -16
  48. package/corpus/templates/slides/app/hooks/use-generating-slide-preview.ts +25 -3
  49. package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
  50. package/corpus/templates/slides/app/lib/create-deck-generation.ts +5 -4
  51. package/corpus/templates/slides/app/lib/recent-references.ts +23 -1
  52. package/corpus/templates/slides/app/lib/slide-animation-elements.ts +41 -3
  53. package/corpus/templates/slides/app/pages/DeckEditor.tsx +4 -27
  54. package/corpus/templates/slides/app/pages/Index.tsx +240 -89
  55. package/corpus/templates/slides/changelog/2026-08-10-ai-editing-avatar-badge.md +6 -0
  56. package/corpus/templates/slides/changelog/2026-08-10-generated-decks-keep-a-clear-human-readable-title.md +6 -0
  57. package/corpus/templates/slides/changelog/2026-08-10-new-deck-creation-now-shows-progress-opens-the-editor-direct.md +6 -0
  58. package/corpus/templates/slides/changelog/2026-08-10-presentation-reveals-now-follow-valid-targets.md +6 -0
  59. package/corpus/templates/slides/changelog/2026-08-10-slides-editing-controls-are-consolidated-into-one-focused-me.md +6 -0
  60. package/corpus/templates/slides/changelog/2026-08-10-slides-now-verify-and-restyle-every-imported-slide-in-one-pa.md +6 -0
  61. package/corpus/templates/slides/changelog/2026-08-10-slides-only-suggests-a-google-connection-for-relevant-reques.md +6 -0
  62. package/corpus/templates/slides/changelog/2026-08-10-the-google-slides-import-option-is-now-labeled-slides.md +6 -0
  63. package/corpus/templates/slides/server/lib/chat-attachments.ts +1 -1
  64. package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -3
  65. package/corpus/templates/slides/shared/api.ts +2 -1
  66. package/corpus/templates/slides/shared/deck-title.ts +135 -0
  67. package/corpus/templates/tasks/app/components/tasks/TaskListRow.tsx +4 -1
  68. package/dist/agent/engine/ai-sdk-engine.js +1 -1
  69. package/dist/agent/production-agent.d.ts +21 -1
  70. package/dist/agent/production-agent.js +129 -10
  71. package/dist/agent/tool-approval-store.d.ts +18 -0
  72. package/dist/agent/tool-approval-store.js +122 -0
  73. package/dist/agent/types.d.ts +1 -1
  74. package/dist/cli/code-agent-executor.js +11 -2
  75. package/dist/client/AgentPanel.js +31 -6
  76. package/dist/client/AssistantChat.d.ts +3 -2
  77. package/dist/client/AssistantChat.js +7 -20
  78. package/dist/client/agent-chat/index.d.ts +1 -1
  79. package/dist/client/agent-chat-adapter.js +11 -0
  80. package/dist/client/agent-chat.d.ts +1 -1
  81. package/dist/client/chat/markdown-renderer.js +14 -2
  82. package/dist/client/chat/message-components.d.ts +6 -2
  83. package/dist/client/chat/message-components.js +25 -47
  84. package/dist/client/chat/tool-call-display.d.ts +3 -5
  85. package/dist/client/chat/tool-call-display.js +7 -212
  86. package/dist/client/chat-first/apps-rail.js +3 -3
  87. package/dist/client/chat-first/browser-pane.js +4 -1
  88. package/dist/client/chat-first/chat-history.d.ts +3 -1
  89. package/dist/client/chat-first/chat-history.js +2 -2
  90. package/dist/client/chat-first/surface-tabs.d.ts +1 -1
  91. package/dist/client/chat-first/surface-tabs.js +52 -41
  92. package/dist/client/chat-first/types.d.ts +3 -0
  93. package/dist/client/chat-first.d.ts +10 -0
  94. package/dist/client/chat-first.js +37 -2
  95. package/dist/client/components/ui/message-scroller.js +4 -1
  96. package/dist/client/i18n.js +3 -1
  97. package/dist/client/resources/McpConnectionSuggestion.js +9 -1
  98. package/dist/client/sse-event-processor.js +10 -0
  99. package/dist/client/use-chat-models.js +156 -80
  100. package/dist/collab/struct-routes.d.ts +1 -1
  101. package/dist/integrations/webhook-handler.js +1 -1
  102. package/dist/observability/routes.d.ts +1 -1
  103. package/dist/provider-api/actions/custom-provider-registration.d.ts +1 -1
  104. package/dist/resources/handlers.d.ts +1 -1
  105. package/dist/secrets/routes.d.ts +9 -9
  106. package/dist/server/auth.js +262 -31
  107. package/dist/server/onboarding-html.js +56 -9
  108. package/dist/shared/reasoning-effort.d.ts +8 -8
  109. package/dist/shared/reasoning-effort.js +10 -10
  110. package/dist/shared/streaming-text-smoothing.js +41 -11
  111. package/dist/styles/agent-native.css +19 -32
  112. package/dist/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  113. package/docs/content/template-chat.mdx +1 -1
  114. package/package.json +2 -2
  115. package/src/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-10
4
+ ---
5
+
6
+ Clips desktop sign-in now starts with magic links and Google, with a password fallback.
@@ -353,6 +353,7 @@ body {
353
353
  border-radius: 999px;
354
354
  background: #d4d4d8;
355
355
  animation: saving-indeterminate 1.1s linear infinite;
356
+ /* compositing-ok: indeterminate bar animates continuously while shown. */
356
357
  will-change: transform;
357
358
  }
358
359
 
@@ -1022,13 +1022,17 @@ export function App() {
1022
1022
  const [videoStorageStatus, setVideoStorageStatus] =
1023
1023
  useState<VideoStorageStatus>("checking");
1024
1024
  const [signedInAs, setSignedInAs] = useState<string | null>(null);
1025
- const [signInPending, setSignInPending] = useState(false);
1025
+ const [signInPending, setSignInPending] = useState<
1026
+ "google" | "magic-link" | null
1027
+ >(null);
1028
+ const [magicLinkEmail, setMagicLinkEmail] = useState<string | null>(null);
1026
1029
  const [signInError, setSignInError] = useState<string | null>(null);
1027
- // Ref-based lock so two fast clicks cannot both enter signInExternal()
1030
+ // Ref-based lock so two fast clicks cannot start competing desktop auth
1028
1031
  // (state updates are async; refs are synchronous).
1029
1032
  const signInInflightRef = useRef(false);
1030
1033
  // Stored so Cancel can stop the polling loop.
1031
1034
  const pollIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
1035
+ const signInVisibilityRef = useRef<(() => void) | null>(null);
1032
1036
  const isRecording = recorder !== null;
1033
1037
  // Whether the popover window is shown; driven by the visibility effect below.
1034
1038
  const [popoverVisible, setPopoverVisible] = useState(false);
@@ -2038,37 +2042,129 @@ export function App() {
2038
2042
  selectedMicLabel,
2039
2043
  });
2040
2044
 
2041
- // OAuth (Google) opens in the system browser — the popover WebView can't
2042
- // share a cookie jar with a separate Tauri WebviewWindow, and the old
2043
- // approach of opening a WebView at the server root produced a blank window.
2044
- // Instead: fetch the Google auth URL, open it externally, then poll a
2045
- // server-side exchange endpoint for the session token.
2046
- async function signInExternal() {
2047
- // Synchronous ref guard — prevents a double-click from opening two OAuth
2048
- // tabs. State updates are async so `signInPending` alone isn't sufficient.
2049
- if (signInInflightRef.current) return;
2050
- signInInflightRef.current = true;
2045
+ type DesktopAuthKind = "google" | "magic-link";
2051
2046
 
2052
- let tickInFlight = false;
2053
- let onVisibilityChange: (() => void) | null = null;
2047
+ function stopDesktopAuthPolling() {
2048
+ if (pollIntervalRef.current !== null) {
2049
+ clearInterval(pollIntervalRef.current);
2050
+ pollIntervalRef.current = null;
2051
+ }
2052
+ if (signInVisibilityRef.current) {
2053
+ document.removeEventListener(
2054
+ "visibilitychange",
2055
+ signInVisibilityRef.current,
2056
+ );
2057
+ signInVisibilityRef.current = null;
2058
+ }
2059
+ }
2054
2060
 
2055
- function stopPolling() {
2056
- if (pollIntervalRef.current !== null) {
2057
- clearInterval(pollIntervalRef.current);
2058
- pollIntervalRef.current = null;
2059
- }
2060
- if (onVisibilityChange) {
2061
- document.removeEventListener("visibilitychange", onVisibilityChange);
2062
- onVisibilityChange = null;
2061
+ function finishDesktopAuthWithError(kind: DesktopAuthKind, message: string) {
2062
+ stopDesktopAuthPolling();
2063
+ signInInflightRef.current = false;
2064
+ setSignInPending(null);
2065
+ if (kind === "magic-link") setMagicLinkEmail(null);
2066
+ setSignInError(message);
2067
+ }
2068
+
2069
+ function startDesktopAuthExchange(
2070
+ flowId: string,
2071
+ kind: DesktopAuthKind,
2072
+ verifier?: string,
2073
+ ) {
2074
+ let tickInFlight = false;
2075
+ const base = serverUrl.replace(/\/+$/, "");
2076
+ const start = Date.now();
2077
+ const TIMEOUT_MS = 5 * 60 * 1000;
2078
+ const POLL_ABORT_MS = Math.max(10_000, 1500 * 4);
2079
+ const timeoutMessage =
2080
+ kind === "magic-link"
2081
+ ? "The sign-in link timed out. Please request a new one."
2082
+ : "Google sign-in timed out. Please try again.";
2083
+ const exchangeErrorMessage =
2084
+ kind === "magic-link"
2085
+ ? "We couldn't complete sign-in with that link. Please request a new one."
2086
+ : "Google sign-in failed. Please try again.";
2087
+
2088
+ const tick = async () => {
2089
+ if (document.hidden || tickInFlight) return;
2090
+ tickInFlight = true;
2091
+ const controller = new AbortController();
2092
+ const abortTimer = setTimeout(() => controller.abort(), POLL_ABORT_MS);
2093
+ try {
2094
+ const exchangeParams = new URLSearchParams({ flow_id: flowId });
2095
+ if (verifier) exchangeParams.set("verifier", verifier);
2096
+ const xr = await fetch(
2097
+ `${base}/_agent-native/auth/desktop-exchange?${exchangeParams.toString()}`,
2098
+ { credentials: "include", signal: controller.signal },
2099
+ );
2100
+ if (!xr.ok) {
2101
+ if (Date.now() - start > TIMEOUT_MS) {
2102
+ finishDesktopAuthWithError(kind, timeoutMessage);
2103
+ }
2104
+ return;
2105
+ }
2106
+ const xd = (await xr.json()) as {
2107
+ error?: string;
2108
+ message?: string;
2109
+ token?: string;
2110
+ } | null;
2111
+ if (xd?.error) {
2112
+ finishDesktopAuthWithError(
2113
+ kind,
2114
+ typeof xd.message === "string"
2115
+ ? xd.message
2116
+ : typeof xd.error === "string"
2117
+ ? xd.error
2118
+ : exchangeErrorMessage,
2119
+ );
2120
+ return;
2121
+ }
2122
+ if (xd?.token) {
2123
+ stopDesktopAuthPolling();
2124
+ saveDesktopAuthToken(base, String(xd.token));
2125
+ // The exchange response sets the WebView cookie. The bearer token
2126
+ // above is the reliable desktop auth path and avoids putting it in a
2127
+ // follow-up URL.
2128
+ signInInflightRef.current = false;
2129
+ setSignInPending(null);
2130
+ setMagicLinkEmail(null);
2131
+ const ok = await checkAuth();
2132
+ if (!ok) {
2133
+ setSignInError(
2134
+ kind === "magic-link"
2135
+ ? "Sign-in completed, but Clips could not save the session. Please try again."
2136
+ : "Google sign-in completed, but Clips could not save the session. Please try again.",
2137
+ );
2138
+ }
2139
+ } else if (Date.now() - start > TIMEOUT_MS) {
2140
+ finishDesktopAuthWithError(kind, timeoutMessage);
2141
+ }
2142
+ } catch {
2143
+ if (Date.now() - start > TIMEOUT_MS) {
2144
+ finishDesktopAuthWithError(kind, timeoutMessage);
2145
+ }
2146
+ } finally {
2147
+ clearTimeout(abortTimer);
2148
+ tickInFlight = false;
2063
2149
  }
2064
- }
2150
+ };
2065
2151
 
2066
- function finishWithError(message: string) {
2067
- stopPolling();
2068
- signInInflightRef.current = false;
2069
- setSignInPending(false);
2070
- setSignInError(message);
2071
- }
2152
+ stopDesktopAuthPolling();
2153
+ pollIntervalRef.current = setInterval(() => void tick(), 1500);
2154
+ const onVisibilityChange = () => {
2155
+ if (!document.hidden) void tick();
2156
+ };
2157
+ signInVisibilityRef.current = onVisibilityChange;
2158
+ document.addEventListener("visibilitychange", onVisibilityChange);
2159
+ void tick();
2160
+ }
2161
+
2162
+ // Google and magic-link verification open in the system browser because
2163
+ // the Tauri WebView has its own cookie jar. Both flows return through the
2164
+ // same short-lived server-side exchange.
2165
+ async function signInExternal() {
2166
+ if (signInInflightRef.current) return;
2167
+ signInInflightRef.current = true;
2072
2168
 
2073
2169
  try {
2074
2170
  setSignInError(null);
@@ -2077,84 +2173,15 @@ export function App() {
2077
2173
  Math.random().toString(36).slice(2) + Date.now().toString(36);
2078
2174
  const base = serverUrl.replace(/\/+$/, "");
2079
2175
 
2080
- // Open directly in the system browser — the server redirects (302)
2081
- // to Google's OAuth page, avoiding any cross-origin fetch from
2082
- // the Tauri WebView.
2083
2176
  await openExternal(
2084
2177
  `${base}/_agent-native/google/auth-url?desktop=1&flow_id=${flowId}&redirect=1`,
2085
2178
  );
2086
-
2087
- setSignInPending(true);
2088
-
2089
- // Poll the exchange endpoint for the session token.
2090
- const start = Date.now();
2091
- const TIMEOUT_MS = 180_000; // 3 minutes
2092
- const POLL_ABORT_MS = Math.max(10_000, 1500 * 4);
2093
- const tick = async () => {
2094
- if (document.hidden || tickInFlight) return;
2095
- tickInFlight = true;
2096
- const controller = new AbortController();
2097
- const abortTimer = setTimeout(() => controller.abort(), POLL_ABORT_MS);
2098
- try {
2099
- const xr = await fetch(
2100
- `${base}/_agent-native/auth/desktop-exchange?flow_id=${flowId}`,
2101
- { credentials: "include", signal: controller.signal },
2102
- );
2103
- if (!xr.ok) {
2104
- if (Date.now() - start > TIMEOUT_MS) {
2105
- stopPolling();
2106
- signInInflightRef.current = false;
2107
- setSignInPending(false);
2108
- }
2109
- return;
2110
- }
2111
- const xd = await xr.json();
2112
- if (xd?.error) {
2113
- finishWithError(
2114
- typeof xd.error === "string"
2115
- ? xd.error
2116
- : "Google sign-in failed. Please try again.",
2117
- );
2118
- return;
2119
- }
2120
- if (xd?.token) {
2121
- stopPolling();
2122
- saveDesktopAuthToken(base, String(xd.token));
2123
- // Establish the session cookie when the WebView accepts it; the
2124
- // bearer token above is the reliable desktop auth path.
2125
- await fetch(
2126
- `${base}/_agent-native/auth/session?_session=${xd.token}`,
2127
- { credentials: "include", signal: controller.signal },
2128
- );
2129
- signInInflightRef.current = false;
2130
- setSignInPending(false);
2131
- const ok = await checkAuth();
2132
- if (!ok) {
2133
- setSignInError(
2134
- "Google sign-in completed, but Clips could not save the session. Please try again.",
2135
- );
2136
- }
2137
- } else if (Date.now() - start > TIMEOUT_MS) {
2138
- finishWithError("Google sign-in timed out. Please try again.");
2139
- }
2140
- } catch {
2141
- if (Date.now() - start > TIMEOUT_MS) {
2142
- finishWithError("Google sign-in timed out. Please try again.");
2143
- }
2144
- } finally {
2145
- clearTimeout(abortTimer);
2146
- tickInFlight = false;
2147
- }
2148
- };
2149
- pollIntervalRef.current = setInterval(() => void tick(), 1500);
2150
- onVisibilityChange = () => {
2151
- if (!document.hidden) void tick();
2152
- };
2153
- document.addEventListener("visibilitychange", onVisibilityChange);
2179
+ setSignInPending("google");
2180
+ startDesktopAuthExchange(flowId, "google");
2154
2181
  } catch (err) {
2155
2182
  console.error("[clips-tray] signInExternal failed:", err);
2156
2183
  signInInflightRef.current = false;
2157
- setSignInPending(false);
2184
+ setSignInPending(null);
2158
2185
  setSignInError(
2159
2186
  err instanceof Error
2160
2187
  ? err.message
@@ -2163,6 +2190,55 @@ export function App() {
2163
2190
  }
2164
2191
  }
2165
2192
 
2193
+ async function requestMagicLink(email: string) {
2194
+ if (signInInflightRef.current) return;
2195
+ signInInflightRef.current = true;
2196
+ const base = serverUrl.replace(/\/+$/, "");
2197
+ try {
2198
+ setSignInError(null);
2199
+ const res = await fetch(`${base}/_agent-native/auth/magic-link`, {
2200
+ method: "POST",
2201
+ headers: { "Content-Type": "application/json" },
2202
+ body: JSON.stringify({
2203
+ email: email.trim(),
2204
+ callbackURL: "/_agent-native/auth/magic-link/desktop-callback",
2205
+ }),
2206
+ credentials: "include",
2207
+ });
2208
+ const json = (await res.json()) as {
2209
+ error?: string;
2210
+ flowId?: string;
2211
+ verifier?: string;
2212
+ } | null;
2213
+ if (!res.ok) {
2214
+ throw new Error(
2215
+ json?.error || `Could not send sign-in link (${res.status})`,
2216
+ );
2217
+ }
2218
+ if (!json?.flowId || !json.verifier) {
2219
+ throw new Error(
2220
+ "The sign-in flow was not initialized. Please request a new link.",
2221
+ );
2222
+ }
2223
+ setMagicLinkEmail(email.trim());
2224
+ setSignInPending("magic-link");
2225
+ startDesktopAuthExchange(json.flowId, "magic-link", json.verifier);
2226
+ } catch (err) {
2227
+ signInInflightRef.current = false;
2228
+ setSignInPending(null);
2229
+ setMagicLinkEmail(null);
2230
+ throw err;
2231
+ }
2232
+ }
2233
+
2234
+ function cancelSignIn() {
2235
+ stopDesktopAuthPolling();
2236
+ signInInflightRef.current = false;
2237
+ setSignInPending(null);
2238
+ setMagicLinkEmail(null);
2239
+ setSignInError(null);
2240
+ }
2241
+
2166
2242
  // Sign out via the framework's logout endpoint. The cookie clears in the
2167
2243
  // same webview that will re-check `/auth/session`, so the popover flips
2168
2244
  // back to the inline sign-in form without a reload.
@@ -3863,8 +3939,8 @@ export function App() {
3863
3939
  // (not a separate Tauri window). This avoids Tauri 2's separate-WebKit-
3864
3940
  // data-store-per-WebviewWindow cookie-jar issue — the cookie is set in
3865
3941
  // the same webview that reads it on the next /auth/session poll.
3866
- // OAuth (Google / Apple) still needs a browser, so we offer that as a
3867
- // secondary link via signInExternal().
3942
+ // Google and magic-link verification use the system browser, while the
3943
+ // password fallback stays inline in this WebView.
3868
3944
  if (authStatus === "anon") {
3869
3945
  return (
3870
3946
  <div className="app" ref={appRef}>
@@ -3875,22 +3951,14 @@ export function App() {
3875
3951
  />
3876
3952
  <UpdateBanner />
3877
3953
  {pendingUploadBanner}
3878
- {signInPending ? (
3954
+ {signInPending === "google" ? (
3879
3955
  <div className="signin-pending">
3880
3956
  <div className="signin-pending-spinner" />
3881
3957
  <p className="signin-pending-text">Waiting for browser sign-in…</p>
3882
3958
  <button
3883
3959
  type="button"
3884
3960
  className="signin-pending-cancel"
3885
- onClick={() => {
3886
- if (pollIntervalRef.current !== null) {
3887
- clearInterval(pollIntervalRef.current);
3888
- pollIntervalRef.current = null;
3889
- }
3890
- signInInflightRef.current = false;
3891
- setSignInPending(false);
3892
- setSignInError(null);
3893
- }}
3961
+ onClick={cancelSignIn}
3894
3962
  >
3895
3963
  Cancel
3896
3964
  </button>
@@ -3907,6 +3975,11 @@ export function App() {
3907
3975
  await checkAuth();
3908
3976
  }}
3909
3977
  onUseBrowser={signInExternal}
3978
+ onMagicLink={requestMagicLink}
3979
+ magicLinkSentEmail={
3980
+ signInPending === "magic-link" ? magicLinkEmail : null
3981
+ }
3982
+ onMagicLinkBack={cancelSignIn}
3910
3983
  />
3911
3984
  </>
3912
3985
  )}
@@ -4779,19 +4852,28 @@ function SignInForm({
4779
4852
  serverUrl,
4780
4853
  onSignedIn,
4781
4854
  onUseBrowser,
4855
+ onMagicLink,
4856
+ magicLinkSentEmail,
4857
+ onMagicLinkBack,
4782
4858
  }: {
4783
4859
  serverUrl: string;
4784
4860
  onSignedIn: () => Promise<void> | void;
4785
- onUseBrowser: () => void;
4861
+ onUseBrowser: () => void | Promise<void>;
4862
+ onMagicLink: (email: string) => Promise<void>;
4863
+ magicLinkSentEmail: string | null;
4864
+ onMagicLinkBack: () => void;
4786
4865
  }) {
4787
4866
  const [email, setEmail] = useState("");
4788
4867
  const [password, setPassword] = useState("");
4868
+ const [authMode, setAuthMode] = useState<"magic-link" | "password">(
4869
+ "magic-link",
4870
+ );
4789
4871
  const [submitting, setSubmitting] = useState(false);
4790
4872
  const [error, setError] = useState<string | null>(null);
4791
4873
  const emailRef = useRef<HTMLInputElement | null>(null);
4792
4874
  useEffect(() => {
4793
- emailRef.current?.focus();
4794
- }, []);
4875
+ if (!magicLinkSentEmail) emailRef.current?.focus();
4876
+ }, [magicLinkSentEmail]);
4795
4877
 
4796
4878
  async function onSubmit(e: React.FormEvent) {
4797
4879
  e.preventDefault();
@@ -4799,6 +4881,10 @@ function SignInForm({
4799
4881
  setError(null);
4800
4882
  setSubmitting(true);
4801
4883
  try {
4884
+ if (authMode === "magic-link") {
4885
+ await onMagicLink(email.trim());
4886
+ return;
4887
+ }
4802
4888
  // Post to the framework's Better Auth-backed email/password endpoint.
4803
4889
  // Production Tauri builds cannot rely on cross-origin cookies sticking,
4804
4890
  // so the desktop fetch interceptor stores the returned session token and
@@ -4828,45 +4914,96 @@ function SignInForm({
4828
4914
  }
4829
4915
  }
4830
4916
 
4917
+ if (magicLinkSentEmail) {
4918
+ return (
4919
+ <div className="signin signin-success" aria-live="polite">
4920
+ <div className="signin-title">Check your email</div>
4921
+ <p className="signin-success-copy">
4922
+ {"We sent a secure sign-in link to "}
4923
+ <strong>{magicLinkSentEmail}</strong>
4924
+ {"."}
4925
+ </p>
4926
+ <button
4927
+ type="button"
4928
+ className="signin-alt signin-mode-link"
4929
+ onClick={onMagicLinkBack}
4930
+ >
4931
+ Back
4932
+ </button>
4933
+ </div>
4934
+ );
4935
+ }
4936
+
4831
4937
  return (
4832
4938
  <form className="signin" onSubmit={onSubmit}>
4833
- <div className="signin-title">Sign in to Clips</div>
4939
+ <div className="signin-title">Welcome</div>
4940
+ <div className="signin-subtitle">Create an account or sign in</div>
4941
+ <button
4942
+ type="button"
4943
+ className="signin-google"
4944
+ onClick={onUseBrowser}
4945
+ title="Opens your default browser to complete Google sign-in"
4946
+ >
4947
+ <GoogleIcon />
4948
+ Sign in with Google
4949
+ </button>
4950
+ <div className="signin-divider">
4951
+ <span>or</span>
4952
+ </div>
4834
4953
  <input
4835
4954
  ref={emailRef}
4836
4955
  type="email"
4837
4956
  autoComplete="email"
4838
- placeholder="Email"
4957
+ placeholder="you@example.com"
4839
4958
  value={email}
4840
- onChange={(e) => setEmail(e.target.value)}
4841
- required
4842
- />
4843
- <input
4844
- type="password"
4845
- autoComplete="current-password"
4846
- placeholder="Password"
4847
- value={password}
4848
- onChange={(e) => setPassword(e.target.value)}
4959
+ onChange={(e) => {
4960
+ setEmail(e.target.value);
4961
+ setError(null);
4962
+ }}
4849
4963
  required
4850
4964
  />
4965
+ {authMode === "password" ? (
4966
+ <input
4967
+ type="password"
4968
+ autoComplete="current-password"
4969
+ placeholder="Password"
4970
+ value={password}
4971
+ onChange={(e) => {
4972
+ setPassword(e.target.value);
4973
+ setError(null);
4974
+ }}
4975
+ required
4976
+ />
4977
+ ) : null}
4851
4978
  {error ? <div className="error-banner">{error}</div> : null}
4852
4979
  <button
4853
4980
  type="submit"
4854
4981
  className="primary start"
4855
- disabled={submitting || !email || !password}
4982
+ disabled={
4983
+ submitting || !email || (authMode === "password" && !password)
4984
+ }
4856
4985
  >
4857
- {submitting ? "Signing in…" : "Sign in"}
4986
+ {submitting
4987
+ ? authMode === "magic-link"
4988
+ ? "Sending…"
4989
+ : "Signing in…"
4990
+ : authMode === "magic-link"
4991
+ ? "Continue"
4992
+ : "Sign in"}
4858
4993
  </button>
4859
- <div className="signin-divider">
4860
- <span>or</span>
4861
- </div>
4862
4994
  <button
4863
4995
  type="button"
4864
- className="signin-google"
4865
- onClick={onUseBrowser}
4866
- title="Opens your default browser to complete Google sign-in"
4996
+ className="signin-alt signin-mode-link"
4997
+ onClick={() => {
4998
+ setError(null);
4999
+ setAuthMode((current) =>
5000
+ current === "magic-link" ? "password" : "magic-link",
5001
+ );
5002
+ }}
4867
5003
  >
4868
- <GoogleIcon />
4869
- Continue with Google
5004
+ {authMode === "magic-link"
5005
+ ? "Use a password instead"
5006
+ : "Use a sign-in link instead"}
4870
5007
  </button>
4871
5008
  </form>
4872
5009
  );
@@ -4259,6 +4259,7 @@ body[data-clips-route="recording-pill"] #root {
4259
4259
  object-fit: cover;
4260
4260
  border-radius: 50%;
4261
4261
  transform: scaleX(-1); /* Mirror — feels more natural for selfie view. */
4262
+ /* compositing-ok: live <video> is composited regardless. */
4262
4263
  will-change: transform, opacity;
4263
4264
  backface-visibility: hidden;
4264
4265
  /* CRITICAL: clicks MUST pass through the canvas to the
@@ -4441,6 +4442,30 @@ body[data-clips-route="recording-pill"] #root {
4441
4442
  margin-bottom: 2px;
4442
4443
  }
4443
4444
 
4445
+ .signin-subtitle {
4446
+ color: var(--fg-muted);
4447
+ font-size: 12px;
4448
+ text-align: center;
4449
+ margin-bottom: 2px;
4450
+ }
4451
+
4452
+ .signin-success {
4453
+ align-items: center;
4454
+ text-align: center;
4455
+ }
4456
+
4457
+ .signin-success-copy {
4458
+ color: var(--fg-muted);
4459
+ font-size: 12px;
4460
+ line-height: 1.45;
4461
+ margin: 0;
4462
+ }
4463
+
4464
+ .signin-success-copy strong {
4465
+ color: var(--fg);
4466
+ font-weight: 600;
4467
+ }
4468
+
4444
4469
  .signin input {
4445
4470
  width: 100%;
4446
4471
  padding: 10px 12px;
@@ -4475,6 +4500,16 @@ body[data-clips-route="recording-pill"] #root {
4475
4500
  cursor: pointer;
4476
4501
  }
4477
4502
 
4503
+ .signin-mode-link {
4504
+ color: var(--fg-muted);
4505
+ text-decoration: underline;
4506
+ text-underline-offset: 2px;
4507
+ }
4508
+
4509
+ .signin-mode-link:hover {
4510
+ color: var(--fg);
4511
+ }
4512
+
4478
4513
  .signin-divider {
4479
4514
  display: flex;
4480
4515
  align-items: center;
@@ -2164,6 +2164,7 @@ audio.media-block__content {
2164
2164
  opacity: 0.55;
2165
2165
  pointer-events: none;
2166
2166
  filter: saturate(0.45);
2167
+ /* compositing-ok: drag preview — transient, transformed every frame. */
2167
2168
  will-change: transform;
2168
2169
  }
2169
2170
 
@@ -307,7 +307,7 @@
307
307
  }
308
308
  .crm-chat-panel [data-agent-empty-state="centered"] .agent-composer-area {
309
309
  width: 100%;
310
- max-width: min(670px, 90%);
310
+ max-width: min(1000px, 100%);
311
311
  padding: 0;
312
312
  }
313
313
  .crm-chat-panel [data-agent-empty-state="centered"] .agent-composer-root {
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-10
4
+ ---
5
+
6
+ Full-page chat now uses the available width up to 1000px for more comfortable prompts and responses.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-10
4
+ ---
5
+
6
+ Full-page chat now uses the available width up to 1000px for more comfortable prompts and responses.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-10
4
+ ---
5
+
6
+ Full-page chat now uses the available width up to 1000px for more comfortable prompts and responses.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-10
4
+ ---
5
+
6
+ Full-page chat now uses the available width up to 1000px for more comfortable prompts and responses.
@@ -2345,6 +2345,7 @@
2345
2345
  box-shadow: none;
2346
2346
  opacity: 0.55;
2347
2347
  pointer-events: none;
2348
+ /* compositing-ok: drag preview — transient, transformed every frame. */
2348
2349
  will-change: transform;
2349
2350
  }
2350
2351
  .notion-drag-preview > * {
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-10
4
+ ---
5
+
6
+ Full-page chat now uses the available width up to 1000px for more comfortable prompts and responses.
@@ -766,7 +766,10 @@ button, input, textarea { font: inherit; }
766
766
  .canvas-controls button:hover { background: color-mix(in srgb, var(--ink) 8%, transparent); }
767
767
  .canvas-viewport { position: absolute; inset: 0; overflow: hidden; cursor: grab; touch-action: none; background-image: linear-gradient(var(--grid-line) 1px, transparent 1px), linear-gradient(90deg, var(--grid-line) 1px, transparent 1px); background-size: var(--grid-size) var(--grid-size); background-position: var(--grid-offset-x) var(--grid-offset-y); }
768
768
  .canvas-viewport.is-panning { cursor: grabbing; user-select: none; }
769
- .board-canvas { position: absolute; left: 0; top: 0; transform-origin: 0 0; will-change: transform; }
769
+ /* No will-change on the pan/zoom canvas: it pins a raster at the scale it was
770
+ promoted at, so zooming in stretches that texture instead of re-rasterizing
771
+ and the board stays blurry. A plain 2D transform re-rasterizes crisply. */
772
+ .board-canvas { position: absolute; left: 0; top: 0; transform-origin: 0 0; }
770
773
  .board-note, .board-frame, .board-card, .board-group-label, .flow-connector, .canvas-helper-note, .canvas-annotation, .annotation-arrow { position: absolute; }
771
774
  .board-note, .board-frame, .board-card, .canvas-helper-note, .canvas-annotation { z-index: 2; color: var(--ink); cursor: default; }
772
775
  .board-group-label { z-index: 2; display: flex; align-items: center; color: var(--ink); font: 760 18px/1 var(--doc-font); }