@agent-native/core 0.72.3 → 0.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +38 -0
- package/corpus/core/docs/content/skills-guide.md +14 -0
- package/corpus/core/docs/content/template-clips.md +7 -1
- package/corpus/core/package.json +6 -6
- package/corpus/core/src/a2a/client.ts +7 -3
- package/corpus/core/src/agent/durable-background.ts +25 -12
- package/corpus/core/src/cli/create.ts +1 -1
- package/corpus/core/src/cli/skills.ts +450 -19
- package/corpus/core/src/client/api-path.ts +28 -0
- package/corpus/core/src/client/embed-auth.ts +26 -0
- package/corpus/core/src/client/mcp-app-host.ts +6 -0
- package/corpus/core/src/client/mcp-apps/McpAppRenderer.tsx +227 -33
- package/corpus/core/src/client/resources/use-mcp-servers.ts +1 -0
- package/corpus/core/src/client/settings/DemoModeSection.tsx +3 -1
- package/corpus/core/src/demo/fetch-interceptor.ts +1 -1
- package/corpus/core/src/embedding/agent.ts +6 -1
- package/corpus/core/src/embedding/bridge.ts +34 -1
- package/corpus/core/src/mcp/build-server.ts +38 -10
- package/corpus/core/src/mcp/embed-app.ts +120 -45
- package/corpus/core/src/mcp/index.ts +5 -0
- package/corpus/core/src/mcp/org-directory.ts +70 -11
- package/corpus/core/src/mcp/server.ts +14 -0
- package/corpus/core/src/mcp-client/config.ts +12 -0
- package/corpus/core/src/mcp-client/index.ts +2 -0
- package/corpus/core/src/mcp-client/manager.ts +268 -9
- package/corpus/core/src/mcp-client/remote-store.ts +154 -0
- package/corpus/core/src/mcp-client/routes.ts +2 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +8 -0
- package/corpus/core/src/server/auth.ts +7 -0
- package/corpus/core/src/server/index.ts +1 -0
- package/corpus/core/src/server/onboarding-html.ts +95 -9
- package/corpus/core/src/shared/mcp-embed-headers.ts +44 -3
- package/corpus/core/src/templates/default/AGENTS.md +4 -0
- package/corpus/core/src/templates/default/package.json +2 -1
- package/corpus/core/src/templates/headless/AGENTS.md +4 -0
- package/corpus/core/src/templates/headless/package.json +1 -0
- package/corpus/core/src/templates/workspace-root/AGENTS.md +4 -0
- package/corpus/core/src/templates/workspace-root/README.md +4 -0
- package/corpus/core/src/templates/workspace-root/package.json +1 -0
- package/corpus/core/src/vite/client.ts +53 -0
- package/corpus/templates/analytics/actions/update-dashboard.ts +3 -4
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +144 -40
- package/corpus/templates/analytics/app/global.css +15 -13
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +9 -32
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +0 -26
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +0 -23
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/pivot.ts +65 -2
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +3 -4
- package/corpus/templates/analytics/changelog/2026-06-24-dashboard-charts-now-fill-sparse-template-time-series-suppor.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-24-dashboard-rows-now-auto-fit-panels-so-one-two-or-three-chart.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-24-dashboard-traffic-tables-now-show-top-urls-and-shared-clips-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-24-first-party-signup-and-pageview-panels-now-honor-dashboard-filters.md +5 -0
- package/corpus/templates/analytics/changelog/2026-06-24-first-party-template-dashboards-now-include-repeat-users-ret.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-24-retention-charts-now-break-out-1-day-and-7-day-return-rates-.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +256 -44
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +7 -2
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +35 -8
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +287 -42
- package/corpus/templates/assets/.agents/skills/a2a-assets/SKILL.md +17 -10
- package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +15 -6
- package/corpus/templates/assets/AGENTS.md +7 -5
- package/corpus/templates/assets/actions/generate-asset.ts +161 -0
- package/corpus/templates/assets/actions/list-assets.ts +4 -2
- package/corpus/templates/assets/actions/open-asset-picker.ts +84 -3
- package/corpus/templates/assets/app/routes/library.tsx +243 -40
- package/corpus/templates/calendar/changelog/2026-06-23-added-a-heads-up-explaining-google-s-app-not-verified-screen.md +1 -1
- package/corpus/templates/calendar/server/plugins/auth.ts +12 -0
- package/corpus/templates/clips/.agents/skills/recording/SKILL.md +7 -7
- package/corpus/templates/clips/AGENTS.md +6 -2
- package/corpus/templates/clips/app/components/capture-install-options.tsx +20 -4
- package/corpus/templates/clips/app/components/player/player-controls.tsx +62 -32
- package/corpus/templates/clips/app/lib/capture-install-options.ts +39 -5
- package/corpus/templates/clips/app/routes/download.tsx +2 -2
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +1 -1
- package/corpus/templates/clips/changelog/2026-06-24-inline-slack-playback-controls-no-longer-shift-when-adjustin.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-24-shared-clips-now-expose-their-full-console-log-stream-all-le.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-24-shared-clips-now-expose-their-full-fetch-xhr-network-request.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-24-the-chrome-extension-option-now-only-appears-on-supported-hosts.md +5 -0
- package/corpus/templates/clips/chrome-extension/STORE_DRAFT.md +13 -8
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/scripts/package.ts +15 -2
- package/corpus/templates/clips/chrome-extension/src/overlay.css +4 -25
- package/corpus/templates/clips/chrome-extension/src/overlay.ts +1 -3
- package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +9 -0
- package/corpus/templates/clips/server/lib/public-agent-context.ts +67 -24
- package/corpus/templates/design/AGENTS.md +10 -5
- package/corpus/templates/design/actions/connect-assets-mcp.ts +131 -0
- package/corpus/templates/design/actions/insert-asset.ts +219 -0
- package/corpus/templates/design/actions/view-screen.ts +3 -1
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +1 -1
- package/corpus/templates/design/app/pages/DesignEditor.tsx +53 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +5 -1
- package/corpus/templates/design/server/plugins/assets-mcp.ts +60 -0
- package/corpus/templates/mail/server/plugins/auth.ts +4 -1
- package/corpus/templates/plan/app/pages/PlansPage.tsx +271 -197
- package/corpus/templates/plan/server/plan-content.ts +45 -2
- package/dist/a2a/client.d.ts +1 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +5 -3
- package/dist/a2a/client.js.map +1 -1
- package/dist/agent/durable-background.d.ts +9 -4
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +24 -11
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/cli/create.js +1 -1
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +361 -19
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/api-path.d.ts.map +1 -1
- package/dist/client/api-path.js +27 -0
- package/dist/client/api-path.js.map +1 -1
- package/dist/client/embed-auth.d.ts.map +1 -1
- package/dist/client/embed-auth.js +27 -0
- package/dist/client/embed-auth.js.map +1 -1
- package/dist/client/mcp-app-host.d.ts.map +1 -1
- package/dist/client/mcp-app-host.js +5 -0
- package/dist/client/mcp-app-host.js.map +1 -1
- package/dist/client/mcp-apps/McpAppRenderer.d.ts +1 -0
- package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -1
- package/dist/client/mcp-apps/McpAppRenderer.js +179 -33
- package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/client/resources/use-mcp-servers.d.ts +1 -0
- package/dist/client/resources/use-mcp-servers.d.ts.map +1 -1
- package/dist/client/resources/use-mcp-servers.js.map +1 -1
- package/dist/client/settings/DemoModeSection.d.ts.map +1 -1
- package/dist/client/settings/DemoModeSection.js +3 -1
- package/dist/client/settings/DemoModeSection.js.map +1 -1
- package/dist/demo/fetch-interceptor.js +1 -1
- package/dist/demo/fetch-interceptor.js.map +1 -1
- package/dist/embedding/agent.d.ts +1 -1
- package/dist/embedding/agent.d.ts.map +1 -1
- package/dist/embedding/agent.js +1 -1
- package/dist/embedding/agent.js.map +1 -1
- package/dist/embedding/bridge.d.ts.map +1 -1
- package/dist/embedding/bridge.js +27 -1
- package/dist/embedding/bridge.js.map +1 -1
- package/dist/extensions/schema.d.ts +2 -2
- package/dist/mcp/build-server.d.ts +2 -0
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +32 -9
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/embed-app.d.ts.map +1 -1
- package/dist/mcp/embed-app.js +120 -45
- package/dist/mcp/embed-app.js.map +1 -1
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/org-directory.d.ts +1 -0
- package/dist/mcp/org-directory.d.ts.map +1 -1
- package/dist/mcp/org-directory.js +59 -11
- package/dist/mcp/org-directory.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +9 -0
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp-client/config.d.ts +12 -0
- package/dist/mcp-client/config.d.ts.map +1 -1
- package/dist/mcp-client/config.js.map +1 -1
- package/dist/mcp-client/index.d.ts +1 -1
- package/dist/mcp-client/index.d.ts.map +1 -1
- package/dist/mcp-client/index.js +1 -1
- package/dist/mcp-client/index.js.map +1 -1
- package/dist/mcp-client/manager.d.ts +2 -0
- package/dist/mcp-client/manager.d.ts.map +1 -1
- package/dist/mcp-client/manager.js +201 -9
- package/dist/mcp-client/manager.js.map +1 -1
- package/dist/mcp-client/remote-store.d.ts +27 -0
- package/dist/mcp-client/remote-store.d.ts.map +1 -1
- package/dist/mcp-client/remote-store.js +105 -0
- package/dist/mcp-client/remote-store.js.map +1 -1
- package/dist/mcp-client/routes.d.ts +1 -0
- package/dist/mcp-client/routes.d.ts.map +1 -1
- package/dist/mcp-client/routes.js +1 -0
- package/dist/mcp-client/routes.js.map +1 -1
- package/dist/provider-api/actions/github-repo-files.d.ts +2 -2
- package/dist/provider-api/actions/query-staged-dataset.d.ts +3 -3
- package/dist/provider-api/corpus-jobs.d.ts +8 -8
- package/dist/server/agent-chat-plugin.d.ts +2 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +8 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/auth.d.ts +7 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +1 -0
- package/dist/server/auth.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/onboarding-html.d.ts +15 -0
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +73 -9
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/shared/mcp-embed-headers.d.ts +3 -1
- package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
- package/dist/shared/mcp-embed-headers.js +40 -3
- package/dist/shared/mcp-embed-headers.js.map +1 -1
- package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
- package/dist/sharing/actions/set-resource-visibility.d.ts +2 -2
- package/dist/sharing/actions/share-resource.d.ts +1 -1
- package/dist/sharing/schema.d.ts +1 -1
- package/dist/templates/default/AGENTS.md +4 -0
- package/dist/templates/default/package.json +2 -1
- package/dist/templates/headless/AGENTS.md +4 -0
- package/dist/templates/headless/package.json +1 -0
- package/dist/templates/workspace-root/AGENTS.md +4 -0
- package/dist/templates/workspace-root/README.md +4 -0
- package/dist/templates/workspace-root/package.json +1 -0
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +44 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/skills-guide.md +14 -0
- package/docs/content/template-clips.md +7 -1
- package/package.json +6 -6
- package/src/templates/default/AGENTS.md +4 -0
- package/src/templates/default/package.json +2 -1
- package/src/templates/headless/AGENTS.md +4 -0
- package/src/templates/headless/package.json +1 -0
- package/src/templates/workspace-root/AGENTS.md +4 -0
- package/src/templates/workspace-root/README.md +4 -0
- package/src/templates/workspace-root/package.json +1 -0
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
appPath,
|
|
10
10
|
getBrowserTabId,
|
|
11
11
|
getEmbedAuthToken,
|
|
12
|
+
isEmbedMcpChatBridgeActive,
|
|
12
13
|
isEmbedAuthActive,
|
|
13
14
|
sendMcpAppHostMessage,
|
|
14
15
|
updateMcpAppModelContext,
|
|
@@ -53,6 +54,7 @@ import {
|
|
|
53
54
|
DialogTitle,
|
|
54
55
|
} from "@/components/ui/dialog";
|
|
55
56
|
import { DEFAULT_LIBRARY_PRESETS } from "../../shared/library-presets";
|
|
57
|
+
import type { ImageQualityTier, StyleStrength } from "../../shared/api";
|
|
56
58
|
|
|
57
59
|
type AssetTab = "all" | "generated" | "references";
|
|
58
60
|
|
|
@@ -127,12 +129,44 @@ type HostConfig = {
|
|
|
127
129
|
prompt?: string;
|
|
128
130
|
query?: string;
|
|
129
131
|
libraryId?: string;
|
|
132
|
+
libraryHint?: string;
|
|
130
133
|
aspectRatio?: string;
|
|
131
134
|
presetId?: string;
|
|
132
135
|
count?: number;
|
|
136
|
+
tier?: ImageQualityTier;
|
|
137
|
+
styleStrength?: StyleStrength;
|
|
138
|
+
includeLogo?: boolean;
|
|
139
|
+
callerAppId?: string;
|
|
133
140
|
autoGenerate?: boolean;
|
|
141
|
+
candidateRunIds?: string[];
|
|
134
142
|
};
|
|
135
143
|
|
|
144
|
+
// Preselect the library whose title/description best matches a free-text brand
|
|
145
|
+
// or use-case hint. Falls back to no match (caller uses the first library).
|
|
146
|
+
function matchLibraryByHint(
|
|
147
|
+
libraries: Library[],
|
|
148
|
+
hint: string | undefined,
|
|
149
|
+
): string | undefined {
|
|
150
|
+
const needle = hint?.trim().toLowerCase();
|
|
151
|
+
if (!needle) return undefined;
|
|
152
|
+
const terms = needle.split(/\s+/).filter(Boolean);
|
|
153
|
+
let best: { id: string; score: number } | null = null;
|
|
154
|
+
for (const library of libraries) {
|
|
155
|
+
const haystack =
|
|
156
|
+
`${library.title ?? ""} ${library.description ?? ""}`.toLowerCase();
|
|
157
|
+
if (!haystack.trim()) continue;
|
|
158
|
+
let score = 0;
|
|
159
|
+
if (haystack.includes(needle)) score += 10;
|
|
160
|
+
for (const term of terms) {
|
|
161
|
+
if (haystack.includes(term)) score += 1;
|
|
162
|
+
}
|
|
163
|
+
if (score > 0 && (!best || score > best.score)) {
|
|
164
|
+
best = { id: library.id, score };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return best?.id;
|
|
168
|
+
}
|
|
169
|
+
|
|
136
170
|
function isEmbeddedWindow() {
|
|
137
171
|
if (typeof window === "undefined") return false;
|
|
138
172
|
try {
|
|
@@ -154,6 +188,40 @@ function normalizeMediaType(value: unknown): PickerMediaType {
|
|
|
154
188
|
return value === "video" ? "video" : "image";
|
|
155
189
|
}
|
|
156
190
|
|
|
191
|
+
function normalizeBoolean(value: unknown): boolean | undefined {
|
|
192
|
+
if (typeof value === "boolean") return value;
|
|
193
|
+
if (typeof value !== "string") return undefined;
|
|
194
|
+
const normalized = value.trim().toLowerCase();
|
|
195
|
+
if (["1", "true", "yes", "on"].includes(normalized)) return true;
|
|
196
|
+
if (["", "0", "false", "no", "off"].includes(normalized)) return false;
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function normalizeTier(value: unknown): ImageQualityTier | undefined {
|
|
201
|
+
return value === "auto" || value === "fast" || value === "best"
|
|
202
|
+
? value
|
|
203
|
+
: undefined;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function normalizeStyleStrength(value: unknown): StyleStrength | undefined {
|
|
207
|
+
return value === "subtle" || value === "balanced" || value === "strong"
|
|
208
|
+
? value
|
|
209
|
+
: undefined;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function normalizeCandidateRunIds(value: unknown): string[] | undefined {
|
|
213
|
+
if (value === null || value === undefined) return undefined;
|
|
214
|
+
const raw = Array.isArray(value)
|
|
215
|
+
? value
|
|
216
|
+
: typeof value === "string"
|
|
217
|
+
? value.split(",")
|
|
218
|
+
: [];
|
|
219
|
+
const ids = raw
|
|
220
|
+
.map((item) => (typeof item === "string" ? item.trim() : ""))
|
|
221
|
+
.filter(Boolean);
|
|
222
|
+
return ids;
|
|
223
|
+
}
|
|
224
|
+
|
|
157
225
|
function normalizeHostConfig(value: unknown): HostConfig {
|
|
158
226
|
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
159
227
|
const record = value as Record<string, unknown>;
|
|
@@ -166,17 +234,22 @@ function normalizeHostConfig(value: unknown): HostConfig {
|
|
|
166
234
|
query: typeof record.query === "string" ? record.query : undefined,
|
|
167
235
|
libraryId:
|
|
168
236
|
typeof record.libraryId === "string" ? record.libraryId : undefined,
|
|
237
|
+
libraryHint:
|
|
238
|
+
typeof record.libraryHint === "string" ? record.libraryHint : undefined,
|
|
169
239
|
aspectRatio:
|
|
170
240
|
typeof record.aspectRatio === "string" ? record.aspectRatio : undefined,
|
|
171
241
|
presetId: typeof record.presetId === "string" ? record.presetId : undefined,
|
|
172
242
|
count:
|
|
173
243
|
record.count === undefined ? undefined : normalizeCount(record.count),
|
|
244
|
+
tier: normalizeTier(record.tier),
|
|
245
|
+
styleStrength: normalizeStyleStrength(record.styleStrength),
|
|
246
|
+
includeLogo: normalizeBoolean(record.includeLogo),
|
|
247
|
+
callerAppId:
|
|
248
|
+
typeof record.callerAppId === "string" ? record.callerAppId : undefined,
|
|
249
|
+
candidateRunIds: normalizeCandidateRunIds(record.candidateRunIds),
|
|
174
250
|
};
|
|
175
251
|
if (Object.prototype.hasOwnProperty.call(record, "autoGenerate")) {
|
|
176
|
-
config.autoGenerate =
|
|
177
|
-
record.autoGenerate === true ||
|
|
178
|
-
record.autoGenerate === "true" ||
|
|
179
|
-
record.autoGenerate === "1";
|
|
252
|
+
config.autoGenerate = normalizeBoolean(record.autoGenerate) ?? false;
|
|
180
253
|
}
|
|
181
254
|
return config;
|
|
182
255
|
}
|
|
@@ -440,32 +513,53 @@ function blobToBase64(blob: Blob): Promise<string> {
|
|
|
440
513
|
});
|
|
441
514
|
}
|
|
442
515
|
|
|
516
|
+
const MCP_IMAGE_CONTENT_MAX_BYTES = 4 * 1024 * 1024;
|
|
517
|
+
|
|
518
|
+
function base64ByteLength(data: string) {
|
|
519
|
+
const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
|
|
520
|
+
return Math.floor((data.length * 3) / 4) - padding;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
async function fetchImageContent(url: string, fallbackMimeType: string) {
|
|
524
|
+
const credentials =
|
|
525
|
+
typeof window !== "undefined" &&
|
|
526
|
+
new URL(url, window.location.href).origin === window.location.origin
|
|
527
|
+
? "same-origin"
|
|
528
|
+
: "omit";
|
|
529
|
+
const response = await fetch(url, { credentials });
|
|
530
|
+
if (!response.ok) return null;
|
|
531
|
+
const blob = await response.blob();
|
|
532
|
+
const detectedMimeType = blob.type.startsWith("image/")
|
|
533
|
+
? blob.type
|
|
534
|
+
: fallbackMimeType;
|
|
535
|
+
return {
|
|
536
|
+
type: "image",
|
|
537
|
+
data: await blobToBase64(blob),
|
|
538
|
+
mimeType: detectedMimeType,
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
|
|
443
542
|
async function imageContentForAsset(payload: ReturnType<typeof assetPayload>) {
|
|
444
543
|
const url = payload.url ?? payload.downloadUrl ?? payload.previewUrl;
|
|
445
544
|
const mimeType = payload.mimeType?.startsWith("image/")
|
|
446
545
|
? payload.mimeType
|
|
447
546
|
: "image/png";
|
|
448
547
|
if (!url || payload.mediaType !== "image") return null;
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
type: "image",
|
|
463
|
-
data: await blobToBase64(blob),
|
|
464
|
-
mimeType: detectedMimeType,
|
|
465
|
-
};
|
|
466
|
-
} catch {
|
|
467
|
-
return null;
|
|
548
|
+
const sources = uniqueSources([url, payload.thumbnailUrl]);
|
|
549
|
+
for (const source of sources) {
|
|
550
|
+
try {
|
|
551
|
+
const content = await fetchImageContent(source, mimeType);
|
|
552
|
+
if (
|
|
553
|
+
content &&
|
|
554
|
+
base64ByteLength(content.data) <= MCP_IMAGE_CONTENT_MAX_BYTES
|
|
555
|
+
) {
|
|
556
|
+
return content;
|
|
557
|
+
}
|
|
558
|
+
} catch {
|
|
559
|
+
// Try the next smaller source.
|
|
560
|
+
}
|
|
468
561
|
}
|
|
562
|
+
return null;
|
|
469
563
|
}
|
|
470
564
|
|
|
471
565
|
function notifyMcpHost(payload: ReturnType<typeof assetPayload>) {
|
|
@@ -490,16 +584,18 @@ function notifyMcpHost(payload: ReturnType<typeof assetPayload>) {
|
|
|
490
584
|
}) || false,
|
|
491
585
|
)
|
|
492
586
|
.catch(() => false)
|
|
493
|
-
.then(() =>
|
|
494
|
-
Promise.resolve(
|
|
587
|
+
.then((contextOk) => {
|
|
588
|
+
return Promise.resolve(
|
|
495
589
|
sendMcpAppHostMessage({
|
|
496
590
|
message,
|
|
497
591
|
context: JSON.stringify(context, null, 2),
|
|
498
592
|
content: chatContent,
|
|
499
593
|
structuredContent: context,
|
|
500
594
|
}) || false,
|
|
501
|
-
)
|
|
502
|
-
|
|
595
|
+
)
|
|
596
|
+
.catch(() => false)
|
|
597
|
+
.then((chatOk) => contextOk || chatOk);
|
|
598
|
+
});
|
|
503
599
|
});
|
|
504
600
|
}
|
|
505
601
|
|
|
@@ -619,6 +715,9 @@ function AssetOverlayImage({ asset }: { asset: Asset }) {
|
|
|
619
715
|
export default function AssetPicker() {
|
|
620
716
|
const [searchParams] = useSearchParams();
|
|
621
717
|
const searchParamsKey = searchParams.toString();
|
|
718
|
+
const mcpChatBridgeActive =
|
|
719
|
+
searchParams.get("__an_mcp_chat_bridge") === "1" ||
|
|
720
|
+
isEmbedMcpChatBridgeActive();
|
|
622
721
|
const urlHostConfig = useMemo(() => {
|
|
623
722
|
const params = new URLSearchParams(searchParamsKey);
|
|
624
723
|
return {
|
|
@@ -626,12 +725,20 @@ export default function AssetPicker() {
|
|
|
626
725
|
prompt: params.get("prompt") ?? undefined,
|
|
627
726
|
query: params.get("q") ?? undefined,
|
|
628
727
|
libraryId: params.get("libraryId") ?? undefined,
|
|
728
|
+
libraryHint: params.get("libraryHint") ?? undefined,
|
|
629
729
|
aspectRatio: params.get("aspectRatio") ?? undefined,
|
|
630
730
|
presetId: params.get("presetId") ?? undefined,
|
|
631
731
|
count: normalizeCount(params.get("count")),
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
732
|
+
tier: normalizeTier(params.get("tier")),
|
|
733
|
+
styleStrength: normalizeStyleStrength(params.get("styleStrength")),
|
|
734
|
+
includeLogo: normalizeBoolean(params.get("includeLogo")),
|
|
735
|
+
callerAppId: params.get("callerAppId") ?? undefined,
|
|
736
|
+
candidateRunIds: normalizeCandidateRunIds(
|
|
737
|
+
params.getAll("candidateRunIds").length > 0
|
|
738
|
+
? params.getAll("candidateRunIds")
|
|
739
|
+
: params.get("candidateRunIds"),
|
|
740
|
+
),
|
|
741
|
+
autoGenerate: normalizeBoolean(params.get("autoGenerate")) ?? false,
|
|
635
742
|
} satisfies HostConfig;
|
|
636
743
|
}, [searchParamsKey]);
|
|
637
744
|
const bridgeRef = useRef<EmbeddedAppBridge | null>(null);
|
|
@@ -655,6 +762,9 @@ export default function AssetPicker() {
|
|
|
655
762
|
useState<Library | null>(null);
|
|
656
763
|
const autoGenerateKeyRef = useRef<string | null>(null);
|
|
657
764
|
const autoCreateLibraryRef = useRef(false);
|
|
765
|
+
const [visibleCandidateRunIds, setVisibleCandidateRunIds] = useState<
|
|
766
|
+
string[]
|
|
767
|
+
>(() => hostConfig.candidateRunIds ?? []);
|
|
658
768
|
|
|
659
769
|
useEffect(() => {
|
|
660
770
|
setHostConfig((current) => ({ ...current, ...urlHostConfig }));
|
|
@@ -665,6 +775,7 @@ export default function AssetPicker() {
|
|
|
665
775
|
setAspectRatio(urlHostConfig.aspectRatio ?? "16:9");
|
|
666
776
|
setPresetId(urlHostConfig.presetId ?? "none");
|
|
667
777
|
setCount(urlHostConfig.count ?? 3);
|
|
778
|
+
setVisibleCandidateRunIds(urlHostConfig.candidateRunIds ?? []);
|
|
668
779
|
}, [urlHostConfig]);
|
|
669
780
|
|
|
670
781
|
const librariesQuery = useActionQuery("list-libraries", {
|
|
@@ -693,7 +804,10 @@ export default function AssetPicker() {
|
|
|
693
804
|
const firstLibraryId = displayLibraries[0]?.id;
|
|
694
805
|
if (!firstLibraryId) return;
|
|
695
806
|
if (!selectedLibraryId) {
|
|
696
|
-
setSelectedLibraryId(
|
|
807
|
+
setSelectedLibraryId(
|
|
808
|
+
matchLibraryByHint(displayLibraries, hostConfig.libraryHint) ??
|
|
809
|
+
firstLibraryId,
|
|
810
|
+
);
|
|
697
811
|
return;
|
|
698
812
|
}
|
|
699
813
|
if (!libraryListReady) return;
|
|
@@ -703,7 +817,12 @@ export default function AssetPicker() {
|
|
|
703
817
|
if (!selectedLibraryExists) {
|
|
704
818
|
setSelectedLibraryId(firstLibraryId);
|
|
705
819
|
}
|
|
706
|
-
}, [
|
|
820
|
+
}, [
|
|
821
|
+
displayLibraries,
|
|
822
|
+
hostConfig.libraryHint,
|
|
823
|
+
libraryListReady,
|
|
824
|
+
selectedLibraryId,
|
|
825
|
+
]);
|
|
707
826
|
|
|
708
827
|
const { data: config } = useActionQuery(
|
|
709
828
|
"get-image-generation-config",
|
|
@@ -742,9 +861,6 @@ export default function AssetPicker() {
|
|
|
742
861
|
Boolean(selectedLibraryId) &&
|
|
743
862
|
(presetsLoading || presetsFetching || presetsPending || !selectedPreset);
|
|
744
863
|
const mediaLabel = mediaType === "video" ? "video" : "image";
|
|
745
|
-
const [visibleCandidateRunIds, setVisibleCandidateRunIds] = useState<
|
|
746
|
-
string[]
|
|
747
|
-
>([]);
|
|
748
864
|
const generateBatch = useActionMutation(
|
|
749
865
|
"generate-image-batch" as any,
|
|
750
866
|
{
|
|
@@ -876,12 +992,28 @@ export default function AssetPicker() {
|
|
|
876
992
|
[],
|
|
877
993
|
);
|
|
878
994
|
|
|
995
|
+
const postEmbeddedSelectionMessage = useCallback(
|
|
996
|
+
(
|
|
997
|
+
name: "chooseAsset" | "chooseImage",
|
|
998
|
+
payload: ReturnType<typeof assetPayload>,
|
|
999
|
+
) => {
|
|
1000
|
+
try {
|
|
1001
|
+
return bridgeRef.current?.postMessage(name, payload) ?? false;
|
|
1002
|
+
} catch {
|
|
1003
|
+
return false;
|
|
1004
|
+
}
|
|
1005
|
+
},
|
|
1006
|
+
[],
|
|
1007
|
+
);
|
|
1008
|
+
|
|
879
1009
|
const chooseAsset = (asset: Asset) => {
|
|
880
1010
|
const payload = assetPayload(asset, mediaType);
|
|
881
1011
|
if (embedded) {
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1012
|
+
if (!mcpChatBridgeActive) {
|
|
1013
|
+
postEmbeddedSelectionMessage("chooseAsset", payload);
|
|
1014
|
+
if (payload.mediaType === "image") {
|
|
1015
|
+
postEmbeddedSelectionMessage("chooseImage", payload);
|
|
1016
|
+
}
|
|
885
1017
|
}
|
|
886
1018
|
void notifyMcpHost(payload).then((ok) => {
|
|
887
1019
|
if (ok) {
|
|
@@ -912,6 +1044,9 @@ export default function AssetPicker() {
|
|
|
912
1044
|
setQuery("");
|
|
913
1045
|
},
|
|
914
1046
|
onError: (error: Error) => {
|
|
1047
|
+
// Allow the auto-create effect to retry after a transient failure;
|
|
1048
|
+
// otherwise the picker stays stuck on "Preparing..." until reload.
|
|
1049
|
+
autoCreateLibraryRef.current = false;
|
|
915
1050
|
toast.error(error.message || "Could not prepare an image library");
|
|
916
1051
|
},
|
|
917
1052
|
} as any,
|
|
@@ -931,12 +1066,20 @@ export default function AssetPicker() {
|
|
|
931
1066
|
imageSize: selectedPreset?.imageSize || "2K",
|
|
932
1067
|
dismissible: false,
|
|
933
1068
|
})),
|
|
1069
|
+
tier: hostConfig.tier,
|
|
1070
|
+
styleStrength: hostConfig.styleStrength ?? "balanced",
|
|
1071
|
+
includeLogo: hostConfig.includeLogo ?? false,
|
|
934
1072
|
source: "ui",
|
|
1073
|
+
callerAppId: hostConfig.callerAppId,
|
|
935
1074
|
} as any);
|
|
936
1075
|
}, [
|
|
937
1076
|
count,
|
|
938
1077
|
effectiveAspectRatio,
|
|
939
1078
|
generateBatch,
|
|
1079
|
+
hostConfig.callerAppId,
|
|
1080
|
+
hostConfig.includeLogo,
|
|
1081
|
+
hostConfig.styleStrength,
|
|
1082
|
+
hostConfig.tier,
|
|
940
1083
|
prompt,
|
|
941
1084
|
setVisibleCandidateRunIds,
|
|
942
1085
|
selectedLibraryId,
|
|
@@ -945,8 +1088,35 @@ export default function AssetPicker() {
|
|
|
945
1088
|
]);
|
|
946
1089
|
|
|
947
1090
|
useEffect(() => {
|
|
1091
|
+
const hostCandidateFilterMatches =
|
|
1092
|
+
hostConfig.candidateRunIds?.length &&
|
|
1093
|
+
normalizeMediaType(hostConfig.mediaType) === mediaType &&
|
|
1094
|
+
(hostConfig.libraryId ?? "") === (selectedLibraryId ?? "") &&
|
|
1095
|
+
(hostConfig.prompt ?? "") === prompt &&
|
|
1096
|
+
(hostConfig.aspectRatio ?? "16:9") === aspectRatio &&
|
|
1097
|
+
(hostConfig.presetId ?? "none") === presetId &&
|
|
1098
|
+
(hostConfig.count ?? 3) === count;
|
|
1099
|
+
if (hostCandidateFilterMatches) return;
|
|
948
1100
|
setVisibleCandidateRunIds([]);
|
|
949
|
-
}, [
|
|
1101
|
+
}, [
|
|
1102
|
+
aspectRatio,
|
|
1103
|
+
count,
|
|
1104
|
+
hostConfig.aspectRatio,
|
|
1105
|
+
hostConfig.candidateRunIds,
|
|
1106
|
+
hostConfig.count,
|
|
1107
|
+
hostConfig.callerAppId,
|
|
1108
|
+
hostConfig.includeLogo,
|
|
1109
|
+
hostConfig.libraryId,
|
|
1110
|
+
hostConfig.mediaType,
|
|
1111
|
+
hostConfig.presetId,
|
|
1112
|
+
hostConfig.prompt,
|
|
1113
|
+
hostConfig.styleStrength,
|
|
1114
|
+
hostConfig.tier,
|
|
1115
|
+
mediaType,
|
|
1116
|
+
presetId,
|
|
1117
|
+
prompt,
|
|
1118
|
+
selectedLibraryId,
|
|
1119
|
+
]);
|
|
950
1120
|
|
|
951
1121
|
useEffect(() => {
|
|
952
1122
|
const bridge = createEmbeddedAppBridge({
|
|
@@ -961,6 +1131,9 @@ export default function AssetPicker() {
|
|
|
961
1131
|
if (next.aspectRatio !== undefined) setAspectRatio(next.aspectRatio);
|
|
962
1132
|
if (next.presetId !== undefined) setPresetId(next.presetId || "none");
|
|
963
1133
|
if (next.count !== undefined) setCount(next.count);
|
|
1134
|
+
if (next.candidateRunIds !== undefined) {
|
|
1135
|
+
setVisibleCandidateRunIds(next.candidateRunIds);
|
|
1136
|
+
}
|
|
964
1137
|
},
|
|
965
1138
|
});
|
|
966
1139
|
bridgeRef.current = bridge;
|
|
@@ -1016,6 +1189,31 @@ export default function AssetPicker() {
|
|
|
1016
1189
|
usingStarterLibrary;
|
|
1017
1190
|
const preparingGenerationLibrary =
|
|
1018
1191
|
needsGenerationLibrary && createPickerLibrary.isPending;
|
|
1192
|
+
const waitingForLibraries = mediaType === "image" && !libraryListReady;
|
|
1193
|
+
const preparingAutoLibrary =
|
|
1194
|
+
mediaType === "image" &&
|
|
1195
|
+
Boolean(prompt.trim()) &&
|
|
1196
|
+
(waitingForLibraries ||
|
|
1197
|
+
needsGenerationLibrary ||
|
|
1198
|
+
preparingGenerationLibrary);
|
|
1199
|
+
const generationButtonLabel = generateBatch.isPending
|
|
1200
|
+
? "Generating..."
|
|
1201
|
+
: waitingForRequestedPreset
|
|
1202
|
+
? "Loading preset..."
|
|
1203
|
+
: setupNeeded
|
|
1204
|
+
? "Setup needed"
|
|
1205
|
+
: preparingAutoLibrary
|
|
1206
|
+
? "Preparing..."
|
|
1207
|
+
: "Generate";
|
|
1208
|
+
const generationStatus = generateBatch.isPending
|
|
1209
|
+
? "Generating candidates..."
|
|
1210
|
+
: preparingAutoLibrary
|
|
1211
|
+
? waitingForLibraries
|
|
1212
|
+
? "Checking image libraries..."
|
|
1213
|
+
: "Preparing an image library..."
|
|
1214
|
+
: waitingForRequestedPreset
|
|
1215
|
+
? "Loading the requested preset..."
|
|
1216
|
+
: null;
|
|
1019
1217
|
|
|
1020
1218
|
useEffect(() => {
|
|
1021
1219
|
if (!selectedPreset?.aspectRatio) return;
|
|
@@ -1213,9 +1411,14 @@ export default function AssetPicker() {
|
|
|
1213
1411
|
disabled={!canGenerate}
|
|
1214
1412
|
onClick={runGenerate}
|
|
1215
1413
|
>
|
|
1216
|
-
{
|
|
1414
|
+
{generationButtonLabel}
|
|
1217
1415
|
</Button>
|
|
1218
1416
|
</div>
|
|
1417
|
+
{generationStatus && (
|
|
1418
|
+
<div className="-mt-1 px-3 pb-2 text-[11px] leading-4 text-muted-foreground">
|
|
1419
|
+
{generationStatus}
|
|
1420
|
+
</div>
|
|
1421
|
+
)}
|
|
1219
1422
|
</div>
|
|
1220
1423
|
) : null}
|
|
1221
1424
|
|
|
@@ -3,4 +3,4 @@ type: added
|
|
|
3
3
|
date: 2026-06-23
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Added a heads-up explaining Google's 'app not verified' screen when connecting your calendar, so you know it's safe to continue
|
|
6
|
+
Added a heads-up explaining Google's 'app not verified' screen when signing in or connecting your calendar, so you know it's safe to continue
|
|
@@ -16,6 +16,18 @@ export default createAuthPlugin({
|
|
|
16
16
|
"Manages availability and booking links automatically",
|
|
17
17
|
"Answers schedule questions and resolves conflicts instantly",
|
|
18
18
|
],
|
|
19
|
+
runLocalCommand:
|
|
20
|
+
"npx @agent-native/core@latest create my-calendar-app --template calendar",
|
|
21
|
+
},
|
|
22
|
+
googleSignInNotice: {
|
|
23
|
+
host: "calendar.agent-native.com",
|
|
24
|
+
title: "Google may show a warning",
|
|
25
|
+
body: [
|
|
26
|
+
"You'll see this screen because this demo uses Agent-Native's Google app, not a Google-reviewed public app.",
|
|
27
|
+
"It's safe to continue: click Advanced, then “Go to … (unsafe)” to finish signing in.",
|
|
28
|
+
],
|
|
29
|
+
continueLabel: "Continue to Google",
|
|
30
|
+
cancelLabel: "Run locally",
|
|
19
31
|
},
|
|
20
32
|
publicPaths: [
|
|
21
33
|
"/book",
|
|
@@ -57,13 +57,13 @@ the recorder page itself. The Clips Chrome extension is the active-tab path for
|
|
|
57
57
|
browser logs: it launches `/record` with an extension capture session and passes
|
|
58
58
|
`developerLogs=1/0`, then saves diagnostics with source `extension`.
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
install-choice component
|
|
63
|
-
two options: Chrome extension for browser logs, and desktop app for the
|
|
64
|
-
seamless native capture.
|
|
65
|
-
|
|
66
|
-
listing.
|
|
60
|
+
The Web Store listing is live, so the public Chrome extension option shows by
|
|
61
|
+
default. UI prompts that otherwise say "Download desktop app" use the shared
|
|
62
|
+
install-choice component (`CaptureInstallButton` / `CaptureInstallInlineLink`)
|
|
63
|
+
to offer two options: Chrome extension for browser logs, and desktop app for the
|
|
64
|
+
most seamless native capture. Set `VITE_CLIPS_CHROME_EXTENSION_ENABLED=0` to hide
|
|
65
|
+
the Chrome option again, or `VITE_CLIPS_CHROME_EXTENSION_URL` to point at a
|
|
66
|
+
different listing.
|
|
67
67
|
|
|
68
68
|
## Pause / resume
|
|
69
69
|
|
|
@@ -65,8 +65,12 @@ Detailed media, meeting, dictation, editing, and sharing rules live in
|
|
|
65
65
|
the recording session. `save-browser-diagnostics` is UI/internal and stores
|
|
66
66
|
bounded console logs plus fetch/XHR method, URL path/query keys, status, and
|
|
67
67
|
duration; it never captures headers, bodies, cookies, or query values. Use
|
|
68
|
-
`get-recording-player-data` for full diagnostics when you have editor access
|
|
69
|
-
|
|
68
|
+
`get-recording-player-data` for full diagnostics when you have editor access.
|
|
69
|
+
Public agent context exposes the redacted console stream (all levels) as
|
|
70
|
+
`browserDiagnostics.consoleLogs` and the fetch/XHR stream as
|
|
71
|
+
`browserDiagnostics.networkRequests` (method, sanitized URL with query values
|
|
72
|
+
redacted, status, duration), plus `consoleIssues` and `failedNetworkRequests`
|
|
73
|
+
highlights. All bounded; page URL, headers, bodies, and cookies stay omitted.
|
|
70
74
|
- The Chrome extension lives in `chrome-extension/`. It launches `/record` with
|
|
71
75
|
`clipsExtensionId` and `clipsCaptureSessionId`, then the recorder sends
|
|
72
76
|
`CLIPS_CAPTURE_START/STOP/CANCEL` back to the extension. The extension uses
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
import { Link } from "react-router";
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
IconBrandApple,
|
|
5
|
+
IconBrandChrome,
|
|
6
|
+
IconBrandWindows,
|
|
5
7
|
IconChevronDown,
|
|
6
8
|
IconDeviceDesktop,
|
|
7
9
|
IconExternalLink,
|
|
@@ -35,8 +37,22 @@ type CaptureInstallInlineLinkProps = PopoverPlacement & {
|
|
|
35
37
|
desktopHref?: string;
|
|
36
38
|
};
|
|
37
39
|
|
|
40
|
+
/**
|
|
41
|
+
* The desktop-app tile shows the icon for the visitor's current OS — Apple on
|
|
42
|
+
* macOS, Windows on Windows — and falls back to a neutral desktop glyph on other
|
|
43
|
+
* platforms or during SSR. The Chrome tile always uses the Chrome brand icon.
|
|
44
|
+
*/
|
|
45
|
+
function desktopOsIcon(): typeof IconDeviceDesktop {
|
|
46
|
+
if (typeof navigator === "undefined") return IconDeviceDesktop;
|
|
47
|
+
const ua = navigator.userAgent;
|
|
48
|
+
if (/Windows/i.test(ua)) return IconBrandWindows;
|
|
49
|
+
if (/Mac|iPhone|iPad/i.test(ua)) return IconBrandApple;
|
|
50
|
+
return IconDeviceDesktop;
|
|
51
|
+
}
|
|
52
|
+
|
|
38
53
|
function InstallOptionsContent({ desktopHref = "/download" }) {
|
|
39
54
|
const chromeAvailable = Boolean(clipsChromeExtensionUrl);
|
|
55
|
+
const DesktopIcon = desktopOsIcon();
|
|
40
56
|
|
|
41
57
|
return (
|
|
42
58
|
<div className="grid gap-2">
|
|
@@ -57,7 +73,7 @@ function InstallOptionsContent({ desktopHref = "/download" }) {
|
|
|
57
73
|
rel="noreferrer"
|
|
58
74
|
className="flex items-start gap-3 rounded-md border border-border p-3 text-left transition hover:bg-accent"
|
|
59
75
|
>
|
|
60
|
-
<
|
|
76
|
+
<IconBrandChrome className="mt-0.5 h-4 w-4 shrink-0 text-primary" />
|
|
61
77
|
<span className="min-w-0 flex-1">
|
|
62
78
|
<span className="block text-sm font-medium">Chrome extension</span>
|
|
63
79
|
<span className="mt-0.5 block text-xs leading-snug text-muted-foreground">
|
|
@@ -69,7 +85,7 @@ function InstallOptionsContent({ desktopHref = "/download" }) {
|
|
|
69
85
|
</a>
|
|
70
86
|
) : (
|
|
71
87
|
<div className="flex items-start gap-3 rounded-md border border-dashed border-border p-3 text-left opacity-70">
|
|
72
|
-
<
|
|
88
|
+
<IconBrandChrome className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
|
|
73
89
|
<span className="min-w-0 flex-1">
|
|
74
90
|
<span className="block text-sm font-medium">Chrome extension</span>
|
|
75
91
|
<span className="mt-0.5 block text-xs leading-snug text-muted-foreground">
|
|
@@ -83,7 +99,7 @@ function InstallOptionsContent({ desktopHref = "/download" }) {
|
|
|
83
99
|
to={desktopHref}
|
|
84
100
|
className="flex items-start gap-3 rounded-md border border-border p-3 text-left transition hover:bg-accent"
|
|
85
101
|
>
|
|
86
|
-
<
|
|
102
|
+
<DesktopIcon className="mt-0.5 h-4 w-4 shrink-0 text-primary" />
|
|
87
103
|
<span className="min-w-0 flex-1">
|
|
88
104
|
<span className="block text-sm font-medium">Desktop app</span>
|
|
89
105
|
<span className="mt-0.5 block text-xs leading-snug text-muted-foreground">
|