@agent-native/core 0.163.4 → 0.164.3
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/analytics/app/components/agents/FeatureFlagsFleetPanel.tsx +78 -19
- package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +29 -0
- package/corpus/templates/analytics/app/lib/use-auto-focus-select.ts +4 -4
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +6 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +1 -2
- package/corpus/templates/analytics/app/routes/_index.tsx +3 -2
- package/corpus/templates/analytics/app/routes/adhoc.$id.tsx +7 -2
- package/corpus/templates/analytics/app/routes/dashboard.tsx +3 -2
- package/corpus/templates/analytics/app/routes/overview.tsx +3 -2
- package/corpus/templates/analytics/app/routes/traffic.tsx +3 -2
- package/corpus/templates/analytics/changelog/2026-08-18-verify-cross-app-feature-flag-changes.md +6 -0
- package/corpus/templates/analytics/netlify.toml +2 -0
- package/corpus/templates/analytics/server/lib/workspace-feature-flags.ts +196 -48
- package/corpus/templates/analytics/server/plugins/feature-flags.ts +5 -0
- package/corpus/templates/analytics/shared/feature-flags.ts +10 -0
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +5 -1
- package/corpus/templates/assets/app/routes/brand-kits._index.tsx +4 -1
- package/corpus/templates/assets/app/routes/libraries.tsx +4 -1
- package/corpus/templates/assets/app/routes/picker.tsx +4 -1
- package/corpus/templates/assets/netlify.toml +2 -0
- package/corpus/templates/brain/netlify.toml +2 -0
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +2 -1
- package/corpus/templates/calendar/changelog/2026-08-19-calendar-page-loads-stay-fast-after-idle-periods.md +6 -0
- package/corpus/templates/calendar/netlify.toml +2 -0
- package/corpus/templates/chat/netlify.toml +2 -0
- package/corpus/templates/clips/app/components/player/player-controls.tsx +3 -3
- package/corpus/templates/clips/app/components/player/reaction-emojis.ts +12 -0
- package/corpus/templates/clips/app/components/player/reactions-tray.tsx +90 -9
- package/corpus/templates/clips/app/components/player/scrubber.tsx +43 -10
- package/corpus/templates/clips/app/components/player/video-player.tsx +105 -15
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +1 -2
- package/corpus/templates/clips/app/routes/_app.wispr.tsx +2 -1
- package/corpus/templates/clips/app/routes/_index.tsx +7 -3
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +13 -8
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +15 -8
- package/corpus/templates/clips/netlify.toml +2 -0
- package/corpus/templates/clips/server/routes/[...page].head.ts +5 -0
- package/corpus/templates/content/netlify.toml +2 -0
- package/corpus/templates/design/.agents/skills/design-editor-architecture/SKILL.md +103 -0
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +2 -1
- package/corpus/templates/design/AGENTS.md +3 -1
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +3 -2
- package/corpus/templates/design/app/components/design/editor/AgentNativeMenuMark.tsx +25 -0
- package/corpus/templates/design/app/components/design/editor/DesignBottomToolbar.tsx +411 -0
- package/corpus/templates/design/app/components/design/editor/DesignCollaborators.tsx +153 -0
- package/corpus/templates/design/app/components/design/editor/DesignWorkspaceRail.tsx +231 -0
- package/corpus/templates/design/app/components/design/editor/MakeRealDialog.tsx +223 -0
- package/corpus/templates/design/app/components/design/editor/PendingScreenDeletionDialog.tsx +58 -0
- package/corpus/templates/design/app/components/design/editor/PendingVisualStyleWarningDialog.tsx +56 -0
- package/corpus/templates/design/app/components/design/editor/ReadOnlyEditorPanel.tsx +23 -0
- package/corpus/templates/design/app/components/design/editor/SaveTemplateDialog.tsx +161 -0
- package/corpus/templates/design/app/components/design/editor/toolbar-controls.tsx +185 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +20913 -0
- package/corpus/templates/design/app/pages/design-editor/command-types.ts +120 -0
- package/corpus/templates/design/app/pages/design-editor/commands/README.md +193 -0
- package/corpus/templates/design/app/pages/design-editor/commands/add-auto-layout.ts +416 -0
- package/corpus/templates/design/app/pages/design-editor/commands/add-screen.ts +112 -0
- package/corpus/templates/design/app/pages/design-editor/commands/align-selection.ts +183 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +243 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +236 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-local-content-update.ts +304 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-pending-visual-styles-with-agent.ts +376 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-to-source.ts +132 -0
- package/corpus/templates/design/app/pages/design-editor/commands/can-move-layer.ts +117 -0
- package/corpus/templates/design/app/pages/design-editor/commands/change-selected-z-index.ts +212 -0
- package/corpus/templates/design/app/pages/design-editor/commands/commit-relative-style-delta-to-selected-layers.ts +176 -0
- package/corpus/templates/design/app/pages/design-editor/commands/commit-styles-to-selected-layers.ts +175 -0
- package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +710 -0
- package/corpus/templates/design/app/pages/design-editor/commands/confirm-make-real.ts +99 -0
- package/corpus/templates/design/app/pages/design-editor/commands/copy-as-figma-svg.ts +95 -0
- package/corpus/templates/design/app/pages/design-editor/commands/copy-selection.ts +156 -0
- package/corpus/templates/design/app/pages/design-editor/commands/create-primitive.ts +291 -0
- package/corpus/templates/design/app/pages/design-editor/commands/create-screen-frame.ts +131 -0
- package/corpus/templates/design/app/pages/design-editor/commands/cross-screen-element-drop.ts +577 -0
- package/corpus/templates/design/app/pages/design-editor/commands/delete-files.ts +364 -0
- package/corpus/templates/design/app/pages/design-editor/commands/delete-selection.ts +521 -0
- package/corpus/templates/design/app/pages/design-editor/commands/detach-instance-menu-action.ts +88 -0
- package/corpus/templates/design/app/pages/design-editor/commands/distribute-selection.ts +114 -0
- package/corpus/templates/design/app/pages/design-editor/commands/download-all-screens-pdf.ts +116 -0
- package/corpus/templates/design/app/pages/design-editor/commands/download-pdf.ts +103 -0
- package/corpus/templates/design/app/pages/design-editor/commands/download-svg.ts +159 -0
- package/corpus/templates/design/app/pages/design-editor/commands/duplicate-screen.ts +201 -0
- package/corpus/templates/design/app/pages/design-editor/commands/duplicate-selection.ts +279 -0
- package/corpus/templates/design/app/pages/design-editor/commands/editor-paste.ts +81 -0
- package/corpus/templates/design/app/pages/design-editor/commands/enter-hotkey.ts +155 -0
- package/corpus/templates/design/app/pages/design-editor/commands/enter-single-screen.ts +168 -0
- package/corpus/templates/design/app/pages/design-editor/commands/escape-hotkey.ts +212 -0
- package/corpus/templates/design/app/pages/design-editor/commands/frame-selection.ts +119 -0
- package/corpus/templates/design/app/pages/design-editor/commands/geometry-commit.ts +169 -0
- package/corpus/templates/design/app/pages/design-editor/commands/get-selected-layer-snapshots.ts +177 -0
- package/corpus/templates/design/app/pages/design-editor/commands/group-selection.ts +127 -0
- package/corpus/templates/design/app/pages/design-editor/commands/iframe-context-menu.ts +121 -0
- package/corpus/templates/design/app/pages/design-editor/commands/import-figma-clipboard-into-design.ts +119 -0
- package/corpus/templates/design/app/pages/design-editor/commands/layer-marquee-selection-change.ts +163 -0
- package/corpus/templates/design/app/pages/design-editor/commands/layer-move-to-screen.ts +331 -0
- package/corpus/templates/design/app/pages/design-editor/commands/layer-move.ts +517 -0
- package/corpus/templates/design/app/pages/design-editor/commands/layer-rename.ts +247 -0
- package/corpus/templates/design/app/pages/design-editor/commands/layer-selection-change.ts +161 -0
- package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +122 -0
- package/corpus/templates/design/app/pages/design-editor/commands/nudge-selection.ts +213 -0
- package/corpus/templates/design/app/pages/design-editor/commands/overview-primitive-reparent.ts +318 -0
- package/corpus/templates/design/app/pages/design-editor/commands/paste-copied-screens.ts +114 -0
- package/corpus/templates/design/app/pages/design-editor/commands/paste-over-selection.ts +70 -0
- package/corpus/templates/design/app/pages/design-editor/commands/paste-selection.ts +436 -0
- package/corpus/templates/design/app/pages/design-editor/commands/paste-to-replace.ts +135 -0
- package/corpus/templates/design/app/pages/design-editor/commands/pasted-image-files.ts +209 -0
- package/corpus/templates/design/app/pages/design-editor/commands/persist-frame-geometry-save.ts +113 -0
- package/corpus/templates/design/app/pages/design-editor/commands/persist-tweak-save.ts +160 -0
- package/corpus/templates/design/app/pages/design-editor/commands/primitive-created.ts +168 -0
- package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-layer-state-edit.ts +149 -0
- package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-structure-edit.ts +200 -0
- package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-text-edit.ts +152 -0
- package/corpus/templates/design/app/pages/design-editor/commands/record-pending-visual-style-edit.ts +247 -0
- package/corpus/templates/design/app/pages/design-editor/commands/redo.ts +1086 -0
- package/corpus/templates/design/app/pages/design-editor/commands/render-png-blob.ts +200 -0
- package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +207 -0
- package/corpus/templates/design/app/pages/design-editor/commands/screen-element-select.ts +262 -0
- package/corpus/templates/design/app/pages/design-editor/commands/screen-text-content-change.ts +216 -0
- package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-duplicate-change.ts +108 -0
- package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-structure-change.ts +280 -0
- package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-style-change.ts +151 -0
- package/corpus/templates/design/app/pages/design-editor/commands/send-overview-annotations.ts +88 -0
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-move-semantic-handoff.ts +122 -0
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-semantic-handoff.ts +143 -0
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-state-semantic-handoff.ts +115 -0
- package/corpus/templates/design/app/pages/design-editor/commands/start-retry-generation.ts +152 -0
- package/corpus/templates/design/app/pages/design-editor/commands/start-sidebar-resize.ts +99 -0
- package/corpus/templates/design/app/pages/design-editor/commands/style-change.ts +152 -0
- package/corpus/templates/design/app/pages/design-editor/commands/styles-change.ts +155 -0
- package/corpus/templates/design/app/pages/design-editor/commands/suggest-auto-layout.ts +125 -0
- package/corpus/templates/design/app/pages/design-editor/commands/text-content-change.ts +201 -0
- package/corpus/templates/design/app/pages/design-editor/commands/tidy-up.ts +123 -0
- package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-hidden.ts +167 -0
- package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-locked.ts +175 -0
- package/corpus/templates/design/app/pages/design-editor/commands/toggle-motion-keyframe.ts +99 -0
- package/corpus/templates/design/app/pages/design-editor/commands/tweak-prompt-submit.ts +84 -0
- package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +906 -0
- package/corpus/templates/design/app/pages/design-editor/commands/ungroup-selection.ts +170 -0
- package/corpus/templates/design/app/pages/design-editor/commands/visual-duplicate-change.ts +119 -0
- package/corpus/templates/design/app/pages/design-editor/commands/visual-structure-change.ts +274 -0
- package/corpus/templates/design/app/pages/design-editor/commands/write-frame-geometry-snapshot.ts +99 -0
- package/corpus/templates/design/app/pages/design-editor/derive/design-breakpoints.ts +56 -0
- package/corpus/templates/design/app/pages/design-editor/derive/overview-screens.ts +146 -0
- package/corpus/templates/design/app/pages/design-editor/domains/use-tweaks.ts +326 -0
- package/corpus/templates/design/app/pages/design-editor/editor-constants.ts +54 -0
- package/corpus/templates/design/app/pages/design-editor/editor-helpers.ts +183 -0
- package/corpus/templates/design/app/pages/design-editor/effects/adopt-db-file-content.ts +270 -0
- package/corpus/templates/design/app/pages/design-editor/effects/mirror-selection-to-agent-chat.ts +134 -0
- package/corpus/templates/design/app/pages/design-editor/effects/motion-autosave.ts +335 -0
- package/corpus/templates/design/app/pages/design-editor/effects/observe-collab-text.ts +183 -0
- package/corpus/templates/design/app/pages/design-editor/effects/publish-agent-selection-context.ts +252 -0
- package/corpus/templates/design/app/pages/design-editor/effects/resume-pending-generation.ts +178 -0
- package/corpus/templates/design/app/pages/design-editor/effects/seed-collab-content.ts +157 -0
- package/corpus/templates/design/app/pages/design-editor/preview-html.ts +19 -0
- package/corpus/templates/design/app/routes/examples.tsx +2 -1
- package/corpus/templates/design/netlify.toml +2 -0
- package/corpus/templates/design/shared/board-file.ts +2 -1
- package/corpus/templates/dispatch/netlify.toml +2 -0
- package/corpus/templates/forms/app/routes/_index.tsx +3 -2
- package/corpus/templates/forms/netlify.toml +2 -0
- package/corpus/templates/mail/app/routes/_index.tsx +3 -2
- package/corpus/templates/mail/netlify.toml +2 -0
- package/corpus/templates/plan/netlify.toml +2 -0
- package/corpus/templates/slides/netlify.toml +2 -0
- package/corpus/templates/tasks/app/routes/_index.tsx +2 -1
- package/dist/agent/engine/builder-engine.d.ts +6 -4
- package/dist/agent/engine/builder-engine.js +91 -26
- package/dist/agent/engine/registry.js +16 -0
- package/dist/cli/code-agent-agent-tools.d.ts +2 -0
- package/dist/cli/code-agent-agent-tools.js +188 -0
- package/dist/cli/code-agent-collaboration.d.ts +34 -0
- package/dist/cli/code-agent-collaboration.js +104 -0
- package/dist/cli/code-agent-executor.js +6 -0
- package/dist/cli/code-agent-schedules.d.ts +54 -0
- package/dist/cli/code-agent-schedules.js +198 -0
- package/dist/client/AgentPanel.js +1 -1
- package/dist/client/ConnectBuilderCard.js +5 -2
- package/dist/client/EnvironmentBadge.js +6 -5
- package/dist/client/chat/run-recovery.js +3 -2
- package/dist/client/chat/tool-call-display.js +2 -3
- package/dist/client/components/CodeRequiredDialog.js +4 -2
- package/dist/client/i18n.js +5 -4
- package/dist/client/settings/SettingsPanel.js +1 -1
- package/dist/client/settings/VoiceTranscriptionSection.js +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts +6 -1
- package/dist/client/settings/useBuilderStatus.js +22 -33
- package/dist/client/transcription/BuilderTranscriptionCta.js +1 -1
- package/dist/code-agents/index.d.ts +2 -0
- package/dist/code-agents/index.js +2 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/deploy/build.js +10 -4
- package/dist/localization/shared.d.ts +2 -0
- package/dist/localization/shared.js +5 -0
- package/dist/mcp-client/oauth-client.d.ts +1 -0
- package/dist/mcp-client/oauth-client.js +50 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +1 -1
- package/dist/provider-api/actions/provider-api.d.ts +2 -2
- package/dist/server/auth-marketing-locales.d.ts +7 -0
- package/dist/server/auth-marketing-locales.js +1322 -0
- package/dist/server/auth-marketing.d.ts +1 -0
- package/dist/server/auth-marketing.js +35 -6
- package/dist/server/auth.js +4 -17
- package/dist/server/builder-browser.d.ts +22 -0
- package/dist/server/builder-browser.js +77 -1
- package/dist/server/builder-oauth.d.ts +50 -0
- package/dist/server/builder-oauth.js +263 -0
- package/dist/server/core-routes-plugin.d.ts +11 -0
- package/dist/server/core-routes-plugin.js +291 -415
- package/dist/server/credential-provider.d.ts +1 -1
- package/dist/server/credential-provider.js +1 -1
- package/dist/server/csrf.js +2 -2
- package/dist/server/onboarding-html.js +52 -120
- package/dist/server/ssr-handler.js +13 -4
- package/dist/settings/store.d.ts +9 -0
- package/dist/settings/store.js +19 -0
- package/dist/shared/cache-control.d.ts +22 -0
- package/dist/shared/cache-control.js +25 -0
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +1 -0
- package/dist/templates/chat/netlify.toml +2 -0
- package/docs/content/deployment.mdx +2 -0
- package/docs/content/multi-app-workspace.mdx +1 -1
- package/package.json +2 -2
- package/src/templates/chat/netlify.toml +2 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
|
|
3
3
|
import { signA2AToken } from "@agent-native/core/a2a";
|
|
4
|
+
import { isFeatureFlagEnabled } from "@agent-native/core/feature-flags";
|
|
4
5
|
import { fetchOrgApps, type OrgApp } from "@agent-native/core/mcp";
|
|
5
6
|
import { getOrgDomain } from "@agent-native/core/org";
|
|
6
7
|
|
|
8
|
+
import { VERIFIED_FLEET_FLAG_MUTATIONS } from "../../shared/feature-flags.js";
|
|
7
9
|
import type { AnalyticsAdminContext } from "./db-admin-connections.js";
|
|
8
10
|
|
|
9
11
|
const TARGET_TIMEOUT_MS = 3_000;
|
|
@@ -31,7 +33,16 @@ export interface WorkspaceFeatureFlagsResult {
|
|
|
31
33
|
apps: FleetFlagApp[];
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
export interface
|
|
36
|
+
export interface VerifiedWorkspaceFeatureFlagMutationResult {
|
|
37
|
+
contractVersion: 3;
|
|
38
|
+
status: "verified";
|
|
39
|
+
key: string;
|
|
40
|
+
rules: Record<string, unknown>;
|
|
41
|
+
scope: { orgId: string | null; orgDomain: string | null };
|
|
42
|
+
enabledForCurrentUser: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface TargetFeatureFlagMutationResult {
|
|
35
46
|
contractVersion: 2;
|
|
36
47
|
status: "ready";
|
|
37
48
|
key: string;
|
|
@@ -39,6 +50,10 @@ export interface WorkspaceFeatureFlagMutationResult {
|
|
|
39
50
|
scope: { orgId: string | null; orgDomain: string | null };
|
|
40
51
|
}
|
|
41
52
|
|
|
53
|
+
export type WorkspaceFeatureFlagMutationResult =
|
|
54
|
+
| TargetFeatureFlagMutationResult
|
|
55
|
+
| VerifiedWorkspaceFeatureFlagMutationResult;
|
|
56
|
+
|
|
42
57
|
export interface WorkspaceFeatureFlagMutationInput {
|
|
43
58
|
appId: string;
|
|
44
59
|
key: string;
|
|
@@ -72,8 +87,8 @@ export function validateWorkspaceFeatureFlagMutation(
|
|
|
72
87
|
rules?: Record<string, unknown>;
|
|
73
88
|
enabledForEmail?: string;
|
|
74
89
|
},
|
|
75
|
-
):
|
|
76
|
-
const payload = body as Partial<
|
|
90
|
+
): TargetFeatureFlagMutationResult {
|
|
91
|
+
const payload = body as Partial<TargetFeatureFlagMutationResult> | null;
|
|
77
92
|
const valid =
|
|
78
93
|
payload?.contractVersion === 2 &&
|
|
79
94
|
payload.status === "ready" &&
|
|
@@ -144,7 +159,7 @@ export function validateWorkspaceFeatureFlagMutation(
|
|
|
144
159
|
"The target app did not enable the feature flag for the delegated operator.",
|
|
145
160
|
);
|
|
146
161
|
}
|
|
147
|
-
return payload as
|
|
162
|
+
return payload as TargetFeatureFlagMutationResult;
|
|
148
163
|
}
|
|
149
164
|
|
|
150
165
|
function targetOrigin(app: OrgApp): string {
|
|
@@ -173,6 +188,39 @@ async function delegatedToken(
|
|
|
173
188
|
}
|
|
174
189
|
}
|
|
175
190
|
|
|
191
|
+
export type WorkspaceFeatureFlagFailurePhase =
|
|
192
|
+
| "directory"
|
|
193
|
+
| "token-generation"
|
|
194
|
+
| "timeout"
|
|
195
|
+
| "network"
|
|
196
|
+
| "authorization"
|
|
197
|
+
| "unsupported-target"
|
|
198
|
+
| "target-action"
|
|
199
|
+
| "persistence"
|
|
200
|
+
| "verification";
|
|
201
|
+
|
|
202
|
+
const FAILURE_MESSAGES: Record<WorkspaceFeatureFlagFailurePhase, string> = {
|
|
203
|
+
directory: "The organization app directory could not resolve this target.",
|
|
204
|
+
"token-generation":
|
|
205
|
+
"Analytics could not create delegated feature flag authority.",
|
|
206
|
+
timeout: "The target app timed out before the feature flag change completed.",
|
|
207
|
+
network: "Analytics could not reach the target app.",
|
|
208
|
+
authorization: "The target app denied this delegated flag operation.",
|
|
209
|
+
"unsupported-target":
|
|
210
|
+
"The target app does not support feature flag management.",
|
|
211
|
+
"target-action": "The target app could not complete the feature flag action.",
|
|
212
|
+
persistence:
|
|
213
|
+
"The target app did not persist the requested feature flag rules.",
|
|
214
|
+
verification: "Analytics could not verify the persisted feature flag change.",
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
export class WorkspaceFeatureFlagFailure extends Error {
|
|
218
|
+
constructor(readonly phase: WorkspaceFeatureFlagFailurePhase) {
|
|
219
|
+
super(`[${phase}] ${FAILURE_MESSAGES[phase]}`);
|
|
220
|
+
this.name = "WorkspaceFeatureFlagFailure";
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
176
224
|
type TargetFailureReason = "token-generation" | "timeout" | "network";
|
|
177
225
|
|
|
178
226
|
class TargetCallFailure extends Error {
|
|
@@ -194,6 +242,30 @@ export function classifyWorkspaceFeatureFlagTargetFailure(
|
|
|
194
242
|
return "network";
|
|
195
243
|
}
|
|
196
244
|
|
|
245
|
+
function targetFailure(error: unknown): WorkspaceFeatureFlagFailure {
|
|
246
|
+
const reason = classifyWorkspaceFeatureFlagTargetFailure(error);
|
|
247
|
+
return new WorkspaceFeatureFlagFailure(reason);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function resolveTargetApp(
|
|
251
|
+
admin: AnalyticsAdminContext,
|
|
252
|
+
appId: string,
|
|
253
|
+
): Promise<OrgApp> {
|
|
254
|
+
let apps: OrgApp[];
|
|
255
|
+
try {
|
|
256
|
+
apps = await fetchOrgApps({
|
|
257
|
+
selfId: "analytics",
|
|
258
|
+
includeDirectoryApp: true,
|
|
259
|
+
serviceOrgId: admin.orgId,
|
|
260
|
+
});
|
|
261
|
+
} catch {
|
|
262
|
+
throw new WorkspaceFeatureFlagFailure("directory");
|
|
263
|
+
}
|
|
264
|
+
const app = apps.find((candidate) => candidate.id === appId);
|
|
265
|
+
if (!app) throw new WorkspaceFeatureFlagFailure("directory");
|
|
266
|
+
return app;
|
|
267
|
+
}
|
|
268
|
+
|
|
197
269
|
async function callTarget(
|
|
198
270
|
app: OrgApp,
|
|
199
271
|
admin: AnalyticsAdminContext,
|
|
@@ -326,63 +398,139 @@ export async function setWorkspaceFeatureFlag(
|
|
|
326
398
|
admin: AnalyticsAdminContext,
|
|
327
399
|
input: WorkspaceFeatureFlagMutationInput,
|
|
328
400
|
): Promise<WorkspaceFeatureFlagMutationResult> {
|
|
329
|
-
const
|
|
330
|
-
selfId: "analytics",
|
|
331
|
-
includeDirectoryApp: true,
|
|
332
|
-
serviceOrgId: admin.orgId,
|
|
333
|
-
});
|
|
334
|
-
const app = apps.find((candidate) => candidate.id === input.appId);
|
|
335
|
-
if (!app)
|
|
336
|
-
throw new Error(
|
|
337
|
-
"The requested app is not available in this organization directory.",
|
|
338
|
-
);
|
|
401
|
+
const app = await resolveTargetApp(admin, input.appId);
|
|
339
402
|
const targetInput = workspaceFeatureFlagTargetInput(input);
|
|
340
403
|
let orgDomain: string | undefined;
|
|
341
404
|
try {
|
|
342
405
|
orgDomain = (await getOrgDomain(admin.orgId))?.trim().toLowerCase();
|
|
343
406
|
} catch {
|
|
344
|
-
throw new
|
|
407
|
+
throw new WorkspaceFeatureFlagFailure("token-generation");
|
|
408
|
+
}
|
|
409
|
+
if (!orgDomain) throw new WorkspaceFeatureFlagFailure("token-generation");
|
|
410
|
+
let result: Awaited<ReturnType<typeof callTarget>>;
|
|
411
|
+
try {
|
|
412
|
+
result = await callTarget(
|
|
413
|
+
app,
|
|
414
|
+
admin,
|
|
415
|
+
"set-feature-flag",
|
|
416
|
+
targetInput,
|
|
417
|
+
orgDomain,
|
|
418
|
+
);
|
|
419
|
+
} catch (error) {
|
|
420
|
+
throw targetFailure(error);
|
|
345
421
|
}
|
|
346
|
-
if (!orgDomain) throw new TargetCallFailure("token-generation");
|
|
347
|
-
const result = await callTarget(
|
|
348
|
-
app,
|
|
349
|
-
admin,
|
|
350
|
-
"set-feature-flag",
|
|
351
|
-
targetInput,
|
|
352
|
-
orgDomain,
|
|
353
|
-
);
|
|
354
422
|
if (result.status === 401 || result.status === 403)
|
|
355
|
-
throw new
|
|
423
|
+
throw new WorkspaceFeatureFlagFailure("authorization");
|
|
356
424
|
if (result.status === 404 || result.status === 405)
|
|
357
|
-
throw new
|
|
425
|
+
throw new WorkspaceFeatureFlagFailure("unsupported-target");
|
|
358
426
|
if (result.status < 200 || result.status >= 300)
|
|
359
|
-
throw new
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
? {
|
|
368
|
-
rules: {
|
|
369
|
-
mode: input.rules.mode,
|
|
370
|
-
emails: input.rules.emails ?? [],
|
|
371
|
-
orgIds: input.rules.orgIds ?? [],
|
|
372
|
-
percentage: input.rules.percentage ?? 0,
|
|
373
|
-
},
|
|
374
|
-
}
|
|
375
|
-
: input.operation === "off"
|
|
427
|
+
throw new WorkspaceFeatureFlagFailure("target-action");
|
|
428
|
+
let mutation: TargetFeatureFlagMutationResult;
|
|
429
|
+
try {
|
|
430
|
+
mutation = validateWorkspaceFeatureFlagMutation(result.body, {
|
|
431
|
+
key: input.key,
|
|
432
|
+
orgDomain,
|
|
433
|
+
allowExplicitNoOrgTarget: true,
|
|
434
|
+
...(input.operation === "replace-rules" && input.rules
|
|
376
435
|
? {
|
|
377
436
|
rules: {
|
|
378
|
-
mode:
|
|
379
|
-
emails: [],
|
|
380
|
-
orgIds: [],
|
|
381
|
-
percentage: 0,
|
|
437
|
+
mode: input.rules.mode,
|
|
438
|
+
emails: input.rules.emails ?? [],
|
|
439
|
+
orgIds: input.rules.orgIds ?? [],
|
|
440
|
+
percentage: input.rules.percentage ?? 0,
|
|
382
441
|
},
|
|
383
442
|
}
|
|
384
|
-
:
|
|
385
|
-
|
|
443
|
+
: input.operation === "off"
|
|
444
|
+
? {
|
|
445
|
+
rules: {
|
|
446
|
+
mode: "off",
|
|
447
|
+
emails: [],
|
|
448
|
+
orgIds: [],
|
|
449
|
+
percentage: 0,
|
|
450
|
+
},
|
|
451
|
+
}
|
|
452
|
+
: { enabledForEmail: admin.userEmail }),
|
|
453
|
+
});
|
|
454
|
+
} catch {
|
|
455
|
+
throw new WorkspaceFeatureFlagFailure("persistence");
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (
|
|
459
|
+
!(await isFeatureFlagEnabled(VERIFIED_FLEET_FLAG_MUTATIONS, {
|
|
460
|
+
userEmail: admin.userEmail,
|
|
461
|
+
userKey: admin.userEmail,
|
|
462
|
+
orgId: admin.orgId,
|
|
463
|
+
}))
|
|
464
|
+
) {
|
|
465
|
+
return mutation;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
let readBack: Awaited<ReturnType<typeof callTarget>>;
|
|
469
|
+
try {
|
|
470
|
+
readBack = await callTarget(
|
|
471
|
+
app,
|
|
472
|
+
admin,
|
|
473
|
+
"list-feature-flags",
|
|
474
|
+
{},
|
|
475
|
+
orgDomain,
|
|
476
|
+
);
|
|
477
|
+
} catch (error) {
|
|
478
|
+
throw targetFailure(error);
|
|
479
|
+
}
|
|
480
|
+
if (readBack.status === 401 || readBack.status === 403)
|
|
481
|
+
throw new WorkspaceFeatureFlagFailure("authorization");
|
|
482
|
+
if (readBack.status === 404 || readBack.status === 405)
|
|
483
|
+
throw new WorkspaceFeatureFlagFailure("unsupported-target");
|
|
484
|
+
if (readBack.status < 200 || readBack.status >= 300)
|
|
485
|
+
throw new WorkspaceFeatureFlagFailure("verification");
|
|
486
|
+
const verifiedApp = classifyWorkspaceFeatureFlagList(app, readBack);
|
|
487
|
+
if (verifiedApp.state === "forbidden")
|
|
488
|
+
throw new WorkspaceFeatureFlagFailure("authorization");
|
|
489
|
+
if (verifiedApp.state === "unsupported")
|
|
490
|
+
throw new WorkspaceFeatureFlagFailure("unsupported-target");
|
|
491
|
+
const verifiedFlag = verifiedApp.flags.find((flag) => flag.key === input.key);
|
|
492
|
+
const expectedEnabled =
|
|
493
|
+
input.operation === "enable-for-current-user"
|
|
494
|
+
? true
|
|
495
|
+
: input.operation === "off"
|
|
496
|
+
? false
|
|
497
|
+
: null;
|
|
498
|
+
if (
|
|
499
|
+
verifiedApp.state !== "ready" ||
|
|
500
|
+
!verifiedFlag ||
|
|
501
|
+
typeof verifiedFlag.enabledForCurrentUser !== "boolean" ||
|
|
502
|
+
(expectedEnabled !== null &&
|
|
503
|
+
verifiedFlag.enabledForCurrentUser !== expectedEnabled)
|
|
504
|
+
) {
|
|
505
|
+
throw new WorkspaceFeatureFlagFailure("verification");
|
|
506
|
+
}
|
|
507
|
+
try {
|
|
508
|
+
validateWorkspaceFeatureFlagMutation(
|
|
509
|
+
{
|
|
510
|
+
contractVersion: 2,
|
|
511
|
+
status: "ready",
|
|
512
|
+
key: input.key,
|
|
513
|
+
rules: verifiedFlag.rules,
|
|
514
|
+
scope: mutation.scope,
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
key: input.key,
|
|
518
|
+
orgDomain,
|
|
519
|
+
allowExplicitNoOrgTarget: true,
|
|
520
|
+
rules: mutation.rules,
|
|
521
|
+
},
|
|
522
|
+
);
|
|
523
|
+
} catch {
|
|
524
|
+
throw new WorkspaceFeatureFlagFailure("verification");
|
|
525
|
+
}
|
|
526
|
+
return {
|
|
527
|
+
contractVersion: 3,
|
|
528
|
+
status: "verified",
|
|
529
|
+
key: mutation.key,
|
|
530
|
+
rules: verifiedFlag.rules as Record<string, unknown>,
|
|
531
|
+
scope: mutation.scope,
|
|
532
|
+
enabledForCurrentUser: verifiedFlag.enabledForCurrentUser,
|
|
533
|
+
};
|
|
386
534
|
}
|
|
387
535
|
|
|
388
536
|
export async function getWorkspaceFlagTarget(
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { defineFeatureFlag } from "@agent-native/core/feature-flags/registry";
|
|
2
|
+
|
|
3
|
+
export const VERIFIED_FLEET_FLAG_MUTATIONS = defineFeatureFlag({
|
|
4
|
+
key: "analytics.verified-fleet-flag-mutations",
|
|
5
|
+
displayName: "Verified fleet flag mutations",
|
|
6
|
+
description:
|
|
7
|
+
"Verify cross-app feature flag changes with an independent target read-back.",
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const ANALYTICS_FEATURE_FLAGS = [VERIFIED_FLEET_FLAG_MUTATIONS];
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "@agent-native/core/client/hooks";
|
|
12
12
|
import { useT } from "@agent-native/core/client/i18n";
|
|
13
13
|
import { ShareButton } from "@agent-native/core/client/sharing";
|
|
14
|
+
import { withSsrHtmlContentType } from "@agent-native/core/shared";
|
|
14
15
|
import { CreativeContextShareSheet } from "@agent-native/creative-context/client";
|
|
15
16
|
import {
|
|
16
17
|
IconCheck,
|
|
@@ -311,7 +312,10 @@ function assetUpdatedTime(asset: any): number {
|
|
|
311
312
|
|
|
312
313
|
export function loader({ params, request }: LoaderFunctionArgs) {
|
|
313
314
|
const url = new URL(request.url);
|
|
314
|
-
return
|
|
315
|
+
return withSsrHtmlContentType(
|
|
316
|
+
redirect(`/library/${params.id}${url.search}`),
|
|
317
|
+
{ varyByQuery: true },
|
|
318
|
+
);
|
|
315
319
|
}
|
|
316
320
|
|
|
317
321
|
export default function BrandKitDetailRedirect() {
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { withSsrHtmlContentType } from "@agent-native/core/shared";
|
|
1
2
|
import { redirect, type LoaderFunctionArgs } from "react-router";
|
|
2
3
|
|
|
3
4
|
export function loader({ request }: LoaderFunctionArgs) {
|
|
4
5
|
const url = new URL(request.url);
|
|
5
|
-
return redirect(`/library${url.search}`)
|
|
6
|
+
return withSsrHtmlContentType(redirect(`/library${url.search}`), {
|
|
7
|
+
varyByQuery: true,
|
|
8
|
+
});
|
|
6
9
|
}
|
|
7
10
|
|
|
8
11
|
export default function BrandKitsIndexRedirect() {
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { withSsrHtmlContentType } from "@agent-native/core/shared";
|
|
1
2
|
import { redirect, type LoaderFunctionArgs } from "react-router";
|
|
2
3
|
|
|
3
4
|
// Legacy redirect: brand containers now live in the unified Library workspace.
|
|
4
5
|
export function loader({ request }: LoaderFunctionArgs) {
|
|
5
6
|
const url = new URL(request.url);
|
|
6
|
-
return redirect(`/library${url.search}`)
|
|
7
|
+
return withSsrHtmlContentType(redirect(`/library${url.search}`), {
|
|
8
|
+
varyByQuery: true,
|
|
9
|
+
});
|
|
7
10
|
}
|
|
8
11
|
|
|
9
12
|
export default function LibrariesRedirect() {
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { withSsrHtmlContentType } from "@agent-native/core/shared";
|
|
1
2
|
import { redirect, type LoaderFunctionArgs } from "react-router";
|
|
2
3
|
|
|
3
4
|
// Legacy redirect: the image browser moved from "Picker" (/picker) to
|
|
4
5
|
// "Library" (/library). Preserve any query string so deep links keep working.
|
|
5
6
|
export function loader({ url }: LoaderFunctionArgs) {
|
|
6
|
-
return redirect(`/library${url.search}`)
|
|
7
|
+
return withSsrHtmlContentType(redirect(`/library${url.search}`), {
|
|
8
|
+
varyByQuery: true,
|
|
9
|
+
});
|
|
7
10
|
}
|
|
8
11
|
|
|
9
12
|
export default function PickerRedirect() {
|
|
@@ -13,6 +13,8 @@ AGENT_CHAT_DURABLE_BACKGROUND = "true"
|
|
|
13
13
|
|
|
14
14
|
[context.production.environment]
|
|
15
15
|
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
16
|
+
AGENT_NATIVE_ENABLE_KEEP_WARM = "1"
|
|
17
|
+
AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND = "1"
|
|
16
18
|
|
|
17
19
|
# Emit the Netlify background function used by Assets' durable agent-chat opt-in.
|
|
18
20
|
# The normal server timeout still protects non-agent image/video HTTP handlers.
|
|
@@ -12,6 +12,8 @@ NPM_CONFIG_PRODUCTION = "false"
|
|
|
12
12
|
|
|
13
13
|
[context.production.environment]
|
|
14
14
|
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
15
|
+
AGENT_NATIVE_ENABLE_KEEP_WARM = "1"
|
|
16
|
+
AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND = "1"
|
|
15
17
|
|
|
16
18
|
# A2A delegation calls run a full agent loop (LLM + tool calls + DB queries).
|
|
17
19
|
# The framework does not impose a default agent-run timeout; if a template sets
|
|
@@ -981,7 +981,8 @@ export const DayView = memo(function DayView({
|
|
|
981
981
|
{/* Positioned events overlay */}
|
|
982
982
|
<div
|
|
983
983
|
data-calendar-create-surface="true"
|
|
984
|
-
className="absolute
|
|
984
|
+
className="absolute left-0 right-0 top-0 ml-[40px] mr-2 sm:ml-[56px] sm:mr-4"
|
|
985
|
+
style={{ height: `${hours.length * HOUR_HEIGHT}px` }}
|
|
985
986
|
onPointerDown={(e) => {
|
|
986
987
|
// Only start a create-drag from empty space, not on an event or its resize handles
|
|
987
988
|
if ((e.target as HTMLElement).closest("button")) return;
|
|
@@ -12,6 +12,8 @@ NPM_CONFIG_PRODUCTION = "false"
|
|
|
12
12
|
|
|
13
13
|
[context.production.environment]
|
|
14
14
|
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
15
|
+
AGENT_NATIVE_ENABLE_KEEP_WARM = "1"
|
|
16
|
+
AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND = "1"
|
|
15
17
|
|
|
16
18
|
# A2A delegation calls run a full agent loop (LLM + tool calls + DB queries).
|
|
17
19
|
# The framework does not impose a default agent-run timeout; if a template sets
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
import { PLAYBACK_SPEED_OPTIONS } from "@/lib/playback-speed";
|
|
35
35
|
import { cn } from "@/lib/utils";
|
|
36
36
|
|
|
37
|
-
import { ReactionsTray } from "./reactions-tray";
|
|
37
|
+
import { ReactionsTray, type ReactionHandler } from "./reactions-tray";
|
|
38
38
|
import { Scrubber, msToClock } from "./scrubber";
|
|
39
39
|
|
|
40
40
|
export const SPEED_OPTIONS = PLAYBACK_SPEED_OPTIONS;
|
|
@@ -73,7 +73,7 @@ export interface PlayerControlsProps {
|
|
|
73
73
|
*/
|
|
74
74
|
showReactionsAndComment?: boolean;
|
|
75
75
|
enableReactions?: boolean;
|
|
76
|
-
onReact?:
|
|
76
|
+
onReact?: ReactionHandler;
|
|
77
77
|
enableComments?: boolean;
|
|
78
78
|
onAddComment?: () => void;
|
|
79
79
|
}
|
|
@@ -306,7 +306,7 @@ export function PlayerControls(props: PlayerControlsProps) {
|
|
|
306
306
|
>
|
|
307
307
|
{enableReactions && onReact ? (
|
|
308
308
|
<div className="pointer-events-auto">
|
|
309
|
-
<ReactionsTray onReact={onReact} />
|
|
309
|
+
<ReactionsTray reactions={reactions} onReact={onReact} />
|
|
310
310
|
</div>
|
|
311
311
|
) : null}
|
|
312
312
|
|
|
@@ -8,3 +8,15 @@ export const REACTION_EMOJIS = [
|
|
|
8
8
|
"🤯",
|
|
9
9
|
"👀",
|
|
10
10
|
] as const;
|
|
11
|
+
|
|
12
|
+
export const REACTION_NAMES: Record<(typeof REACTION_EMOJIS)[number], string> =
|
|
13
|
+
{
|
|
14
|
+
"👍": "thumbs up",
|
|
15
|
+
"❤️": "heart",
|
|
16
|
+
"🔥": "fire",
|
|
17
|
+
"👏": "clap",
|
|
18
|
+
"🎉": "celebrate",
|
|
19
|
+
"😂": "laugh",
|
|
20
|
+
"🤯": "mind blown",
|
|
21
|
+
"👀": "eyes",
|
|
22
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IconCheck } from "@tabler/icons-react";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
Tooltip,
|
|
@@ -7,10 +8,20 @@ import {
|
|
|
7
8
|
} from "@/components/ui/tooltip";
|
|
8
9
|
import { cn } from "@/lib/utils";
|
|
9
10
|
|
|
10
|
-
import { REACTION_EMOJIS } from "./reaction-emojis";
|
|
11
|
+
import { REACTION_EMOJIS, REACTION_NAMES } from "./reaction-emojis";
|
|
12
|
+
|
|
13
|
+
export type ReactionHandlerResult = boolean | void | Promise<boolean | void>;
|
|
14
|
+
export type ReactionHandler = (emoji: string) => ReactionHandlerResult;
|
|
15
|
+
|
|
16
|
+
export interface ReactionSummary {
|
|
17
|
+
id: string;
|
|
18
|
+
emoji: string;
|
|
19
|
+
videoTimestampMs: number;
|
|
20
|
+
}
|
|
11
21
|
|
|
12
22
|
export interface ReactionsTrayProps {
|
|
13
|
-
onReact:
|
|
23
|
+
onReact: ReactionHandler;
|
|
24
|
+
reactions?: Pick<ReactionSummary, "emoji">[];
|
|
14
25
|
disabled?: boolean;
|
|
15
26
|
}
|
|
16
27
|
|
|
@@ -22,18 +33,62 @@ interface Float {
|
|
|
22
33
|
|
|
23
34
|
let idc = 0;
|
|
24
35
|
|
|
25
|
-
export function ReactionsTray({
|
|
36
|
+
export function ReactionsTray({
|
|
37
|
+
onReact,
|
|
38
|
+
reactions,
|
|
39
|
+
disabled,
|
|
40
|
+
}: ReactionsTrayProps) {
|
|
26
41
|
const [floats, setFloats] = useState<Float[]>([]);
|
|
42
|
+
const [savingEmoji, setSavingEmoji] = useState<string | null>(null);
|
|
43
|
+
const [savedEmoji, setSavedEmoji] = useState<string | null>(null);
|
|
44
|
+
|
|
45
|
+
const reactionCounts = useMemo(() => {
|
|
46
|
+
const counts: Partial<Record<(typeof REACTION_EMOJIS)[number], number>> =
|
|
47
|
+
{};
|
|
48
|
+
for (const reaction of reactions ?? []) {
|
|
49
|
+
if (REACTION_EMOJIS.includes(reaction.emoji as never)) {
|
|
50
|
+
const emoji = reaction.emoji as (typeof REACTION_EMOJIS)[number];
|
|
51
|
+
counts[emoji] = (counts[emoji] ?? 0) + 1;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return counts;
|
|
55
|
+
}, [reactions]);
|
|
27
56
|
|
|
28
57
|
function fire(emoji: string) {
|
|
29
|
-
if (disabled) return;
|
|
30
|
-
onReact(emoji);
|
|
58
|
+
if (disabled || savingEmoji === emoji) return;
|
|
31
59
|
const id = ++idc;
|
|
32
60
|
const left = 10 + Math.random() * 80; // random horizontal variance within tray
|
|
33
61
|
setFloats((f) => [...f, { id, emoji, left }]);
|
|
34
62
|
setTimeout(() => {
|
|
35
63
|
setFloats((f) => f.filter((x) => x.id !== id));
|
|
36
64
|
}, 2500);
|
|
65
|
+
|
|
66
|
+
let result: ReactionHandlerResult;
|
|
67
|
+
try {
|
|
68
|
+
result = onReact(emoji);
|
|
69
|
+
} catch {
|
|
70
|
+
setSavedEmoji((current) => (current === emoji ? null : current));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (result && typeof result === "object" && "then" in result) {
|
|
75
|
+
setSavingEmoji(emoji);
|
|
76
|
+
void Promise.resolve(result)
|
|
77
|
+
.then((saved) => {
|
|
78
|
+
setSavedEmoji((current) =>
|
|
79
|
+
saved === false ? (current === emoji ? null : current) : emoji,
|
|
80
|
+
);
|
|
81
|
+
})
|
|
82
|
+
.catch(() => {
|
|
83
|
+
setSavedEmoji((current) => (current === emoji ? null : current));
|
|
84
|
+
})
|
|
85
|
+
.finally(() => {
|
|
86
|
+
setSavingEmoji((current) => (current === emoji ? null : current));
|
|
87
|
+
});
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (result !== false) setSavedEmoji(emoji);
|
|
37
92
|
}
|
|
38
93
|
|
|
39
94
|
return (
|
|
@@ -42,17 +97,43 @@ export function ReactionsTray({ onReact, disabled }: ReactionsTrayProps) {
|
|
|
42
97
|
<Tooltip key={emoji}>
|
|
43
98
|
<TooltipTrigger asChild>
|
|
44
99
|
<button
|
|
100
|
+
type="button"
|
|
45
101
|
onClick={() => fire(emoji)}
|
|
46
|
-
disabled={disabled}
|
|
102
|
+
disabled={disabled || savingEmoji === emoji}
|
|
103
|
+
aria-label={
|
|
104
|
+
reactionCounts[emoji]
|
|
105
|
+
? `${REACTION_NAMES[emoji]} ${reactionCounts[emoji]}`
|
|
106
|
+
: `React with ${emoji}`
|
|
107
|
+
}
|
|
108
|
+
aria-pressed={savedEmoji === emoji}
|
|
109
|
+
data-reaction-emoji={emoji}
|
|
110
|
+
data-reaction-count={reactionCounts[emoji] ?? 0}
|
|
111
|
+
data-reaction-saved={savedEmoji === emoji}
|
|
47
112
|
className={cn(
|
|
48
|
-
"flex h-
|
|
113
|
+
"relative flex h-8 w-8 items-center justify-center rounded-full text-base transition-[background-color,transform,box-shadow] hover:scale-110 hover:bg-accent sm:h-9 sm:w-9 sm:text-xl",
|
|
114
|
+
savedEmoji === emoji &&
|
|
115
|
+
"bg-accent shadow-[0_0_0_2px_hsl(var(--primary)/0.35)]",
|
|
116
|
+
savingEmoji === emoji && "animate-pulse",
|
|
49
117
|
disabled && "opacity-50 cursor-not-allowed",
|
|
50
118
|
)}
|
|
51
119
|
>
|
|
52
120
|
{emoji}
|
|
121
|
+
{savedEmoji === emoji ? (
|
|
122
|
+
<span className="absolute -right-0.5 -top-0.5 flex h-3.5 w-3.5 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-sm">
|
|
123
|
+
<IconCheck className="h-2.5 w-2.5" stroke={3} />
|
|
124
|
+
</span>
|
|
125
|
+
) : reactionCounts[emoji] ? (
|
|
126
|
+
<span className="absolute -right-0.5 -top-0.5 min-w-3.5 rounded-full bg-muted px-0.5 text-[9px] font-semibold leading-3 text-muted-foreground shadow-sm">
|
|
127
|
+
{reactionCounts[emoji]}
|
|
128
|
+
</span>
|
|
129
|
+
) : null}
|
|
53
130
|
</button>
|
|
54
131
|
</TooltipTrigger>
|
|
55
|
-
<TooltipContent>
|
|
132
|
+
<TooltipContent>
|
|
133
|
+
{reactionCounts[emoji]
|
|
134
|
+
? `${REACTION_NAMES[emoji]} ${reactionCounts[emoji]}`
|
|
135
|
+
: `React with ${emoji}`}
|
|
136
|
+
</TooltipContent>
|
|
56
137
|
</Tooltip>
|
|
57
138
|
))}
|
|
58
139
|
|