@agent-native/core 0.164.4 → 0.164.12
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/templates/calendar/.agents/skills/event-management/SKILL.md +84 -0
- package/corpus/templates/calendar/AGENTS.md +5 -0
- package/corpus/templates/calendar/actions/delete-event.ts +1 -1
- package/corpus/templates/calendar/actions/delete-events.ts +508 -0
- package/corpus/templates/calendar/actions/event-action-helpers.ts +1 -1
- package/corpus/templates/calendar/actions/list-events.ts +53 -0
- package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +36 -47
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +3 -0
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +18 -1
- package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +256 -38
- package/corpus/templates/calendar/app/hooks/use-google-auth.ts +43 -3
- package/corpus/templates/calendar/app/i18n-data.ts +120 -0
- package/corpus/templates/calendar/app/lib/event-form-utils.ts +113 -0
- package/corpus/templates/calendar/changelog/2026-08-18-the-agent-can-now-remove-many-meetings-at-once-such-as-every.md +6 -0
- package/corpus/templates/calendar/server/handlers/google-auth.ts +119 -3
- package/corpus/templates/calendar/server/lib/event-weekday.ts +112 -0
- package/corpus/templates/calendar/server/routes/[base]/_agent-native/google/add-account/auth-url.post.ts +1 -0
- package/corpus/templates/calendar/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/calendar/server/routes/_agent-native/google/add-account/auth-url.post.ts +1 -0
- package/corpus/templates/calendar/server/routes/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/clips/app/components/player/video-player.tsx +12 -0
- package/corpus/templates/clips/app/routes/_app.dictate.tsx +18 -4
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +114 -20
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +83 -78
- package/corpus/templates/clips/changelog/2026-08-19-clips-nightly-builds-are-available-for-trying-the-latest-upd.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-19-social-preview-video-frames.md +1 -0
- package/corpus/templates/clips/desktop/README.md +19 -4
- package/corpus/templates/clips/desktop/src/app.tsx +78 -16
- package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +3 -2
- package/corpus/templates/clips/desktop/src-tauri/tauri.conf.json +1 -0
- package/corpus/templates/clips/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/clips/server/routes/_agent-native/google/auth-url.get.ts +41 -3
- package/corpus/templates/clips/server/routes/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/clips/server/routes/_agent-native/google/callback.get.ts +22 -1
- package/corpus/templates/clips/server/routes/api/clips-updater.json.get.ts +41 -22
- package/corpus/templates/factory/AGENTS.md +3 -2
- package/corpus/templates/factory/actions/get-factory-graph-version.ts +69 -0
- package/corpus/templates/factory/actions/list-factory-automations.ts +4 -0
- package/corpus/templates/factory/actions/list-factory-graph-versions.ts +82 -0
- package/corpus/templates/factory/actions/restore-factory-graph-version.ts +163 -0
- package/corpus/templates/factory/actions/save-factory-graph.ts +93 -60
- package/corpus/templates/factory/actions/start-builder-for-item.ts +19 -17
- package/corpus/templates/factory/app/components/factory/FactoryAuditView.tsx +100 -36
- package/corpus/templates/factory/app/components/factory/FactoryHistoryView.tsx +709 -0
- package/corpus/templates/factory/app/components/factory/FactoryInspector.tsx +62 -2
- package/corpus/templates/factory/app/components/ui/alert-dialog.tsx +1 -0
- package/corpus/templates/factory/app/i18n/en-US.ts +47 -0
- package/corpus/templates/factory/app/routes/factory.tsx +324 -61
- package/corpus/templates/factory/server/connectors/slack.ts +44 -2
- package/corpus/templates/factory/server/plugins/agent-chat.ts +12 -8
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +13 -0
- package/corpus/templates/factory/server/triage/slack-client.ts +5 -0
- package/corpus/templates/factory/server/triage/slack-poller.ts +72 -4
- package/corpus/templates/mail/actions/view-screen.ts +1 -1
- package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +100 -18
- package/corpus/templates/mail/server/handlers/google-auth.ts +125 -24
- package/corpus/templates/mail/server/lib/gmail-query.ts +5 -2
- package/corpus/templates/mail/server/routes/[base]/_agent-native/google/add-account/auth-url.post.ts +1 -0
- package/corpus/templates/mail/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/mail/server/routes/_agent-native/google/add-account/auth-url.post.ts +1 -0
- package/corpus/templates/mail/server/routes/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +0 -12
- package/corpus/templates/plan/changelog/2026-08-19-plan-prompts-now-guide-you-to-connect-ai-before-sending-and-.md +6 -0
- package/corpus/templates/slides/actions/approve-deck-access-request.ts +234 -0
- package/corpus/templates/slides/actions/get-deck-access-status.ts +35 -2
- package/corpus/templates/slides/actions/request-deck-access.ts +263 -64
- package/corpus/templates/slides/app/components/editor/MissingDeckAccessPane.tsx +196 -0
- package/corpus/templates/slides/app/components/editor/RequestDeckAccessDialog.tsx +101 -0
- package/corpus/templates/slides/app/hooks/use-deck-access.ts +7 -4
- package/corpus/templates/slides/app/i18n/en-US.ts +29 -6
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +147 -169
- package/corpus/templates/slides/app/routes/access-request.approve.tsx +250 -0
- package/corpus/templates/slides/server/db/schema.ts +8 -0
- package/corpus/templates/slides/server/lib/access-request-email.ts +36 -0
- package/corpus/templates/slides/server/lib/emails.ts +26 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +7 -0
- package/corpus/templates/slides/server/plugins/db.ts +50 -0
- package/corpus/templates/slides/shared/deck-access.ts +31 -0
- package/dist/a2a/task-store.d.ts +1 -0
- package/dist/a2a/task-store.js +1 -1
- package/dist/agent/observational-memory/store.d.ts +1 -0
- package/dist/agent/observational-memory/store.js +1 -1
- package/dist/agent/run-store.d.ts +1 -0
- package/dist/agent/run-store.js +1 -1
- package/dist/agent/tool-approval-store.d.ts +1 -0
- package/dist/agent/tool-approval-store.js +1 -1
- package/dist/application-state/store.d.ts +1 -0
- package/dist/application-state/store.js +1 -1
- package/dist/browser-sessions/store.d.ts +1 -0
- package/dist/browser-sessions/store.js +1 -1
- package/dist/checkpoints/store.d.ts +1 -0
- package/dist/checkpoints/store.js +1 -1
- package/dist/cli/code-agent-schedules.d.ts +11 -0
- package/dist/cli/code-agent-schedules.js +44 -5
- package/dist/client/AgentPanel.d.ts +5 -1
- package/dist/client/AgentPanel.js +2 -2
- package/dist/client/AssistantChat.d.ts +5 -1
- package/dist/client/AssistantChat.js +40 -6
- package/dist/client/EnvironmentBadge.d.ts +7 -13
- package/dist/client/EnvironmentBadge.js +43 -92
- package/dist/client/agent-chat/index.d.ts +1 -0
- package/dist/client/agent-chat/index.js +1 -0
- package/dist/client/agent-chat-adapter.js +76 -50
- package/dist/client/app-providers.js +4 -4
- package/dist/client/chat/message-components.d.ts +4 -0
- package/dist/client/chat/message-components.js +28 -1
- package/dist/client/chat/run-recovery.js +26 -0
- package/dist/client/chat-first/apps-rail.d.ts +1 -0
- package/dist/client/chat-first/apps-rail.js +28 -2
- package/dist/client/sse-event-processor.js +13 -8
- package/dist/client/use-active-agent-chat-run.d.ts +2 -0
- package/dist/client/use-active-agent-chat-run.js +22 -0
- package/dist/coding-tools/sandbox/executions-store.d.ts +1 -0
- package/dist/coding-tools/sandbox/executions-store.js +1 -1
- package/dist/collab/awareness-store.d.ts +1 -0
- package/dist/collab/awareness-store.js +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/storage.d.ts +1 -0
- package/dist/collab/storage.js +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/db/client.js +3 -3
- package/dist/db/ddl-guard.js +8 -0
- package/dist/db/migration-runtime.d.ts +23 -0
- package/dist/db/migration-runtime.js +41 -0
- package/dist/db/migrations.d.ts +1 -2
- package/dist/db/migrations.js +2 -24
- package/dist/email-catalog/log.d.ts +1 -0
- package/dist/email-catalog/log.js +1 -1
- package/dist/guards/index.d.ts +1 -0
- package/dist/guards/index.js +1 -0
- package/dist/guards/release-schema-complete.d.ts +22 -0
- package/dist/guards/release-schema-complete.js +144 -0
- package/dist/integrations/a2a-continuations-store.d.ts +1 -0
- package/dist/integrations/a2a-continuations-store.js +1 -1
- package/dist/integrations/awaiting-input-store.d.ts +1 -0
- package/dist/integrations/awaiting-input-store.js +1 -1
- package/dist/integrations/config-store.d.ts +1 -0
- package/dist/integrations/config-store.js +1 -1
- package/dist/integrations/controls-store.d.ts +1 -0
- package/dist/integrations/controls-store.js +1 -1
- package/dist/integrations/identity-links-store.d.ts +1 -0
- package/dist/integrations/identity-links-store.js +1 -1
- package/dist/integrations/installations-store.d.ts +1 -0
- package/dist/integrations/installations-store.js +1 -1
- package/dist/integrations/remote-commands-store.d.ts +1 -0
- package/dist/integrations/remote-commands-store.js +1 -1
- package/dist/integrations/remote-devices-store.d.ts +1 -0
- package/dist/integrations/remote-devices-store.js +1 -1
- package/dist/integrations/remote-push-store.d.ts +1 -0
- package/dist/integrations/remote-push-store.js +1 -1
- package/dist/integrations/remote-run-events-store.d.ts +1 -0
- package/dist/integrations/remote-run-events-store.js +1 -1
- package/dist/integrations/scope-store.d.ts +1 -0
- package/dist/integrations/scope-store.js +1 -1
- package/dist/integrations/thread-mapping-store.d.ts +1 -0
- package/dist/integrations/thread-mapping-store.js +1 -1
- package/dist/integrations/usage-budget-store.d.ts +1 -0
- package/dist/integrations/usage-budget-store.js +1 -1
- package/dist/jobs/background-automation-runner.js +111 -9
- package/dist/jobs/run-history.d.ts +1 -0
- package/dist/jobs/run-history.js +1 -1
- package/dist/jobs/scheduler-health.d.ts +1 -0
- package/dist/jobs/scheduler-health.js +1 -1
- package/dist/mcp/approval-store.d.ts +1 -0
- package/dist/mcp/approval-store.js +1 -1
- package/dist/mcp/connect-store.d.ts +1 -0
- package/dist/mcp/connect-store.js +1 -1
- package/dist/mcp/oauth-store.d.ts +1 -0
- package/dist/mcp/oauth-store.js +1 -1
- package/dist/notifications/store.d.ts +1 -0
- package/dist/notifications/store.js +1 -1
- package/dist/oauth-tokens/store.d.ts +1 -0
- package/dist/oauth-tokens/store.js +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/progress/store.d.ts +1 -0
- package/dist/progress/store.js +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +5 -5
- package/dist/provider-api/actions/provider-api.d.ts +4 -4
- package/dist/provider-api/corpus-jobs-store.d.ts +1 -0
- package/dist/provider-api/corpus-jobs-store.js +1 -1
- package/dist/provider-api/custom-registry.d.ts +1 -0
- package/dist/provider-api/custom-registry.js +1 -1
- package/dist/provider-api/quota-governor.d.ts +1 -0
- package/dist/provider-api/quota-governor.js +1 -1
- package/dist/provider-api/staged-datasets-store.d.ts +1 -0
- package/dist/provider-api/staged-datasets-store.js +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/resources/store.d.ts +1 -0
- package/dist/resources/store.js +1 -1
- package/dist/scripts/dev/index.js +15 -1
- package/dist/search/index.js +3 -0
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/secrets/storage.d.ts +1 -0
- package/dist/secrets/storage.js +1 -1
- package/dist/server/agent-chat-plugin.js +38 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-teams-run-queue.d.ts +1 -0
- package/dist/server/agent-teams-run-queue.js +1 -1
- package/dist/server/auth-marketing.js +3 -1
- package/dist/server/auth.d.ts +18 -1
- package/dist/server/auth.js +370 -154
- package/dist/server/embed-session.d.ts +1 -0
- package/dist/server/embed-session.js +1 -1
- package/dist/server/google-auth-plugin.js +3 -1
- package/dist/server/google-oauth.d.ts +10 -0
- package/dist/server/google-oauth.js +25 -1
- package/dist/server/identity-sso-store.d.ts +1 -0
- package/dist/server/identity-sso-store.js +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/onboarding-html.js +200 -31
- package/dist/server/poll.d.ts +2 -0
- package/dist/server/poll.js +19 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/release-migrations.js +5 -0
- package/dist/server/release-schema.d.ts +46 -0
- package/dist/server/release-schema.js +307 -0
- package/dist/settings/store.d.ts +1 -0
- package/dist/settings/store.js +1 -1
- package/dist/shared/environment-lanes.d.ts +28 -0
- package/dist/shared/environment-lanes.js +61 -0
- package/dist/sharing/actions/share-resource.js +22 -2
- package/dist/styles/agent-native.css +17 -15
- package/dist/triggers/routes.d.ts +11 -0
- package/dist/triggers/routes.js +10 -0
- package/dist/usage/alerts-store.d.ts +1 -0
- package/dist/usage/alerts-store.js +1 -1
- package/dist/usage/store.d.ts +1 -0
- package/dist/usage/store.js +1 -1
- package/docs/content/actions-access-control.mdx +136 -2
- package/docs/content/actions-agent-tools.mdx +101 -0
- package/docs/content/actions-defining.mdx +2 -2
- package/docs/content/audit-log.mdx +1 -1
- package/docs/content/authentication.mdx +1 -1
- package/docs/content/cloudflare.mdx +43 -0
- package/docs/content/creating-templates.mdx +1 -1
- package/docs/content/deploy-an-app.mdx +109 -0
- package/docs/content/deployment-environment-variables.mdx +101 -0
- package/docs/content/deployment.mdx +125 -792
- package/docs/content/docs-components.mdx +2 -2
- package/docs/content/environment-variables.mdx +41 -41
- package/docs/content/file-uploads.mdx +74 -40
- package/docs/content/harness-agents.mdx +2 -2
- package/docs/content/internationalization.mdx +61 -65
- package/docs/content/key-concepts.mdx +3 -3
- package/docs/content/multi-app-workspace.mdx +2 -2
- package/docs/content/netlify.mdx +47 -0
- package/docs/content/node-docker.mdx +138 -0
- package/docs/content/organizations-teams-permissions.mdx +5 -5
- package/docs/content/other-platforms.mdx +27 -0
- package/docs/content/ssr-caching.mdx +45 -0
- package/docs/content/template-assets-developers.mdx +2 -2
- package/docs/content/template-chat-developers.mdx +2 -2
- package/docs/content/template-content-developers.mdx +2 -2
- package/docs/content/{local-file-mode.mdx → template-content-local-files.mdx} +2 -1
- package/docs/content/template-content-sync.mdx +2 -2
- package/docs/content/toolkit-agent-ux.mdx +1 -1
- package/docs/content/toolkit-resources.mdx +2 -2
- package/docs/content/updating-ui-in-production.mdx +35 -0
- package/docs/content/vercel.mdx +38 -0
- package/docs/content/what-is-agent-native.mdx +1 -1
- package/docs/content/workspace-deployment.mdx +350 -0
- package/package.json +2 -2
- package/docs/content/agents.mdx +0 -9
- package/docs/content/database.mdx +0 -317
- package/docs/content/human-approval.mdx +0 -156
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const DATE_ONLY_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
2
|
+
|
|
3
|
+
export const WEEKDAY_NAMES = [
|
|
4
|
+
"sunday",
|
|
5
|
+
"monday",
|
|
6
|
+
"tuesday",
|
|
7
|
+
"wednesday",
|
|
8
|
+
"thursday",
|
|
9
|
+
"friday",
|
|
10
|
+
"saturday",
|
|
11
|
+
] as const;
|
|
12
|
+
|
|
13
|
+
export type WeekdayName = (typeof WEEKDAY_NAMES)[number];
|
|
14
|
+
|
|
15
|
+
const WEEKDAY_ALIASES = new Map<string, WeekdayName>([
|
|
16
|
+
...WEEKDAY_NAMES.map((day) => [day, day] as const),
|
|
17
|
+
...WEEKDAY_NAMES.map((day) => [day.slice(0, 3), day] as const),
|
|
18
|
+
["tues", "tuesday"],
|
|
19
|
+
["thur", "thursday"],
|
|
20
|
+
["thurs", "thursday"],
|
|
21
|
+
["weekend", "saturday"],
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Accepts the shapes a natural-language request arrives in — "Saturday",
|
|
26
|
+
* "sat", "SUN", or an array/comma string mixing them — and rejects anything
|
|
27
|
+
* else loudly. A silently dropped day name would delete a different set of
|
|
28
|
+
* events than the user asked about.
|
|
29
|
+
*/
|
|
30
|
+
export function normalizeWeekdays(
|
|
31
|
+
input: string | readonly string[] | undefined,
|
|
32
|
+
): WeekdayName[] {
|
|
33
|
+
if (input === undefined) return [];
|
|
34
|
+
const raw = (Array.isArray(input) ? input : String(input).split(","))
|
|
35
|
+
.map((value) => value.trim().toLowerCase())
|
|
36
|
+
.filter(Boolean);
|
|
37
|
+
const resolved = new Set<WeekdayName>();
|
|
38
|
+
for (const value of raw) {
|
|
39
|
+
if (value === "weekend" || value === "weekends") {
|
|
40
|
+
resolved.add("saturday");
|
|
41
|
+
resolved.add("sunday");
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (value === "weekday" || value === "weekdays") {
|
|
45
|
+
for (const day of WEEKDAY_NAMES) {
|
|
46
|
+
if (day !== "saturday" && day !== "sunday") resolved.add(day);
|
|
47
|
+
}
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const match = WEEKDAY_ALIASES.get(value.replace(/s$/, ""));
|
|
51
|
+
if (!match) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Unrecognized day of week: ${value}. Use full or 3-letter English day names.`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
resolved.add(match);
|
|
57
|
+
}
|
|
58
|
+
return WEEKDAY_NAMES.filter((day) => resolved.has(day));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The weekday the user sees for an event, which is the weekday in the calendar
|
|
63
|
+
* view's timezone — not UTC. A Sunday 5pm America/Los_Angeles meeting is Monday
|
|
64
|
+
* in UTC, so filtering on the raw ISO string silently misses it and instead
|
|
65
|
+
* deletes a Monday meeting the user never mentioned.
|
|
66
|
+
*
|
|
67
|
+
* All-day starts carry no instant, so their `YYYY-MM-DD` date IS the local day
|
|
68
|
+
* and must not be re-projected through a timezone.
|
|
69
|
+
*/
|
|
70
|
+
export function eventWeekday(start: string, timezone: string): WeekdayName {
|
|
71
|
+
if (DATE_ONLY_RE.test(start)) {
|
|
72
|
+
const [year, month, day] = start.split("-").map(Number);
|
|
73
|
+
return WEEKDAY_NAMES[new Date(Date.UTC(year, month - 1, day)).getUTCDay()];
|
|
74
|
+
}
|
|
75
|
+
const parsed = new Date(start);
|
|
76
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
77
|
+
throw new Error(`Invalid event start: ${start}`);
|
|
78
|
+
}
|
|
79
|
+
const label = new Intl.DateTimeFormat("en-US", {
|
|
80
|
+
timeZone: timezone,
|
|
81
|
+
weekday: "long",
|
|
82
|
+
})
|
|
83
|
+
.format(parsed)
|
|
84
|
+
.toLowerCase();
|
|
85
|
+
const match = WEEKDAY_NAMES.find((day) => day === label);
|
|
86
|
+
if (!match) throw new Error(`Unable to resolve weekday for ${start}`);
|
|
87
|
+
return match;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function matchesWeekdays(
|
|
91
|
+
start: string,
|
|
92
|
+
timezone: string,
|
|
93
|
+
weekdays: readonly WeekdayName[],
|
|
94
|
+
): boolean {
|
|
95
|
+
if (weekdays.length === 0) return true;
|
|
96
|
+
return weekdays.includes(eventWeekday(start, timezone));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Validate a caller-supplied IANA timezone instead of falling back to UTC.
|
|
101
|
+
* `normalizeTimezone` silently returns UTC for an unparseable zone, which on a
|
|
102
|
+
* destructive weekday filter would quietly reclassify every event and delete a
|
|
103
|
+
* different set of days than the caller named.
|
|
104
|
+
*/
|
|
105
|
+
export function requireValidTimezone(timezone: string): string {
|
|
106
|
+
try {
|
|
107
|
+
new Intl.DateTimeFormat("en-US", { timeZone: timezone }).format();
|
|
108
|
+
} catch {
|
|
109
|
+
throw new Error(`Invalid IANA timezone: ${timezone}`);
|
|
110
|
+
}
|
|
111
|
+
return timezone;
|
|
112
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "../../../../_agent-native/google/add-account/auth-url.post.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "../../../_agent-native/google/auth-url.post.js";
|
package/corpus/templates/calendar/server/routes/_agent-native/google/add-account/auth-url.post.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { getGoogleAddAccountUrl as default } from "../../../../handlers/google-auth";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { getGoogleAuthUrl as default } from "../../../handlers/google-auth";
|
|
@@ -168,6 +168,7 @@ export interface VideoPlayerHandle {
|
|
|
168
168
|
play: () => Promise<void> | void;
|
|
169
169
|
pause: () => void;
|
|
170
170
|
seek: (ms: number) => void;
|
|
171
|
+
getCurrentOriginalMs: () => number;
|
|
171
172
|
setSpeed: (rate: number) => void;
|
|
172
173
|
toggleMute: () => void;
|
|
173
174
|
toggleCaptions: () => void;
|
|
@@ -1159,6 +1160,17 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
1159
1160
|
play: requestPlay,
|
|
1160
1161
|
pause: pauseVideo,
|
|
1161
1162
|
seek: seekToVisibleMs,
|
|
1163
|
+
getCurrentOriginalMs: () => {
|
|
1164
|
+
const v = videoRef.current;
|
|
1165
|
+
const liveOriginalMs =
|
|
1166
|
+
v &&
|
|
1167
|
+
Number.isFinite(v.currentTime) &&
|
|
1168
|
+
v.currentTime >= 0 &&
|
|
1169
|
+
v.currentTime < 1e7
|
|
1170
|
+
? Math.floor(v.currentTime * 1000)
|
|
1171
|
+
: currentMsRef.current;
|
|
1172
|
+
return liveOriginalMs;
|
|
1173
|
+
},
|
|
1162
1174
|
setSpeed: applySpeed,
|
|
1163
1175
|
toggleMute: () => {
|
|
1164
1176
|
if (videoRef.current) {
|
|
@@ -159,6 +159,10 @@ function dayBucket(iso: string): string {
|
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
export function dictationsRefetchInterval(isActive: boolean): number | false {
|
|
163
|
+
return isActive ? 2_000 : false;
|
|
164
|
+
}
|
|
165
|
+
|
|
162
166
|
function sourceMeta(
|
|
163
167
|
source: string | undefined,
|
|
164
168
|
t: ReturnType<typeof useT>,
|
|
@@ -694,10 +698,6 @@ function DownloadDesktopAppCard() {
|
|
|
694
698
|
|
|
695
699
|
export default function DictateRoute() {
|
|
696
700
|
const t = useT();
|
|
697
|
-
const { data, isLoading, isError } = useActionQuery<
|
|
698
|
-
{ dictations: Dictation[] } | Dictation[] | undefined
|
|
699
|
-
>("list-dictations", {}, { retry: false });
|
|
700
|
-
|
|
701
701
|
const { isDesktopApp } = useDesktopPromo();
|
|
702
702
|
const [filter, setFilter] = useState<SourceFilter>("all");
|
|
703
703
|
const [listening, setListening] = useState(false);
|
|
@@ -706,6 +706,20 @@ export default function DictateRoute() {
|
|
|
706
706
|
const [speechSupported, setSpeechSupported] = useState(false);
|
|
707
707
|
const qc = useQueryClient();
|
|
708
708
|
const createDictation = useActionMutation("create-dictation");
|
|
709
|
+
const { data, isLoading, isError } = useActionQuery<
|
|
710
|
+
{ dictations: Dictation[] } | Dictation[] | undefined
|
|
711
|
+
>(
|
|
712
|
+
"list-dictations",
|
|
713
|
+
{},
|
|
714
|
+
{
|
|
715
|
+
retry: false,
|
|
716
|
+
// Action-backed mutations invalidate this query when a desktop-created
|
|
717
|
+
// dictation lands. Poll only while browser work is active or saving,
|
|
718
|
+
// rather than running a permanent interval over idle history.
|
|
719
|
+
refetchInterval: () =>
|
|
720
|
+
dictationsRefetchInterval(listening || createDictation.isPending),
|
|
721
|
+
},
|
|
722
|
+
);
|
|
709
723
|
|
|
710
724
|
const recognitionRef = useRef<SpeechRecognitionLike | null>(null);
|
|
711
725
|
const transcriptRef = useRef("");
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
IconSparkles,
|
|
46
46
|
IconExternalLink,
|
|
47
47
|
} from "@tabler/icons-react";
|
|
48
|
-
import { useQuery } from "@tanstack/react-query";
|
|
48
|
+
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
49
49
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
50
50
|
import { createPortal } from "react-dom";
|
|
51
51
|
import {
|
|
@@ -110,6 +110,64 @@ const PROCESSING_STUCK_TIMEOUT_MS = 12 * 60 * 1000;
|
|
|
110
110
|
const READY_MEDIA_SETTLE_POLL_MS = 20 * 1000;
|
|
111
111
|
const READY_MEDIA_SETTLE_POLL_INTERVAL_MS = 1000;
|
|
112
112
|
|
|
113
|
+
type RecordingReaction = {
|
|
114
|
+
id: string;
|
|
115
|
+
emoji: string;
|
|
116
|
+
videoTimestampMs: number;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
type PendingRecordingReaction = RecordingReaction & {
|
|
120
|
+
recordingId: string;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export function mergeRecordingReactions(
|
|
124
|
+
serverReactions: RecordingReaction[] | undefined,
|
|
125
|
+
pendingReactions: PendingRecordingReaction[],
|
|
126
|
+
recordingId: string | undefined,
|
|
127
|
+
) {
|
|
128
|
+
const seen = new Set<string>();
|
|
129
|
+
const merged: RecordingReaction[] = [];
|
|
130
|
+
const visiblePendingReactions = recordingId
|
|
131
|
+
? pendingReactions.filter(
|
|
132
|
+
(reaction) => reaction.recordingId === recordingId,
|
|
133
|
+
)
|
|
134
|
+
: [];
|
|
135
|
+
|
|
136
|
+
for (const reaction of [
|
|
137
|
+
...(serverReactions ?? []),
|
|
138
|
+
...visiblePendingReactions,
|
|
139
|
+
]) {
|
|
140
|
+
const key = `${reaction.id}:${reaction.emoji}:${reaction.videoTimestampMs}`;
|
|
141
|
+
if (seen.has(key)) continue;
|
|
142
|
+
seen.add(key);
|
|
143
|
+
merged.push(reaction);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return merged;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function removePendingReaction(
|
|
150
|
+
pendingReactions: PendingRecordingReaction[],
|
|
151
|
+
pendingId: string,
|
|
152
|
+
) {
|
|
153
|
+
return pendingReactions.filter((reaction) => reaction.id !== pendingId);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function handleReactionWrite(
|
|
157
|
+
response: { ok: boolean; status: number },
|
|
158
|
+
refresh: () => Promise<unknown>,
|
|
159
|
+
) {
|
|
160
|
+
if (!response.ok) throw new Error(`react failed: ${response.status}`);
|
|
161
|
+
// The write is authoritative. A transient read failure must not tell the
|
|
162
|
+
// player that the reaction failed.
|
|
163
|
+
void Promise.resolve()
|
|
164
|
+
.then(refresh)
|
|
165
|
+
.catch((refreshError) => {
|
|
166
|
+
console.warn("[clips] reaction refresh failed", refreshError);
|
|
167
|
+
});
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
|
|
113
171
|
export function meta() {
|
|
114
172
|
return [{ title: enMessages.recordingRoute.pageTitle }];
|
|
115
173
|
}
|
|
@@ -263,16 +321,7 @@ export default function RecordingPage() {
|
|
|
263
321
|
// live time bridge, so we fall back to the last position the player reported
|
|
264
322
|
// via onTimeUpdate (seek/initial start).
|
|
265
323
|
const resolvePlaybackMs = useCallback(() => {
|
|
266
|
-
|
|
267
|
-
if (
|
|
268
|
-
typeof liveCt === "number" &&
|
|
269
|
-
Number.isFinite(liveCt) &&
|
|
270
|
-
liveCt >= 0 &&
|
|
271
|
-
liveCt < 1e7
|
|
272
|
-
) {
|
|
273
|
-
return Math.floor(liveCt * 1000);
|
|
274
|
-
}
|
|
275
|
-
return currentMs;
|
|
324
|
+
return playerRef.current?.getCurrentOriginalMs() ?? currentMs;
|
|
276
325
|
}, [currentMs]);
|
|
277
326
|
// The compact layout stacks the panel below the video, so switching tabs
|
|
278
327
|
// alone leaves the user looking at the player. Desktop always renders the
|
|
@@ -310,9 +359,13 @@ export default function RecordingPage() {
|
|
|
310
359
|
recordingId ? { type: "recording" as const, id: recordingId } : null,
|
|
311
360
|
[recordingId],
|
|
312
361
|
);
|
|
362
|
+
const queryClient = useQueryClient();
|
|
313
363
|
const lastPlayerStateWriteRef = useRef(0);
|
|
314
364
|
const readyMediaPollRef = useRef<{ key: string; until: number } | null>(null);
|
|
315
365
|
const [metadataRefreshUntil, setMetadataRefreshUntil] = useState(0);
|
|
366
|
+
const [pendingReactions, setPendingReactions] = useState<
|
|
367
|
+
PendingRecordingReaction[]
|
|
368
|
+
>([]);
|
|
316
369
|
|
|
317
370
|
const playerDataQ = useActionQuery<any>(
|
|
318
371
|
"get-recording-player-data",
|
|
@@ -414,7 +467,15 @@ export default function RecordingPage() {
|
|
|
414
467
|
| "viewer"
|
|
415
468
|
| undefined;
|
|
416
469
|
const comments = playerDataQ.data?.comments ?? [];
|
|
417
|
-
const reactions =
|
|
470
|
+
const reactions = useMemo(
|
|
471
|
+
() =>
|
|
472
|
+
mergeRecordingReactions(
|
|
473
|
+
playerDataQ.data?.reactions,
|
|
474
|
+
pendingReactions,
|
|
475
|
+
recordingId,
|
|
476
|
+
),
|
|
477
|
+
[pendingReactions, playerDataQ.data?.reactions, recordingId],
|
|
478
|
+
);
|
|
418
479
|
const chapters = playerDataQ.data?.chapters ?? [];
|
|
419
480
|
const transcriptSegments = playerDataQ.data?.transcript?.segments ?? [];
|
|
420
481
|
const transcriptFullText = playerDataQ.data?.transcript?.fullText ?? null;
|
|
@@ -1643,11 +1704,10 @@ export default function RecordingPage() {
|
|
|
1643
1704
|
},
|
|
1644
1705
|
)
|
|
1645
1706
|
.then((res) => {
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1707
|
+
return handleReactionWrite(res, () =>
|
|
1708
|
+
playerDataQ.refetch(),
|
|
1709
|
+
);
|
|
1649
1710
|
})
|
|
1650
|
-
.then(() => true)
|
|
1651
1711
|
.catch((err) => {
|
|
1652
1712
|
console.warn("[clips] react failed", err);
|
|
1653
1713
|
return false;
|
|
@@ -1743,6 +1803,18 @@ export default function RecordingPage() {
|
|
|
1743
1803
|
onReact={(emoji) => {
|
|
1744
1804
|
tracking.reportReaction(emoji);
|
|
1745
1805
|
const liveMs = resolvePlaybackMs();
|
|
1806
|
+
const pendingReaction: PendingRecordingReaction = {
|
|
1807
|
+
id: `pending-${Date.now()}-${Math.random()
|
|
1808
|
+
.toString(36)
|
|
1809
|
+
.slice(2)}`,
|
|
1810
|
+
emoji,
|
|
1811
|
+
videoTimestampMs: liveMs,
|
|
1812
|
+
recordingId: recording.id,
|
|
1813
|
+
};
|
|
1814
|
+
setPendingReactions((current) => [
|
|
1815
|
+
...current,
|
|
1816
|
+
pendingReaction,
|
|
1817
|
+
]);
|
|
1746
1818
|
return fetch(
|
|
1747
1819
|
agentNativePath(
|
|
1748
1820
|
"/_agent-native/actions/react-to-recording",
|
|
@@ -1758,12 +1830,34 @@ export default function RecordingPage() {
|
|
|
1758
1830
|
},
|
|
1759
1831
|
)
|
|
1760
1832
|
.then((res) => {
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1833
|
+
const writeSucceeded = handleReactionWrite(
|
|
1834
|
+
res,
|
|
1835
|
+
() =>
|
|
1836
|
+
Promise.all([
|
|
1837
|
+
queryClient.invalidateQueries({
|
|
1838
|
+
queryKey: [
|
|
1839
|
+
"action",
|
|
1840
|
+
"get-recording-player-data",
|
|
1841
|
+
],
|
|
1842
|
+
}),
|
|
1843
|
+
playerDataQ.refetch(),
|
|
1844
|
+
]),
|
|
1845
|
+
);
|
|
1846
|
+
setPendingReactions((current) =>
|
|
1847
|
+
removePendingReaction(
|
|
1848
|
+
current,
|
|
1849
|
+
pendingReaction.id,
|
|
1850
|
+
),
|
|
1851
|
+
);
|
|
1852
|
+
return writeSucceeded;
|
|
1764
1853
|
})
|
|
1765
|
-
.then(() => true)
|
|
1766
1854
|
.catch((err) => {
|
|
1855
|
+
setPendingReactions((current) =>
|
|
1856
|
+
removePendingReaction(
|
|
1857
|
+
current,
|
|
1858
|
+
pendingReaction.id,
|
|
1859
|
+
),
|
|
1860
|
+
);
|
|
1767
1861
|
console.warn("[clips] react failed", err);
|
|
1768
1862
|
return false;
|
|
1769
1863
|
});
|
|
@@ -679,6 +679,7 @@ export default function ShareRoute() {
|
|
|
679
679
|
// an anonymous viewer sees the same controls but triggers the sign-in
|
|
680
680
|
// prompt instead (see `requireSignIn` below).
|
|
681
681
|
const viewerCanComment = Boolean(session) && viewerRole != null;
|
|
682
|
+
const viewerCanUseFullscreenInteractions = !session || viewerCanComment;
|
|
682
683
|
const viewerIsOwner = Boolean(dataQ.data?.data?.viewer?.isOwner);
|
|
683
684
|
const canReshareLink =
|
|
684
685
|
(viewerRole === "viewer" || viewerRole === "commenter") &&
|
|
@@ -1252,66 +1253,85 @@ export default function ShareRoute() {
|
|
|
1252
1253
|
cta={firstCta}
|
|
1253
1254
|
onCtaClick={() => tracking.reportCtaClick()}
|
|
1254
1255
|
onTimeUpdate={(ms) => setCurrentMs(ms)}
|
|
1255
|
-
onCommentClick={
|
|
1256
|
+
onCommentClick={
|
|
1257
|
+
viewerCanUseFullscreenInteractions
|
|
1258
|
+
? () => setPanel("comments")
|
|
1259
|
+
: undefined
|
|
1260
|
+
}
|
|
1256
1261
|
onFullscreenChange={setIsPlayerFullscreen}
|
|
1257
|
-
enableComments={
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1262
|
+
enableComments={
|
|
1263
|
+
recording.enableComments && viewerCanUseFullscreenInteractions
|
|
1264
|
+
}
|
|
1265
|
+
onAddComment={
|
|
1266
|
+
viewerCanUseFullscreenInteractions
|
|
1267
|
+
? () => {
|
|
1268
|
+
if (!session) {
|
|
1269
|
+
requireSignIn("comment");
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
const liveCt = isLoomEmbedBacked
|
|
1273
|
+
? null
|
|
1274
|
+
: playerRef.current?.video?.currentTime;
|
|
1275
|
+
const liveMs =
|
|
1276
|
+
typeof liveCt === "number" &&
|
|
1277
|
+
Number.isFinite(liveCt) &&
|
|
1278
|
+
liveCt >= 0 &&
|
|
1279
|
+
liveCt < 1e7
|
|
1280
|
+
? Math.floor(liveCt * 1000)
|
|
1281
|
+
: currentMs;
|
|
1282
|
+
setCommentAtMs(liveMs);
|
|
1283
|
+
setCommentOpen(true);
|
|
1284
|
+
}
|
|
1285
|
+
: undefined
|
|
1286
|
+
}
|
|
1287
|
+
enableReactions={
|
|
1288
|
+
recording.enableReactions && viewerCanUseFullscreenInteractions
|
|
1289
|
+
}
|
|
1290
|
+
onReact={
|
|
1291
|
+
viewerCanUseFullscreenInteractions
|
|
1292
|
+
? (emoji) => {
|
|
1293
|
+
if (!session) {
|
|
1294
|
+
requireSignIn("react");
|
|
1295
|
+
return false;
|
|
1296
|
+
}
|
|
1297
|
+
tracking.reportReaction(emoji);
|
|
1298
|
+
const liveCt = isLoomEmbedBacked
|
|
1299
|
+
? null
|
|
1300
|
+
: playerRef.current?.video?.currentTime;
|
|
1301
|
+
const liveMs =
|
|
1302
|
+
typeof liveCt === "number" &&
|
|
1303
|
+
Number.isFinite(liveCt) &&
|
|
1304
|
+
liveCt >= 0 &&
|
|
1305
|
+
liveCt < 1e7
|
|
1306
|
+
? Math.floor(liveCt * 1000)
|
|
1307
|
+
: currentMs;
|
|
1308
|
+
return fetch(
|
|
1309
|
+
agentNativePath(
|
|
1310
|
+
"/_agent-native/actions/react-to-recording",
|
|
1311
|
+
),
|
|
1312
|
+
{
|
|
1313
|
+
method: "POST",
|
|
1314
|
+
headers: { "Content-Type": "application/json" },
|
|
1315
|
+
body: JSON.stringify({
|
|
1316
|
+
recordingId: recording.id,
|
|
1317
|
+
emoji,
|
|
1318
|
+
videoTimestampMs: liveMs,
|
|
1319
|
+
}),
|
|
1320
|
+
},
|
|
1321
|
+
)
|
|
1322
|
+
.then((res) => {
|
|
1323
|
+
if (!res.ok)
|
|
1324
|
+
throw new Error(`react failed: ${res.status}`);
|
|
1325
|
+
return dataQ.refetch();
|
|
1326
|
+
})
|
|
1327
|
+
.then(() => true)
|
|
1328
|
+
.catch((err) => {
|
|
1329
|
+
console.warn("[clips] react failed", err);
|
|
1330
|
+
return false;
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
: undefined
|
|
1334
|
+
}
|
|
1315
1335
|
className="h-full w-full rounded-none sm:rounded-xl"
|
|
1316
1336
|
/>
|
|
1317
1337
|
{commentOpen && viewerCanComment
|
|
@@ -1354,23 +1374,15 @@ export default function ShareRoute() {
|
|
|
1354
1374
|
{recording.enableComments ? (
|
|
1355
1375
|
<TimestampedCommentButton
|
|
1356
1376
|
enableComments={recording.enableComments}
|
|
1357
|
-
canComment
|
|
1377
|
+
canComment={!session || viewerCanComment}
|
|
1358
1378
|
className="shrink-0"
|
|
1359
1379
|
onOpen={() => {
|
|
1360
1380
|
if (!session) {
|
|
1361
1381
|
requireSignIn("comment");
|
|
1362
1382
|
return;
|
|
1363
1383
|
}
|
|
1364
|
-
const liveCt = isLoomEmbedBacked
|
|
1365
|
-
? null
|
|
1366
|
-
: playerRef.current?.video?.currentTime;
|
|
1367
1384
|
const liveMs =
|
|
1368
|
-
|
|
1369
|
-
Number.isFinite(liveCt) &&
|
|
1370
|
-
liveCt >= 0 &&
|
|
1371
|
-
liveCt < 1e7
|
|
1372
|
-
? Math.floor(liveCt * 1000)
|
|
1373
|
-
: currentMs;
|
|
1385
|
+
playerRef.current?.getCurrentOriginalMs() ?? currentMs;
|
|
1374
1386
|
setCommentAtMs(liveMs);
|
|
1375
1387
|
setCommentOpen(true);
|
|
1376
1388
|
}}
|
|
@@ -1379,22 +1391,15 @@ export default function ShareRoute() {
|
|
|
1379
1391
|
{recording.enableReactions ? (
|
|
1380
1392
|
<ReactionsTray
|
|
1381
1393
|
reactions={reactions}
|
|
1394
|
+
disabled={Boolean(session) && !viewerCanComment}
|
|
1382
1395
|
onReact={(emoji) => {
|
|
1383
1396
|
if (!session) {
|
|
1384
1397
|
requireSignIn("react");
|
|
1385
1398
|
return false;
|
|
1386
1399
|
}
|
|
1387
1400
|
tracking.reportReaction(emoji);
|
|
1388
|
-
const liveCt = isLoomEmbedBacked
|
|
1389
|
-
? null
|
|
1390
|
-
: playerRef.current?.video?.currentTime;
|
|
1391
1401
|
const liveMs =
|
|
1392
|
-
|
|
1393
|
-
Number.isFinite(liveCt) &&
|
|
1394
|
-
liveCt >= 0 &&
|
|
1395
|
-
liveCt < 1e7
|
|
1396
|
-
? Math.floor(liveCt * 1000)
|
|
1397
|
-
: currentMs;
|
|
1402
|
+
playerRef.current?.getCurrentOriginalMs() ?? currentMs;
|
|
1398
1403
|
return fetch(
|
|
1399
1404
|
agentNativePath(
|
|
1400
1405
|
"/_agent-native/actions/react-to-recording",
|
|
@@ -79,13 +79,28 @@ Clips registers itself to open at login by default, then runs quietly in the men
|
|
|
79
79
|
|
|
80
80
|
## Releases + auto-update
|
|
81
81
|
|
|
82
|
-
Clips Desktop
|
|
82
|
+
Clips Desktop has separate stable and Nightly lanes. The stable app keeps the
|
|
83
|
+
`Clips` name, `Clips` binary, `clips://` scheme, and `com.clips.tray` identifier;
|
|
84
|
+
its releases use `clips-v*` tags, the `clips-latest` updater pointer, and
|
|
85
|
+
`/api/clips-updater.json`. Nightly builds are named `Clips Nightly`, use the
|
|
86
|
+
`Clips-Nightly` binary, the `clips-nightly://` scheme, and
|
|
87
|
+
`com.clips.tray.nightly`. They use the `clips-nightly-v*` tags,
|
|
88
|
+
`clips-nightly-latest` pointer, and `/api/clips-updater.json?channel=nightly`.
|
|
89
|
+
The in-app updater only sees the pointer for the channel that produced the
|
|
90
|
+
installed app, and the separate native binary/desktop entry keeps both lanes
|
|
91
|
+
installable at once.
|
|
83
92
|
|
|
84
93
|
### Shipping a release
|
|
85
94
|
|
|
86
|
-
1.
|
|
87
|
-
|
|
88
|
-
|
|
95
|
+
1. For a stable release, dispatch **Clips Desktop Release** in GitHub Actions
|
|
96
|
+
with `channel: production` and an explicit version. Stable releases are
|
|
97
|
+
deliberate workflow runs; pushes to `main` never replace the stable lane.
|
|
98
|
+
2. Pushes to `main` automatically build the Nightly lane. A Nightly run can
|
|
99
|
+
also be dispatched explicitly with `channel: nightly`.
|
|
100
|
+
3. The workflow builds macOS (universal), Windows, and Linux x86_64 installers,
|
|
101
|
+
signs updater artifacts, and publishes the versioned release plus that
|
|
102
|
+
channel's pointer manifest. Installed copies auto-download only updates
|
|
103
|
+
from their own lane on the next hourly or app-focus check.
|
|
89
104
|
|
|
90
105
|
### Auto-update flow (inside the app)
|
|
91
106
|
|