diff --git a/packages/twenty-apps/public/call-recorder/css.d.ts b/packages/twenty-apps/public/call-recorder/css.d.ts
new file mode 100644
index 0000000000..35306c6fc9
--- /dev/null
+++ b/packages/twenty-apps/public/call-recorder/css.d.ts
@@ -0,0 +1 @@
+declare module '*.css';
diff --git a/packages/twenty-apps/public/call-recorder/package.json b/packages/twenty-apps/public/call-recorder/package.json
index 4ada0a95fe..26127ae322 100644
--- a/packages/twenty-apps/public/call-recorder/package.json
+++ b/packages/twenty-apps/public/call-recorder/package.json
@@ -34,8 +34,9 @@
"oxlint": "^0.16.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
- "twenty-client-sdk": "^2.16.0",
- "twenty-sdk": "^2.16.0",
+ "twenty-client-sdk": "^2.18.0",
+ "twenty-sdk": "^2.18.0",
+ "twenty-ui": "^1.0.0-alpha.1",
"typescript": "^5.9.3",
"vite-tsconfig-paths": "^4.2.1",
"vitest": "^4.1.9"
diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/calendar-event-recording.front-component.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/calendar-event-recording.front-component.tsx
index 76eae30dc5..d5eae72046 100644
--- a/packages/twenty-apps/public/call-recorder/src/front-components/calendar-event-recording.front-component.tsx
+++ b/packages/twenty-apps/public/call-recorder/src/front-components/calendar-event-recording.front-component.tsx
@@ -1,5 +1,7 @@
import { defineFrontComponent } from 'twenty-sdk/define';
+import 'twenty-ui/style.css';
+
import { CALENDAR_EVENT_RECORDING_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from 'src/constants/calendar-event-recording-front-component-universal-identifier';
import { CalendarEventRecording } from 'src/front-components/components/CalendarEventRecording';
diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventRecording.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventRecording.tsx
index ce05393821..586a783700 100644
--- a/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventRecording.tsx
+++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventRecording.tsx
@@ -1,20 +1,20 @@
import styled from '@emotion/styled';
import { isUndefined } from '@sniptt/guards';
import { useSelectedRecordIds } from 'twenty-sdk/front-component';
+import { themeCssVariables } from 'twenty-ui/theme-constants';
import { CalendarEventRecordingContent } from 'src/front-components/components/CalendarEventRecordingContent';
-import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
const StyledCenteredState = styled.div`
align-items: center;
box-sizing: border-box;
- color: ${recordingThemeCssVariables.font.colorTertiary};
+ color: ${() => themeCssVariables.font.color.tertiary};
display: flex;
- font-family: ${recordingThemeCssVariables.font.family};
- font-size: ${recordingThemeCssVariables.font.sizeSm};
+ font-family: ${() => themeCssVariables.font.family};
+ font-size: ${() => themeCssVariables.font.size.sm};
height: 100%;
justify-content: center;
- padding: ${recordingThemeCssVariables.spacing[4]};
+ padding: ${() => themeCssVariables.spacing[4]};
`;
export const CalendarEventRecording = () => {
diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventRecordingBody.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventRecordingBody.tsx
index 7847f43409..e537770fe9 100644
--- a/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventRecordingBody.tsx
+++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventRecordingBody.tsx
@@ -1,29 +1,30 @@
import styled from '@emotion/styled';
import { isUndefined } from '@sniptt/guards';
+import { themeCssVariables } from 'twenty-ui/theme-constants';
+import { RecordingSkeletonLoader } from 'src/front-components/components/RecordingSkeletonLoader';
import { RecordingTranscript } from 'src/front-components/components/RecordingTranscript';
import { RecordingVideoPlayer } from 'src/front-components/components/RecordingVideoPlayer';
import { TranscriptErrorBox } from 'src/front-components/components/TranscriptErrorBox';
-import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
import { type CalendarEventRecordingParticipant } from 'src/front-components/types/calendar-event-recording-participant.type';
const StyledCenteredState = styled.div`
align-items: center;
box-sizing: border-box;
- color: ${recordingThemeCssVariables.font.colorTertiary};
+ color: ${() => themeCssVariables.font.color.tertiary};
display: flex;
- font-family: ${recordingThemeCssVariables.font.family};
- font-size: ${recordingThemeCssVariables.font.sizeSm};
+ font-family: ${() => themeCssVariables.font.family};
+ font-size: ${() => themeCssVariables.font.size.sm};
justify-content: center;
min-height: 240px;
- padding: ${recordingThemeCssVariables.spacing[4]};
+ padding: ${() => themeCssVariables.spacing[4]};
`;
const StyledRecordingContainer = styled.div<{
$hasVideo?: boolean;
}>`
display: grid;
- gap: ${recordingThemeCssVariables.spacing[2]};
+ gap: ${() => themeCssVariables.spacing[2]};
grid-template-rows: ${({ $hasVideo }) =>
$hasVideo ? 'auto minmax(0, 1fr)' : 'minmax(0, 1fr)'};
min-height: 0;
@@ -37,6 +38,7 @@ type CalendarEventRecordingBodyProps = {
currentTimeSeconds: number;
calendarEventParticipants: CalendarEventRecordingParticipant[];
onVideoTimeUpdate: (videoCurrentTimeSeconds: number) => void;
+ onVideoRetry: () => void;
};
export const CalendarEventRecordingBody = ({
@@ -47,6 +49,7 @@ export const CalendarEventRecordingBody = ({
currentTimeSeconds,
calendarEventParticipants,
onVideoTimeUpdate,
+ onVideoRetry,
}: CalendarEventRecordingBodyProps) => {
const hasVideo = !isUndefined(videoFileUrl);
@@ -60,14 +63,7 @@ export const CalendarEventRecordingBody = ({
}
if (isCalendarEventRecordingQueryLoading) {
- return (
-
-
-
- );
+ return ;
}
if (isUndefined(transcript) && !hasVideo) {
@@ -84,6 +80,7 @@ export const CalendarEventRecordingBody = ({
)}
themeCssVariables.background.primary};
border: 1px solid transparent;
border-bottom: 1px solid transparent;
- border-radius: ${recordingThemeCssVariables.border.radiusMd};
+ border-radius: ${() => themeCssVariables.border.radius.md};
box-sizing: border-box;
- font-family: ${recordingThemeCssVariables.font.family};
- padding: ${recordingThemeCssVariables.spacing[4]};
+ font-family: ${() => themeCssVariables.font.family};
+ padding: ${() => themeCssVariables.spacing[4]};
position: relative;
width: 100%;
`;
@@ -24,31 +24,31 @@ const StyledRecordingHeader = styled.div`
align-items: center;
box-sizing: border-box;
display: flex;
- height: ${recordingThemeCssVariables.spacing[6]};
+ height: ${() => themeCssVariables.spacing[6]};
`;
const StyledRecordingTitle = styled.h2`
- color: ${recordingThemeCssVariables.font.colorPrimary};
+ color: ${() => themeCssVariables.font.color.primary};
flex: 1;
- font-size: ${recordingThemeCssVariables.font.sizeMd};
- font-weight: ${recordingThemeCssVariables.font.weightMedium};
+ font-size: ${() => themeCssVariables.font.size.md};
+ font-weight: ${() => themeCssVariables.font.weight.medium};
margin: 0;
overflow: hidden;
- padding-inline: ${recordingThemeCssVariables.spacing[1]};
+ padding-inline: ${() => themeCssVariables.spacing[1]};
user-select: none;
`;
const StyledRecordingBody = styled.div`
box-sizing: border-box;
- margin-top: ${recordingThemeCssVariables.spacing[2]};
+ margin-top: ${() => themeCssVariables.spacing[2]};
`;
const StyledRecordingContentFrame = styled.div`
- background-color: ${recordingThemeCssVariables.background.secondary};
- border: 1px solid ${recordingThemeCssVariables.border.colorMedium};
- border-radius: ${recordingThemeCssVariables.border.radiusMd};
+ background-color: ${() => themeCssVariables.background.secondary};
+ border: 1px solid ${() => themeCssVariables.border.color.medium};
+ border-radius: ${() => themeCssVariables.border.radius.md};
box-sizing: border-box;
- padding: ${recordingThemeCssVariables.spacing[2]};
+ padding: ${() => themeCssVariables.spacing[2]};
`;
type CalendarEventRecordingContentProps = {
@@ -59,31 +59,35 @@ export const CalendarEventRecordingContent = ({
calendarEventId,
}: CalendarEventRecordingContentProps) => {
const [currentTimeSeconds, setCurrentTimeSeconds] = useState(0);
- const updateCurrentTimeSeconds = useCallback(
- (videoCurrentTimeSeconds: number) => {
- const nextCurrentTimeSeconds =
- Math.floor(
- videoCurrentTimeSeconds / TRANSCRIPT_TIME_UPDATE_INTERVAL_SECONDS,
- ) * TRANSCRIPT_TIME_UPDATE_INTERVAL_SECONDS;
- setCurrentTimeSeconds((previousCurrentTimeSeconds) =>
- previousCurrentTimeSeconds === nextCurrentTimeSeconds
- ? previousCurrentTimeSeconds
- : nextCurrentTimeSeconds,
- );
- },
- [],
- );
+ const updateCurrentTimeSeconds = (videoCurrentTimeSeconds: number) => {
+ const nextCurrentTimeSeconds =
+ Math.floor(
+ videoCurrentTimeSeconds / TRANSCRIPT_TIME_UPDATE_INTERVAL_SECONDS,
+ ) * TRANSCRIPT_TIME_UPDATE_INTERVAL_SECONDS;
+
+ setCurrentTimeSeconds((previousCurrentTimeSeconds) =>
+ previousCurrentTimeSeconds === nextCurrentTimeSeconds
+ ? previousCurrentTimeSeconds
+ : nextCurrentTimeSeconds,
+ );
+ };
const {
transcript,
videoFile,
isCalendarEventRecordingQueryLoading,
errorMessage,
+ refetchCalendarEventRecording,
} = useCalendarEventRecording(calendarEventId);
const { calendarEventParticipants } =
useCalendarEventParticipants(calendarEventId);
+ const handleVideoRetry = () => {
+ setCurrentTimeSeconds(0);
+ refetchCalendarEventRecording();
+ };
+
const videoFileUrl = videoFile?.url ?? undefined;
return (
@@ -103,6 +107,7 @@ export const CalendarEventRecordingContent = ({
currentTimeSeconds={currentTimeSeconds}
calendarEventParticipants={calendarEventParticipants}
onVideoTimeUpdate={updateCurrentTimeSeconds}
+ onVideoRetry={handleVideoRetry}
/>
diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummary.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummary.tsx
index 2b26364925..ffeed13539 100644
--- a/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummary.tsx
+++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummary.tsx
@@ -1,20 +1,20 @@
import styled from '@emotion/styled';
import { isUndefined } from '@sniptt/guards';
import { useSelectedRecordIds } from 'twenty-sdk/front-component';
+import { themeCssVariables } from 'twenty-ui/theme-constants';
import { CalendarEventSummaryContent } from 'src/front-components/components/CalendarEventSummaryContent';
-import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
const StyledCenteredState = styled.div`
align-items: center;
box-sizing: border-box;
- color: ${recordingThemeCssVariables.font.colorTertiary};
+ color: ${() => themeCssVariables.font.color.tertiary};
display: flex;
- font-family: ${recordingThemeCssVariables.font.family};
- font-size: ${recordingThemeCssVariables.font.sizeSm};
+ font-family: ${() => themeCssVariables.font.family};
+ font-size: ${() => themeCssVariables.font.size.sm};
height: 100%;
justify-content: center;
- padding: ${recordingThemeCssVariables.spacing[4]};
+ padding: ${() => themeCssVariables.spacing[4]};
`;
export const CalendarEventSummary = () => {
diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummaryBody.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummaryBody.tsx
index 379712a5df..4cd4417862 100644
--- a/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummaryBody.tsx
+++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummaryBody.tsx
@@ -1,20 +1,20 @@
import styled from '@emotion/styled';
import { isUndefined } from '@sniptt/guards';
+import { themeCssVariables } from 'twenty-ui/theme-constants';
import { SummaryMarkdown } from 'src/front-components/components/SummaryMarkdown';
import { TranscriptErrorBox } from 'src/front-components/components/TranscriptErrorBox';
-import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
const StyledCenteredState = styled.div`
align-items: center;
box-sizing: border-box;
- color: ${recordingThemeCssVariables.font.colorTertiary};
+ color: ${() => themeCssVariables.font.color.tertiary};
display: flex;
- font-family: ${recordingThemeCssVariables.font.family};
- font-size: ${recordingThemeCssVariables.font.sizeSm};
+ font-family: ${() => themeCssVariables.font.family};
+ font-size: ${() => themeCssVariables.font.size.sm};
justify-content: center;
min-height: 240px;
- padding: ${recordingThemeCssVariables.spacing[4]};
+ padding: ${() => themeCssVariables.spacing[4]};
`;
type CalendarEventSummaryBodyProps = {
diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummaryContent.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummaryContent.tsx
index 572a8f573b..6656ff3067 100644
--- a/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummaryContent.tsx
+++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummaryContent.tsx
@@ -1,16 +1,16 @@
import styled from '@emotion/styled';
+import { themeCssVariables } from 'twenty-ui/theme-constants';
import { CalendarEventSummaryBody } from 'src/front-components/components/CalendarEventSummaryBody';
-import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
import { useCalendarEventSummary } from 'src/front-components/hooks/use-calendar-event-summary';
const StyledSummaryShell = styled.div`
- background: ${recordingThemeCssVariables.background.primary};
+ background: ${() => themeCssVariables.background.primary};
border: 1px solid transparent;
- border-radius: ${recordingThemeCssVariables.border.radiusMd};
+ border-radius: ${() => themeCssVariables.border.radius.md};
box-sizing: border-box;
- font-family: ${recordingThemeCssVariables.font.family};
- padding: ${recordingThemeCssVariables.spacing[4]};
+ font-family: ${() => themeCssVariables.font.family};
+ padding: ${() => themeCssVariables.spacing[4]};
position: relative;
width: 100%;
`;
@@ -19,31 +19,31 @@ const StyledSummaryHeader = styled.div`
align-items: center;
box-sizing: border-box;
display: flex;
- height: ${recordingThemeCssVariables.spacing[6]};
+ height: ${() => themeCssVariables.spacing[6]};
`;
const StyledSummaryTitle = styled.h2`
- color: ${recordingThemeCssVariables.font.colorPrimary};
+ color: ${() => themeCssVariables.font.color.primary};
flex: 1;
- font-size: ${recordingThemeCssVariables.font.sizeMd};
- font-weight: ${recordingThemeCssVariables.font.weightMedium};
+ font-size: ${() => themeCssVariables.font.size.md};
+ font-weight: ${() => themeCssVariables.font.weight.medium};
margin: 0;
overflow: hidden;
- padding-inline: ${recordingThemeCssVariables.spacing[1]};
+ padding-inline: ${() => themeCssVariables.spacing[1]};
user-select: none;
`;
const StyledSummaryBody = styled.div`
box-sizing: border-box;
- margin-top: ${recordingThemeCssVariables.spacing[2]};
+ margin-top: ${() => themeCssVariables.spacing[2]};
`;
const StyledSummaryContentFrame = styled.div`
- background-color: ${recordingThemeCssVariables.background.secondary};
- border: 1px solid ${recordingThemeCssVariables.border.colorMedium};
- border-radius: ${recordingThemeCssVariables.border.radiusMd};
+ background-color: ${() => themeCssVariables.background.secondary};
+ border: 1px solid ${() => themeCssVariables.border.color.medium};
+ border-radius: ${() => themeCssVariables.border.radius.md};
box-sizing: border-box;
- padding: ${recordingThemeCssVariables.spacing[3]};
+ padding: ${() => themeCssVariables.spacing[3]};
`;
type CalendarEventSummaryContentProps = {
diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/RecordingSkeletonLoader.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/RecordingSkeletonLoader.tsx
new file mode 100644
index 0000000000..7984851dd8
--- /dev/null
+++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/RecordingSkeletonLoader.tsx
@@ -0,0 +1,98 @@
+import { keyframes } from '@emotion/react';
+import styled from '@emotion/styled';
+import { themeCssVariables } from 'twenty-ui/theme-constants';
+
+const SKELETON_ENTRY_COUNT = 3;
+const SKELETON_BAR_HEIGHT = '16px';
+const SKELETON_BORDER_RADIUS = '4px';
+
+const skeletonHighlightSweep = keyframes`
+ 100% {
+ transform: translateX(100%);
+ }
+`;
+
+const StyledSkeletonBar = styled.div`
+ background-color: ${() => themeCssVariables.background.tertiary};
+ border-radius: ${SKELETON_BORDER_RADIUS};
+ height: ${SKELETON_BAR_HEIGHT};
+ overflow: hidden;
+ position: relative;
+
+ &::after {
+ animation: ${skeletonHighlightSweep} 1.5s ease-in-out infinite;
+ background-image: linear-gradient(
+ 90deg,
+ transparent,
+ ${() => themeCssVariables.background.transparent.lighter},
+ transparent
+ );
+ content: '';
+ inset: 0;
+ position: absolute;
+ transform: translateX(-100%);
+ }
+`;
+
+const StyledSkeletonAvatar = styled(StyledSkeletonBar)`
+ border-radius: 50px;
+ flex-shrink: 0;
+ width: ${SKELETON_BAR_HEIGHT};
+`;
+
+const StyledSkeletonSpeakerBar = styled(StyledSkeletonBar)`
+ width: 96px;
+`;
+
+const StyledSkeletonTextBar = styled(StyledSkeletonBar)<{ $width: string }>`
+ width: ${({ $width }) => $width};
+`;
+
+const StyledSkeletonEntry = styled.div`
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ gap: ${() => themeCssVariables.spacing[2]};
+ padding: ${() => themeCssVariables.spacing[2]};
+ width: 100%;
+`;
+
+const StyledSkeletonEntryHeader = styled.div`
+ align-items: center;
+ display: flex;
+ gap: ${() => themeCssVariables.spacing[2]};
+ min-height: ${() => themeCssVariables.spacing[6]};
+`;
+
+const StyledSkeletonContainer = styled.div`
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ gap: ${() => themeCssVariables.spacing[2]};
+ min-height: 240px;
+ width: 100%;
+`;
+
+export const RecordingSkeletonLoader = () => {
+ const skeletonEntries = Array.from(
+ { length: SKELETON_ENTRY_COUNT },
+ (_, skeletonEntryIndex) => skeletonEntryIndex,
+ );
+
+ return (
+
+ {skeletonEntries.map((skeletonEntryIndex) => (
+
+
+
+
+
+
+
+
+ ))}
+
+ );
+};
diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/RecordingTranscript.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/RecordingTranscript.tsx
index 5666e2f5e0..0cd53258e9 100644
--- a/packages/twenty-apps/public/call-recorder/src/front-components/components/RecordingTranscript.tsx
+++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/RecordingTranscript.tsx
@@ -1,10 +1,10 @@
import styled from '@emotion/styled';
import { isUndefined } from '@sniptt/guards';
import { useMemo } from 'react';
+import { themeCssVariables } from 'twenty-ui/theme-constants';
import { TranscriptEntryList } from 'src/front-components/components/TranscriptEntryList';
import { TranscriptErrorBox } from 'src/front-components/components/TranscriptErrorBox';
-import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
import { type CalendarEventRecordingParticipant } from 'src/front-components/types/calendar-event-recording-participant.type';
import { parseTranscriptEntries } from 'src/front-components/utils/parse-transcript-entries.util';
import { parseTranscriptMarker } from 'src/logic-functions/domain/parse-transcript-marker.util';
@@ -12,10 +12,10 @@ import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.
const StyledTranscriptCenteredState = styled.div`
align-items: center;
- color: ${recordingThemeCssVariables.font.colorTertiary};
+ color: ${() => themeCssVariables.font.color.tertiary};
display: flex;
flex: 1;
- font-size: ${recordingThemeCssVariables.font.sizeSm};
+ font-size: ${() => themeCssVariables.font.size.sm};
justify-content: center;
`;
diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/RecordingVideoPlayer.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/RecordingVideoPlayer.tsx
index 40a60ab04a..057217f8d3 100644
--- a/packages/twenty-apps/public/call-recorder/src/front-components/components/RecordingVideoPlayer.tsx
+++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/RecordingVideoPlayer.tsx
@@ -1,52 +1,170 @@
import styled from '@emotion/styled';
-import { memo, type SyntheticEvent } from 'react';
-
-import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
+import { useState, type SyntheticEvent } from 'react';
+import { CircularProgressBar } from 'twenty-ui/feedback';
+import { themeCssVariables } from 'twenty-ui/theme-constants';
const DEFAULT_VIDEO_ASPECT_RATIO = '16 / 9';
+// Forces the browser to decode the first frame under preload="metadata"
+// (Safari otherwise paints nothing until play). Fragments stay client-side,
+// so the signed file URL is unaffected.
+const FIRST_FRAME_SEEK_FRAGMENT = '#t=0.001';
+
+type RecordingVideoLoadState = 'awaiting-first-frame' | 'ready' | 'errored';
+
const StyledVideoViewport = styled.div`
aspect-ratio: ${DEFAULT_VIDEO_ASPECT_RATIO};
- background: ${recordingThemeCssVariables.background.primary};
- border-radius: ${recordingThemeCssVariables.border.radiusSm};
+ background: ${() => themeCssVariables.background.primary};
+ border-radius: ${() => themeCssVariables.border.radius.sm};
overflow: hidden;
+ position: relative;
width: 100%;
`;
-const StyledVideo = styled.video`
- accent-color: ${recordingThemeCssVariables.accent.primary};
- background: ${recordingThemeCssVariables.background.primary};
+const StyledVideo = styled.video<{ $isFirstFrameReady: boolean }>`
+ accent-color: ${() => themeCssVariables.accent.accent9};
+ background: ${() => themeCssVariables.background.primary};
color-scheme: light dark;
display: block;
height: 100%;
object-fit: contain;
+ opacity: ${({ $isFirstFrameReady }) => ($isFirstFrameReady ? 1 : 0)};
+ transition: opacity 200ms ease;
width: 100%;
+
+ @media (prefers-reduced-motion: reduce) {
+ transition: none;
+ }
+`;
+
+const StyledBufferingOverlay = styled.div<{ $isVisible: boolean }>`
+ align-items: center;
+ color: ${() => themeCssVariables.font.color.tertiary};
+ display: flex;
+ inset: 0;
+ justify-content: center;
+ opacity: ${({ $isVisible }) => ($isVisible ? 1 : 0)};
+ pointer-events: none;
+ position: absolute;
+ transition: opacity 150ms ease
+ ${({ $isVisible }) => ($isVisible ? '300ms' : '0ms')};
+
+ @media (prefers-reduced-motion: reduce) {
+ transition-duration: 0ms;
+ }
+`;
+
+const StyledPlaybackErrorState = styled.div`
+ align-items: center;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ gap: ${() => themeCssVariables.spacing[2]};
+ height: 100%;
+ justify-content: center;
+ padding: ${() => themeCssVariables.spacing[4]};
+`;
+
+const StyledPlaybackErrorTitle = styled.span`
+ color: ${() => themeCssVariables.font.color.primary};
+ font-size: ${() => themeCssVariables.font.size.sm};
+ font-weight: ${() => themeCssVariables.font.weight.medium};
+`;
+
+const StyledPlaybackErrorDescription = styled.span`
+ color: ${() => themeCssVariables.font.color.secondary};
+ font-size: ${() => themeCssVariables.font.size.xs};
+`;
+
+const StyledRetryButton = styled.button`
+ background: ${() => themeCssVariables.background.secondary};
+ border: 1px solid ${() => themeCssVariables.border.color.medium};
+ border-radius: ${() => themeCssVariables.border.radius.sm};
+ color: ${() => themeCssVariables.font.color.secondary};
+ cursor: pointer;
+ font-family: ${() => themeCssVariables.font.family};
+ font-size: ${() => themeCssVariables.font.size.sm};
+ font-weight: ${() => themeCssVariables.font.weight.medium};
+ margin-top: ${() => themeCssVariables.spacing[1]};
+ padding: ${() => themeCssVariables.spacing[1]}
+ ${() => themeCssVariables.spacing[3]};
+
+ &:hover {
+ background: ${() => themeCssVariables.background.tertiary};
+ }
`;
type RecordingVideoPlayerProps = {
- src: string | undefined;
+ src: string;
onTimeUpdate: (currentTimeSeconds: number) => void;
+ onRetry: () => void;
};
-const RecordingVideoPlayerComponent = ({
+export const RecordingVideoPlayer = ({
src,
onTimeUpdate,
+ onRetry,
}: RecordingVideoPlayerProps) => {
+ const [loadState, setLoadState] = useState(
+ 'awaiting-first-frame',
+ );
+ const [isPlaybackStalled, setIsPlaybackStalled] = useState(false);
+
+ const markFirstFrameReady = () => {
+ setLoadState((previousLoadState) =>
+ previousLoadState === 'awaiting-first-frame'
+ ? 'ready'
+ : previousLoadState,
+ );
+ setIsPlaybackStalled(false);
+ };
+
const handleTimeUpdate = (event: SyntheticEvent) => {
+ markFirstFrameReady();
onTimeUpdate(event.currentTarget.currentTime);
};
+ const handleWaiting = () => setIsPlaybackStalled(true);
+
+ const handlePause = () => setIsPlaybackStalled(false);
+
+ const handleError = () => setLoadState('errored');
+
+ if (loadState === 'errored') {
+ return (
+
+
+ Playback failed
+
+ The recording could not be loaded.
+
+ Retry
+
+
+ );
+ }
+
return (
+
+
+
);
};
-
-export const RecordingVideoPlayer = memo(RecordingVideoPlayerComponent);
diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/SummaryInlineSegments.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/SummaryInlineSegments.tsx
index eb27ac70f0..4008facb2c 100644
--- a/packages/twenty-apps/public/call-recorder/src/front-components/components/SummaryInlineSegments.tsx
+++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/SummaryInlineSegments.tsx
@@ -1,12 +1,12 @@
import styled from '@emotion/styled';
import { Fragment } from 'react';
+import { themeCssVariables } from 'twenty-ui/theme-constants';
-import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
import { type SummaryInlineSegment } from 'src/front-components/types/summary-inline-segment.type';
const StyledBold = styled.strong`
- color: ${recordingThemeCssVariables.font.colorPrimary};
- font-weight: ${recordingThemeCssVariables.font.weightMedium};
+ color: ${() => themeCssVariables.font.color.primary};
+ font-weight: ${() => themeCssVariables.font.weight.medium};
`;
type SummaryInlineSegmentsProps = {
diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/SummaryMarkdown.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/SummaryMarkdown.tsx
index 0d97c002ce..a663b22b31 100644
--- a/packages/twenty-apps/public/call-recorder/src/front-components/components/SummaryMarkdown.tsx
+++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/SummaryMarkdown.tsx
@@ -1,31 +1,31 @@
import styled from '@emotion/styled';
import { useMemo } from 'react';
+import { themeCssVariables } from 'twenty-ui/theme-constants';
import { SummaryInlineSegments } from 'src/front-components/components/SummaryInlineSegments';
-import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
import { parseSummaryMarkdownBlocks } from 'src/front-components/utils/parse-summary-markdown-blocks.util';
const TOP_LEVEL_HEADING_MAX = 2;
const StyledSummary = styled.div`
- color: ${recordingThemeCssVariables.font.colorSecondary};
+ color: ${() => themeCssVariables.font.color.secondary};
display: flex;
flex-direction: column;
- font-family: ${recordingThemeCssVariables.font.family};
- font-size: ${recordingThemeCssVariables.font.sizeSm};
- gap: ${recordingThemeCssVariables.spacing[2]};
+ font-family: ${() => themeCssVariables.font.family};
+ font-size: ${() => themeCssVariables.font.size.sm};
+ gap: ${() => themeCssVariables.spacing[2]};
line-height: 1.5;
`;
const StyledHeading = styled.h3<{ $isTopLevel: boolean }>`
- color: ${recordingThemeCssVariables.font.colorPrimary};
+ color: ${() => themeCssVariables.font.color.primary};
font-size: ${({ $isTopLevel }) =>
$isTopLevel
- ? recordingThemeCssVariables.font.sizeMd
- : recordingThemeCssVariables.font.sizeSm};
- font-weight: ${recordingThemeCssVariables.font.weightMedium};
+ ? themeCssVariables.font.size.md
+ : themeCssVariables.font.size.sm};
+ font-weight: ${() => themeCssVariables.font.weight.medium};
margin: 0;
- margin-top: ${recordingThemeCssVariables.spacing[1]};
+ margin-top: ${() => themeCssVariables.spacing[1]};
`;
const StyledParagraph = styled.p`
@@ -35,9 +35,9 @@ const StyledParagraph = styled.p`
const StyledList = styled.ul`
display: flex;
flex-direction: column;
- gap: ${recordingThemeCssVariables.spacing[1]};
+ gap: ${() => themeCssVariables.spacing[1]};
margin: 0;
- padding-left: ${recordingThemeCssVariables.spacing[3]};
+ padding-left: ${() => themeCssVariables.spacing[3]};
`;
type SummaryMarkdownProps = {
diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptEntryList.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptEntryList.tsx
index 43f4b7a479..fcab6228a0 100644
--- a/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptEntryList.tsx
+++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptEntryList.tsx
@@ -1,8 +1,8 @@
import styled from '@emotion/styled';
import { useMemo } from 'react';
+import { themeCssVariables } from 'twenty-ui/theme-constants';
import { TranscriptEntryListItem } from 'src/front-components/components/TranscriptEntryListItem';
-import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
import { type CalendarEventRecordingParticipant } from 'src/front-components/types/calendar-event-recording-participant.type';
import { type TranscriptEntry } from 'src/front-components/types/transcript-entry.type';
import { buildCalendarEventParticipantBySpeakerName } from 'src/front-components/utils/build-calendar-event-participant-by-speaker-name.util';
@@ -13,7 +13,7 @@ const StyledTranscriptContainer = styled.div`
display: flex;
flex: 1;
flex-direction: column;
- gap: ${recordingThemeCssVariables.spacing[2]};
+ gap: ${() => themeCssVariables.spacing[2]};
min-height: 0;
`;
diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptEntryListItem.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptEntryListItem.tsx
index c8b3af6af7..40fd0a304b 100644
--- a/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptEntryListItem.tsx
+++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptEntryListItem.tsx
@@ -1,8 +1,8 @@
import styled from '@emotion/styled';
import { isUndefined } from '@sniptt/guards';
+import { Avatar, Chip, ChipVariant } from 'twenty-ui/data-display';
+import { themeCssVariables } from 'twenty-ui/theme-constants';
-import { TranscriptSpeakerChip } from 'src/front-components/components/TranscriptSpeakerChip';
-import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
import { type CalendarEventRecordingParticipant } from 'src/front-components/types/calendar-event-recording-participant.type';
import {
type TranscriptEntry,
@@ -14,15 +14,15 @@ const StyledEntry = styled.div<{ $isActive: boolean }>`
align-items: flex-start;
background: ${({ $isActive }) =>
$isActive
- ? recordingThemeCssVariables.background.transparentBlue
+ ? themeCssVariables.background.transparent.blue
: 'transparent'};
- border-radius: ${recordingThemeCssVariables.border.radiusSm};
+ border-radius: ${() => themeCssVariables.border.radius.sm};
box-sizing: border-box;
display: flex;
flex-direction: column;
- gap: ${recordingThemeCssVariables.spacing[2]};
+ gap: ${() => themeCssVariables.spacing[2]};
justify-content: center;
- padding: ${recordingThemeCssVariables.spacing[2]};
+ padding: ${() => themeCssVariables.spacing[2]};
width: 100%;
`;
@@ -30,21 +30,21 @@ const StyledEntryHeader = styled.div`
align-items: center;
align-self: stretch;
display: flex;
- gap: ${recordingThemeCssVariables.spacing[2]};
- min-height: ${recordingThemeCssVariables.spacing[6]};
+ gap: ${() => themeCssVariables.spacing[2]};
+ min-height: ${() => themeCssVariables.spacing[6]};
min-width: 0;
`;
const StyledTimestamp = styled.span`
- color: ${recordingThemeCssVariables.font.colorTertiary};
- font-size: ${recordingThemeCssVariables.font.sizeXs};
+ color: ${() => themeCssVariables.font.color.tertiary};
+ font-size: ${() => themeCssVariables.font.size.xs};
line-height: 1.4;
`;
const StyledEntryText = styled.p`
align-self: stretch;
- color: ${recordingThemeCssVariables.font.colorSecondary};
- font-size: ${recordingThemeCssVariables.font.sizeSm};
+ color: ${() => themeCssVariables.font.color.secondary};
+ font-size: ${() => themeCssVariables.font.size.sm};
line-height: 1.4;
margin: 0;
`;
@@ -52,8 +52,8 @@ const StyledEntryText = styled.p`
const StyledWord = styled.span<{ $isSpoken: boolean }>`
color: ${({ $isSpoken }) =>
$isSpoken
- ? recordingThemeCssVariables.font.colorPrimary
- : recordingThemeCssVariables.font.colorSecondary};
+ ? themeCssVariables.font.color.primary
+ : themeCssVariables.font.color.secondary};
line-height: 1.4;
transition: color 0.15s ease;
`;
@@ -77,11 +77,22 @@ export const TranscriptEntryListItem = ({
return (
-
}
/>
{!isUndefined(entry.startSeconds) && (
diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptErrorBox.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptErrorBox.tsx
index 2808bd3f97..29020b4bb7 100644
--- a/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptErrorBox.tsx
+++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptErrorBox.tsx
@@ -1,33 +1,32 @@
import styled from '@emotion/styled';
-
-import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
+import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledStateContainer = styled.div`
box-sizing: border-box;
- font-family: ${recordingThemeCssVariables.font.family};
+ font-family: ${() => themeCssVariables.font.family};
height: 100%;
- padding: ${recordingThemeCssVariables.spacing[4]};
+ padding: ${() => themeCssVariables.spacing[4]};
`;
const StyledErrorBox = styled.div`
- background: ${recordingThemeCssVariables.background.transparentDanger};
- border: 1px solid ${recordingThemeCssVariables.border.colorDanger};
- border-radius: ${recordingThemeCssVariables.border.radiusMd};
+ background: ${() => themeCssVariables.background.transparent.danger};
+ border: 1px solid ${() => themeCssVariables.border.color.danger};
+ border-radius: ${() => themeCssVariables.border.radius.md};
display: flex;
flex-direction: column;
- gap: ${recordingThemeCssVariables.spacing[1]};
- padding: ${recordingThemeCssVariables.spacing[3]};
+ gap: ${() => themeCssVariables.spacing[1]};
+ padding: ${() => themeCssVariables.spacing[3]};
`;
const StyledErrorTitle = styled.span`
- color: ${recordingThemeCssVariables.font.colorDanger};
- font-size: ${recordingThemeCssVariables.font.sizeSm};
- font-weight: ${recordingThemeCssVariables.font.weightMedium};
+ color: ${() => themeCssVariables.font.color.danger};
+ font-size: ${() => themeCssVariables.font.size.sm};
+ font-weight: ${() => themeCssVariables.font.weight.medium};
`;
const StyledErrorDescription = styled.span`
- color: ${recordingThemeCssVariables.font.colorSecondary};
- font-size: ${recordingThemeCssVariables.font.sizeSm};
+ color: ${() => themeCssVariables.font.color.secondary};
+ font-size: ${() => themeCssVariables.font.size.sm};
`;
type TranscriptErrorBoxProps = {
diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptSpeakerAvatar.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptSpeakerAvatar.tsx
deleted file mode 100644
index 620bf5d32c..0000000000
--- a/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptSpeakerAvatar.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-// Duplicates minimal twenty-ui Avatar logic for this app.
-// Remove once twenty-ui can be imported safely in front components.
-import styled from '@emotion/styled';
-import { useState } from 'react';
-
-import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
-import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.util';
-
-const AVATAR_COLOR_NAMES = [
- 'red',
- 'ruby',
- 'crimson',
- 'tomato',
- 'orange',
- 'amber',
- 'yellow',
- 'lime',
- 'grass',
- 'green',
- 'jade',
- 'mint',
- 'turquoise',
- 'cyan',
- 'sky',
- 'blue',
- 'iris',
- 'violet',
- 'purple',
- 'plum',
- 'pink',
- 'bronze',
- 'gold',
- 'brown',
- 'gray',
-] as const;
-
-const StyledAvatar = styled.div<{
- $backgroundColor: string;
- $color: string;
-}>`
- align-items: center;
- background: ${({ $backgroundColor }) => $backgroundColor};
- border-radius: 50px;
- box-sizing: border-box;
- color: ${({ $color }) => $color};
- display: flex;
- flex-shrink: 0;
- font-size: ${recordingThemeCssVariables.font.sizeXs};
- font-weight: ${recordingThemeCssVariables.font.weightMedium};
- height: 16px;
- justify-content: center;
- line-height: 15px;
- overflow: hidden;
- width: 16px;
-`;
-
-const StyledAvatarImage = styled.img`
- height: 100%;
- object-fit: cover;
- width: 100%;
-`;
-
-type TranscriptSpeakerAvatarProps = {
- speakerName: string;
- avatarUrl: string | undefined;
- placeholderColorSeed: string;
-};
-
-const getSpeakerInitial = (speakerName: string) =>
- speakerName.trim().charAt(0).toUpperCase() || '-';
-
-export const TranscriptSpeakerAvatar = ({
- speakerName,
- avatarUrl,
- placeholderColorSeed,
-}: TranscriptSpeakerAvatarProps) => {
- const [erroredAvatarUrl, setErroredAvatarUrl] = useState(
- undefined,
- );
-
- const shouldShowAvatarImage =
- isNonEmptyString(avatarUrl) && erroredAvatarUrl !== avatarUrl;
-
- const handleAvatarImageError = () => {
- if (isNonEmptyString(avatarUrl)) {
- setErroredAvatarUrl(avatarUrl);
- }
- };
-
- const avatarPlaceholderColor = getAvatarPlaceholderColor({
- placeholderColorSeed,
- variant: 12,
- });
- const avatarPlaceholderBackgroundColor = getAvatarPlaceholderColor({
- placeholderColorSeed,
- variant: 4,
- });
-
- return (
-
- {shouldShowAvatarImage ? (
-
- ) : (
- getSpeakerInitial(speakerName)
- )}
-
- );
-};
-
-const getAvatarPlaceholderColor = ({
- placeholderColorSeed,
- variant,
-}: {
- placeholderColorSeed: string;
- variant: 4 | 12;
-}): string => {
- const avatarColorName =
- AVATAR_COLOR_NAMES[
- Math.abs(hashString(placeholderColorSeed)) % AVATAR_COLOR_NAMES.length
- ];
-
- return `var(--t-color-${avatarColorName}${variant})`;
-};
-
-const hashString = (value: string): number => {
- let hash = 0;
-
- for (let valueIndex = 0; valueIndex < value.length; valueIndex++) {
- hash = value.charCodeAt(valueIndex) + ((hash << 5) - hash);
- }
-
- return hash;
-};
diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptSpeakerChip.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptSpeakerChip.tsx
deleted file mode 100644
index d7863eb42a..0000000000
--- a/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptSpeakerChip.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-// Duplicates minimal twenty-ui Chip logic for this app.
-// Remove once twenty-ui can be imported safely in front components.
-import styled from '@emotion/styled';
-
-import { TranscriptSpeakerAvatar } from 'src/front-components/components/TranscriptSpeakerAvatar';
-import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
-
-const StyledSpeakerChip = styled.span`
- align-items: center;
- border-radius: ${recordingThemeCssVariables.border.radiusSm};
- color: ${recordingThemeCssVariables.font.colorPrimary};
- display: inline-flex;
- font-size: ${recordingThemeCssVariables.font.sizeSm};
- font-weight: ${recordingThemeCssVariables.font.weightMedium};
- gap: ${recordingThemeCssVariables.spacing[1]};
- line-height: 1.4;
- max-width: 100%;
- min-width: 0;
- text-decoration: none;
- white-space: nowrap;
-`;
-
-const StyledSpeakerName = styled.span`
- flex-shrink: 1;
- min-width: 0;
- overflow: hidden;
- text-overflow: ellipsis;
-`;
-
-type TranscriptSpeakerChipProps = {
- speakerName: string;
- avatarUrl: string | undefined;
- placeholderColorSeed: string;
-};
-
-export const TranscriptSpeakerChip = ({
- speakerName,
- avatarUrl,
- placeholderColorSeed,
-}: TranscriptSpeakerChipProps) => {
- return (
-
-
- {speakerName}
-
- );
-};
diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/constants/recording-theme-css-variables.ts b/packages/twenty-apps/public/call-recorder/src/front-components/constants/recording-theme-css-variables.ts
deleted file mode 100644
index a8cce2347f..0000000000
--- a/packages/twenty-apps/public/call-recorder/src/front-components/constants/recording-theme-css-variables.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-// Avoid the SDK UI entrypoint until its bundle is safe for the browser runtime.
-export const recordingThemeCssVariables = {
- accent: {
- primary: 'var(--t-accent-accent9)',
- },
- background: {
- primary: 'var(--t-background-primary)',
- secondary: 'var(--t-background-secondary)',
- transparentBlue: 'var(--t-background-transparent-blue)',
- transparentDanger: 'var(--t-background-transparent-danger)',
- },
- border: {
- colorDanger: 'var(--t-border-color-danger)',
- colorLight: 'var(--t-border-color-light)',
- colorMedium: 'var(--t-border-color-medium)',
- radiusMd: 'var(--t-border-radius-md)',
- radiusSm: 'var(--t-border-radius-sm)',
- },
- boxShadow: {
- light: 'var(--t-box-shadow-light)',
- },
- font: {
- colorDanger: 'var(--t-font-color-danger)',
- colorPrimary: 'var(--t-font-color-primary)',
- colorSecondary: 'var(--t-font-color-secondary)',
- colorTertiary: 'var(--t-font-color-tertiary)',
- family: 'var(--t-font-family)',
- sizeMd: 'var(--t-font-size-md)',
- sizeSm: 'var(--t-font-size-sm)',
- sizeXs: 'var(--t-font-size-xs)',
- weightMedium: 'var(--t-font-weight-medium)',
- },
- spacing: {
- 1: 'var(--t-spacing-1)',
- 2: 'var(--t-spacing-2)',
- 3: 'var(--t-spacing-3)',
- 4: 'var(--t-spacing-4)',
- 6: 'var(--t-spacing-6)',
- },
-} as const;
diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/hooks/use-calendar-event-recording.ts b/packages/twenty-apps/public/call-recorder/src/front-components/hooks/use-calendar-event-recording.ts
index b6c986fc82..63954c1ecf 100644
--- a/packages/twenty-apps/public/call-recorder/src/front-components/hooks/use-calendar-event-recording.ts
+++ b/packages/twenty-apps/public/call-recorder/src/front-components/hooks/use-calendar-event-recording.ts
@@ -9,6 +9,10 @@ type CalendarEventRecordingState = {
errorMessage: string | undefined;
};
+type CalendarEventRecordingResult = CalendarEventRecordingState & {
+ refetchCalendarEventRecording: () => void;
+};
+
type CalendarEventRecordingVideoFile = {
fileId: string;
label: string | null;
@@ -31,13 +35,18 @@ const CALENDAR_EVENT_RECORDING_ERROR_MESSAGE = 'Please try again later.';
export const useCalendarEventRecording = (
calendarEventId: string | undefined,
-): CalendarEventRecordingState => {
+): CalendarEventRecordingResult => {
const [state, setState] = useState({
transcript: undefined,
videoFile: undefined,
isCalendarEventRecordingQueryLoading: !isUndefined(calendarEventId),
errorMessage: undefined,
});
+ const [refetchToken, setRefetchToken] = useState(0);
+
+ const refetchCalendarEventRecording = () => {
+ setRefetchToken((previousRefetchToken) => previousRefetchToken + 1);
+ };
useEffect(() => {
if (isUndefined(calendarEventId)) {
@@ -123,9 +132,9 @@ export const useCalendarEventRecording = (
return () => {
cancelled = true;
};
- }, [calendarEventId]);
+ }, [calendarEventId, refetchToken]);
- return state;
+ return { ...state, refetchCalendarEventRecording };
};
const hasTranscript = (
diff --git a/packages/twenty-apps/public/call-recorder/tsconfig.spec.json b/packages/twenty-apps/public/call-recorder/tsconfig.spec.json
index ea69a6c010..b780a60a65 100644
--- a/packages/twenty-apps/public/call-recorder/tsconfig.spec.json
+++ b/packages/twenty-apps/public/call-recorder/tsconfig.spec.json
@@ -4,6 +4,6 @@
"composite": true,
"types": ["vitest/globals", "node"]
},
- "include": ["src/**/*.ts", "src/**/*.tsx"],
+ "include": ["css.d.ts", "src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules", "dist"]
}
diff --git a/packages/twenty-apps/public/call-recorder/yarn.lock b/packages/twenty-apps/public/call-recorder/yarn.lock
index 5a23521880..287c923884 100644
--- a/packages/twenty-apps/public/call-recorder/yarn.lock
+++ b/packages/twenty-apps/public/call-recorder/yarn.lock
@@ -81,7 +81,7 @@ __metadata:
languageName: node
linkType: hard
-"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3":
+"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.29.2":
version: 7.29.7
resolution: "@babel/runtime@npm:7.29.7"
checksum: 10c0/ca11572f7146b21e0bde6a9ed4bb6a89eafbee5f0944c7eb54d0d8a2dac962c33638a1d611e14faa71dfbb92b4b5f9236232208568a6b7d5c6f3f39ddb91771e
@@ -124,6 +124,51 @@ __metadata:
languageName: node
linkType: hard
+"@base-ui/react@npm:^1.5.0":
+ version: 1.6.0
+ resolution: "@base-ui/react@npm:1.6.0"
+ dependencies:
+ "@babel/runtime": "npm:^7.29.2"
+ "@base-ui/utils": "npm:0.3.1"
+ "@floating-ui/react-dom": "npm:^2.1.8"
+ "@floating-ui/utils": "npm:^0.2.11"
+ use-sync-external-store: "npm:^1.6.0"
+ peerDependencies:
+ "@date-fns/tz": ^1.2.0
+ "@types/react": ^17 || ^18 || ^19
+ date-fns: ^4.0.0
+ react: ^17 || ^18 || ^19
+ react-dom: ^17 || ^18 || ^19
+ peerDependenciesMeta:
+ "@date-fns/tz":
+ optional: true
+ "@types/react":
+ optional: true
+ date-fns:
+ optional: true
+ checksum: 10c0/5e5994dc782710eac900d68cc0691aea70a47fabc6ef8d2ea943103d9aa96c4b44f9fd76ba65d29fa2667a5fbd7ea7a1cc02fe111c4bf639d4e3e65a1c281118
+ languageName: node
+ linkType: hard
+
+"@base-ui/utils@npm:0.3.1":
+ version: 0.3.1
+ resolution: "@base-ui/utils@npm:0.3.1"
+ dependencies:
+ "@babel/runtime": "npm:^7.29.2"
+ "@floating-ui/utils": "npm:^0.2.11"
+ reselect: "npm:^5.2.0"
+ use-sync-external-store: "npm:^1.6.0"
+ peerDependencies:
+ "@types/react": ^17 || ^18 || ^19
+ react: ^17 || ^18 || ^19
+ react-dom: ^17 || ^18 || ^19
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ checksum: 10c0/4de858c3e2c8a4486a549d51c5e887bba6921e5a7f8db54439ba558feaf1656c3592a873928aee8a1711ee9210be3a65ca43b26713e97f649695ed2fa8c3af2c
+ languageName: node
+ linkType: hard
+
"@emnapi/core@npm:1.10.0":
version: 1.10.0
resolution: "@emnapi/core@npm:1.10.0"
@@ -489,6 +534,44 @@ __metadata:
languageName: node
linkType: hard
+"@floating-ui/core@npm:^1.7.5":
+ version: 1.7.5
+ resolution: "@floating-ui/core@npm:1.7.5"
+ dependencies:
+ "@floating-ui/utils": "npm:^0.2.11"
+ checksum: 10c0/f9c52205e198b231d63a387b09c659aab08c46a1899e0b0bbe147b8b4f048b546f15ba17cb5d2a471da9534f1883d979425e13e5c4ceee67be63e4b0abd4db5d
+ languageName: node
+ linkType: hard
+
+"@floating-ui/dom@npm:^1.7.6":
+ version: 1.7.6
+ resolution: "@floating-ui/dom@npm:1.7.6"
+ dependencies:
+ "@floating-ui/core": "npm:^1.7.5"
+ "@floating-ui/utils": "npm:^0.2.11"
+ checksum: 10c0/5c098e0d7b58c9bc769f276cca1766994c2c9c70c92d091a61bba8b3e9be53c011e0a79a8457fc2fb2f3d91697a26eb52e0a4962ef936dc963b45f58613c212f
+ languageName: node
+ linkType: hard
+
+"@floating-ui/react-dom@npm:^2.1.8":
+ version: 2.1.8
+ resolution: "@floating-ui/react-dom@npm:2.1.8"
+ dependencies:
+ "@floating-ui/dom": "npm:^1.7.6"
+ peerDependencies:
+ react: ">=16.8.0"
+ react-dom: ">=16.8.0"
+ checksum: 10c0/26260ca4bb23b57c73b824062505abf977a008ce6e0463bdacca74f7e49853c4cd1d2bbf1a77c6caa17fa37dfffda2c6c4cd07a8737ebd7474aaff7818401d75
+ languageName: node
+ linkType: hard
+
+"@floating-ui/utils@npm:^0.2.11":
+ version: 0.2.11
+ resolution: "@floating-ui/utils@npm:0.2.11"
+ checksum: 10c0/f4bcea1559bdbb721ecc8e8ead423ac58d6a5b6e70b602cf0810ba6ad4ed1c77211b207faa88b278a9042f0c743133de08a203ed6741c1b6443423332884d5b3
+ languageName: node
+ linkType: hard
+
"@genql/runtime@npm:^2.10.0":
version: 2.10.0
resolution: "@genql/runtime@npm:2.10.0"
@@ -1020,6 +1103,28 @@ __metadata:
languageName: node
linkType: hard
+"@monaco-editor/loader@npm:^1.5.0":
+ version: 1.7.0
+ resolution: "@monaco-editor/loader@npm:1.7.0"
+ dependencies:
+ state-local: "npm:^1.0.6"
+ checksum: 10c0/1fd3579cb09b669493cf553dfc0723a93483140a44353ce9a739827edfa7b2c6541b254024d15c48176ece749ef666b6d16cce6cf0e4396c7fa1c5a48012d08a
+ languageName: node
+ linkType: hard
+
+"@monaco-editor/react@npm:^4.7.0":
+ version: 4.7.0
+ resolution: "@monaco-editor/react@npm:4.7.0"
+ dependencies:
+ "@monaco-editor/loader": "npm:^1.5.0"
+ peerDependencies:
+ monaco-editor: ">= 0.25.0 < 1"
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ checksum: 10c0/a4e91c6eda1a71f5fd23871bd801de435490ccbc43934d23cba65cefe2be7e9d02c9a57c4ef80fc9fe99e4d4deea51e5db19cb4e0ecf3f51818dda0eb9cdbf12
+ languageName: node
+ linkType: hard
+
"@napi-rs/wasm-runtime@npm:^1.1.4":
version: 1.1.5
resolution: "@napi-rs/wasm-runtime@npm:1.1.5"
@@ -1095,6 +1200,20 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/colors@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "@radix-ui/colors@npm:3.0.0"
+ checksum: 10c0/612c6bb4efe987f2d49aaca2f785687d08150da59a248328bc2b4e187ece4ef731c1dc5c6a9430fffcac4af9e65cf2f89ceb9806f8809940c9eb238b79d2cf2f
+ languageName: node
+ linkType: hard
+
+"@remix-run/router@npm:1.23.3":
+ version: 1.23.3
+ resolution: "@remix-run/router@npm:1.23.3"
+ checksum: 10c0/73622465dd9e9a2c5a7ced7d1151ea6e9195a8a979c99b4f70a67093eeff7f339daf03a7c6304709a9c27deb2081a8fff6737663aacb33529c1a12a0a0827a17
+ languageName: node
+ linkType: hard
+
"@rolldown/binding-android-arm64@npm:1.0.3":
version: 1.0.3
resolution: "@rolldown/binding-android-arm64@npm:1.0.3"
@@ -1225,6 +1344,24 @@ __metadata:
languageName: node
linkType: hard
+"@tabler/icons-react@npm:^3.31.0":
+ version: 3.44.0
+ resolution: "@tabler/icons-react@npm:3.44.0"
+ dependencies:
+ "@tabler/icons": "npm:3.44.0"
+ peerDependencies:
+ react: ">= 16"
+ checksum: 10c0/9a4bb6f1121d001049be8bb5757160ce4d5f2430671479ee94763f88ef43a69ab8d89b835d6c6451de22f3071793b779fefeed43db49d7993de1ea65c50145c9
+ languageName: node
+ linkType: hard
+
+"@tabler/icons@npm:3.44.0":
+ version: 3.44.0
+ resolution: "@tabler/icons@npm:3.44.0"
+ checksum: 10c0/0d5c1f9d6e68aa04c4a661e96035190e0884e0bc0022d31922efad3cfba210a25b2f013c0b6c0ab6aeb3d5a402a9b85a168966bd11a30ba6087f614f8521ccf3
+ languageName: node
+ linkType: hard
+
"@twentyhq/call-recorder@workspace:.":
version: 0.0.0-use.local
resolution: "@twentyhq/call-recorder@workspace:."
@@ -1239,8 +1376,9 @@ __metadata:
react: "npm:^19.0.0"
react-dom: "npm:^19.0.0"
sharp: "npm:^0.34.5"
- twenty-client-sdk: "npm:^2.16.0"
- twenty-sdk: "npm:^2.16.0"
+ twenty-client-sdk: "npm:^2.18.0"
+ twenty-sdk: "npm:^2.18.0"
+ twenty-ui: "npm:^1.0.0-alpha.1"
typescript: "npm:^5.9.3"
vite-tsconfig-paths: "npm:^4.2.1"
vitest: "npm:^4.1.9"
@@ -1691,6 +1829,13 @@ __metadata:
languageName: node
linkType: hard
+"clsx@npm:^2.1.1":
+ version: 2.1.1
+ resolution: "clsx@npm:2.1.1"
+ checksum: 10c0/c4c8eb865f8c82baab07e71bfa8897c73454881c4f99d6bc81585aecd7c441746c1399d08363dc096c550cceaf97bd4ce1e8854e1771e9998d9f94c4fe075839
+ languageName: node
+ linkType: hard
+
"code-excerpt@npm:^4.0.0":
version: 4.0.0
resolution: "code-excerpt@npm:4.0.0"
@@ -1750,6 +1895,13 @@ __metadata:
languageName: node
linkType: hard
+"css-mediaquery@npm:^0.1.2":
+ version: 0.1.2
+ resolution: "css-mediaquery@npm:0.1.2"
+ checksum: 10c0/b7825a78f52ce8a8198e004fcad0d7be1d3c9a0463ecd05ba31a0f2c94fb81468ad6f4d7bf715a6ca775696e7a17500c2a339b5216a6d0f789cbf78f9454d048
+ languageName: node
+ linkType: hard
+
"csstype@npm:^3.0.2, csstype@npm:^3.2.2":
version: 3.2.3
resolution: "csstype@npm:3.2.3"
@@ -2239,6 +2391,13 @@ __metadata:
languageName: node
linkType: hard
+"hyphenate-style-name@npm:^1.0.0":
+ version: 1.1.0
+ resolution: "hyphenate-style-name@npm:1.1.0"
+ checksum: 10c0/bfe88deac2414a41a0d08811e277c8c098f23993d6a1eb17f14a0f11b54c4d42865a63d3cfe1914668eefb9a188e2de58f38b55a179a238fd1fef606893e194f
+ languageName: node
+ linkType: hard
+
"iconv-lite@npm:^0.7.2":
version: 0.7.2
resolution: "iconv-lite@npm:0.7.2"
@@ -2384,7 +2543,7 @@ __metadata:
languageName: node
linkType: hard
-"js-tokens@npm:^4.0.0":
+"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0":
version: 4.0.0
resolution: "js-tokens@npm:4.0.0"
checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed
@@ -2548,6 +2707,17 @@ __metadata:
languageName: node
linkType: hard
+"loose-envify@npm:^1.4.0":
+ version: 1.4.0
+ resolution: "loose-envify@npm:1.4.0"
+ dependencies:
+ js-tokens: "npm:^3.0.0 || ^4.0.0"
+ bin:
+ loose-envify: cli.js
+ checksum: 10c0/655d110220983c1a4b9c0c679a2e8016d4b67f6e9c7b5435ff5979ecdb20d0813f4dec0a08674fcbdd4846a3f07edbb50a36811fd37930b94aaa0d9daceb017e
+ languageName: node
+ linkType: hard
+
"magic-string@npm:^0.30.21":
version: 0.30.21
resolution: "magic-string@npm:0.30.21"
@@ -2557,6 +2727,15 @@ __metadata:
languageName: node
linkType: hard
+"matchmediaquery@npm:^0.3.0":
+ version: 0.3.1
+ resolution: "matchmediaquery@npm:0.3.1"
+ dependencies:
+ css-mediaquery: "npm:^0.1.2"
+ checksum: 10c0/19a254e0a1eb639fcc6aa7428a6e2d36cb5646a165579897e771580653e470b43d7f4a224c522700dbf1d8f19fb7c63a9719e0a6017727ade967155a2ae89fea
+ languageName: node
+ linkType: hard
+
"math-intrinsics@npm:^1.1.0":
version: 1.1.0
resolution: "math-intrinsics@npm:1.1.0"
@@ -2671,6 +2850,13 @@ __metadata:
languageName: node
linkType: hard
+"object-assign@npm:^4.1.1":
+ version: 4.1.1
+ resolution: "object-assign@npm:4.1.1"
+ checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414
+ languageName: node
+ linkType: hard
+
"obug@npm:^2.1.1":
version: 2.1.3
resolution: "obug@npm:2.1.3"
@@ -2820,6 +3006,17 @@ __metadata:
languageName: node
linkType: hard
+"prop-types@npm:^15.6.1":
+ version: 15.8.1
+ resolution: "prop-types@npm:15.8.1"
+ dependencies:
+ loose-envify: "npm:^1.4.0"
+ object-assign: "npm:^4.1.1"
+ react-is: "npm:^16.13.1"
+ checksum: 10c0/59ece7ca2fb9838031d73a48d4becb9a7cc1ed10e610517c7d8f19a1e02fa47f7c27d557d8a5702bec3cfeccddc853579832b43f449e54635803f277b1c78077
+ languageName: node
+ linkType: hard
+
"proxy-from-env@npm:^2.1.0":
version: 2.1.0
resolution: "proxy-from-env@npm:2.1.0"
@@ -2838,7 +3035,7 @@ __metadata:
languageName: node
linkType: hard
-"react-is@npm:^16.7.0":
+"react-is@npm:^16.13.1, react-is@npm:^16.7.0":
version: 16.13.1
resolution: "react-is@npm:16.13.1"
checksum: 10c0/33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1
@@ -2856,6 +3053,44 @@ __metadata:
languageName: node
linkType: hard
+"react-responsive@npm:^9.0.2":
+ version: 9.0.2
+ resolution: "react-responsive@npm:9.0.2"
+ dependencies:
+ hyphenate-style-name: "npm:^1.0.0"
+ matchmediaquery: "npm:^0.3.0"
+ prop-types: "npm:^15.6.1"
+ shallow-equal: "npm:^1.2.1"
+ peerDependencies:
+ react: ">=16.8.0"
+ checksum: 10c0/c58b30d8b8ff993edbc73f7de59a2d86d8abd6380b06a9b41667c3bdd5401e5e710fa1d0fdac21368759ab9b958e8b479a111329a06e9b0241ee08f500b37a20
+ languageName: node
+ linkType: hard
+
+"react-router-dom@npm:^6.4.4":
+ version: 6.30.4
+ resolution: "react-router-dom@npm:6.30.4"
+ dependencies:
+ "@remix-run/router": "npm:1.23.3"
+ react-router: "npm:6.30.4"
+ peerDependencies:
+ react: ">=16.8"
+ react-dom: ">=16.8"
+ checksum: 10c0/1b25ab26a288da852f7b58eec5c14b3f37919e6773a557cd846d3a05d7a7d890c8d49bda93c0a7f497f240df1df8b0ad50635f990aafb55f2fc545ad8269a822
+ languageName: node
+ linkType: hard
+
+"react-router@npm:6.30.4":
+ version: 6.30.4
+ resolution: "react-router@npm:6.30.4"
+ dependencies:
+ "@remix-run/router": "npm:1.23.3"
+ peerDependencies:
+ react: ">=16.8"
+ checksum: 10c0/fb6de7d1002bcab9ea12c4072d93792eca494f1e4f30cfec334ccb6523756d23ce3e093c7c1241399296cebed94789a3fb89f96ee76004e0e746458a8f6bab33
+ languageName: node
+ linkType: hard
+
"react@npm:^19.0.0, react@npm:^19.2.0":
version: 19.2.7
resolution: "react@npm:19.2.7"
@@ -2870,6 +3105,13 @@ __metadata:
languageName: node
linkType: hard
+"reselect@npm:^5.2.0":
+ version: 5.2.0
+ resolution: "reselect@npm:5.2.0"
+ checksum: 10c0/d0a8497e404b25a769fe792eacae88b9b576c30870450cd243d76ec9427d91a59c4a2cc00d824d283448b444c4c698a8f961d771c8ae39c759313afb31950e2e
+ languageName: node
+ linkType: hard
+
"resolve-from@npm:^4.0.0":
version: 4.0.0
resolution: "resolve-from@npm:4.0.0"
@@ -3021,6 +3263,13 @@ __metadata:
languageName: node
linkType: hard
+"shallow-equal@npm:^1.2.1":
+ version: 1.2.1
+ resolution: "shallow-equal@npm:1.2.1"
+ checksum: 10c0/51e03abadd97c9ebe590547d92db9148446962a3f23a3a0fb1ba2fccab80af881eef0ff1f8ccefd3f066c0bc5a4c8ca53706194813b95c8835fa66448a843a26
+ languageName: node
+ linkType: hard
+
"sharp@npm:^0.34.5":
version: 0.34.5
resolution: "sharp@npm:0.34.5"
@@ -3166,6 +3415,13 @@ __metadata:
languageName: node
linkType: hard
+"state-local@npm:^1.0.6":
+ version: 1.0.7
+ resolution: "state-local@npm:1.0.7"
+ checksum: 10c0/8dc7daeac71844452fafb514a6d6b6f40d7e2b33df398309ea1c7b3948d6110c57f112b7196500a10c54fdde40291488c52c875575670fb5c819602deca48bd9
+ languageName: node
+ linkType: hard
+
"std-env@npm:^4.0.0-rc.1":
version: 4.1.0
resolution: "std-env@npm:4.1.0"
@@ -3332,22 +3588,22 @@ __metadata:
languageName: node
linkType: hard
-"twenty-client-sdk@npm:2.16.0, twenty-client-sdk@npm:^2.16.0":
- version: 2.16.0
- resolution: "twenty-client-sdk@npm:2.16.0"
+"twenty-client-sdk@npm:2.18.0, twenty-client-sdk@npm:^2.18.0":
+ version: 2.18.0
+ resolution: "twenty-client-sdk@npm:2.18.0"
dependencies:
"@genql/runtime": "npm:^2.10.0"
esbuild: "npm:^0.28.1"
graphql: "npm:^16.8.1"
lodash: "npm:^4.17.21"
prettier: "npm:^3.8.3"
- checksum: 10c0/f990220bec103a04ca50dc8bb38c295e94a4e51b27a3188f4a0ae515c204b76bf87f5591739eb705b97e7aa5a9aa6a215d0d4da05297e50a0392801ea64a2234
+ checksum: 10c0/3b7d6ca4e7b59c04cd2348af0f2ba8de153e70d565b4a38c2744b44998dfcc6c2bdb84ba6315cec208953d8c6fa7573ab33c17355f971f279299d470c57be06e
languageName: node
linkType: hard
-"twenty-sdk@npm:^2.16.0":
- version: 2.16.0
- resolution: "twenty-sdk@npm:2.16.0"
+"twenty-sdk@npm:^2.18.0":
+ version: 2.18.0
+ resolution: "twenty-sdk@npm:2.18.0"
dependencies:
"@sniptt/guards": "npm:^0.2.0"
axios: "npm:^1.16.0"
@@ -3366,12 +3622,41 @@ __metadata:
semver: "npm:7.6.3"
sharp: "npm:^0.34.5"
tinyglobby: "npm:^0.2.15"
- twenty-client-sdk: "npm:2.16.0"
+ twenty-client-sdk: "npm:2.18.0"
typescript: "npm:^5.9.3"
uuid: "npm:^13.0.2"
bin:
twenty: dist/cli.cjs
- checksum: 10c0/528f275f8bda022061a52c3563af514d2257f291fc5a232be90ca6011c50ff18a250984b6c68fc8e4c690644c92e1ccb0b89c511b8a19d832455c8860f2751a8
+ checksum: 10c0/7e6d300894c427029e4124047614b55988671c914332613acb38f830cee11ecc480ff2a586b6153756aab1b7fa7f4520cf072c9db1428cd9ce9b9f7a62dfcd64
+ languageName: node
+ linkType: hard
+
+"twenty-ui@npm:^1.0.0-alpha.1":
+ version: 1.0.0-alpha.1
+ resolution: "twenty-ui@npm:1.0.0-alpha.1"
+ dependencies:
+ "@base-ui/react": "npm:^1.5.0"
+ "@monaco-editor/react": "npm:^4.7.0"
+ "@radix-ui/colors": "npm:^3.0.0"
+ "@sniptt/guards": "npm:^0.2.0"
+ "@tabler/icons-react": "npm:^3.31.0"
+ clsx: "npm:^2.1.1"
+ react-responsive: "npm:^9.0.2"
+ react-router-dom: "npm:^6.4.4"
+ type-fest: "npm:^4.10.1"
+ zod: "npm:^4.1.11"
+ peerDependencies:
+ monaco-editor: ">= 0.25.0 < 1"
+ react: ^19.0.0
+ react-dom: ^19.0.0
+ checksum: 10c0/213582e582949d6ffe90a7a8ab89a1d4e9dc724d34ed1b80bd7d50e834a8cb8539723556d41b39dc658f76f176bd8490ae0da8a3d6a713e79e6573b732084179
+ languageName: node
+ linkType: hard
+
+"type-fest@npm:^4.10.1":
+ version: 4.41.0
+ resolution: "type-fest@npm:4.41.0"
+ checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4
languageName: node
linkType: hard
@@ -3432,6 +3717,15 @@ __metadata:
languageName: node
linkType: hard
+"use-sync-external-store@npm:^1.6.0":
+ version: 1.6.0
+ resolution: "use-sync-external-store@npm:1.6.0"
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ checksum: 10c0/35e1179f872a53227bdf8a827f7911da4c37c0f4091c29b76b1e32473d1670ebe7bcd880b808b7549ba9a5605c233350f800ffab963ee4a4ee346ee983b6019b
+ languageName: node
+ linkType: hard
+
"utility-types@npm:^3.10.0":
version: 3.11.0
resolution: "utility-types@npm:3.11.0"
@@ -3725,3 +4019,10 @@ __metadata:
checksum: 10c0/71cc2f2bbb537300c3f569e25693d37b3bc91f225cefce251a71c30bc6bb3e7f8e9420ca0eb57f2ac9e492b085b8dfa075fd1e8195c40b83c951dd59c6e4fbf8
languageName: node
linkType: hard
+
+"zod@npm:^4.1.11":
+ version: 4.4.3
+ resolution: "zod@npm:4.4.3"
+ checksum: 10c0/7ea31b558e88f9faf44f31dd185e2e1cbf51fed3081787fb96cc2534749b50c0acfc6da7f0922a7353ed092dd358c7d50c28ea96c94d04af64191bd33152eca3
+ languageName: node
+ linkType: hard