diff --git a/packages/twenty-apps/public/twenty-fireflies/package.json b/packages/twenty-apps/public/twenty-fireflies/package.json
index b7dcff7a29..9bb2dd528f 100644
--- a/packages/twenty-apps/public/twenty-fireflies/package.json
+++ b/packages/twenty-apps/public/twenty-fireflies/package.json
@@ -21,7 +21,12 @@
"test:watch": "vitest",
"test:unit": "vitest run --config vitest.unit.config.ts"
},
+ "dependencies": {
+ "@sniptt/guards": "^0.2.0"
+ },
"devDependencies": {
+ "@emotion/react": "^11.14.0",
+ "@emotion/styled": "^11.14.0",
"@types/node": "^24.7.2",
"@types/react": "^18.2.0",
"@typescript/native-preview": "^7.0.0-dev.20260116.1",
@@ -30,6 +35,7 @@
"react-dom": "^18.2.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.0.0"
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/constants/universal-identifiers.ts b/packages/twenty-apps/public/twenty-fireflies/src/constants/universal-identifiers.ts
index a8adca1945..d94354baa6 100644
--- a/packages/twenty-apps/public/twenty-fireflies/src/constants/universal-identifiers.ts
+++ b/packages/twenty-apps/public/twenty-fireflies/src/constants/universal-identifiers.ts
@@ -21,3 +21,25 @@ export const FIREFLIES_LIST_CALLS_BY_PARTICIPANT_UNIVERSAL_IDENTIFIER =
export const FIREFLIES_SEARCH_CALLS_UNIVERSAL_IDENTIFIER =
'cb2dd01d-8dca-4222-acce-1d1dbdef9146';
+
+// Shared core CalendarEvent record page layout that apps attach tabs to.
+export const CALENDAR_EVENT_RECORD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER =
+ 'b9b10e40-9ce2-4704-8ac6-c6e92e2563c1';
+
+export const CALENDAR_EVENT_SUMMARY_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER =
+ 'f4fed96d-1551-40dc-a0c1-0b4d2a782733';
+
+export const CALENDAR_EVENT_SUMMARY_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIER =
+ '240925c4-7599-44e1-84ee-4330d450a736';
+
+export const CALENDAR_EVENT_SUMMARY_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER =
+ '64bd540d-f611-4f9e-8b8d-ae8443c310cd';
+
+export const CALENDAR_EVENT_TRANSCRIPT_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER =
+ '02acda71-77e0-4176-a134-c79f40510419';
+
+export const CALENDAR_EVENT_TRANSCRIPT_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIER =
+ 'ac9d7c53-15f8-4c89-85ce-f6451ace2eb2';
+
+export const CALENDAR_EVENT_TRANSCRIPT_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER =
+ '364642a6-addb-4df1-8c57-25228b826259';
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/calendar-event-summary.front-component.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/calendar-event-summary.front-component.tsx
new file mode 100644
index 0000000000..0bc1271c21
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/calendar-event-summary.front-component.tsx
@@ -0,0 +1,13 @@
+import { defineFrontComponent } from 'twenty-sdk/define';
+
+import { CALENDAR_EVENT_SUMMARY_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
+import { CalendarEventSummary } from 'src/front-components/components/CalendarEventSummary';
+
+export default defineFrontComponent({
+ universalIdentifier:
+ CALENDAR_EVENT_SUMMARY_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
+ name: 'calendar-event-summary',
+ description:
+ 'Read-only Fireflies AI summary viewer for the calendar event record page.',
+ component: CalendarEventSummary,
+});
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/calendar-event-transcript.front-component.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/calendar-event-transcript.front-component.tsx
new file mode 100644
index 0000000000..46e8e4abc5
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/calendar-event-transcript.front-component.tsx
@@ -0,0 +1,13 @@
+import { defineFrontComponent } from 'twenty-sdk/define';
+
+import { CALENDAR_EVENT_TRANSCRIPT_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
+import { CalendarEventTranscript } from 'src/front-components/components/CalendarEventTranscript';
+
+export default defineFrontComponent({
+ universalIdentifier:
+ CALENDAR_EVENT_TRANSCRIPT_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
+ name: 'calendar-event-transcript',
+ description:
+ 'Read-only diarized Fireflies transcript viewer for the calendar event record page.',
+ component: CalendarEventTranscript,
+});
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventSummary.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventSummary.tsx
new file mode 100644
index 0000000000..ffeed13539
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventSummary.tsx
@@ -0,0 +1,39 @@
+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';
+
+const StyledCenteredState = styled.div`
+ align-items: center;
+ box-sizing: border-box;
+ color: ${() => themeCssVariables.font.color.tertiary};
+ display: flex;
+ font-family: ${() => themeCssVariables.font.family};
+ font-size: ${() => themeCssVariables.font.size.sm};
+ height: 100%;
+ justify-content: center;
+ padding: ${() => themeCssVariables.spacing[4]};
+`;
+
+export const CalendarEventSummary = () => {
+ const selectedRecordIds = useSelectedRecordIds();
+ const calendarEventId =
+ selectedRecordIds.length === 1 ? selectedRecordIds[0] : undefined;
+
+ if (isUndefined(calendarEventId)) {
+ return (
+
+ Open a calendar event to see its summary.
+
+ );
+ }
+
+ return (
+
+ );
+};
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventSummaryBody.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventSummaryBody.tsx
new file mode 100644
index 0000000000..4cd4417862
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventSummaryBody.tsx
@@ -0,0 +1,53 @@
+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';
+
+const StyledCenteredState = styled.div`
+ align-items: center;
+ box-sizing: border-box;
+ color: ${() => themeCssVariables.font.color.tertiary};
+ display: flex;
+ font-family: ${() => themeCssVariables.font.family};
+ font-size: ${() => themeCssVariables.font.size.sm};
+ justify-content: center;
+ min-height: 240px;
+ padding: ${() => themeCssVariables.spacing[4]};
+`;
+
+type CalendarEventSummaryBodyProps = {
+ summaryMarkdown: string | undefined;
+ isCalendarEventSummaryQueryLoading: boolean;
+ errorMessage: string | undefined;
+};
+
+export const CalendarEventSummaryBody = ({
+ summaryMarkdown,
+ isCalendarEventSummaryQueryLoading,
+ errorMessage,
+}: CalendarEventSummaryBodyProps) => {
+ if (!isUndefined(errorMessage)) {
+ return (
+
+ );
+ }
+
+ if (isCalendarEventSummaryQueryLoading) {
+ return Loading summary…;
+ }
+
+ if (isUndefined(summaryMarkdown)) {
+ return (
+
+ No summary for this calendar event yet.
+
+ );
+ }
+
+ return ;
+};
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventSummaryContent.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventSummaryContent.tsx
new file mode 100644
index 0000000000..6656ff3067
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventSummaryContent.tsx
@@ -0,0 +1,77 @@
+import styled from '@emotion/styled';
+import { themeCssVariables } from 'twenty-ui/theme-constants';
+
+import { CalendarEventSummaryBody } from 'src/front-components/components/CalendarEventSummaryBody';
+import { useCalendarEventSummary } from 'src/front-components/hooks/use-calendar-event-summary';
+
+const StyledSummaryShell = styled.div`
+ background: ${() => themeCssVariables.background.primary};
+ border: 1px solid transparent;
+ border-radius: ${() => themeCssVariables.border.radius.md};
+ box-sizing: border-box;
+ font-family: ${() => themeCssVariables.font.family};
+ padding: ${() => themeCssVariables.spacing[4]};
+ position: relative;
+ width: 100%;
+`;
+
+const StyledSummaryHeader = styled.div`
+ align-items: center;
+ box-sizing: border-box;
+ display: flex;
+ height: ${() => themeCssVariables.spacing[6]};
+`;
+
+const StyledSummaryTitle = styled.h2`
+ color: ${() => themeCssVariables.font.color.primary};
+ flex: 1;
+ font-size: ${() => themeCssVariables.font.size.md};
+ font-weight: ${() => themeCssVariables.font.weight.medium};
+ margin: 0;
+ overflow: hidden;
+ padding-inline: ${() => themeCssVariables.spacing[1]};
+ user-select: none;
+`;
+
+const StyledSummaryBody = styled.div`
+ box-sizing: border-box;
+ margin-top: ${() => themeCssVariables.spacing[2]};
+`;
+
+const StyledSummaryContentFrame = styled.div`
+ background-color: ${() => themeCssVariables.background.secondary};
+ border: 1px solid ${() => themeCssVariables.border.color.medium};
+ border-radius: ${() => themeCssVariables.border.radius.md};
+ box-sizing: border-box;
+ padding: ${() => themeCssVariables.spacing[3]};
+`;
+
+type CalendarEventSummaryContentProps = {
+ calendarEventId: string;
+};
+
+export const CalendarEventSummaryContent = ({
+ calendarEventId,
+}: CalendarEventSummaryContentProps) => {
+ const { summaryMarkdown, isCalendarEventSummaryQueryLoading, errorMessage } =
+ useCalendarEventSummary(calendarEventId);
+
+ return (
+
+
+ Summary
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventTranscript.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventTranscript.tsx
new file mode 100644
index 0000000000..e58f620555
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventTranscript.tsx
@@ -0,0 +1,39 @@
+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 { CalendarEventTranscriptContent } from 'src/front-components/components/CalendarEventTranscriptContent';
+
+const StyledCenteredState = styled.div`
+ align-items: center;
+ box-sizing: border-box;
+ color: ${() => themeCssVariables.font.color.tertiary};
+ display: flex;
+ font-family: ${() => themeCssVariables.font.family};
+ font-size: ${() => themeCssVariables.font.size.sm};
+ height: 100%;
+ justify-content: center;
+ padding: ${() => themeCssVariables.spacing[4]};
+`;
+
+export const CalendarEventTranscript = () => {
+ const selectedRecordIds = useSelectedRecordIds();
+ const calendarEventId =
+ selectedRecordIds.length === 1 ? selectedRecordIds[0] : undefined;
+
+ if (isUndefined(calendarEventId)) {
+ return (
+
+ Open a calendar event to see its transcript.
+
+ );
+ }
+
+ return (
+
+ );
+};
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventTranscriptBody.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventTranscriptBody.tsx
new file mode 100644
index 0000000000..861e861927
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventTranscriptBody.tsx
@@ -0,0 +1,74 @@
+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 { parseTranscriptEntries } from 'src/front-components/utils/parse-transcript-entries.util';
+
+const StyledCenteredState = styled.div`
+ align-items: center;
+ box-sizing: border-box;
+ color: ${() => themeCssVariables.font.color.tertiary};
+ display: flex;
+ flex: 1;
+ font-family: ${() => themeCssVariables.font.family};
+ font-size: ${() => themeCssVariables.font.size.sm};
+ justify-content: center;
+ min-height: 240px;
+ padding: ${() => themeCssVariables.spacing[4]};
+`;
+
+type CalendarEventTranscriptBodyProps = {
+ transcript: unknown;
+ isCalendarEventTranscriptQueryLoading: boolean;
+ errorMessage: string | undefined;
+};
+
+export const CalendarEventTranscriptBody = ({
+ transcript,
+ isCalendarEventTranscriptQueryLoading,
+ errorMessage,
+}: CalendarEventTranscriptBodyProps) => {
+ const entries = useMemo(
+ () => parseTranscriptEntries(transcript),
+ [transcript],
+ );
+
+ if (!isUndefined(errorMessage)) {
+ return (
+
+ );
+ }
+
+ if (isCalendarEventTranscriptQueryLoading) {
+ return Loading transcript…;
+ }
+
+ if (isUndefined(transcript)) {
+ return (
+
+ No transcript for this calendar event yet.
+
+ );
+ }
+
+ if (isUndefined(entries)) {
+ return (
+
+ );
+ }
+
+ if (entries.length === 0) {
+ return The transcript is empty.;
+ }
+
+ return ;
+};
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventTranscriptContent.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventTranscriptContent.tsx
new file mode 100644
index 0000000000..db370435dd
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventTranscriptContent.tsx
@@ -0,0 +1,84 @@
+import styled from '@emotion/styled';
+import { themeCssVariables } from 'twenty-ui/theme-constants';
+
+import { CalendarEventTranscriptBody } from 'src/front-components/components/CalendarEventTranscriptBody';
+import { useCalendarEventTranscript } from 'src/front-components/hooks/use-calendar-event-transcript';
+
+const StyledTranscriptShell = styled.div`
+ background: ${() => themeCssVariables.background.primary};
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ font-family: ${() => themeCssVariables.font.family};
+ height: 100%;
+ padding: ${() => themeCssVariables.spacing[4]};
+ width: 100%;
+`;
+
+const StyledTranscriptHeader = styled.div`
+ align-items: center;
+ box-sizing: border-box;
+ display: flex;
+ height: ${() => themeCssVariables.spacing[6]};
+`;
+
+const StyledTranscriptTitle = styled.h2`
+ color: ${() => themeCssVariables.font.color.primary};
+ flex: 1;
+ font-size: ${() => themeCssVariables.font.size.md};
+ font-weight: ${() => themeCssVariables.font.weight.medium};
+ margin: 0;
+ overflow: hidden;
+ padding-inline: ${() => themeCssVariables.spacing[1]};
+ user-select: none;
+`;
+
+const StyledTranscriptBody = styled.div`
+ box-sizing: border-box;
+ display: flex;
+ flex: 1;
+ margin-top: ${() => themeCssVariables.spacing[2]};
+ min-height: 0;
+`;
+
+const StyledTranscriptContentFrame = styled.div`
+ background-color: ${() => themeCssVariables.background.secondary};
+ border: 1px solid ${() => themeCssVariables.border.color.medium};
+ border-radius: ${() => themeCssVariables.border.radius.md};
+ box-sizing: border-box;
+ display: flex;
+ flex: 1;
+ min-height: 0;
+ overflow-y: auto;
+ padding: ${() => themeCssVariables.spacing[3]};
+`;
+
+type CalendarEventTranscriptContentProps = {
+ calendarEventId: string;
+};
+
+export const CalendarEventTranscriptContent = ({
+ calendarEventId,
+}: CalendarEventTranscriptContentProps) => {
+ const { transcript, isCalendarEventTranscriptQueryLoading, errorMessage } =
+ useCalendarEventTranscript(calendarEventId);
+
+ return (
+
+
+ Transcript
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/SummaryInlineSegments.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/SummaryInlineSegments.tsx
new file mode 100644
index 0000000000..4008facb2c
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/SummaryInlineSegments.tsx
@@ -0,0 +1,28 @@
+import styled from '@emotion/styled';
+import { Fragment } from 'react';
+import { themeCssVariables } from 'twenty-ui/theme-constants';
+
+import { type SummaryInlineSegment } from 'src/front-components/types/summary-inline-segment.type';
+
+const StyledBold = styled.strong`
+ color: ${() => themeCssVariables.font.color.primary};
+ font-weight: ${() => themeCssVariables.font.weight.medium};
+`;
+
+type SummaryInlineSegmentsProps = {
+ segments: SummaryInlineSegment[];
+};
+
+export const SummaryInlineSegments = ({
+ segments,
+}: SummaryInlineSegmentsProps) => (
+ <>
+ {segments.map((segment, index) =>
+ segment.isBold ? (
+ {segment.text}
+ ) : (
+ {segment.text}
+ ),
+ )}
+ >
+);
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/SummaryMarkdown.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/SummaryMarkdown.tsx
new file mode 100644
index 0000000000..a663b22b31
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/SummaryMarkdown.tsx
@@ -0,0 +1,87 @@
+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 { parseSummaryMarkdownBlocks } from 'src/front-components/utils/parse-summary-markdown-blocks.util';
+
+const TOP_LEVEL_HEADING_MAX = 2;
+
+const StyledSummary = styled.div`
+ color: ${() => themeCssVariables.font.color.secondary};
+ display: flex;
+ flex-direction: column;
+ 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: ${() => themeCssVariables.font.color.primary};
+ font-size: ${({ $isTopLevel }) =>
+ $isTopLevel
+ ? themeCssVariables.font.size.md
+ : themeCssVariables.font.size.sm};
+ font-weight: ${() => themeCssVariables.font.weight.medium};
+ margin: 0;
+ margin-top: ${() => themeCssVariables.spacing[1]};
+`;
+
+const StyledParagraph = styled.p`
+ margin: 0;
+`;
+
+const StyledList = styled.ul`
+ display: flex;
+ flex-direction: column;
+ gap: ${() => themeCssVariables.spacing[1]};
+ margin: 0;
+ padding-left: ${() => themeCssVariables.spacing[3]};
+`;
+
+type SummaryMarkdownProps = {
+ markdown: string;
+};
+
+export const SummaryMarkdown = ({ markdown }: SummaryMarkdownProps) => {
+ const blocks = useMemo(
+ () => parseSummaryMarkdownBlocks(markdown),
+ [markdown],
+ );
+
+ return (
+
+ {blocks.map((block, index) => {
+ if (block.type === 'heading') {
+ return (
+
+
+
+ );
+ }
+
+ if (block.type === 'list') {
+ return (
+
+ {block.items.map((item, itemIndex) => (
+
+
+
+ ))}
+
+ );
+ }
+
+ return (
+
+
+
+ );
+ })}
+
+ );
+};
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/TranscriptEntryList.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/TranscriptEntryList.tsx
new file mode 100644
index 0000000000..83116e0f7a
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/TranscriptEntryList.tsx
@@ -0,0 +1,25 @@
+import styled from '@emotion/styled';
+import { themeCssVariables } from 'twenty-ui/theme-constants';
+
+import { TranscriptEntryListItem } from 'src/front-components/components/TranscriptEntryListItem';
+import { type TranscriptEntry } from 'src/front-components/types/transcript-entry.type';
+
+const StyledTranscriptContainer = styled.div`
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ gap: ${() => themeCssVariables.spacing[2]};
+ min-height: 0;
+`;
+
+type TranscriptEntryListProps = {
+ entries: TranscriptEntry[];
+};
+
+export const TranscriptEntryList = ({ entries }: TranscriptEntryListProps) => (
+
+ {entries.map((entry, entryIndex) => (
+
+ ))}
+
+);
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/TranscriptEntryListItem.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/TranscriptEntryListItem.tsx
new file mode 100644
index 0000000000..ae4a81e047
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/TranscriptEntryListItem.tsx
@@ -0,0 +1,76 @@
+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 { type TranscriptEntry } from 'src/front-components/types/transcript-entry.type';
+import { formatSecondsAsClockTimestamp } from 'src/front-components/utils/format-seconds-as-clock-timestamp.util';
+
+const StyledEntry = styled.div`
+ align-items: flex-start;
+ border-radius: ${() => themeCssVariables.border.radius.sm};
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ gap: ${() => themeCssVariables.spacing[2]};
+ justify-content: center;
+ padding: ${() => themeCssVariables.spacing[2]};
+ width: 100%;
+`;
+
+const StyledEntryHeader = styled.div`
+ align-items: center;
+ align-self: stretch;
+ display: flex;
+ gap: ${() => themeCssVariables.spacing[2]};
+ min-height: ${() => themeCssVariables.spacing[6]};
+ min-width: 0;
+`;
+
+const StyledTimestamp = styled.span`
+ color: ${() => themeCssVariables.font.color.tertiary};
+ font-size: ${() => themeCssVariables.font.size.xs};
+ line-height: 1.4;
+`;
+
+const StyledEntryText = styled.p`
+ align-self: stretch;
+ color: ${() => themeCssVariables.font.color.secondary};
+ font-size: ${() => themeCssVariables.font.size.sm};
+ line-height: 1.4;
+ margin: 0;
+`;
+
+type TranscriptEntryListItemProps = {
+ entry: TranscriptEntry;
+};
+
+export const TranscriptEntryListItem = ({
+ entry,
+}: TranscriptEntryListItemProps) => (
+
+
+
+ }
+ />
+ {!isUndefined(entry.startSeconds) && (
+
+ {formatSecondsAsClockTimestamp(entry.startSeconds)}
+
+ )}
+
+ {entry.text}
+
+);
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/TranscriptErrorBox.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/TranscriptErrorBox.tsx
new file mode 100644
index 0000000000..29020b4bb7
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/TranscriptErrorBox.tsx
@@ -0,0 +1,47 @@
+import styled from '@emotion/styled';
+import { themeCssVariables } from 'twenty-ui/theme-constants';
+
+const StyledStateContainer = styled.div`
+ box-sizing: border-box;
+ font-family: ${() => themeCssVariables.font.family};
+ height: 100%;
+ padding: ${() => themeCssVariables.spacing[4]};
+`;
+
+const StyledErrorBox = styled.div`
+ background: ${() => themeCssVariables.background.transparent.danger};
+ border: 1px solid ${() => themeCssVariables.border.color.danger};
+ border-radius: ${() => themeCssVariables.border.radius.md};
+ display: flex;
+ flex-direction: column;
+ gap: ${() => themeCssVariables.spacing[1]};
+ padding: ${() => themeCssVariables.spacing[3]};
+`;
+
+const StyledErrorTitle = styled.span`
+ color: ${() => themeCssVariables.font.color.danger};
+ font-size: ${() => themeCssVariables.font.size.sm};
+ font-weight: ${() => themeCssVariables.font.weight.medium};
+`;
+
+const StyledErrorDescription = styled.span`
+ color: ${() => themeCssVariables.font.color.secondary};
+ font-size: ${() => themeCssVariables.font.size.sm};
+`;
+
+type TranscriptErrorBoxProps = {
+ title: string;
+ description: string;
+};
+
+export const TranscriptErrorBox = ({
+ title,
+ description,
+}: TranscriptErrorBoxProps) => (
+
+
+ {title}
+ {description}
+
+
+);
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/hooks/use-calendar-event-summary.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/hooks/use-calendar-event-summary.ts
new file mode 100644
index 0000000000..1844244f3a
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/hooks/use-calendar-event-summary.ts
@@ -0,0 +1,115 @@
+import { isNonEmptyString, isUndefined } from '@sniptt/guards';
+import { useEffect, useState } from 'react';
+import { CoreApiClient } from 'twenty-client-sdk/core';
+
+type CalendarEventSummaryState = {
+ summaryMarkdown: string | undefined;
+ isCalendarEventSummaryQueryLoading: boolean;
+ errorMessage: string | undefined;
+};
+
+type CalendarEventSummaryCallRecordingNode = {
+ id: string;
+ summary: { markdown: string | null } | null;
+};
+
+type CalendarEventSummaryCallRecordingEdge = {
+ node: CalendarEventSummaryCallRecordingNode;
+};
+
+const CALENDAR_EVENT_SUMMARY_LOOKUP_LIMIT = 10;
+const CALENDAR_EVENT_SUMMARY_ERROR_MESSAGE = 'Please try again later.';
+
+const selectSummaryMarkdown = (
+ callRecordingNodes: CalendarEventSummaryCallRecordingNode[],
+): string | undefined => {
+ const summaryMarkdown = callRecordingNodes.find((callRecordingNode) =>
+ isNonEmptyString(callRecordingNode.summary?.markdown),
+ )?.summary?.markdown;
+
+ return isNonEmptyString(summaryMarkdown) ? summaryMarkdown : undefined;
+};
+
+export const useCalendarEventSummary = (
+ calendarEventId: string | undefined,
+): CalendarEventSummaryState => {
+ const [state, setState] = useState({
+ summaryMarkdown: undefined,
+ isCalendarEventSummaryQueryLoading: !isUndefined(calendarEventId),
+ errorMessage: undefined,
+ });
+
+ useEffect(() => {
+ if (isUndefined(calendarEventId)) {
+ setState({
+ summaryMarkdown: undefined,
+ isCalendarEventSummaryQueryLoading: false,
+ errorMessage: undefined,
+ });
+ return;
+ }
+
+ let cancelled = false;
+
+ const fetchSummary = async () => {
+ setState({
+ summaryMarkdown: undefined,
+ isCalendarEventSummaryQueryLoading: true,
+ errorMessage: undefined,
+ });
+
+ try {
+ const client = new CoreApiClient();
+ const queryResult = await client.query({
+ callRecordings: {
+ __args: {
+ filter: { calendarEventId: { eq: calendarEventId } },
+ orderBy: [{ startedAt: 'DescNullsLast' }],
+ first: CALENDAR_EVENT_SUMMARY_LOOKUP_LIMIT,
+ },
+ edges: {
+ node: {
+ id: true,
+ summary: { markdown: true },
+ },
+ },
+ },
+ });
+
+ if (cancelled) {
+ return;
+ }
+
+ const callRecordingEdges = (queryResult.callRecordings?.edges ??
+ []) as CalendarEventSummaryCallRecordingEdge[];
+ const callRecordingNodes = callRecordingEdges.map(
+ (callRecordingEdge) => callRecordingEdge.node,
+ );
+
+ setState({
+ summaryMarkdown: selectSummaryMarkdown(callRecordingNodes),
+ isCalendarEventSummaryQueryLoading: false,
+ errorMessage: undefined,
+ });
+ } catch {
+ if (cancelled) {
+ return;
+ }
+
+ setState({
+ summaryMarkdown: undefined,
+ isCalendarEventSummaryQueryLoading: false,
+ errorMessage: CALENDAR_EVENT_SUMMARY_ERROR_MESSAGE,
+ });
+ }
+ };
+
+ fetchSummary();
+
+ return () => {
+ cancelled = true;
+ };
+ }, [calendarEventId]);
+
+ return state;
+};
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/hooks/use-calendar-event-transcript.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/hooks/use-calendar-event-transcript.ts
new file mode 100644
index 0000000000..6e90efa413
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/hooks/use-calendar-event-transcript.ts
@@ -0,0 +1,115 @@
+import { isUndefined } from '@sniptt/guards';
+import { useEffect, useState } from 'react';
+import { CoreApiClient } from 'twenty-client-sdk/core';
+
+type CalendarEventTranscriptState = {
+ transcript: unknown;
+ isCalendarEventTranscriptQueryLoading: boolean;
+ errorMessage: string | undefined;
+};
+
+type CalendarEventTranscriptCallRecordingNode = {
+ id: string;
+ transcript: unknown;
+};
+
+type CalendarEventTranscriptCallRecordingEdge = {
+ node: CalendarEventTranscriptCallRecordingNode;
+};
+
+const CALENDAR_EVENT_TRANSCRIPT_LOOKUP_LIMIT = 10;
+const CALENDAR_EVENT_TRANSCRIPT_ERROR_MESSAGE = 'Please try again later.';
+
+const hasTranscript = (
+ callRecordingNode: CalendarEventTranscriptCallRecordingNode,
+): boolean =>
+ !isUndefined(callRecordingNode.transcript) &&
+ callRecordingNode.transcript !== null;
+
+const selectTranscript = (
+ callRecordingNodes: CalendarEventTranscriptCallRecordingNode[],
+): unknown => callRecordingNodes.find(hasTranscript)?.transcript ?? undefined;
+
+export const useCalendarEventTranscript = (
+ calendarEventId: string | undefined,
+): CalendarEventTranscriptState => {
+ const [state, setState] = useState({
+ transcript: undefined,
+ isCalendarEventTranscriptQueryLoading: !isUndefined(calendarEventId),
+ errorMessage: undefined,
+ });
+
+ useEffect(() => {
+ if (isUndefined(calendarEventId)) {
+ setState({
+ transcript: undefined,
+ isCalendarEventTranscriptQueryLoading: false,
+ errorMessage: undefined,
+ });
+ return;
+ }
+
+ let cancelled = false;
+
+ const fetchTranscript = async () => {
+ setState({
+ transcript: undefined,
+ isCalendarEventTranscriptQueryLoading: true,
+ errorMessage: undefined,
+ });
+
+ try {
+ const client = new CoreApiClient();
+ const queryResult = await client.query({
+ callRecordings: {
+ __args: {
+ filter: { calendarEventId: { eq: calendarEventId } },
+ orderBy: [{ startedAt: 'DescNullsLast' }],
+ first: CALENDAR_EVENT_TRANSCRIPT_LOOKUP_LIMIT,
+ },
+ edges: {
+ node: {
+ id: true,
+ transcript: true,
+ },
+ },
+ },
+ });
+
+ if (cancelled) {
+ return;
+ }
+
+ const callRecordingEdges = (queryResult.callRecordings?.edges ??
+ []) as CalendarEventTranscriptCallRecordingEdge[];
+ const callRecordingNodes = callRecordingEdges.map(
+ (callRecordingEdge) => callRecordingEdge.node,
+ );
+
+ setState({
+ transcript: selectTranscript(callRecordingNodes),
+ isCalendarEventTranscriptQueryLoading: false,
+ errorMessage: undefined,
+ });
+ } catch {
+ if (cancelled) {
+ return;
+ }
+
+ setState({
+ transcript: undefined,
+ isCalendarEventTranscriptQueryLoading: false,
+ errorMessage: CALENDAR_EVENT_TRANSCRIPT_ERROR_MESSAGE,
+ });
+ }
+ };
+
+ fetchTranscript();
+
+ return () => {
+ cancelled = true;
+ };
+ }, [calendarEventId]);
+
+ return state;
+};
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/types/summary-inline-segment.type.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/types/summary-inline-segment.type.ts
new file mode 100644
index 0000000000..474eff8e90
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/types/summary-inline-segment.type.ts
@@ -0,0 +1,4 @@
+export type SummaryInlineSegment = {
+ text: string;
+ isBold: boolean;
+};
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/types/summary-markdown-block.type.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/types/summary-markdown-block.type.ts
new file mode 100644
index 0000000000..2ba417b7d7
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/types/summary-markdown-block.type.ts
@@ -0,0 +1,6 @@
+import { type SummaryInlineSegment } from 'src/front-components/types/summary-inline-segment.type';
+
+export type SummaryMarkdownBlock =
+ | { type: 'heading'; level: number; segments: SummaryInlineSegment[] }
+ | { type: 'paragraph'; segments: SummaryInlineSegment[] }
+ | { type: 'list'; items: SummaryInlineSegment[][] };
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/types/transcript-entry.type.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/types/transcript-entry.type.ts
new file mode 100644
index 0000000000..0be2540670
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/types/transcript-entry.type.ts
@@ -0,0 +1,13 @@
+export type TranscriptWord = {
+ text: string;
+ startSeconds: number | undefined;
+ endSeconds: number | undefined;
+};
+
+export type TranscriptEntry = {
+ speakerName: string;
+ startSeconds: number | undefined;
+ endSeconds: number | undefined;
+ text: string;
+ words: TranscriptWord[];
+};
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/__tests__/parse-summary-inline-segments.test.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/__tests__/parse-summary-inline-segments.test.ts
new file mode 100644
index 0000000000..1cf010606c
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/__tests__/parse-summary-inline-segments.test.ts
@@ -0,0 +1,33 @@
+import { describe, expect, it } from 'vitest';
+
+import { parseSummaryInlineSegments } from 'src/front-components/utils/parse-summary-inline-segments.util';
+
+describe('parseSummaryInlineSegments', () => {
+ it('should return a single plain segment for text without markers', () => {
+ expect(parseSummaryInlineSegments('plain text')).toEqual([
+ { text: 'plain text', isBold: false },
+ ]);
+ });
+
+ it('should parse **bold** runs', () => {
+ expect(parseSummaryInlineSegments('a **b** c')).toEqual([
+ { text: 'a ', isBold: false },
+ { text: 'b', isBold: true },
+ { text: ' c', isBold: false },
+ ]);
+ });
+
+ it('should parse __bold__ runs', () => {
+ expect(parseSummaryInlineSegments('a __b__ c')).toEqual([
+ { text: 'a ', isBold: false },
+ { text: 'b', isBold: true },
+ { text: ' c', isBold: false },
+ ]);
+ });
+
+ it('should not match mismatched delimiters', () => {
+ expect(parseSummaryInlineSegments('**b__')).toEqual([
+ { text: '**b__', isBold: false },
+ ]);
+ });
+});
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/__tests__/parse-summary-markdown-blocks.test.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/__tests__/parse-summary-markdown-blocks.test.ts
new file mode 100644
index 0000000000..4a03e558cd
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/__tests__/parse-summary-markdown-blocks.test.ts
@@ -0,0 +1,35 @@
+import { describe, expect, it } from 'vitest';
+
+import { parseSummaryMarkdownBlocks } from 'src/front-components/utils/parse-summary-markdown-blocks.util';
+
+describe('parseSummaryMarkdownBlocks', () => {
+ it('should parse headings, paragraphs and lists preserving order', () => {
+ expect(
+ parseSummaryMarkdownBlocks('## Overview\n\nSome text\n\n- first\n- second'),
+ ).toEqual([
+ {
+ type: 'heading',
+ level: 2,
+ segments: [{ text: 'Overview', isBold: false }],
+ },
+ { type: 'paragraph', segments: [{ text: 'Some text', isBold: false }] },
+ {
+ type: 'list',
+ items: [
+ [{ text: 'first', isBold: false }],
+ [{ text: 'second', isBold: false }],
+ ],
+ },
+ ]);
+ });
+
+ it('should flush a trailing list at end of input', () => {
+ expect(parseSummaryMarkdownBlocks('- only item')).toEqual([
+ { type: 'list', items: [[{ text: 'only item', isBold: false }]] },
+ ]);
+ });
+
+ it('should return an empty array for empty input', () => {
+ expect(parseSummaryMarkdownBlocks('')).toEqual([]);
+ });
+});
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/__tests__/parse-transcript-entries.test.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/__tests__/parse-transcript-entries.test.ts
new file mode 100644
index 0000000000..855657260f
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/__tests__/parse-transcript-entries.test.ts
@@ -0,0 +1,50 @@
+import { describe, expect, it } from 'vitest';
+
+import { parseTranscriptEntries } from 'src/front-components/utils/parse-transcript-entries.util';
+
+describe('parseTranscriptEntries', () => {
+ it('should parse a diarized entry with speaker and relative timestamps', () => {
+ expect(
+ parseTranscriptEntries([
+ {
+ participant: { name: 'Sarah' },
+ words: [
+ {
+ text: 'Hello',
+ start_timestamp: { relative: 1.5 },
+ end_timestamp: { relative: 2 },
+ },
+ ],
+ },
+ ]),
+ ).toEqual([
+ {
+ speakerName: 'Sarah',
+ startSeconds: 1.5,
+ endSeconds: 2,
+ text: 'Hello',
+ words: [{ text: 'Hello', startSeconds: 1.5, endSeconds: 2 }],
+ },
+ ]);
+ });
+
+ it('should fall back to "Unknown speaker" for a whitespace-only name', () => {
+ const entries = parseTranscriptEntries([
+ { participant: { name: ' ' }, words: [{ text: 'Hi' }] },
+ ]);
+
+ expect(entries?.[0]?.speakerName).toBe('Unknown speaker');
+ });
+
+ it('should drop entries whose words are all whitespace', () => {
+ expect(
+ parseTranscriptEntries([
+ { participant: { name: 'Sarah' }, words: [{ text: ' ' }] },
+ ]),
+ ).toEqual([]);
+ });
+
+ it('should return undefined for a non-array transcript', () => {
+ expect(parseTranscriptEntries({})).toBeUndefined();
+ });
+});
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/as-record.util.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/as-record.util.ts
new file mode 100644
index 0000000000..63e7cfcddd
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/as-record.util.ts
@@ -0,0 +1,6 @@
+import { isArray, isObject } from '@sniptt/guards';
+
+export const asRecord = (value: unknown): Record | undefined =>
+ isObject(value) && !isArray(value)
+ ? (value as Record)
+ : undefined;
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/format-seconds-as-clock-timestamp.util.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/format-seconds-as-clock-timestamp.util.ts
new file mode 100644
index 0000000000..c92cae504c
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/format-seconds-as-clock-timestamp.util.ts
@@ -0,0 +1,20 @@
+const SECONDS_PER_MINUTE = 60;
+const SECONDS_PER_HOUR = 3600;
+
+export const formatSecondsAsClockTimestamp = (totalSeconds: number): string => {
+ const safeSeconds = Number.isFinite(totalSeconds)
+ ? Math.max(0, Math.floor(totalSeconds))
+ : 0;
+ const hours = Math.floor(safeSeconds / SECONDS_PER_HOUR);
+ const minutes = Math.floor(
+ (safeSeconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE,
+ );
+ const seconds = safeSeconds % SECONDS_PER_MINUTE;
+ const paddedSeconds = String(seconds).padStart(2, '0');
+
+ if (hours > 0) {
+ return `${hours}:${String(minutes).padStart(2, '0')}:${paddedSeconds}`;
+ }
+
+ return `${minutes}:${paddedSeconds}`;
+};
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/is-non-empty-string.util.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/is-non-empty-string.util.ts
new file mode 100644
index 0000000000..175943f383
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/is-non-empty-string.util.ts
@@ -0,0 +1,5 @@
+import { isString } from '@sniptt/guards';
+
+// Trimming variant of @sniptt/guards isNonEmptyString, for normalizing at read boundaries.
+export const isNonEmptyString = (value: unknown): value is string =>
+ isString(value) && value.trim() !== '';
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/parse-summary-inline-segments.util.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/parse-summary-inline-segments.util.ts
new file mode 100644
index 0000000000..d275f08182
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/parse-summary-inline-segments.util.ts
@@ -0,0 +1,39 @@
+import { type SummaryInlineSegment } from 'src/front-components/types/summary-inline-segment.type';
+
+const BOLD_PATTERN = /(\*\*|__)(.+?)\1/g;
+
+// Splits a markdown line into plain and bold (**bold** or __bold__) runs,
+// preserving surrounding whitespace so adjacent bold runs don't collapse together.
+export const parseSummaryInlineSegments = (
+ text: string,
+): SummaryInlineSegment[] => {
+ const boldMatches = [...text.matchAll(BOLD_PATTERN)];
+
+ const { segments, cursor } = boldMatches.reduce<{
+ segments: SummaryInlineSegment[];
+ cursor: number;
+ }>(
+ (accumulator, match) => {
+ const matchStart = match.index ?? 0;
+ const leadingText = text.slice(accumulator.cursor, matchStart);
+ const leadingSegments =
+ leadingText.length > 0 ? [{ text: leadingText, isBold: false }] : [];
+
+ return {
+ segments: [
+ ...accumulator.segments,
+ ...leadingSegments,
+ { text: match[2], isBold: true },
+ ],
+ cursor: matchStart + match[0].length,
+ };
+ },
+ { segments: [], cursor: 0 },
+ );
+
+ const trailingText = text.slice(cursor);
+
+ return trailingText.length > 0
+ ? [...segments, { text: trailingText, isBold: false }]
+ : segments;
+};
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/parse-summary-markdown-blocks.util.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/parse-summary-markdown-blocks.util.ts
new file mode 100644
index 0000000000..131043467e
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/parse-summary-markdown-blocks.util.ts
@@ -0,0 +1,58 @@
+import { type SummaryInlineSegment } from 'src/front-components/types/summary-inline-segment.type';
+import { type SummaryMarkdownBlock } from 'src/front-components/types/summary-markdown-block.type';
+import { parseSummaryInlineSegments } from 'src/front-components/utils/parse-summary-inline-segments.util';
+
+const HEADING_PATTERN = /^(#{1,6})\s+(.+)$/;
+const BULLET_PATTERN = /^[-*]\s+(.+)$/;
+
+export const parseSummaryMarkdownBlocks = (
+ markdown: string,
+): SummaryMarkdownBlock[] => {
+ const blocks: SummaryMarkdownBlock[] = [];
+ let pendingListItems: SummaryInlineSegment[][] = [];
+
+ const flushPendingList = () => {
+ if (pendingListItems.length > 0) {
+ blocks.push({ type: 'list', items: pendingListItems });
+ pendingListItems = [];
+ }
+ };
+
+ for (const rawLine of markdown.split('\n')) {
+ const line = rawLine.trim();
+
+ if (line === '') {
+ flushPendingList();
+ continue;
+ }
+
+ const headingMatch = HEADING_PATTERN.exec(line);
+
+ if (headingMatch !== null) {
+ flushPendingList();
+ blocks.push({
+ type: 'heading',
+ level: headingMatch[1].length,
+ segments: parseSummaryInlineSegments(headingMatch[2]),
+ });
+ continue;
+ }
+
+ const bulletMatch = BULLET_PATTERN.exec(line);
+
+ if (bulletMatch !== null) {
+ pendingListItems.push(parseSummaryInlineSegments(bulletMatch[1]));
+ continue;
+ }
+
+ flushPendingList();
+ blocks.push({
+ type: 'paragraph',
+ segments: parseSummaryInlineSegments(line),
+ });
+ }
+
+ flushPendingList();
+
+ return blocks;
+};
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/parse-transcript-entries.util.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/parse-transcript-entries.util.ts
new file mode 100644
index 0000000000..e15fa74c38
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/parse-transcript-entries.util.ts
@@ -0,0 +1,85 @@
+import { isArray, isNumber, isUndefined } from '@sniptt/guards';
+
+import {
+ type TranscriptEntry,
+ type TranscriptWord,
+} from 'src/front-components/types/transcript-entry.type';
+import { asRecord } from 'src/front-components/utils/as-record.util';
+import { isNonEmptyString } from 'src/front-components/utils/is-non-empty-string.util';
+
+type TranscriptRecord = NonNullable>;
+
+const isTranscriptRecord = (
+ candidate: TranscriptRecord | undefined,
+): candidate is TranscriptRecord => !isUndefined(candidate);
+
+const readRelativeTimestamp = (
+ timestamp: TranscriptRecord | undefined,
+): number | undefined => {
+ const relativeTimestamp = timestamp?.relative;
+
+ return isNumber(relativeTimestamp) && Number.isFinite(relativeTimestamp)
+ ? relativeTimestamp
+ : undefined;
+};
+
+const readTranscriptWord = (
+ candidate: TranscriptRecord,
+): TranscriptWord | undefined => {
+ if (!isNonEmptyString(candidate.text)) {
+ return undefined;
+ }
+
+ return {
+ text: candidate.text.trim(),
+ startSeconds: readRelativeTimestamp(asRecord(candidate.start_timestamp)),
+ endSeconds: readRelativeTimestamp(asRecord(candidate.end_timestamp)),
+ };
+};
+
+const readSpeakerName = (participant: TranscriptRecord | undefined): string => {
+ const name = participant?.name;
+
+ return isNonEmptyString(name) ? name.trim() : 'Unknown speaker';
+};
+
+const readTranscriptEntry = (
+ candidate: TranscriptRecord,
+): TranscriptEntry | undefined => {
+ if (!isArray(candidate.words)) {
+ return undefined;
+ }
+
+ const words = candidate.words
+ .map(asRecord)
+ .filter(isTranscriptRecord)
+ .map(readTranscriptWord)
+ .filter((word): word is TranscriptWord => !isUndefined(word));
+
+ if (words.length === 0) {
+ return undefined;
+ }
+
+ return {
+ speakerName: readSpeakerName(asRecord(candidate.participant)),
+ startSeconds: words[0].startSeconds,
+ endSeconds: words[words.length - 1].endSeconds,
+ text: words.map((word) => word.text).join(' '),
+ words,
+ };
+};
+
+// Undefined means the value is not a diarized transcript; malformed entries are skipped, not fatal.
+export const parseTranscriptEntries = (
+ transcript: unknown,
+): TranscriptEntry[] | undefined => {
+ if (!isArray(transcript)) {
+ return undefined;
+ }
+
+ return transcript
+ .map(asRecord)
+ .filter(isTranscriptRecord)
+ .map(readTranscriptEntry)
+ .filter((entry): entry is TranscriptEntry => !isUndefined(entry));
+};
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/page-layouts/calendar-event-summary-tab.ts b/packages/twenty-apps/public/twenty-fireflies/src/page-layouts/calendar-event-summary-tab.ts
new file mode 100644
index 0000000000..675080ebb1
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/page-layouts/calendar-event-summary-tab.ts
@@ -0,0 +1,35 @@
+import {
+ definePageLayoutTab,
+ PageLayoutTabLayoutMode,
+} from 'twenty-sdk/define';
+
+import {
+ CALENDAR_EVENT_RECORD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER,
+ CALENDAR_EVENT_SUMMARY_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
+ CALENDAR_EVENT_SUMMARY_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER,
+ CALENDAR_EVENT_SUMMARY_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIER,
+} from 'src/constants/universal-identifiers';
+
+export default definePageLayoutTab({
+ universalIdentifier:
+ CALENDAR_EVENT_SUMMARY_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER,
+ title: 'Summary',
+ position: 16,
+ icon: 'IconFileText',
+ layoutMode: PageLayoutTabLayoutMode.CANVAS,
+ pageLayoutUniversalIdentifier:
+ CALENDAR_EVENT_RECORD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER,
+ widgets: [
+ {
+ universalIdentifier:
+ CALENDAR_EVENT_SUMMARY_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIER,
+ title: 'Summary',
+ type: 'FRONT_COMPONENT',
+ configuration: {
+ configurationType: 'FRONT_COMPONENT',
+ frontComponentUniversalIdentifier:
+ CALENDAR_EVENT_SUMMARY_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
+ },
+ },
+ ],
+});
diff --git a/packages/twenty-apps/public/twenty-fireflies/src/page-layouts/calendar-event-transcript-tab.ts b/packages/twenty-apps/public/twenty-fireflies/src/page-layouts/calendar-event-transcript-tab.ts
new file mode 100644
index 0000000000..f064599c2a
--- /dev/null
+++ b/packages/twenty-apps/public/twenty-fireflies/src/page-layouts/calendar-event-transcript-tab.ts
@@ -0,0 +1,35 @@
+import {
+ definePageLayoutTab,
+ PageLayoutTabLayoutMode,
+} from 'twenty-sdk/define';
+
+import {
+ CALENDAR_EVENT_RECORD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER,
+ CALENDAR_EVENT_TRANSCRIPT_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
+ CALENDAR_EVENT_TRANSCRIPT_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER,
+ CALENDAR_EVENT_TRANSCRIPT_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIER,
+} from 'src/constants/universal-identifiers';
+
+export default definePageLayoutTab({
+ universalIdentifier:
+ CALENDAR_EVENT_TRANSCRIPT_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER,
+ title: 'Transcript',
+ position: 17,
+ icon: 'IconMicrophone',
+ layoutMode: PageLayoutTabLayoutMode.CANVAS,
+ pageLayoutUniversalIdentifier:
+ CALENDAR_EVENT_RECORD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER,
+ widgets: [
+ {
+ universalIdentifier:
+ CALENDAR_EVENT_TRANSCRIPT_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIER,
+ title: 'Transcript',
+ type: 'FRONT_COMPONENT',
+ configuration: {
+ configurationType: 'FRONT_COMPONENT',
+ frontComponentUniversalIdentifier:
+ CALENDAR_EVENT_TRANSCRIPT_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
+ },
+ },
+ ],
+});
diff --git a/packages/twenty-apps/public/twenty-fireflies/yarn.lock b/packages/twenty-apps/public/twenty-fireflies/yarn.lock
index 379ce447ee..e6163e95e7 100644
--- a/packages/twenty-apps/public/twenty-fireflies/yarn.lock
+++ b/packages/twenty-apps/public/twenty-fireflies/yarn.lock
@@ -15,6 +15,160 @@ __metadata:
languageName: node
linkType: hard
+"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.29.7":
+ version: 7.29.7
+ resolution: "@babel/code-frame@npm:7.29.7"
+ dependencies:
+ "@babel/helper-validator-identifier": "npm:^7.29.7"
+ js-tokens: "npm:^4.0.0"
+ picocolors: "npm:^1.1.1"
+ checksum: 10c0/169fc2080169a40c1760155eaaaf739bcb882df0bec76a83adbda5493645bc17270a3434b8848c494b1933e96fe1d147370001e3cda09a39f43ae30f08ef2069
+ languageName: node
+ linkType: hard
+
+"@babel/generator@npm:^7.29.7":
+ version: 7.29.7
+ resolution: "@babel/generator@npm:7.29.7"
+ dependencies:
+ "@babel/parser": "npm:^7.29.7"
+ "@babel/types": "npm:^7.29.7"
+ "@jridgewell/gen-mapping": "npm:^0.3.12"
+ "@jridgewell/trace-mapping": "npm:^0.3.28"
+ jsesc: "npm:^3.0.2"
+ checksum: 10c0/9bf72b01b5bd0ea5b1288a0e37dbd360bff2f2b1ce73342c0d40fb3db2ec3dc004ada5ffa925c5e12939a416eed59e600d562b8ecd938ce0d27dfd0eb6c6c2b7
+ languageName: node
+ linkType: hard
+
+"@babel/helper-globals@npm:^7.29.7":
+ version: 7.29.7
+ resolution: "@babel/helper-globals@npm:7.29.7"
+ checksum: 10c0/f38417c40b1129a1b2b519ca961b9040c8827d1444fd74068702286b91b77089431dc76b6b9d5c1496e5da2a4f3ad329c6946e688ba3fa0d1d0b3d2b4f34f36a
+ languageName: node
+ linkType: hard
+
+"@babel/helper-module-imports@npm:^7.16.7":
+ version: 7.29.7
+ resolution: "@babel/helper-module-imports@npm:7.29.7"
+ dependencies:
+ "@babel/traverse": "npm:^7.29.7"
+ "@babel/types": "npm:^7.29.7"
+ checksum: 10c0/6adf60d97356027413342a092f818d9678c4f5caff716a33e3284b5ae14e47a9e88059d421dde4ee4894691260039a12602c0e7becadc175602194b40dfa345d
+ languageName: node
+ linkType: hard
+
+"@babel/helper-string-parser@npm:^7.29.7":
+ version: 7.29.7
+ resolution: "@babel/helper-string-parser@npm:7.29.7"
+ checksum: 10c0/194bc0f1716e396d5ffde56ad6119745fb9557662c98611590e5e454906783a4ccb21ce93056b8eb69a4909044834e45d96e50ac695bbe9e3221648fe033c06c
+ languageName: node
+ linkType: hard
+
+"@babel/helper-validator-identifier@npm:^7.29.7":
+ version: 7.29.7
+ resolution: "@babel/helper-validator-identifier@npm:7.29.7"
+ checksum: 10c0/4795354e7ae0dcafa72de1cd04ec51252dc1498517170beaf019e03effc5b7bf13c6b21a3949a77e07b8125be7f106ed1131350d8ebd4566ae874094a726d62b
+ languageName: node
+ linkType: hard
+
+"@babel/parser@npm:^7.29.7":
+ version: 7.29.7
+ resolution: "@babel/parser@npm:7.29.7"
+ dependencies:
+ "@babel/types": "npm:^7.29.7"
+ bin:
+ parser: ./bin/babel-parser.js
+ checksum: 10c0/65133038f80b54a714d6027cb77cee3f9a6b5c4c6842ce674301e13947cbcbfa8055e63acaf1b84c085d34226a14425b2c2b97b829e0e226d2e8f1299942a51d
+ languageName: node
+ linkType: hard
+
+"@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
+ languageName: node
+ linkType: hard
+
+"@babel/template@npm:^7.29.7":
+ version: 7.29.7
+ resolution: "@babel/template@npm:7.29.7"
+ dependencies:
+ "@babel/code-frame": "npm:^7.29.7"
+ "@babel/parser": "npm:^7.29.7"
+ "@babel/types": "npm:^7.29.7"
+ checksum: 10c0/8bb7f900dcab0e9e1c5ffbc33ca10e0d26b7b2e2ca804becb73ee771b9c4ed6e2908a4ae4a14c08560febb45d2b6b9a173955e42ad404d05f8b04840a14d9c58
+ languageName: node
+ linkType: hard
+
+"@babel/traverse@npm:^7.29.7":
+ version: 7.29.7
+ resolution: "@babel/traverse@npm:7.29.7"
+ dependencies:
+ "@babel/code-frame": "npm:^7.29.7"
+ "@babel/generator": "npm:^7.29.7"
+ "@babel/helper-globals": "npm:^7.29.7"
+ "@babel/parser": "npm:^7.29.7"
+ "@babel/template": "npm:^7.29.7"
+ "@babel/types": "npm:^7.29.7"
+ debug: "npm:^4.3.1"
+ checksum: 10c0/e256a1fbdb956555b76f3c285b1e453f6bedec8b3afb61751d99d933efd11c7d79caf5ddf2493570058a9f7deaa1b48324380d7c1aa1443fd9508becbf56331a
+ languageName: node
+ linkType: hard
+
+"@babel/types@npm:^7.29.7":
+ version: 7.29.7
+ resolution: "@babel/types@npm:7.29.7"
+ dependencies:
+ "@babel/helper-string-parser": "npm:^7.29.7"
+ "@babel/helper-validator-identifier": "npm:^7.29.7"
+ checksum: 10c0/b6623994c69717fa27294f5fa46d59140338e2d86c6c1c13085c84ef7d53086ee357fbf4fe9abe3dd3da75734dc77c4c0df2f90fb29e667558bb3b3fb705e88f
+ 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"
@@ -52,6 +206,152 @@ __metadata:
languageName: node
linkType: hard
+"@emotion/babel-plugin@npm:^11.13.5":
+ version: 11.13.5
+ resolution: "@emotion/babel-plugin@npm:11.13.5"
+ dependencies:
+ "@babel/helper-module-imports": "npm:^7.16.7"
+ "@babel/runtime": "npm:^7.18.3"
+ "@emotion/hash": "npm:^0.9.2"
+ "@emotion/memoize": "npm:^0.9.0"
+ "@emotion/serialize": "npm:^1.3.3"
+ babel-plugin-macros: "npm:^3.1.0"
+ convert-source-map: "npm:^1.5.0"
+ escape-string-regexp: "npm:^4.0.0"
+ find-root: "npm:^1.1.0"
+ source-map: "npm:^0.5.7"
+ stylis: "npm:4.2.0"
+ checksum: 10c0/8ccbfec7defd0e513cb8a1568fa179eac1e20c35fda18aed767f6c59ea7314363ebf2de3e9d2df66c8ad78928dc3dceeded84e6fa8059087cae5c280090aeeeb
+ languageName: node
+ linkType: hard
+
+"@emotion/cache@npm:^11.14.0":
+ version: 11.14.0
+ resolution: "@emotion/cache@npm:11.14.0"
+ dependencies:
+ "@emotion/memoize": "npm:^0.9.0"
+ "@emotion/sheet": "npm:^1.4.0"
+ "@emotion/utils": "npm:^1.4.2"
+ "@emotion/weak-memoize": "npm:^0.4.0"
+ stylis: "npm:4.2.0"
+ checksum: 10c0/3fa3e7a431ab6f8a47c67132a00ac8358f428c1b6c8421d4b20de9df7c18e95eec04a5a6ff5a68908f98d3280044f247b4965ac63df8302d2c94dba718769724
+ languageName: node
+ linkType: hard
+
+"@emotion/hash@npm:^0.9.2":
+ version: 0.9.2
+ resolution: "@emotion/hash@npm:0.9.2"
+ checksum: 10c0/0dc254561a3cc0a06a10bbce7f6a997883fd240c8c1928b93713f803a2e9153a257a488537012efe89dbe1246f2abfe2add62cdb3471a13d67137fcb808e81c2
+ languageName: node
+ linkType: hard
+
+"@emotion/is-prop-valid@npm:^1.3.0":
+ version: 1.4.0
+ resolution: "@emotion/is-prop-valid@npm:1.4.0"
+ dependencies:
+ "@emotion/memoize": "npm:^0.9.0"
+ checksum: 10c0/5f857814ec7d8c7e727727346dfb001af6b1fb31d621a3ce9c3edf944a484d8b0d619546c30899ae3ade2f317c76390ba4394449728e9bf628312defc2c41ac3
+ languageName: node
+ linkType: hard
+
+"@emotion/memoize@npm:^0.9.0":
+ version: 0.9.0
+ resolution: "@emotion/memoize@npm:0.9.0"
+ checksum: 10c0/13f474a9201c7f88b543e6ea42f55c04fb2fdc05e6c5a3108aced2f7e7aa7eda7794c56bba02985a46d8aaa914fcdde238727a98341a96e2aec750d372dadd15
+ languageName: node
+ linkType: hard
+
+"@emotion/react@npm:^11.14.0":
+ version: 11.14.0
+ resolution: "@emotion/react@npm:11.14.0"
+ dependencies:
+ "@babel/runtime": "npm:^7.18.3"
+ "@emotion/babel-plugin": "npm:^11.13.5"
+ "@emotion/cache": "npm:^11.14.0"
+ "@emotion/serialize": "npm:^1.3.3"
+ "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.2.0"
+ "@emotion/utils": "npm:^1.4.2"
+ "@emotion/weak-memoize": "npm:^0.4.0"
+ hoist-non-react-statics: "npm:^3.3.1"
+ peerDependencies:
+ react: ">=16.8.0"
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ checksum: 10c0/d0864f571a9f99ec643420ef31fde09e2006d3943a6aba079980e4d5f6e9f9fecbcc54b8f617fe003c00092ff9d5241179149ffff2810cb05cf72b4620cfc031
+ languageName: node
+ linkType: hard
+
+"@emotion/serialize@npm:^1.3.3":
+ version: 1.3.3
+ resolution: "@emotion/serialize@npm:1.3.3"
+ dependencies:
+ "@emotion/hash": "npm:^0.9.2"
+ "@emotion/memoize": "npm:^0.9.0"
+ "@emotion/unitless": "npm:^0.10.0"
+ "@emotion/utils": "npm:^1.4.2"
+ csstype: "npm:^3.0.2"
+ checksum: 10c0/b28cb7de59de382021de2b26c0c94ebbfb16967a1b969a56fdb6408465a8993df243bfbd66430badaa6800e1834724e84895f5a6a9d97d0d224de3d77852acb4
+ languageName: node
+ linkType: hard
+
+"@emotion/sheet@npm:^1.4.0":
+ version: 1.4.0
+ resolution: "@emotion/sheet@npm:1.4.0"
+ checksum: 10c0/3ca72d1650a07d2fbb7e382761b130b4a887dcd04e6574b2d51ce578791240150d7072a9bcb4161933abbcd1e38b243a6fb4464a7fe991d700c17aa66bb5acc7
+ languageName: node
+ linkType: hard
+
+"@emotion/styled@npm:^11.14.0":
+ version: 11.14.1
+ resolution: "@emotion/styled@npm:11.14.1"
+ dependencies:
+ "@babel/runtime": "npm:^7.18.3"
+ "@emotion/babel-plugin": "npm:^11.13.5"
+ "@emotion/is-prop-valid": "npm:^1.3.0"
+ "@emotion/serialize": "npm:^1.3.3"
+ "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.2.0"
+ "@emotion/utils": "npm:^1.4.2"
+ peerDependencies:
+ "@emotion/react": ^11.0.0-rc.0
+ react: ">=16.8.0"
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ checksum: 10c0/2bbf8451df49c967e41fbcf8111a7f6dafe6757f0cc113f2f6e287206c45ac1d54dc8a95a483b7c0cee8614b8a8d08155bded6453d6721de1f8cc8d5b9216963
+ languageName: node
+ linkType: hard
+
+"@emotion/unitless@npm:^0.10.0":
+ version: 0.10.0
+ resolution: "@emotion/unitless@npm:0.10.0"
+ checksum: 10c0/150943192727b7650eb9a6851a98034ddb58a8b6958b37546080f794696141c3760966ac695ab9af97efe10178690987aee4791f9f0ad1ff76783cdca83c1d49
+ languageName: node
+ linkType: hard
+
+"@emotion/use-insertion-effect-with-fallbacks@npm:^1.2.0":
+ version: 1.2.0
+ resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.2.0"
+ peerDependencies:
+ react: ">=16.8.0"
+ checksum: 10c0/074dbc92b96bdc09209871070076e3b0351b6b47efefa849a7d9c37ab142130767609ca1831da0055988974e3b895c1de7606e4c421fecaa27c3e56a2afd3b08
+ languageName: node
+ linkType: hard
+
+"@emotion/utils@npm:^1.4.2":
+ version: 1.4.2
+ resolution: "@emotion/utils@npm:1.4.2"
+ checksum: 10c0/7d0010bf60a2a8c1a033b6431469de4c80e47aeb8fd856a17c1d1f76bbc3a03161a34aeaa78803566e29681ca551e7bf9994b68e9c5f5c796159923e44f78d9a
+ languageName: node
+ linkType: hard
+
+"@emotion/weak-memoize@npm:^0.4.0":
+ version: 0.4.0
+ resolution: "@emotion/weak-memoize@npm:0.4.0"
+ checksum: 10c0/64376af11f1266042d03b3305c30b7502e6084868e33327e944b539091a472f089db307af69240f7188f8bc6b319276fd7b141a36613f1160d73d12a60f6ca1a
+ languageName: node
+ linkType: hard
+
"@esbuild/aix-ppc64@npm:0.28.1":
version: 0.28.1
resolution: "@esbuild/aix-ppc64@npm:0.28.1"
@@ -234,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"
@@ -731,13 +1069,62 @@ __metadata:
languageName: node
linkType: hard
-"@jridgewell/sourcemap-codec@npm:^1.5.5":
+"@jridgewell/gen-mapping@npm:^0.3.12":
+ version: 0.3.13
+ resolution: "@jridgewell/gen-mapping@npm:0.3.13"
+ dependencies:
+ "@jridgewell/sourcemap-codec": "npm:^1.5.0"
+ "@jridgewell/trace-mapping": "npm:^0.3.24"
+ checksum: 10c0/9a7d65fb13bd9aec1fbab74cda08496839b7e2ceb31f5ab922b323e94d7c481ce0fc4fd7e12e2610915ed8af51178bdc61e168e92a8c8b8303b030b03489b13b
+ languageName: node
+ linkType: hard
+
+"@jridgewell/resolve-uri@npm:^3.1.0":
+ version: 3.1.2
+ resolution: "@jridgewell/resolve-uri@npm:3.1.2"
+ checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e
+ languageName: node
+ linkType: hard
+
+"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0, @jridgewell/sourcemap-codec@npm:^1.5.5":
version: 1.5.5
resolution: "@jridgewell/sourcemap-codec@npm:1.5.5"
checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0
languageName: node
linkType: hard
+"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.28":
+ version: 0.3.31
+ resolution: "@jridgewell/trace-mapping@npm:0.3.31"
+ dependencies:
+ "@jridgewell/resolve-uri": "npm:^3.1.0"
+ "@jridgewell/sourcemap-codec": "npm:^1.4.14"
+ checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9
+ 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"
@@ -813,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"
@@ -943,10 +1344,31 @@ __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/twenty-fireflies@workspace:.":
version: 0.0.0-use.local
resolution: "@twentyhq/twenty-fireflies@workspace:."
dependencies:
+ "@emotion/react": "npm:^11.14.0"
+ "@emotion/styled": "npm:^11.14.0"
+ "@sniptt/guards": "npm:^0.2.0"
"@types/node": "npm:^24.7.2"
"@types/react": "npm:^18.2.0"
"@typescript/native-preview": "npm:^7.0.0-dev.20260116.1"
@@ -955,6 +1377,7 @@ __metadata:
react-dom: "npm:^18.2.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.0.0"
@@ -1012,6 +1435,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/parse-json@npm:^4.0.0":
+ version: 4.0.2
+ resolution: "@types/parse-json@npm:4.0.2"
+ checksum: 10c0/b1b863ac34a2c2172fbe0807a1ec4d5cb684e48d422d15ec95980b81475fac4fdb3768a8b13eef39130203a7c04340fc167bae057c7ebcafd7dec9fe6c36aeb1
+ languageName: node
+ linkType: hard
+
"@types/prop-types@npm:*":
version: 15.7.15
resolution: "@types/prop-types@npm:15.7.15"
@@ -1301,6 +1731,17 @@ __metadata:
languageName: node
linkType: hard
+"babel-plugin-macros@npm:^3.1.0":
+ version: 3.1.0
+ resolution: "babel-plugin-macros@npm:3.1.0"
+ dependencies:
+ "@babel/runtime": "npm:^7.12.5"
+ cosmiconfig: "npm:^7.0.0"
+ resolve: "npm:^1.19.0"
+ checksum: 10c0/c6dfb15de96f67871d95bd2e8c58b0c81edc08b9b087dc16755e7157f357dc1090a8dc60ebab955e92587a9101f02eba07e730adc253a1e4cf593ca3ebd3839c
+ languageName: node
+ linkType: hard
+
"backo2@npm:^1.0.2":
version: 1.0.2
resolution: "backo2@npm:1.0.2"
@@ -1318,6 +1759,13 @@ __metadata:
languageName: node
linkType: hard
+"callsites@npm:^3.0.0":
+ version: 3.1.0
+ resolution: "callsites@npm:3.1.0"
+ checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301
+ languageName: node
+ linkType: hard
+
"chai@npm:^6.2.2":
version: 6.2.2
resolution: "chai@npm:6.2.2"
@@ -1388,6 +1836,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"
@@ -1413,6 +1868,13 @@ __metadata:
languageName: node
linkType: hard
+"convert-source-map@npm:^1.5.0":
+ version: 1.9.0
+ resolution: "convert-source-map@npm:1.9.0"
+ checksum: 10c0/281da55454bf8126cbc6625385928c43479f2060984180c42f3a86c8b8c12720a24eac260624a7d1e090004028d2dee78602330578ceec1a08e27cb8bb0a8a5b
+ languageName: node
+ linkType: hard
+
"convert-source-map@npm:^2.0.0":
version: 2.0.0
resolution: "convert-source-map@npm:2.0.0"
@@ -1427,14 +1889,34 @@ __metadata:
languageName: node
linkType: hard
-"csstype@npm:^3.2.2":
+"cosmiconfig@npm:^7.0.0":
+ version: 7.1.0
+ resolution: "cosmiconfig@npm:7.1.0"
+ dependencies:
+ "@types/parse-json": "npm:^4.0.0"
+ import-fresh: "npm:^3.2.1"
+ parse-json: "npm:^5.0.0"
+ path-type: "npm:^4.0.0"
+ yaml: "npm:^1.10.0"
+ checksum: 10c0/b923ff6af581638128e5f074a5450ba12c0300b71302398ea38dbeabd33bbcaa0245ca9adbedfcf284a07da50f99ede5658c80bb3e39e2ce770a99d28a21ef03
+ 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"
checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce
languageName: node
linkType: hard
-"debug@npm:4, debug@npm:^4.1.1":
+"debug@npm:4, debug@npm:^4.1.1, debug@npm:^4.3.1":
version: 4.4.3
resolution: "debug@npm:4.4.3"
dependencies:
@@ -1492,6 +1974,15 @@ __metadata:
languageName: node
linkType: hard
+"error-ex@npm:^1.3.1":
+ version: 1.3.4
+ resolution: "error-ex@npm:1.3.4"
+ dependencies:
+ is-arrayish: "npm:^0.2.1"
+ checksum: 10c0/b9e34ff4778b8f3b31a8377e1c654456f4c41aeaa3d10a1138c3b7635d8b7b2e03eb2475d46d8ae055c1f180a1063e100bffabf64ea7e7388b37735df5328664
+ languageName: node
+ linkType: hard
+
"es-define-property@npm:^1.0.1":
version: 1.0.1
resolution: "es-define-property@npm:1.0.1"
@@ -1642,6 +2133,13 @@ __metadata:
languageName: node
linkType: hard
+"escape-string-regexp@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "escape-string-regexp@npm:4.0.0"
+ checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9
+ languageName: node
+ linkType: hard
+
"estree-walker@npm:^3.0.3":
version: 3.0.3
resolution: "estree-walker@npm:3.0.3"
@@ -1709,6 +2207,13 @@ __metadata:
languageName: node
linkType: hard
+"find-root@npm:^1.1.0":
+ version: 1.1.0
+ resolution: "find-root@npm:1.1.0"
+ checksum: 10c0/1abc7f3bf2f8d78ff26d9e00ce9d0f7b32e5ff6d1da2857bcdf4746134c422282b091c672cde0572cac3840713487e0a7a636af9aa1b74cb11894b447a521efa
+ languageName: node
+ linkType: hard
+
"follow-redirects@npm:^1.16.0":
version: 1.16.0
resolution: "follow-redirects@npm:1.16.0"
@@ -1872,7 +2377,7 @@ __metadata:
languageName: node
linkType: hard
-"hasown@npm:^2.0.4":
+"hasown@npm:^2.0.3, hasown@npm:^2.0.4":
version: 2.0.4
resolution: "hasown@npm:2.0.4"
dependencies:
@@ -1881,6 +2386,15 @@ __metadata:
languageName: node
linkType: hard
+"hoist-non-react-statics@npm:^3.3.1":
+ version: 3.3.2
+ resolution: "hoist-non-react-statics@npm:3.3.2"
+ dependencies:
+ react-is: "npm:^16.7.0"
+ checksum: 10c0/fe0889169e845d738b59b64badf5e55fa3cf20454f9203d1eb088df322d49d4318df774828e789898dcb280e8a5521bb59b3203385662ca5e9218a6ca5820e74
+ languageName: node
+ linkType: hard
+
"https-proxy-agent@npm:^5.0.1":
version: 5.0.1
resolution: "https-proxy-agent@npm:5.0.1"
@@ -1891,6 +2405,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"
@@ -1900,6 +2421,16 @@ __metadata:
languageName: node
linkType: hard
+"import-fresh@npm:^3.2.1":
+ version: 3.3.1
+ resolution: "import-fresh@npm:3.3.1"
+ dependencies:
+ parent-module: "npm:^1.0.0"
+ resolve-from: "npm:^4.0.0"
+ checksum: 10c0/bf8cc494872fef783249709385ae883b447e3eb09db0ebd15dcead7d9afe7224dad7bd7591c6b73b0b19b3c0f9640eb8ee884f01cfaf2887ab995b0b36a0cbec
+ languageName: node
+ linkType: hard
+
"indent-string@npm:^5.0.0":
version: 5.0.0
resolution: "indent-string@npm:5.0.0"
@@ -1968,6 +2499,22 @@ __metadata:
languageName: node
linkType: hard
+"is-arrayish@npm:^0.2.1":
+ version: 0.2.1
+ resolution: "is-arrayish@npm:0.2.1"
+ checksum: 10c0/e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729
+ languageName: node
+ linkType: hard
+
+"is-core-module@npm:^2.16.1":
+ version: 2.16.2
+ resolution: "is-core-module@npm:2.16.2"
+ dependencies:
+ hasown: "npm:^2.0.3"
+ checksum: 10c0/14b4258390283709c15476d023ec173e27458d5d014ccdb8ed39d576e551c3fa45498b7c9fe178f1529c4cb2648ddd58852a6a62107a019f6e349529f277518a
+ languageName: node
+ linkType: hard
+
"is-fullwidth-code-point@npm:^5.0.0, is-fullwidth-code-point@npm:^5.1.0":
version: 5.1.0
resolution: "is-fullwidth-code-point@npm:5.1.0"
@@ -2010,13 +2557,29 @@ __metadata:
languageName: node
linkType: hard
-"js-tokens@npm:^3.0.0 || ^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
languageName: node
linkType: hard
+"jsesc@npm:^3.0.2":
+ version: 3.1.0
+ resolution: "jsesc@npm:3.1.0"
+ bin:
+ jsesc: bin/jsesc
+ checksum: 10c0/531779df5ec94f47e462da26b4cbf05eb88a83d9f08aac2ba04206508fc598527a153d08bd462bae82fc78b3eaa1a908e1a4a79f886e9238641c4cdefaf118b1
+ languageName: node
+ linkType: hard
+
+"json-parse-even-better-errors@npm:^2.3.0":
+ version: 2.3.1
+ resolution: "json-parse-even-better-errors@npm:2.3.1"
+ checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3
+ languageName: node
+ linkType: hard
+
"jsonc-parser@npm:^3.2.0":
version: 3.3.1
resolution: "jsonc-parser@npm:3.3.1"
@@ -2144,6 +2707,13 @@ __metadata:
languageName: node
linkType: hard
+"lines-and-columns@npm:^1.1.6":
+ version: 1.2.4
+ resolution: "lines-and-columns@npm:1.2.4"
+ checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d
+ languageName: node
+ linkType: hard
+
"lodash@npm:^4.17.20, lodash@npm:^4.17.21":
version: 4.18.1
resolution: "lodash@npm:4.18.1"
@@ -2151,7 +2721,7 @@ __metadata:
languageName: node
linkType: hard
-"loose-envify@npm:^1.1.0":
+"loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0":
version: 1.4.0
resolution: "loose-envify@npm:1.4.0"
dependencies:
@@ -2171,6 +2741,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"
@@ -2285,6 +2864,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.2
resolution: "obug@npm:2.1.2"
@@ -2337,6 +2923,27 @@ __metadata:
languageName: node
linkType: hard
+"parent-module@npm:^1.0.0":
+ version: 1.0.1
+ resolution: "parent-module@npm:1.0.1"
+ dependencies:
+ callsites: "npm:^3.0.0"
+ checksum: 10c0/c63d6e80000d4babd11978e0d3fee386ca7752a02b035fd2435960ffaa7219dc42146f07069fb65e6e8bf1caef89daf9af7535a39bddf354d78bf50d8294f556
+ languageName: node
+ linkType: hard
+
+"parse-json@npm:^5.0.0":
+ version: 5.2.0
+ resolution: "parse-json@npm:5.2.0"
+ dependencies:
+ "@babel/code-frame": "npm:^7.0.0"
+ error-ex: "npm:^1.3.1"
+ json-parse-even-better-errors: "npm:^2.3.0"
+ lines-and-columns: "npm:^1.1.6"
+ checksum: 10c0/77947f2253005be7a12d858aedbafa09c9ae39eb4863adf330f7b416ca4f4a08132e453e08de2db46459256fb66afaac5ee758b44fe6541b7cdaf9d252e59585
+ languageName: node
+ linkType: hard
+
"patch-console@npm:^2.0.0":
version: 2.0.0
resolution: "patch-console@npm:2.0.0"
@@ -2344,6 +2951,20 @@ __metadata:
languageName: node
linkType: hard
+"path-parse@npm:^1.0.7":
+ version: 1.0.7
+ resolution: "path-parse@npm:1.0.7"
+ checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1
+ languageName: node
+ linkType: hard
+
+"path-type@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "path-type@npm:4.0.0"
+ checksum: 10c0/666f6973f332f27581371efaf303fd6c272cc43c2057b37aa99e3643158c7e4b2626549555d88626e99ea9e046f82f32e41bbde5f1508547e9a11b149b52387c
+ languageName: node
+ linkType: hard
+
"pathe@npm:^2.0.3":
version: 2.0.3
resolution: "pathe@npm:2.0.3"
@@ -2399,6 +3020,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"
@@ -2429,6 +3061,13 @@ __metadata:
languageName: node
linkType: hard
+"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
+ languageName: node
+ linkType: hard
+
"react-reconciler@npm:^0.33.0":
version: 0.33.0
resolution: "react-reconciler@npm:0.33.0"
@@ -2440,6 +3079,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:^18.2.0":
version: 18.3.1
resolution: "react@npm:18.3.1"
@@ -2463,6 +3140,48 @@ __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"
+ checksum: 10c0/8408eec31a3112ef96e3746c37be7d64020cda07c03a920f5024e77290a218ea758b26ca9529fd7b1ad283947f34b2291c1c0f6aa0ed34acfdda9c6014c8d190
+ languageName: node
+ linkType: hard
+
+"resolve@npm:^1.19.0":
+ version: 1.22.12
+ resolution: "resolve@npm:1.22.12"
+ dependencies:
+ es-errors: "npm:^1.3.0"
+ is-core-module: "npm:^2.16.1"
+ path-parse: "npm:^1.0.7"
+ supports-preserve-symlinks-flag: "npm:^1.0.0"
+ bin:
+ resolve: bin/resolve
+ checksum: 10c0/b16dc9b537c02e8c3388f7d3dcff9741d3071625f9a97ac1c885f2b0ca51e78df22328fb6d6ef214dd9101fb7cfc19aa2836fe3410402a94f3f7b8639c7149bf
+ languageName: node
+ linkType: hard
+
+"resolve@patch:resolve@npm%3A^1.19.0#optional!builtin":
+ version: 1.22.12
+ resolution: "resolve@patch:resolve@npm%3A1.22.12#optional!builtin::version=1.22.12&hash=c3c19d"
+ dependencies:
+ es-errors: "npm:^1.3.0"
+ is-core-module: "npm:^2.16.1"
+ path-parse: "npm:^1.0.7"
+ supports-preserve-symlinks-flag: "npm:^1.0.0"
+ bin:
+ resolve: bin/resolve
+ checksum: 10c0/fc6519984ae1f894d877c0060ba8b1f5ba3bc0e85a02f74e141929c118c23d74d9735619a9cc2965397387e514884245c65d72a40731dcb6cfc84c7bcdc8321e
+ languageName: node
+ linkType: hard
+
"restore-cursor@npm:^4.0.0":
version: 4.0.0
resolution: "restore-cursor@npm:4.0.0"
@@ -2588,6 +3307,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"
@@ -2710,6 +3436,13 @@ __metadata:
languageName: node
linkType: hard
+"source-map@npm:^0.5.7":
+ version: 0.5.7
+ resolution: "source-map@npm:0.5.7"
+ checksum: 10c0/904e767bb9c494929be013017380cbba013637da1b28e5943b566031e29df04fba57edf3f093e0914be094648b577372bd8ad247fa98cfba9c600794cd16b599
+ languageName: node
+ linkType: hard
+
"stack-utils@npm:^2.0.6":
version: 2.0.6
resolution: "stack-utils@npm:2.0.6"
@@ -2726,6 +3459,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"
@@ -2763,6 +3503,13 @@ __metadata:
languageName: node
linkType: hard
+"stylis@npm:4.2.0":
+ version: 4.2.0
+ resolution: "stylis@npm:4.2.0"
+ checksum: 10c0/a7128ad5a8ed72652c6eba46bed4f416521bc9745a460ef5741edc725252cebf36ee45e33a8615a7057403c93df0866ab9ee955960792db210bb80abd5ac6543
+ languageName: node
+ linkType: hard
+
"subscriptions-transport-ws@npm:^0.9.16":
version: 0.9.19
resolution: "subscriptions-transport-ws@npm:0.9.19"
@@ -2778,6 +3525,13 @@ __metadata:
languageName: node
linkType: hard
+"supports-preserve-symlinks-flag@npm:^1.0.0":
+ version: 1.0.0
+ resolution: "supports-preserve-symlinks-flag@npm:1.0.0"
+ checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39
+ languageName: node
+ linkType: hard
+
"symbol-observable@npm:^1.0.4":
version: 1.2.0
resolution: "symbol-observable@npm:1.2.0"
@@ -2931,6 +3685,35 @@ __metadata:
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
+
"type-fest@npm:^5.4.1":
version: 5.6.0
resolution: "type-fest@npm:5.6.0"
@@ -2988,6 +3771,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"
@@ -3251,6 +4043,13 @@ __metadata:
languageName: node
linkType: hard
+"yaml@npm:^1.10.0":
+ version: 1.10.3
+ resolution: "yaml@npm:1.10.3"
+ checksum: 10c0/c309ff85a0a569a981d71ab9cf0fef68672a16b9cdf40639d1c3b30034f6cd16ee428602bd6d64ecf006f8c8bee499023cac236538f79898aa99fb5db529a2ed
+ languageName: node
+ linkType: hard
+
"yoga-layout@npm:~3.2.1":
version: 3.2.1
resolution: "yoga-layout@npm:3.2.1"
@@ -3274,3 +4073,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