@factorialco/f0-react 4.14.1 → 4.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -512,6 +512,12 @@ declare type AiChatFileAttachmentConfig = {
512
512
  */
513
513
  declare type AiChatProviderProps = {
514
514
  enabled?: boolean;
515
+ /**
516
+ * Edge the whole side panel docks to (AI chat, hosted content and canvas).
517
+ * Hosts set "left" for a chat-first experience (e.g. communications).
518
+ * @default "right"
519
+ */
520
+ side?: "left" | "right";
515
521
  /**
516
522
  * Greeting phrase(s) shown by the welcome screen when the chat is empty.
517
523
  * A single string renders once; an array rotates through phrases. Purely
@@ -3028,7 +3034,7 @@ declare type DateValue = {
3028
3034
  */
3029
3035
  export declare const DaytimePage: WithDataTestIdReturnType_5<typeof _DaytimePage>;
3030
3036
 
3031
- declare function _DaytimePage({ children, header, period, embedded, }: DaytimePageProps): JSX_2.Element;
3037
+ declare function _DaytimePage({ children, header, period, embedded, hideOneSwitch, }: DaytimePageProps): JSX_2.Element;
3032
3038
 
3033
3039
  declare namespace _DaytimePage {
3034
3040
  var displayName: string;
@@ -3046,6 +3052,11 @@ export declare interface DaytimePageProps extends VariantProps<typeof daytimePag
3046
3052
  onPulseClick?: ComponentProps<typeof F0AvatarPulse>["onPulseClick"];
3047
3053
  };
3048
3054
  embedded?: boolean;
3055
+ /**
3056
+ * Hides the One AI toggle in the header. Use when One is reached elsewhere
3057
+ * (e.g. a sidebar tab) so the home header doesn't show a redundant switch.
3058
+ */
3059
+ hideOneSwitch?: boolean;
3049
3060
  }
3050
3061
 
3051
3062
  declare const daytimePageVariants: (props?: ({
@@ -3627,6 +3638,86 @@ declare const defaultTranslations: {
3627
3638
  };
3628
3639
  };
3629
3640
  };
3641
+ readonly chat: {
3642
+ readonly placeholder: "Write something here..";
3643
+ readonly searchPlaceholder: "Search messages";
3644
+ readonly closeSearch: "Close search";
3645
+ readonly noResults: "No chats found";
3646
+ readonly backToLatest: "Jump to latest";
3647
+ readonly muted: "Muted";
3648
+ readonly attachFile: "Attach file";
3649
+ readonly addEmoji: "Add emoji";
3650
+ readonly recordAudio: "Record audio";
3651
+ readonly listening: "Listening…";
3652
+ readonly stopRecording: "Stop and transcribe";
3653
+ readonly cancelRecording: "Cancel recording";
3654
+ readonly dropFilesHere: "Drop your files here";
3655
+ readonly removeFile: "Remove";
3656
+ readonly tooManyFilesError: "You can attach up to {{maxFiles}} files at once";
3657
+ readonly fileUploadError: "Upload failed";
3658
+ readonly micPermissionDenied: "Microphone access is blocked. Allow it in your browser settings to dictate.";
3659
+ readonly micError: "Couldn't access the microphone.";
3660
+ readonly transcriptionError: "Couldn't transcribe the audio. Try again.";
3661
+ readonly sent: "Sent";
3662
+ readonly read: "Read";
3663
+ readonly readBy: {
3664
+ readonly one: "Read by {{count}}";
3665
+ readonly other: "Read by {{count}}";
3666
+ };
3667
+ readonly delivered: "Delivered";
3668
+ readonly back: "Back";
3669
+ readonly writing: "Writing…";
3670
+ readonly isTyping: "{{name}} is writing…";
3671
+ readonly twoTyping: "{{first}} and {{second}} are writing…";
3672
+ readonly severalTyping: "Several people are writing…";
3673
+ readonly deletedMessage: "Message deleted";
3674
+ readonly moreActions: "Message actions";
3675
+ readonly options: "Options";
3676
+ readonly pin: "Pin";
3677
+ readonly unpin: "Unpin";
3678
+ readonly info: "Info";
3679
+ readonly viewProfile: "View profile";
3680
+ readonly mentionEveryone: "here";
3681
+ readonly mentionEveryoneDescription: "Notify everyone in this group";
3682
+ readonly reply: "Reply";
3683
+ readonly react: "Add reaction";
3684
+ readonly download: "Download";
3685
+ readonly removeQuote: "Remove quote";
3686
+ readonly edit: "Edit";
3687
+ readonly editing: "Editing";
3688
+ readonly edited: "edited";
3689
+ readonly cancelEdit: "Cancel edit";
3690
+ readonly saveEdit: "Save";
3691
+ readonly you: "You";
3692
+ readonly openImage: "Open image";
3693
+ readonly imagePreview: "Image preview";
3694
+ readonly closePreview: "Close";
3695
+ readonly previousImage: "Previous image";
3696
+ readonly nextImage: "Next image";
3697
+ readonly photo: "Photo";
3698
+ readonly photoCount: {
3699
+ readonly one: "{{count}} photo";
3700
+ readonly other: "{{count}} photos";
3701
+ };
3702
+ readonly fileCount: {
3703
+ readonly one: "{{count}} file";
3704
+ readonly other: "{{count}} files";
3705
+ };
3706
+ readonly attachmentCount: {
3707
+ readonly one: "{{count}} attachment";
3708
+ readonly other: "{{count}} attachments";
3709
+ };
3710
+ readonly scrollToBottom: "Scroll to bottom";
3711
+ readonly newMessages: "New messages";
3712
+ readonly unreadCount: {
3713
+ readonly one: "{{count}} unread";
3714
+ readonly other: "{{count}} unread";
3715
+ };
3716
+ readonly emptyConversation: "No messages yet";
3717
+ readonly emptyConversationDescription: "Send a message to start the conversation.";
3718
+ readonly error: "Couldn't load this conversation";
3719
+ readonly loadingOlder: "Loading earlier messages…";
3720
+ };
3630
3721
  readonly dataChart: {
3631
3722
  readonly heatmapNotSupported: "Heatmap not supported at this size";
3632
3723
  readonly barChartVertical: "Bar (vertical)";
@@ -4700,9 +4791,11 @@ export declare type F0CalloutProps = CalloutInternalProps;
4700
4791
  /**
4701
4792
  * @experimental This is an experimental component, use it at your own risk.
4702
4793
  */
4703
- export declare const F0CardHorizontal: WithDataTestIdReturnType_2<ForwardRefExoticComponent<F0CardHorizontalProps & RefAttributes<HTMLDivElement>> & {
4794
+ declare const F0CardHorizontal: WithDataTestIdReturnType_2<ForwardRefExoticComponent<F0CardHorizontalProps & RefAttributes<HTMLDivElement>> & {
4704
4795
  Skeleton: () => JSX_2.Element;
4705
4796
  }>;
4797
+ export { F0CardHorizontal }
4798
+ export { F0CardHorizontal as F0CardRow }
4706
4799
 
4707
4800
  export declare interface F0CardHorizontalProps {
4708
4801
  /**
@@ -4790,8 +4883,323 @@ export declare interface F0CardHorizontalProps {
4790
4883
  * drag-and-drop while still allowing click navigation via `onClick`.
4791
4884
  */
4792
4885
  disableOverlayLink?: boolean;
4886
+ /**
4887
+ * Dims the whole card and disables interaction (including its actions and any
4888
+ * row-level link/click). Purely a visual + interaction affordance.
4889
+ */
4890
+ disabled?: boolean;
4891
+ /**
4892
+ * Renders the description on a single line, truncating overflow with an
4893
+ * ellipsis (and a tooltip with the full text) instead of wrapping. Has no
4894
+ * effect when there's no `description`.
4895
+ */
4896
+ descriptionAsSingleLine?: boolean;
4793
4897
  }
4794
4898
 
4899
+ /**
4900
+ * Headless chat surface — header, transcript and composer — driven entirely by
4901
+ * the {@link F0ChatRuntime} from a surrounding `F0ChatProvider`. Panel controls
4902
+ * (fullscreen / close) are wired by the host so F0Chat stays transport-agnostic.
4903
+ */
4904
+ export declare const F0Chat: (props: F0ChatProps) => ReactNode;
4905
+
4906
+ export declare type F0ChatAttachment = F0ChatImageAttachment | F0ChatFileAttachment;
4907
+
4908
+ /** The conversation currently shown in the panel (header + behaviour differs by type). */
4909
+ export declare type F0ChatChannel = {
4910
+ id: string;
4911
+ type: F0ChatChannelType;
4912
+ title: string;
4913
+ avatar: AvatarVariant;
4914
+ /** DM only — the other person's presence. */
4915
+ presence?: "online" | "offline";
4916
+ muted?: boolean;
4917
+ /**
4918
+ * Whether the conversation is pinned (favourited) by the current user. Drives
4919
+ * the header pin toggle and lets the host surface a "Pinned" group in the
4920
+ * sidebar. factorial → Stream's per-member pin (`member.pinned_at`).
4921
+ */
4922
+ pinned?: boolean;
4923
+ /**
4924
+ * Extra status badges shown in the header (e.g. on vacation). Host-provided
4925
+ * metadata — not necessarily transport-backed: Stream has no such concept, so
4926
+ * factorial sources these from its own data (e.g. HR vacation status).
4927
+ */
4928
+ statuses?: F0ChatChannelStatus[];
4929
+ /** Group only. */
4930
+ memberCount?: number;
4931
+ /** DM only — the counterpart, used for the header identity hover card. */
4932
+ user?: F0ChatUser;
4933
+ };
4934
+
4935
+ /** A status badge shown in the header next to the title (e.g. on vacation, away).
4936
+ * The host decides the icon + label; the UI just renders it like the mute icon. */
4937
+ export declare type F0ChatChannelStatus = {
4938
+ icon: IconType;
4939
+ label: string;
4940
+ };
4941
+
4942
+ export declare type F0ChatChannelType = "dm" | "group";
4943
+
4944
+ /**
4945
+ * Edits applied to an existing message. Text, mentions and attachments are all
4946
+ * editable; the host maps this to the transport's partial-update (factorial →
4947
+ * Stream `partialUpdateMessage`).
4948
+ */
4949
+ export declare type F0ChatEditInput = {
4950
+ body: string;
4951
+ attachments?: F0ChatAttachment[];
4952
+ /** People mentioned in the edited body (groups only). */
4953
+ mentions?: F0ChatMention[];
4954
+ /** Whether the edited message mentions the whole group (`@here`). */
4955
+ mentionedEveryone?: boolean;
4956
+ };
4957
+
4958
+ export declare type F0ChatFileAttachment = {
4959
+ kind: "file";
4960
+ url: string;
4961
+ name: string;
4962
+ size?: number;
4963
+ mimeType?: string;
4964
+ /** 0–100 while uploading; undefined once done. */
4965
+ progress?: number;
4966
+ };
4967
+
4968
+ export declare type F0ChatImageAttachment = {
4969
+ kind: "image";
4970
+ url: string;
4971
+ thumbnailUrl?: string;
4972
+ name: string;
4973
+ mimeType?: string;
4974
+ width?: number;
4975
+ height?: number;
4976
+ };
4977
+
4978
+ /**
4979
+ * A person mentioned in a message. `id` matches an {@link F0ChatUser.id}; `name`
4980
+ * is the display name as it appears in the body (e.g. "Ana García"), used to
4981
+ * locate and highlight the `@name` token. "Everyone" (`@here`) is tracked
4982
+ * separately via `mentionedEveryone`, not as an entry here.
4983
+ */
4984
+ export declare type F0ChatMention = {
4985
+ id: string;
4986
+ name: string;
4987
+ /** Optional display data so the `@name` chip can show the same profile hover
4988
+ * card as the avatar (avatar, role line, "View profile" link). Omit for a
4989
+ * name-only card. */
4990
+ avatar?: AvatarVariant;
4991
+ subtitle?: string;
4992
+ profileHref?: string;
4993
+ };
4994
+
4995
+ export declare type F0ChatMessage = {
4996
+ id: string;
4997
+ author: F0ChatUser;
4998
+ body: string;
4999
+ /** ISO timestamp. */
5000
+ createdAt: string;
5001
+ isMine: boolean;
5002
+ status?: F0ChatMessageStatus;
5003
+ /**
5004
+ * When the message was read (DM read receipt), ISO. Approximated from the
5005
+ * counterpart's per-channel last-read pointer — Stream has no per-message
5006
+ * read time — so it's "read at or before this", not an exact per-message stamp.
5007
+ */
5008
+ readAt?: string;
5009
+ reactions?: F0ChatReaction[];
5010
+ attachments?: F0ChatAttachment[];
5011
+ replyTo?: F0ChatMessageReply;
5012
+ /**
5013
+ * People mentioned in this message (groups only). Drives the `@name` chip
5014
+ * highlighting in the bubble; a chip whose id is the current user gets the
5015
+ * self-mention emphasis.
5016
+ */
5017
+ mentions?: F0ChatMention[];
5018
+ /**
5019
+ * Whether this message mentions the whole group (`@here`). Renders the
5020
+ * `@here` token with the self-mention emphasis for every member.
5021
+ */
5022
+ mentionedEveryone?: boolean;
5023
+ /**
5024
+ * Group read receipts — how many other members have read this message.
5025
+ * Approximated by counting members whose last-read pointer is at/after this
5026
+ * message (Stream exposes no per-message reader list).
5027
+ */
5028
+ readByCount?: number;
5029
+ /**
5030
+ * Soft-deleted tombstone — render an italic "[Message deleted]" placeholder
5031
+ * instead of the body (Stream keeps these when a message is deleted after the
5032
+ * unsend window). Hard-deleted messages are removed from `messages` entirely.
5033
+ */
5034
+ deleted?: boolean;
5035
+ /**
5036
+ * When the message text was last edited (ISO). Presence drives the muted
5037
+ * "edited" label after the body (WhatsApp-style). factorial → Stream's
5038
+ * `message_text_updated_at`, which is set only on a text edit (not on
5039
+ * reactions/read updates), so it never false-positives the label.
5040
+ */
5041
+ editedAt?: string;
5042
+ };
5043
+
5044
+ export declare type F0ChatMessageReply = {
5045
+ id: string;
5046
+ author: F0ChatUser;
5047
+ body: string;
5048
+ /** Attachments of the quoted message (preview thumbnail / file name). */
5049
+ attachments?: F0ChatAttachment[];
5050
+ };
5051
+
5052
+ /** iMessage-style delivery state — only meaningful for messages I sent. */
5053
+ export declare type F0ChatMessageStatus = "sending" | "sent" | "read" | "failed";
5054
+
5055
+ export declare type F0ChatProps = {
5056
+ /** Whether the hosting panel is in fullscreen (controls the header toggle icon). */
5057
+ isFullscreen?: boolean;
5058
+ /** Toggle the hosting panel's fullscreen. Hidden when omitted. */
5059
+ onToggleFullscreen?: () => void;
5060
+ /** Close the hosting panel. Hidden when omitted. */
5061
+ onClose?: () => void;
5062
+ };
5063
+
5064
+ /**
5065
+ * Makes a chat {@link F0ChatRuntime} available to the F0Chat UI. The host owns
5066
+ * the runtime (mock in stories, GetStream adapter in factorial); F0 only reads it.
5067
+ */
5068
+ export declare const F0ChatProvider: ({ runtime, children, }: {
5069
+ runtime: F0ChatRuntime;
5070
+ children: ReactNode;
5071
+ }) => ReactNode;
5072
+
5073
+ export declare type F0ChatReaction = {
5074
+ emoji: string;
5075
+ count: number;
5076
+ reactedByMe: boolean;
5077
+ users?: F0ChatUser[];
5078
+ };
5079
+
5080
+ /**
5081
+ * The data + actions a host provides to drive the chat UI. F0 is headless: it
5082
+ * never touches the transport (GetStream, websockets, …). A mock runtime powers
5083
+ * Storybook; factorial implements this against GetStream after the bump.
5084
+ */
5085
+ export declare type F0ChatRuntime = {
5086
+ currentUserId: string;
5087
+ channel: F0ChatChannel;
5088
+ status: F0ChatStatus;
5089
+ /** Oldest → newest. */
5090
+ messages: F0ChatMessage[];
5091
+ /** Users currently typing (excluding me). */
5092
+ typingUsers: F0ChatUser[];
5093
+ hasMoreOlder: boolean;
5094
+ loadingOlder: boolean;
5095
+ /**
5096
+ * Whether there are newer messages than the loaded window — true after jumping
5097
+ * to an old message (e.g. a search hit), so the transcript isn't anchored to
5098
+ * the live tail. Omit (→ false) when the newest messages are always loaded.
5099
+ * factorial → `channel.state.messagePagination.hasNext`.
5100
+ */
5101
+ hasMoreNewer?: boolean;
5102
+ loadingNewer?: boolean;
5103
+ /** Load the next page of newer messages (mirror of `loadOlder`). */
5104
+ loadNewer?: () => void;
5105
+ /** Count of incoming messages below the user's last-read position. */
5106
+ unreadCount: number;
5107
+ /** Id of the first unread message — where the "new messages" divider goes. */
5108
+ firstUnreadId: string | null;
5109
+ sendMessage: (input: F0ChatSendInput) => void;
5110
+ retryMessage: (id: string) => void;
5111
+ loadOlder: () => void;
5112
+ toggleReaction: (messageId: string, emoji: string) => void;
5113
+ deleteMessage: (id: string) => void;
5114
+ /**
5115
+ * Edit an existing message (text, mentions, attachments). Omit to disable
5116
+ * editing — the "Edit" action then never shows. factorial →
5117
+ * `client.partialUpdateMessage`.
5118
+ */
5119
+ editMessage?: (id: string, input: F0ChatEditInput) => void;
5120
+ /**
5121
+ * How long after sending a message stays editable (ms). The "Edit" action is
5122
+ * hidden once a message is older than this. Omit for no limit (editable
5123
+ * anytime). factorial sets a fixed window (e.g. 15 min).
5124
+ */
5125
+ editWindowMs?: number;
5126
+ /** Called as the user types so the runtime can emit typing.start/stop. */
5127
+ onInputActivity: () => void;
5128
+ uploadFiles?: (files: File[]) => Promise<F0ChatAttachment[]>;
5129
+ /**
5130
+ * Max files attachable at once. When a selection/drop would exceed it, the
5131
+ * composer rejects the whole batch and flashes a transient error in the
5132
+ * textarea (mirrors the AI chat). Omit for no limit.
5133
+ */
5134
+ maxFiles?: number;
5135
+ /**
5136
+ * Optional voice dictation — same signature as the AI chat (streams partials).
5137
+ * Not part of the Stream transport; a host wires it to its own speech service
5138
+ * (the Stream adapter omits it, so the mic button stays hidden there).
5139
+ */
5140
+ transcribe?: TranscribeFn;
5141
+ markRead?: () => void;
5142
+ /**
5143
+ * Search the conversation's members for the `@`-mention popover, returning
5144
+ * matches for `query` (empty string → the full member list). Provide it
5145
+ * wherever mentions should work — DMs (both people) and groups alike; omit it
5146
+ * to disable mentions for the conversation. The `@here` everyone option is a
5147
+ * group-only concept and is gated separately by the composer.
5148
+ * factorial → `channel.state.members`.
5149
+ */
5150
+ searchMembers?: (query: string) => Promise<F0ChatUser[]>;
5151
+ /**
5152
+ * Toggle the conversation's pinned (favourite) state for the current user.
5153
+ * Drives the header "Pin / Unpin" action; omit to hide it. factorial →
5154
+ * `channel.pin()` / `channel.unpin()`.
5155
+ */
5156
+ togglePin?: () => void;
5157
+ /**
5158
+ * Full-text search within this conversation, returning matches oldest→newest.
5159
+ * Omit to fall back to a client-side substring search over the loaded
5160
+ * `messages`. factorial → `channel.search`.
5161
+ */
5162
+ searchMessages?: (query: string) => Promise<F0ChatSearchResult[]>;
5163
+ /**
5164
+ * Ensure a message is in `messages` so the UI can jump to it: pass a message
5165
+ * id (e.g. a search hit outside the loaded window) to load it + its context,
5166
+ * or {@link LATEST} to return to the live tail. After it resolves, `messages`
5167
+ * (and `hasMoreNewer`) reflect the new window. factorial →
5168
+ * `channel.state.loadMessageIntoState(id | "latest")`.
5169
+ */
5170
+ loadMessageContext?: (idOrLatest: string) => Promise<void>;
5171
+ };
5172
+
5173
+ /** A message that matched an in-conversation search (room to grow: preview, author…). */
5174
+ export declare type F0ChatSearchResult = {
5175
+ id: string;
5176
+ };
5177
+
5178
+ export declare type F0ChatSendInput = {
5179
+ body: string;
5180
+ attachments?: F0ChatAttachment[];
5181
+ replyToId?: string;
5182
+ /** People mentioned in the body (groups only). The host maps these to the
5183
+ * transport's mention field (factorial → Stream `mentioned_users`). */
5184
+ mentions?: F0ChatMention[];
5185
+ /** Whether the message mentions the whole group (`@here`). The host fans this
5186
+ * out to every member so they all get notified. */
5187
+ mentionedEveryone?: boolean;
5188
+ };
5189
+
5190
+ export declare type F0ChatStatus = "connecting" | "ready" | "error";
5191
+
5192
+ /** A participant in a conversation. */
5193
+ export declare type F0ChatUser = {
5194
+ id: string;
5195
+ name: string;
5196
+ avatar?: AvatarVariant;
5197
+ /** Secondary line for the hover card (e.g. job title). */
5198
+ subtitle?: string;
5199
+ /** Link to the person's profile, shown as "View profile" in the hover card. */
5200
+ profileHref?: string;
5201
+ };
5202
+
4795
5203
  export declare type F0FileAction = {
4796
5204
  icon?: IconType;
4797
5205
  label: string;
@@ -5862,6 +6270,11 @@ declare type HeaderProps_2 = {
5862
6270
  whenEnabled?: string;
5863
6271
  };
5864
6272
  oneSwitchAutoOpen?: boolean;
6273
+ /**
6274
+ * Hide the per-page One switch. Use when One is reachable from elsewhere
6275
+ * (e.g. a sidebar tab) so the page header doesn't duplicate the entry point.
6276
+ */
6277
+ hideOneSwitch?: boolean;
5865
6278
  };
5866
6279
 
5867
6280
  export declare type HeaderSecondaryAction = HeaderSecondaryButtonAction | HeaderSecondaryDropdownAction;
@@ -6310,6 +6723,9 @@ export declare type lastIntentType = {
6310
6723
  customIntent?: string;
6311
6724
  } | null;
6312
6725
 
6726
+ /** Sentinel for {@link F0ChatRuntime.loadMessageContext} meaning "the live tail". */
6727
+ export declare const LATEST: "latest";
6728
+
6313
6729
  declare type Level = (typeof levels)[number];
6314
6730
 
6315
6731
  declare const levels: readonly ["info", "warning", "critical", "positive"];
@@ -7381,7 +7797,7 @@ export declare type PageBasedPaginatedResponse<TRecord> = BasePaginatedResponse<
7381
7797
  pagesCount: number;
7382
7798
  };
7383
7799
 
7384
- export declare function PageHeader({ module, statusTag, breadcrumbs, actions, embedded, navigation, productUpdates, favorites, oneSwitchTooltip, oneSwitchAutoOpen, }: HeaderProps_2): JSX_2.Element;
7800
+ export declare function PageHeader({ module, statusTag, breadcrumbs, actions, embedded, navigation, productUpdates, favorites, oneSwitchTooltip, oneSwitchAutoOpen, hideOneSwitch, }: HeaderProps_2): JSX_2.Element;
7385
7801
 
7386
7802
  export declare type PageHeaderItemNavigationInput<R extends RecordType> = Pick<UseDataSourceItemNavigationReturn<R>, "previousItem" | "nextItem" | "previousItemUrl" | "nextItemUrl" | "absoluteIndex" | "totalItems" | "activeIndex" | "hasPrevious" | "hasNext" | "goToPrevious" | "goToNext">;
7387
7803
 
@@ -7865,6 +8281,7 @@ declare interface ReactionProps {
7865
8281
  hasReacted?: boolean;
7866
8282
  users?: User_2[];
7867
8283
  onInteraction?: (emoji: string) => void;
8284
+ size?: "sm" | "md" | "lg";
7868
8285
  }
7869
8286
 
7870
8287
  /**
@@ -8224,16 +8641,48 @@ declare function _Sidebar({ header, body, footer, onFooterDropdownClick, }: Side
8224
8641
  export declare type SidebarChat = {
8225
8642
  id: string;
8226
8643
  label: string;
8227
- /** Person / team / company avatar (F0Avatar variant). */
8228
- avatar: AvatarVariant;
8644
+ /**
8645
+ * Person / team / company avatar (F0Avatar variant). Optional: omit it for
8646
+ * avatar-less rows (e.g. an AI chat history that shows titles only).
8647
+ */
8648
+ avatar?: AvatarVariant;
8649
+ /**
8650
+ * When true, the row renders as a skeleton (avatar + name placeholders) but
8651
+ * keeps its position. Use it for the "cascade" case: the conversation is
8652
+ * known (id, group, order) but its name/avatar haven't resolved yet. As each
8653
+ * chat resolves, flip this to false and pass the real `label`/`avatar`.
8654
+ */
8655
+ loading?: boolean;
8229
8656
  onClick?: () => void;
8230
8657
  /** When > 0, the chat is rendered as unread (darker, bolder name). */
8231
8658
  unreadCount?: number;
8659
+ /**
8660
+ * When > 0, the unread badge is prefixed with an `@` (groups only) — the
8661
+ * unread run includes a message that mentions you, Slack-style. Just a marker
8662
+ * on the existing badge, not a separate count; cleared on read.
8663
+ */
8664
+ mentionCount?: number;
8665
+ /** When true, the name is replaced by a live "Writing…" label. */
8666
+ typing?: boolean;
8232
8667
  presence?: SidebarChatPresence;
8233
8668
  /** Status icon shown to the right of the name. People only. */
8234
8669
  status?: SidebarChatStatus;
8235
8670
  /** Epoch ms of the last activity; used for ordering. */
8236
8671
  lastActivityAt?: number;
8672
+ /** Whether the chat is pinned (favourited) — selects the solid pin icon. */
8673
+ pinned?: boolean;
8674
+ /**
8675
+ * Toggle the pinned state. When set, a pin/unpin button appears on row hover
8676
+ * in place of the unread badge / status icon. Omit to hide the affordance.
8677
+ */
8678
+ onTogglePin?: () => void;
8679
+ /**
8680
+ * A pin/unpin request for this chat is in flight (e.g. waiting on the
8681
+ * backend). Replaces the pin button with a spinner, kept visible off-hover,
8682
+ * so the row reads as "saving". The move between groups can still be applied
8683
+ * optimistically — this only reflects that persistence is pending.
8684
+ */
8685
+ pinPending?: boolean;
8237
8686
  };
8238
8687
 
8239
8688
  /**
@@ -8248,6 +8697,45 @@ export declare type SidebarChatAction = {
8248
8697
 
8249
8698
  export declare type SidebarChatActions = Omit<SidebarChatStore, "groups" | "activeChatId" | "unreadChatsCount">;
8250
8699
 
8700
+ export declare const SidebarChatBlankState: WithDataTestIdReturnType_3<typeof _SidebarChatBlankState>;
8701
+
8702
+ /**
8703
+ * Compact blank state for a sidebar conversation list. Shared by the people
8704
+ * chat (`SidebarChatList`) and the AI history list so the two read identically.
8705
+ * Deliberately lighter than `OneEmptyState` — no emoji/avatar and tight
8706
+ * paddings — because it lives in a narrow sidebar column. The host (factorial)
8707
+ * supplies the copy + actions.
8708
+ */
8709
+ declare function _SidebarChatBlankState({ title, description, actions, ...rest }: SidebarChatBlankStateProps): ReactNode;
8710
+
8711
+ /** A CTA shown under the blank-state copy — e.g. "Start a conversation". */
8712
+ export declare type SidebarChatBlankStateAction = {
8713
+ label: string;
8714
+ onClick?: () => void;
8715
+ icon?: IconType;
8716
+ /** Button variant — supports the gradient "ai" variant. @default "outline" */
8717
+ variant?: ActionButtonVariant;
8718
+ };
8719
+
8720
+ export declare type SidebarChatBlankStateProps = {
8721
+ title: string;
8722
+ description?: string;
8723
+ /** Optional CTA(s) shown under the copy — e.g. "Start a conversation". */
8724
+ actions?: SidebarChatBlankStateAction[];
8725
+ };
8726
+
8727
+ /**
8728
+ * Copy shown when there are no chats at all. Override via the `emptyState` prop.
8729
+ * Rendered through the shared `OneEmptyState`, so the AI history list and this
8730
+ * one read identically — the host (factorial) just supplies the copy + actions.
8731
+ */
8732
+ export declare type SidebarChatEmptyState = {
8733
+ title: string;
8734
+ description?: string;
8735
+ /** Optional CTA(s) shown under the copy — e.g. "Start a conversation". */
8736
+ actions?: SidebarChatBlankStateAction[];
8737
+ };
8738
+
8251
8739
  export declare type SidebarChatGroup = {
8252
8740
  id: string;
8253
8741
  title: string;
@@ -8263,14 +8751,45 @@ export declare const SidebarChatItem: ({ chat, isActive, onClick, }: {
8263
8751
  }) => JSX_2.Element;
8264
8752
 
8265
8753
  /**
8266
- * Body of the "Messages" tab: chat groups read from `SidebarChatProvider`.
8267
- * Chats fade in/out as they are added/removed; live reordering from the store
8268
- * is applied instantly (Slack-style), without layout projectionthat avoids
8269
- * the resize-like "bounce" when the tab mounts.
8754
+ * A single chat row rendered as a skeleton. Matches `SidebarChatItem`'s layout
8755
+ * and height (36px: a 24px avatar + the row's vertical padding) so the row
8756
+ * doesn't shift when the real data resolves. Used for the "cascade" case a
8757
+ * known conversation whose name/avatar is still loading (`SidebarChat.loading`).
8270
8758
  */
8271
- export declare const SidebarChatList: ({ actions, }: {
8759
+ export declare const SidebarChatItemSkeleton: ({ className, }: {
8760
+ className?: string;
8761
+ }) => JSX_2.Element;
8762
+
8763
+ /**
8764
+ * Body of the "Messages" tab: chat groups read from `SidebarChatProvider`,
8765
+ * rendered through the shared `SidebarTabPanel` (search + actions + collapsible
8766
+ * groups). This stays a thin adapter — it maps the chat store onto the panel
8767
+ * and owns only chat-specific bits (unread badges, the blank state copy).
8768
+ */
8769
+ export declare const SidebarChatList: ({ actions, emptyState, loading, }: {
8272
8770
  /** Ghost actions rendered at the very top (e.g. New chat, New group). */
8273
8771
  actions?: SidebarChatAction[];
8772
+ /** Copy for the blank state shown when there are no chats. */
8773
+ emptyState: SidebarChatEmptyState;
8774
+ /**
8775
+ * Whole-list loading: the conversations aren't known yet. Renders a generic
8776
+ * skeleton instead of the blank state. Once any chats are known, pass them
8777
+ * (with `loading` on the individual chats whose name is still resolving) and
8778
+ * set this back to false — the per-chat skeletons take over (cascade).
8779
+ */
8780
+ loading?: boolean;
8781
+ }) => JSX_2.Element;
8782
+
8783
+ /**
8784
+ * Full-list skeleton shown while the conversations are still loading and the
8785
+ * groups aren't known yet. A few placeholder groups, each with a 32px title row
8786
+ * and some 36px chat rows.
8787
+ */
8788
+ export declare const SidebarChatListSkeleton: ({ groups, rowsPerGroup, }: {
8789
+ /** Number of placeholder groups. @default 2 */
8790
+ groups?: number;
8791
+ /** Placeholder rows per group. @default 4 */
8792
+ rowsPerGroup?: number;
8274
8793
  }) => JSX_2.Element;
8275
8794
 
8276
8795
  export declare type SidebarChatPresence = "online" | "offline";
@@ -8330,7 +8849,7 @@ export declare type SidebarChatStore = {
8330
8849
  * Collapsible titled section used across the Sidebar (navigation categories,
8331
8850
  * chat groups). Title + rotating chevron + animated height.
8332
8851
  */
8333
- export declare const SidebarCollapsibleSection: ({ title, isOpen: initialIsOpen, isRoot, onCollapse, children, highlightWhenCollapsed, isDragging, wasDragging, }: SidebarCollapsibleSectionProps) => JSX_2.Element;
8852
+ export declare const SidebarCollapsibleSection: ({ title, isOpen: initialIsOpen, isRoot, onCollapse, children, highlightWhenCollapsed, collapsedBadge, isDragging, wasDragging, }: SidebarCollapsibleSectionProps) => JSX_2.Element;
8334
8853
 
8335
8854
  export declare interface SidebarCollapsibleSectionProps {
8336
8855
  title: string;
@@ -8345,6 +8864,11 @@ export declare interface SidebarCollapsibleSectionProps {
8345
8864
  * Slack-style hint that hidden items need attention (e.g. unread chats).
8346
8865
  */
8347
8866
  highlightWhenCollapsed?: boolean;
8867
+ /**
8868
+ * Content shown at the end of the header only while collapsed (e.g. a total
8869
+ * unread badge) — surfaces what's hidden inside without expanding.
8870
+ */
8871
+ collapsedBadge?: ReactNode;
8348
8872
  /** Drag-aware guards used by the sortable Menu; safe to omit elsewhere. */
8349
8873
  isDragging?: boolean;
8350
8874
  wasDragging?: RefObject<boolean>;
@@ -8390,6 +8914,91 @@ export declare type SidebarTab = {
8390
8914
  icon: IconType;
8391
8915
  /** Unread counter shown next to the tab. */
8392
8916
  badge?: number;
8917
+ /**
8918
+ * Visual variant. "ai" renders the tab as an AI-variant button — the animated
8919
+ * gradient shimmer on hover — e.g. the "One" tab. Defaults to the plain ghost
8920
+ * tab (transparent background; the icon darkens on hover).
8921
+ */
8922
+ variant?: "default" | "ai";
8923
+ };
8924
+
8925
+ /**
8926
+ * Shared skeleton for a sidebar tab body: an optional search box pinned at the
8927
+ * top, optional actions, and collapsible groups of arbitrary item rows.
8928
+ *
8929
+ * The panel is agnostic about what a row is — it renders `item.content` — so it
8930
+ * backs the Messages (chats) and One (AI history) tabs from one place, keeping
8931
+ * paddings, search behaviour and the empty / no-results states consistent.
8932
+ */
8933
+ export declare const SidebarTabPanel: ({ groups, actions, searchPlaceholder, loading, skeleton, emptyState, noResultsLabel, animateItems, className, }: SidebarTabPanelProps) => JSX_2.Element;
8934
+
8935
+ /**
8936
+ * A top-of-panel action (e.g. "New chat", "New group"). The panel renders one
8937
+ * ghost button per action, so they all share the same design. For an action
8938
+ * that needs custom chrome (e.g. a popover trigger), pass `render` to wrap the
8939
+ * default button while keeping its look.
8940
+ */
8941
+ export declare type SidebarTabPanelAction = {
8942
+ label: string;
8943
+ icon?: IconType;
8944
+ onClick?: () => void;
8945
+ /**
8946
+ * Wrap the default ghost button (received as `button`) in custom chrome —
8947
+ * e.g. a popover/credits trigger. Return the wrapped node. The button keeps
8948
+ * the standard action design; only its surroundings change.
8949
+ */
8950
+ render?: (button: ReactNode) => ReactNode;
8951
+ };
8952
+
8953
+ export declare type SidebarTabPanelGroup = {
8954
+ id: string;
8955
+ /** Omit to render the items without a collapsible header (root group). */
8956
+ title?: string;
8957
+ /** Initial open state of the collapsible group. @default true */
8958
+ isOpen?: boolean;
8959
+ /** Emphasise the title (darker, bolder) while collapsed. */
8960
+ highlightWhenCollapsed?: boolean;
8961
+ /** Content shown at the end of the header only while collapsed. */
8962
+ collapsedBadge?: ReactNode;
8963
+ items: SidebarTabPanelItem[];
8964
+ };
8965
+
8966
+ /**
8967
+ * A single row in a group. The panel is agnostic about what a row looks like:
8968
+ * it renders `content` opaquely. `searchText` is what the search box matches
8969
+ * against (omit it to keep the item out of search results while filtering).
8970
+ */
8971
+ export declare type SidebarTabPanelItem = {
8972
+ id: string;
8973
+ searchText?: string;
8974
+ content: ReactNode;
8975
+ };
8976
+
8977
+ export declare type SidebarTabPanelProps = {
8978
+ groups: SidebarTabPanelGroup[];
8979
+ /** Top-of-panel actions, rendered as a stack of ghost buttons. */
8980
+ actions?: SidebarTabPanelAction[];
8981
+ /**
8982
+ * When set, renders a search box pinned at the very top of the panel that
8983
+ * fuzzy-filters items by their `searchText`. Omit for no search.
8984
+ */
8985
+ searchPlaceholder?: string;
8986
+ /** Whole-panel loading: renders `skeleton` instead of the groups. */
8987
+ loading?: boolean;
8988
+ /** Shown while `loading` and there are no items yet. */
8989
+ skeleton?: ReactNode;
8990
+ /** Shown when there are no items at all (not while searching). */
8991
+ emptyState?: ReactNode;
8992
+ /** Shown when a search yields no matches. */
8993
+ noResultsLabel?: ReactNode;
8994
+ /**
8995
+ * Animate rows as they appear/disappear/move between groups — a pin/unpin
8996
+ * glides a row across, search reflows smoothly (reduced-motion aware).
8997
+ * On by default; pass `false` to render rows statically.
8998
+ */
8999
+ animateItems?: boolean;
9000
+ /** Override the outer container className. */
9001
+ className?: string;
8393
9002
  };
8394
9003
 
8395
9004
  /**
@@ -8400,17 +9009,12 @@ export declare type SidebarTab = {
8400
9009
  * When no tabs are needed, keep composing the Sidebar header with `SearchBar`
8401
9010
  * instead — that path is unchanged.
8402
9011
  */
8403
- export declare const SidebarTabs: ({ tabs, activeTab, onTabChange, search, }: SidebarTabsProps) => JSX_2.Element;
9012
+ export declare const SidebarTabs: ({ tabs, activeTab, onTabChange, }: SidebarTabsProps) => JSX_2.Element;
8404
9013
 
8405
9014
  export declare type SidebarTabsProps = {
8406
9015
  tabs: SidebarTab[];
8407
9016
  activeTab: string;
8408
9017
  onTabChange: (id: string) => void;
8409
- search: {
8410
- /** Accessible label / tooltip for the search icon button. */
8411
- placeholder?: string;
8412
- onClick?: () => void;
8413
- };
8414
9018
  };
8415
9019
 
8416
9020
  /**
@@ -9078,7 +9682,7 @@ export declare interface TopLevelPrependNotesTextEditorPageDocumentPatch {
9078
9682
  * intermediate results for live textarea fill. When omitted from the chat
9079
9683
  * config, the microphone button is not rendered.
9080
9684
  */
9081
- declare type TranscribeFn = (audio: Blob, options: TranscribeOptions) => Promise<string>;
9685
+ export declare type TranscribeFn = (audio: Blob, options: TranscribeOptions) => Promise<string>;
9082
9686
 
9083
9687
  declare type TranscribeOptions = {
9084
9688
  /**
@@ -9380,6 +9984,9 @@ declare interface UseExportActionProps<R extends RecordType, Filters extends Fil
9380
9984
  enabled?: boolean;
9381
9985
  }
9382
9986
 
9987
+ /** Read the chat runtime. Throws when used outside an {@link F0ChatProvider}. */
9988
+ export declare function useF0Chat(): F0ChatRuntime;
9989
+
9383
9990
  export declare const useInfiniteScrollPagination: (paginationInfo: PaginationInfo | null, isLoading: boolean, isLoadingMore: boolean, loadMore: () => void) => {
9384
9991
  loadingIndicatorRef: RefObject<HTMLTableCellElement>;
9385
9992
  };