@tightknitai/block-kitchen 0.9.10 → 0.10.1

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.
@@ -648,6 +648,14 @@ interface PrimaryActionContext {
648
648
  * {@link BlockKitchenBaseProps.onValidationChange} reports.
649
649
  */
650
650
  validation: ValidationSummary;
651
+ /**
652
+ * The existing message currently loaded into the builder (via
653
+ * {@link BlockKitchenBaseProps.loading}), or `null` when composing a new
654
+ * message. Carries `channelId` + `ts` + the host's editability verdict, so
655
+ * a host-owned commitment step can offer "update in place" alongside
656
+ * "post as new".
657
+ */
658
+ loadedMessage: LoadedMessage | null;
651
659
  }
652
660
  /**
653
661
  * A host-owned primary action for compose-only mode, rendered in the
@@ -681,7 +689,7 @@ interface PrimaryActionConfig {
681
689
  */
682
690
  type EditableVia = 'bot' | 'user';
683
691
  /**
684
- * Input passed to {@link EditingConfig.onLoadMessage}. The user pastes a
692
+ * Input passed to {@link LoadingConfig.onLoadMessage}. The user pastes a
685
693
  * Slack message permalink (Slack's "Copy link"); the host parses
686
694
  * `channel + ts` out of it — the package never touches raw timestamps.
687
695
  */
@@ -689,38 +697,56 @@ interface LoadMessageInput {
689
697
  link: string;
690
698
  }
691
699
  /**
692
- * Result of loading an existing message for editing, computed by the host.
693
- *
694
- * On `ok`, the package enters edit mode: it hydrates the editor with
695
- * `blocks`, locks the destination to `channelId`, and constrains the
696
- * post-as identity to `editableVia`.
697
- *
698
- * On `!ok`, the package renders the host's `reason` inline and offers
699
- * "Open as a new message instead". If the message round-trips through the
700
- * block editor (e.g. someone else's block message), the host may include
701
- * `blocks` so that fallback can hydrate the draft; for non-block or
702
- * attachment-only messages, omit it.
700
+ * The existing message currently loaded into the builder: its source
701
+ * coordinates (`channelId` + `ts`), the host's editability verdict, and
702
+ * display metadata for the preview header / loaded banner. The `ok` branch
703
+ * of {@link LoadResult} is this plus `blocks`, so the loaded-message field
704
+ * list lives in exactly one place. Reported through
705
+ * {@link LoadingConfig.onLoadedMessageChange} and
706
+ * {@link PrimaryActionContext.loadedMessage}, and consumed by the built-in
707
+ * update flow ({@link BlockKitchenSendProps.onUpdate}).
703
708
  */
704
- type LoadResult = {
705
- ok: true;
709
+ interface LoadedMessage {
706
710
  channelId: string;
707
- /** Channel name for the edit-mode badge (display only). Falls back to `channelId`. */
711
+ /**
712
+ * Channel name for the loaded banner (display only). Falls back to
713
+ * `channelId`.
714
+ */
708
715
  channelName?: string;
709
716
  ts: string;
710
- blocks: SupportedBlock[];
711
717
  editableVia: EditableVia;
712
718
  workspaceName?: string;
713
719
  /**
714
- * Display name of the existing message's author. When provided, the
715
- * preview header shows it instead of `workspaceName`. Optional.
720
+ * Display name of the message's author. When provided, the preview header
721
+ * shows it instead of `workspaceName`. Optional.
716
722
  */
717
723
  username?: string;
718
724
  /**
719
- * Avatar image URL of the existing message's author, shown in the
720
- * preview header. Optional; ignored if it isn't a safe `http(s)` URL.
725
+ * Avatar image URL of the message's author, shown in the preview header.
726
+ * Optional; ignored if it isn't a safe `http(s)` URL.
721
727
  */
722
728
  iconUrl?: string;
723
- } | {
729
+ }
730
+ /**
731
+ * Result of loading an existing message, computed by the host.
732
+ *
733
+ * On `ok` — the {@link LoadedMessage} shape plus `blocks` — the package
734
+ * hydrates the editor with `blocks` and shows the loaded banner. Only the
735
+ * *update* flow ({@link BlockKitchenSendProps.onUpdate}) locks the
736
+ * destination to `channelId` and constrains the post-as identity to
737
+ * `editableVia`; the plain send dialog always posts a brand-new message
738
+ * with free channel/identity choice, loaded draft or not.
739
+ *
740
+ * On `!ok`, the package renders the host's `reason` inline and offers
741
+ * "Open as a new message instead". If the message round-trips through the
742
+ * block editor (e.g. someone else's block message), the host may include
743
+ * `blocks` so that fallback can hydrate the draft; for non-block or
744
+ * attachment-only messages, omit it.
745
+ */
746
+ type LoadResult = (LoadedMessage & {
747
+ ok: true;
748
+ blocks: SupportedBlock[];
749
+ }) | {
724
750
  ok: false;
725
751
  reason: string;
726
752
  blocks?: SupportedBlock[];
@@ -734,7 +760,7 @@ type LoadResult = {
734
760
  oauthUrl?: string;
735
761
  };
736
762
  /**
737
- * Payload passed to {@link EditingConfig.onUpdate}. Sibling to
763
+ * Payload passed to {@link BlockKitchenSendProps.onUpdate}. Sibling to
738
764
  * {@link SendPayload} but carries the source `channel + ts`. `chat.update`
739
765
  * requires re-sending the full blocks payload (no partial update).
740
766
  */
@@ -745,7 +771,7 @@ interface UpdatePayload {
745
771
  asUser: boolean;
746
772
  }
747
773
  /**
748
- * Result returned from {@link EditingConfig.onUpdate}. Mirrors
774
+ * Result returned from {@link BlockKitchenSendProps.onUpdate}. Mirrors
749
775
  * {@link SendResult}; `error` is rendered as-is.
750
776
  */
751
777
  interface UpdateResult {
@@ -754,7 +780,7 @@ interface UpdateResult {
754
780
  }
755
781
  /**
756
782
  * One entry in the "recent messages from this app" picker, returned by
757
- * {@link EditingConfig.loadRecentMessages} for the selected channel. These are
783
+ * {@link LoadingConfig.loadRecentMessages} for the selected channel. These are
758
784
  * editable-by-construction — the app authored them — so they load straight into
759
785
  * edit mode without a separate verdict round-trip. `editableVia` defaults to
760
786
  * `'bot'` when omitted.
@@ -775,43 +801,70 @@ interface RecentMessage {
775
801
  iconUrl?: string;
776
802
  }
777
803
  /**
778
- * Opt-in edit-mode configuration. Presence of {@link BlockKitchenProps.editing}
779
- * enables loading an existing message and dispatching an update; omit it to
780
- * keep the send-only behavior. The package stays integration-agnostic — it
781
- * makes no network calls and has no Slack-token knowledge; the host brokers
782
- * I/O and computes the editability verdict.
804
+ * Opt-in configuration for loading an existing message into the editor —
805
+ * a *composition* concern, so it works in both send mode and
806
+ * [compose-only mode](https://github.com/TightknitAI/block-kitchen#compose-only-mode-bring-your-own-send-flow).
807
+ * Presence of {@link BlockKitchenBaseProps.loading} adds the toolbar's
808
+ * "Find message" entry: the user pastes a Slack permalink (or picks a recent
809
+ * message), the host loads it, and the draft is hydrated with its blocks.
810
+ *
811
+ * What happens *after* composing stays mode-specific: in send mode, pair
812
+ * with {@link BlockKitchenSendProps.onUpdate} to update the message in place;
813
+ * in compose-only mode the host owns the commitment step and receives the
814
+ * loaded target via {@link PrimaryActionContext.loadedMessage} /
815
+ * {@link LoadingConfig.onLoadedMessageChange}.
783
816
  */
784
- interface EditingConfig {
817
+ interface LoadingConfig {
785
818
  /**
786
819
  * Host parses the pasted permalink, fetches the message, and returns a
787
820
  * host-computed editability verdict plus blocks. See {@link LoadResult}.
788
821
  */
789
822
  onLoadMessage: (input: LoadMessageInput) => Promise<LoadResult>;
790
823
  /**
791
- * Dispatches the update for the loaded message. Sibling to
792
- * {@link BlockKitchenProps.onSend}; carries `channel + ts`.
824
+ * Optional. When provided (together with a channel source — see
825
+ * {@link LoadingConfig.loadChannels}), the load dialog adds a "recent
826
+ * messages from this app" picker alongside the paste-a-link input. The user
827
+ * first picks a channel; only then is this called with the chosen
828
+ * `channelId`, scoping the lookup to that single channel. Returns messages
829
+ * the app authored in that channel (editable-by-construction); picking one
830
+ * loads it directly. Omit to offer the paste-link entry only.
793
831
  */
794
- onUpdate: (payload: UpdatePayload) => Promise<UpdateResult>;
832
+ loadRecentMessages?: (channelId: string) => Promise<RecentMessage[]>;
795
833
  /**
796
- * Optional. When provided, the load dialog adds a "recent messages from this
797
- * app" picker alongside the paste-a-link input. The user first picks a channel
798
- * (reusing {@link BlockKitchenProps.loadChannels}); only then is this called
799
- * with the chosen `channelId`, scoping the lookup to that single channel.
800
- * Returns messages the app authored in that channel (editable-by-construction);
801
- * picking one loads it straight into edit mode. Omit to offer the paste-link
802
- * entry only.
834
+ * Channels used to scope the recent-messages picker. Only consulted when
835
+ * {@link LoadingConfig.loadRecentMessages} is provided. In send mode this
836
+ * defaults to the send integration's
837
+ * {@link BlockKitchenSendProps.loadChannels}; in compose-only mode there is
838
+ * no send integration, so provide it here or the picker is hidden.
803
839
  */
804
- loadRecentMessages?: (channelId: string) => Promise<RecentMessage[]>;
840
+ loadChannels?: () => Promise<ChannelOption[]>;
805
841
  /**
806
- * Optional. Pre-load a message straight into edit mode at mount, skipping the
807
- * load dialog. Same shape the host returns from {@link onLoadMessage} on
808
- * success. Read once at mount (like {@link BlockKitchenProps.initialBlocks});
809
- * changing it later needs a remount. When set, its `blocks` seed the draft and
810
- * {@link BlockKitchenProps.initialBlocks} is ignored.
842
+ * Optional. Pre-load a message at mount, skipping the load dialog. Same
843
+ * shape the host returns from {@link LoadingConfig.onLoadMessage} on
844
+ * success. Read once at mount (like
845
+ * {@link BlockKitchenBaseProps.initialBlocks}); changing it later needs a
846
+ * remount. When set, its `blocks` seed the draft and `initialBlocks` is
847
+ * ignored.
811
848
  */
812
849
  initialTarget?: Extract<LoadResult, {
813
850
  ok: true;
814
851
  }>;
852
+ /**
853
+ * Notified when the loaded message changes: the target after a successful
854
+ * load (including a mount-time {@link LoadingConfig.initialTarget}), and
855
+ * `null` when the user exits it ("Switch to a new message" / "Open as a
856
+ * new message instead"). Useful in compose-only mode to keep host state in
857
+ * sync — e.g. so a host-owned commitment step knows whether to offer
858
+ * "update in place" without waiting for a
859
+ * {@link PrimaryActionConfig.onClick}.
860
+ *
861
+ * Dedupe is by value, so this can re-fire for the same `channelId` + `ts`
862
+ * when the host's verdict or display metadata changed (e.g. `editableVia`
863
+ * flipping after a Slack sign-in) — key host state on the full target,
864
+ * not `ts` alone. Notifications flow only while `loading` is configured;
865
+ * removing the config mid-session is treated as unsubscribing.
866
+ */
867
+ onLoadedMessageChange?: (message: LoadedMessage | null) => void;
815
868
  }
816
869
  /**
817
870
  * A workspace custom emoji as configured in Slack. Mirrors the shape Slack's
@@ -935,11 +988,22 @@ interface BlockKitchenBaseProps {
935
988
  * is never serialized into the emitted Block Kit JSON.
936
989
  */
937
990
  customEmojis?: CustomEmoji[];
991
+ /**
992
+ * Opt-in: load an existing Slack message into the editor. Adds a
993
+ * "Find message" toolbar entry — the user pastes a permalink (or picks a
994
+ * recent message) and the draft is hydrated with its blocks. Loading is a
995
+ * *composition* concern, so it works in both send mode and compose-only
996
+ * mode. Pair with {@link BlockKitchenSendProps.onUpdate} (send mode) for
997
+ * the built-in update-in-place flow, or consume
998
+ * {@link PrimaryActionContext.loadedMessage} /
999
+ * {@link LoadingConfig.onLoadedMessageChange} (compose-only) to run your
1000
+ * own.
1001
+ */
1002
+ loading?: LoadingConfig;
938
1003
  /**
939
1004
  * Label + accessible name for the toolbar button that opens the
940
- * load-message dialog (the edit-mode entry point). Defaults to
941
- * `'Find message'`. Only shown when {@link BlockKitchenProps.editing} is set
942
- * and no message is currently loaded.
1005
+ * load-message dialog. Defaults to `'Find message'`. Only shown when
1006
+ * {@link BlockKitchenBaseProps.loading} is configured.
943
1007
  */
944
1008
  loadButtonLabel?: string;
945
1009
  /**
@@ -951,7 +1015,7 @@ interface BlockKitchenBaseProps {
951
1015
  updateButtonLabel?: string;
952
1016
  /**
953
1017
  * Label for the update dialog's final confirm button, which dispatches the
954
- * update via {@link EditingConfig.onUpdate}. Defaults to `'Update message'`
1018
+ * update via {@link BlockKitchenSendProps.onUpdate}. Defaults to `'Update message'`
955
1019
  * (and shows `'Updating…'` while in flight).
956
1020
  */
957
1021
  confirmUpdateLabel?: string;
@@ -1125,14 +1189,16 @@ interface BlockKitchenSendProps {
1125
1189
  */
1126
1190
  onSend: (payload: SendPayload) => Promise<SendResult>;
1127
1191
  /**
1128
- * Opt-in edit mode. When provided, the toolbar exposes an "Edit existing
1129
- * message" entry: the user pastes a Slack message link, the host loads it
1130
- * and returns an editability verdict ({@link EditingConfig.onLoadMessage}),
1131
- * and a successful load flips the primary action from Send to "Update
1132
- * message" ({@link EditingConfig.onUpdate}). Omit to keep send-only
1133
- * behavior. The user-token path reuses {@link BlockKitchenSendProps.loadSendAsUserStatus}.
1192
+ * Opt-in update-in-place mode. Dispatches the update for the loaded
1193
+ * message — sibling to {@link BlockKitchenSendProps.onSend}, but carries
1194
+ * the source `channel + ts`. When provided alongside
1195
+ * {@link BlockKitchenBaseProps.loading}, a loaded message flips the
1196
+ * primary action from Send to "Update message". Omit to keep send-only
1197
+ * behavior: with `loading` alone, a loaded message can still be posted as
1198
+ * a brand-new message. The user-token path reuses
1199
+ * {@link BlockKitchenSendProps.loadSendAsUserStatus}.
1134
1200
  */
1135
- editing?: EditingConfig;
1201
+ onUpdate?: (payload: UpdatePayload) => Promise<UpdateResult>;
1136
1202
  /**
1137
1203
  * Renders host-defined fields inside the built-in send dialog, below the
1138
1204
  * channel and identity pickers. Values collected via
@@ -1165,8 +1231,15 @@ interface BlockKitchenComposeOnlyProps {
1165
1231
  loadChannels?: undefined;
1166
1232
  loadSendAsUserStatus?: undefined;
1167
1233
  onSend?: undefined;
1168
- /** Edit mode depends on the send integration, so it is unavailable too. */
1169
- editing?: undefined;
1234
+ /**
1235
+ * The update-in-place flow (`chat.update`) depends on the send
1236
+ * integration, so it is unavailable here. *Loading* an existing message
1237
+ * is a composition concern and stays available via
1238
+ * {@link BlockKitchenBaseProps.loading} — the host receives the loaded
1239
+ * target ({@link PrimaryActionContext.loadedMessage} /
1240
+ * {@link LoadingConfig.onLoadedMessageChange}) and owns any update flow.
1241
+ */
1242
+ onUpdate?: undefined;
1170
1243
  /** Extends the built-in send dialog, which doesn't exist in this mode. */
1171
1244
  renderSendExtras?: undefined;
1172
1245
  /**
@@ -1184,4 +1257,4 @@ interface BlockKitchenComposeOnlyProps {
1184
1257
  */
1185
1258
  type BlockKitchenProps = BlockKitchenBaseProps & (BlockKitchenSendProps | BlockKitchenComposeOnlyProps);
1186
1259
 
1187
- export { type SupportedHeaderBlock as $, type AlertBlock as A, type BlockKitchenBaseProps as B, type CardBlock as C, type DataVisualizationBlock as D, type EditableVia as E, type FeedbackButtonSubobject as F, type PaletteVariant as G, type HeaderLevel as H, type IconButtonElement as I, type PieChart as J, type PieChartSegment as K, type LoadMessageInput as L, type MarkdownBlock as M, type PlanBlock as N, type PreviewHooks as O, type PaletteSection as P, type PreviewSurface as Q, type PreviewTheme as R, type PrimaryActionConfig as S, type PrimaryActionContext as T, type RecentMessage as U, type SendAsUserStatus as V, type SendExtrasContext as W, type SendPayload as X, type SendResult as Y, type SupportedBlock as Z, type SupportedBlockType as _, type AlertLevel as a, type TableBlock as a0, type TableCell as a1, type TableColumnSetting as a2, type TaskCardBlock as a3, type TaskCardStatus as a4, type Template as a5, type UpdatePayload as a6, type UpdateResult as a7, type UrlSourceElement as a8, type ValidationSummary as a9, type VideoBlock as aa, buildVariantById as ab, defaultPalette as ac, extraAlertVariant as ad, legacyInputVariants as ae, type BlockKitchenComposeOnlyProps as b, type BlockKitchenProps as c, type BlockKitchenSendProps as d, type BrandPreset as e, type BrandTheme as f, type BrandTokens as g, type BuilderBlock as h, type CarouselBlock as i, type CartesianChart as j, type ChannelOption as k, type Chart as l, type ChartAxisConfig as m, type ChartDataPoint as n, type ChartSeries as o, type ChartType as p, type ContainerBlock as q, type ContainerChildBlock as r, type ContextActionsBlock as s, type ContextActionsElement as t, type CustomEmoji as u, type EditingConfig as v, type FeedbackButtonsElement as w, type IconButtonIcon as x, type InputBlock as y, type LoadResult as z };
1260
+ export { type SupportedBlockType as $, type AlertBlock as A, type BlockKitchenBaseProps as B, type CardBlock as C, type DataVisualizationBlock as D, type EditableVia as E, type FeedbackButtonSubobject as F, type LoadingConfig as G, type HeaderLevel as H, type IconButtonElement as I, type PaletteVariant as J, type PieChart as K, type LoadMessageInput as L, type MarkdownBlock as M, type PieChartSegment as N, type PlanBlock as O, type PaletteSection as P, type PreviewHooks as Q, type PreviewSurface as R, type PreviewTheme as S, type PrimaryActionConfig as T, type PrimaryActionContext as U, type RecentMessage as V, type SendAsUserStatus as W, type SendExtrasContext as X, type SendPayload as Y, type SendResult as Z, type SupportedBlock as _, type AlertLevel as a, type SupportedHeaderBlock as a0, type TableBlock as a1, type TableCell as a2, type TableColumnSetting as a3, type TaskCardBlock as a4, type TaskCardStatus as a5, type Template as a6, type UpdatePayload as a7, type UpdateResult as a8, type UrlSourceElement as a9, type ValidationSummary as aa, type VideoBlock as ab, buildVariantById as ac, defaultPalette as ad, extraAlertVariant as ae, legacyInputVariants as af, type BlockKitchenComposeOnlyProps as b, type BlockKitchenProps as c, type BlockKitchenSendProps as d, type BrandPreset as e, type BrandTheme as f, type BrandTokens as g, type BuilderBlock as h, type CarouselBlock as i, type CartesianChart as j, type ChannelOption as k, type Chart as l, type ChartAxisConfig as m, type ChartDataPoint as n, type ChartSeries as o, type ChartType as p, type ContainerBlock as q, type ContainerChildBlock as r, type ContextActionsBlock as s, type ContextActionsElement as t, type CustomEmoji as u, type FeedbackButtonsElement as v, type IconButtonIcon as w, type InputBlock as x, type LoadResult as y, type LoadedMessage as z };
@@ -648,6 +648,14 @@ interface PrimaryActionContext {
648
648
  * {@link BlockKitchenBaseProps.onValidationChange} reports.
649
649
  */
650
650
  validation: ValidationSummary;
651
+ /**
652
+ * The existing message currently loaded into the builder (via
653
+ * {@link BlockKitchenBaseProps.loading}), or `null` when composing a new
654
+ * message. Carries `channelId` + `ts` + the host's editability verdict, so
655
+ * a host-owned commitment step can offer "update in place" alongside
656
+ * "post as new".
657
+ */
658
+ loadedMessage: LoadedMessage | null;
651
659
  }
652
660
  /**
653
661
  * A host-owned primary action for compose-only mode, rendered in the
@@ -681,7 +689,7 @@ interface PrimaryActionConfig {
681
689
  */
682
690
  type EditableVia = 'bot' | 'user';
683
691
  /**
684
- * Input passed to {@link EditingConfig.onLoadMessage}. The user pastes a
692
+ * Input passed to {@link LoadingConfig.onLoadMessage}. The user pastes a
685
693
  * Slack message permalink (Slack's "Copy link"); the host parses
686
694
  * `channel + ts` out of it — the package never touches raw timestamps.
687
695
  */
@@ -689,38 +697,56 @@ interface LoadMessageInput {
689
697
  link: string;
690
698
  }
691
699
  /**
692
- * Result of loading an existing message for editing, computed by the host.
693
- *
694
- * On `ok`, the package enters edit mode: it hydrates the editor with
695
- * `blocks`, locks the destination to `channelId`, and constrains the
696
- * post-as identity to `editableVia`.
697
- *
698
- * On `!ok`, the package renders the host's `reason` inline and offers
699
- * "Open as a new message instead". If the message round-trips through the
700
- * block editor (e.g. someone else's block message), the host may include
701
- * `blocks` so that fallback can hydrate the draft; for non-block or
702
- * attachment-only messages, omit it.
700
+ * The existing message currently loaded into the builder: its source
701
+ * coordinates (`channelId` + `ts`), the host's editability verdict, and
702
+ * display metadata for the preview header / loaded banner. The `ok` branch
703
+ * of {@link LoadResult} is this plus `blocks`, so the loaded-message field
704
+ * list lives in exactly one place. Reported through
705
+ * {@link LoadingConfig.onLoadedMessageChange} and
706
+ * {@link PrimaryActionContext.loadedMessage}, and consumed by the built-in
707
+ * update flow ({@link BlockKitchenSendProps.onUpdate}).
703
708
  */
704
- type LoadResult = {
705
- ok: true;
709
+ interface LoadedMessage {
706
710
  channelId: string;
707
- /** Channel name for the edit-mode badge (display only). Falls back to `channelId`. */
711
+ /**
712
+ * Channel name for the loaded banner (display only). Falls back to
713
+ * `channelId`.
714
+ */
708
715
  channelName?: string;
709
716
  ts: string;
710
- blocks: SupportedBlock[];
711
717
  editableVia: EditableVia;
712
718
  workspaceName?: string;
713
719
  /**
714
- * Display name of the existing message's author. When provided, the
715
- * preview header shows it instead of `workspaceName`. Optional.
720
+ * Display name of the message's author. When provided, the preview header
721
+ * shows it instead of `workspaceName`. Optional.
716
722
  */
717
723
  username?: string;
718
724
  /**
719
- * Avatar image URL of the existing message's author, shown in the
720
- * preview header. Optional; ignored if it isn't a safe `http(s)` URL.
725
+ * Avatar image URL of the message's author, shown in the preview header.
726
+ * Optional; ignored if it isn't a safe `http(s)` URL.
721
727
  */
722
728
  iconUrl?: string;
723
- } | {
729
+ }
730
+ /**
731
+ * Result of loading an existing message, computed by the host.
732
+ *
733
+ * On `ok` — the {@link LoadedMessage} shape plus `blocks` — the package
734
+ * hydrates the editor with `blocks` and shows the loaded banner. Only the
735
+ * *update* flow ({@link BlockKitchenSendProps.onUpdate}) locks the
736
+ * destination to `channelId` and constrains the post-as identity to
737
+ * `editableVia`; the plain send dialog always posts a brand-new message
738
+ * with free channel/identity choice, loaded draft or not.
739
+ *
740
+ * On `!ok`, the package renders the host's `reason` inline and offers
741
+ * "Open as a new message instead". If the message round-trips through the
742
+ * block editor (e.g. someone else's block message), the host may include
743
+ * `blocks` so that fallback can hydrate the draft; for non-block or
744
+ * attachment-only messages, omit it.
745
+ */
746
+ type LoadResult = (LoadedMessage & {
747
+ ok: true;
748
+ blocks: SupportedBlock[];
749
+ }) | {
724
750
  ok: false;
725
751
  reason: string;
726
752
  blocks?: SupportedBlock[];
@@ -734,7 +760,7 @@ type LoadResult = {
734
760
  oauthUrl?: string;
735
761
  };
736
762
  /**
737
- * Payload passed to {@link EditingConfig.onUpdate}. Sibling to
763
+ * Payload passed to {@link BlockKitchenSendProps.onUpdate}. Sibling to
738
764
  * {@link SendPayload} but carries the source `channel + ts`. `chat.update`
739
765
  * requires re-sending the full blocks payload (no partial update).
740
766
  */
@@ -745,7 +771,7 @@ interface UpdatePayload {
745
771
  asUser: boolean;
746
772
  }
747
773
  /**
748
- * Result returned from {@link EditingConfig.onUpdate}. Mirrors
774
+ * Result returned from {@link BlockKitchenSendProps.onUpdate}. Mirrors
749
775
  * {@link SendResult}; `error` is rendered as-is.
750
776
  */
751
777
  interface UpdateResult {
@@ -754,7 +780,7 @@ interface UpdateResult {
754
780
  }
755
781
  /**
756
782
  * One entry in the "recent messages from this app" picker, returned by
757
- * {@link EditingConfig.loadRecentMessages} for the selected channel. These are
783
+ * {@link LoadingConfig.loadRecentMessages} for the selected channel. These are
758
784
  * editable-by-construction — the app authored them — so they load straight into
759
785
  * edit mode without a separate verdict round-trip. `editableVia` defaults to
760
786
  * `'bot'` when omitted.
@@ -775,43 +801,70 @@ interface RecentMessage {
775
801
  iconUrl?: string;
776
802
  }
777
803
  /**
778
- * Opt-in edit-mode configuration. Presence of {@link BlockKitchenProps.editing}
779
- * enables loading an existing message and dispatching an update; omit it to
780
- * keep the send-only behavior. The package stays integration-agnostic — it
781
- * makes no network calls and has no Slack-token knowledge; the host brokers
782
- * I/O and computes the editability verdict.
804
+ * Opt-in configuration for loading an existing message into the editor —
805
+ * a *composition* concern, so it works in both send mode and
806
+ * [compose-only mode](https://github.com/TightknitAI/block-kitchen#compose-only-mode-bring-your-own-send-flow).
807
+ * Presence of {@link BlockKitchenBaseProps.loading} adds the toolbar's
808
+ * "Find message" entry: the user pastes a Slack permalink (or picks a recent
809
+ * message), the host loads it, and the draft is hydrated with its blocks.
810
+ *
811
+ * What happens *after* composing stays mode-specific: in send mode, pair
812
+ * with {@link BlockKitchenSendProps.onUpdate} to update the message in place;
813
+ * in compose-only mode the host owns the commitment step and receives the
814
+ * loaded target via {@link PrimaryActionContext.loadedMessage} /
815
+ * {@link LoadingConfig.onLoadedMessageChange}.
783
816
  */
784
- interface EditingConfig {
817
+ interface LoadingConfig {
785
818
  /**
786
819
  * Host parses the pasted permalink, fetches the message, and returns a
787
820
  * host-computed editability verdict plus blocks. See {@link LoadResult}.
788
821
  */
789
822
  onLoadMessage: (input: LoadMessageInput) => Promise<LoadResult>;
790
823
  /**
791
- * Dispatches the update for the loaded message. Sibling to
792
- * {@link BlockKitchenProps.onSend}; carries `channel + ts`.
824
+ * Optional. When provided (together with a channel source — see
825
+ * {@link LoadingConfig.loadChannels}), the load dialog adds a "recent
826
+ * messages from this app" picker alongside the paste-a-link input. The user
827
+ * first picks a channel; only then is this called with the chosen
828
+ * `channelId`, scoping the lookup to that single channel. Returns messages
829
+ * the app authored in that channel (editable-by-construction); picking one
830
+ * loads it directly. Omit to offer the paste-link entry only.
793
831
  */
794
- onUpdate: (payload: UpdatePayload) => Promise<UpdateResult>;
832
+ loadRecentMessages?: (channelId: string) => Promise<RecentMessage[]>;
795
833
  /**
796
- * Optional. When provided, the load dialog adds a "recent messages from this
797
- * app" picker alongside the paste-a-link input. The user first picks a channel
798
- * (reusing {@link BlockKitchenProps.loadChannels}); only then is this called
799
- * with the chosen `channelId`, scoping the lookup to that single channel.
800
- * Returns messages the app authored in that channel (editable-by-construction);
801
- * picking one loads it straight into edit mode. Omit to offer the paste-link
802
- * entry only.
834
+ * Channels used to scope the recent-messages picker. Only consulted when
835
+ * {@link LoadingConfig.loadRecentMessages} is provided. In send mode this
836
+ * defaults to the send integration's
837
+ * {@link BlockKitchenSendProps.loadChannels}; in compose-only mode there is
838
+ * no send integration, so provide it here or the picker is hidden.
803
839
  */
804
- loadRecentMessages?: (channelId: string) => Promise<RecentMessage[]>;
840
+ loadChannels?: () => Promise<ChannelOption[]>;
805
841
  /**
806
- * Optional. Pre-load a message straight into edit mode at mount, skipping the
807
- * load dialog. Same shape the host returns from {@link onLoadMessage} on
808
- * success. Read once at mount (like {@link BlockKitchenProps.initialBlocks});
809
- * changing it later needs a remount. When set, its `blocks` seed the draft and
810
- * {@link BlockKitchenProps.initialBlocks} is ignored.
842
+ * Optional. Pre-load a message at mount, skipping the load dialog. Same
843
+ * shape the host returns from {@link LoadingConfig.onLoadMessage} on
844
+ * success. Read once at mount (like
845
+ * {@link BlockKitchenBaseProps.initialBlocks}); changing it later needs a
846
+ * remount. When set, its `blocks` seed the draft and `initialBlocks` is
847
+ * ignored.
811
848
  */
812
849
  initialTarget?: Extract<LoadResult, {
813
850
  ok: true;
814
851
  }>;
852
+ /**
853
+ * Notified when the loaded message changes: the target after a successful
854
+ * load (including a mount-time {@link LoadingConfig.initialTarget}), and
855
+ * `null` when the user exits it ("Switch to a new message" / "Open as a
856
+ * new message instead"). Useful in compose-only mode to keep host state in
857
+ * sync — e.g. so a host-owned commitment step knows whether to offer
858
+ * "update in place" without waiting for a
859
+ * {@link PrimaryActionConfig.onClick}.
860
+ *
861
+ * Dedupe is by value, so this can re-fire for the same `channelId` + `ts`
862
+ * when the host's verdict or display metadata changed (e.g. `editableVia`
863
+ * flipping after a Slack sign-in) — key host state on the full target,
864
+ * not `ts` alone. Notifications flow only while `loading` is configured;
865
+ * removing the config mid-session is treated as unsubscribing.
866
+ */
867
+ onLoadedMessageChange?: (message: LoadedMessage | null) => void;
815
868
  }
816
869
  /**
817
870
  * A workspace custom emoji as configured in Slack. Mirrors the shape Slack's
@@ -935,11 +988,22 @@ interface BlockKitchenBaseProps {
935
988
  * is never serialized into the emitted Block Kit JSON.
936
989
  */
937
990
  customEmojis?: CustomEmoji[];
991
+ /**
992
+ * Opt-in: load an existing Slack message into the editor. Adds a
993
+ * "Find message" toolbar entry — the user pastes a permalink (or picks a
994
+ * recent message) and the draft is hydrated with its blocks. Loading is a
995
+ * *composition* concern, so it works in both send mode and compose-only
996
+ * mode. Pair with {@link BlockKitchenSendProps.onUpdate} (send mode) for
997
+ * the built-in update-in-place flow, or consume
998
+ * {@link PrimaryActionContext.loadedMessage} /
999
+ * {@link LoadingConfig.onLoadedMessageChange} (compose-only) to run your
1000
+ * own.
1001
+ */
1002
+ loading?: LoadingConfig;
938
1003
  /**
939
1004
  * Label + accessible name for the toolbar button that opens the
940
- * load-message dialog (the edit-mode entry point). Defaults to
941
- * `'Find message'`. Only shown when {@link BlockKitchenProps.editing} is set
942
- * and no message is currently loaded.
1005
+ * load-message dialog. Defaults to `'Find message'`. Only shown when
1006
+ * {@link BlockKitchenBaseProps.loading} is configured.
943
1007
  */
944
1008
  loadButtonLabel?: string;
945
1009
  /**
@@ -951,7 +1015,7 @@ interface BlockKitchenBaseProps {
951
1015
  updateButtonLabel?: string;
952
1016
  /**
953
1017
  * Label for the update dialog's final confirm button, which dispatches the
954
- * update via {@link EditingConfig.onUpdate}. Defaults to `'Update message'`
1018
+ * update via {@link BlockKitchenSendProps.onUpdate}. Defaults to `'Update message'`
955
1019
  * (and shows `'Updating…'` while in flight).
956
1020
  */
957
1021
  confirmUpdateLabel?: string;
@@ -1125,14 +1189,16 @@ interface BlockKitchenSendProps {
1125
1189
  */
1126
1190
  onSend: (payload: SendPayload) => Promise<SendResult>;
1127
1191
  /**
1128
- * Opt-in edit mode. When provided, the toolbar exposes an "Edit existing
1129
- * message" entry: the user pastes a Slack message link, the host loads it
1130
- * and returns an editability verdict ({@link EditingConfig.onLoadMessage}),
1131
- * and a successful load flips the primary action from Send to "Update
1132
- * message" ({@link EditingConfig.onUpdate}). Omit to keep send-only
1133
- * behavior. The user-token path reuses {@link BlockKitchenSendProps.loadSendAsUserStatus}.
1192
+ * Opt-in update-in-place mode. Dispatches the update for the loaded
1193
+ * message — sibling to {@link BlockKitchenSendProps.onSend}, but carries
1194
+ * the source `channel + ts`. When provided alongside
1195
+ * {@link BlockKitchenBaseProps.loading}, a loaded message flips the
1196
+ * primary action from Send to "Update message". Omit to keep send-only
1197
+ * behavior: with `loading` alone, a loaded message can still be posted as
1198
+ * a brand-new message. The user-token path reuses
1199
+ * {@link BlockKitchenSendProps.loadSendAsUserStatus}.
1134
1200
  */
1135
- editing?: EditingConfig;
1201
+ onUpdate?: (payload: UpdatePayload) => Promise<UpdateResult>;
1136
1202
  /**
1137
1203
  * Renders host-defined fields inside the built-in send dialog, below the
1138
1204
  * channel and identity pickers. Values collected via
@@ -1165,8 +1231,15 @@ interface BlockKitchenComposeOnlyProps {
1165
1231
  loadChannels?: undefined;
1166
1232
  loadSendAsUserStatus?: undefined;
1167
1233
  onSend?: undefined;
1168
- /** Edit mode depends on the send integration, so it is unavailable too. */
1169
- editing?: undefined;
1234
+ /**
1235
+ * The update-in-place flow (`chat.update`) depends on the send
1236
+ * integration, so it is unavailable here. *Loading* an existing message
1237
+ * is a composition concern and stays available via
1238
+ * {@link BlockKitchenBaseProps.loading} — the host receives the loaded
1239
+ * target ({@link PrimaryActionContext.loadedMessage} /
1240
+ * {@link LoadingConfig.onLoadedMessageChange}) and owns any update flow.
1241
+ */
1242
+ onUpdate?: undefined;
1170
1243
  /** Extends the built-in send dialog, which doesn't exist in this mode. */
1171
1244
  renderSendExtras?: undefined;
1172
1245
  /**
@@ -1184,4 +1257,4 @@ interface BlockKitchenComposeOnlyProps {
1184
1257
  */
1185
1258
  type BlockKitchenProps = BlockKitchenBaseProps & (BlockKitchenSendProps | BlockKitchenComposeOnlyProps);
1186
1259
 
1187
- export { type SupportedHeaderBlock as $, type AlertBlock as A, type BlockKitchenBaseProps as B, type CardBlock as C, type DataVisualizationBlock as D, type EditableVia as E, type FeedbackButtonSubobject as F, type PaletteVariant as G, type HeaderLevel as H, type IconButtonElement as I, type PieChart as J, type PieChartSegment as K, type LoadMessageInput as L, type MarkdownBlock as M, type PlanBlock as N, type PreviewHooks as O, type PaletteSection as P, type PreviewSurface as Q, type PreviewTheme as R, type PrimaryActionConfig as S, type PrimaryActionContext as T, type RecentMessage as U, type SendAsUserStatus as V, type SendExtrasContext as W, type SendPayload as X, type SendResult as Y, type SupportedBlock as Z, type SupportedBlockType as _, type AlertLevel as a, type TableBlock as a0, type TableCell as a1, type TableColumnSetting as a2, type TaskCardBlock as a3, type TaskCardStatus as a4, type Template as a5, type UpdatePayload as a6, type UpdateResult as a7, type UrlSourceElement as a8, type ValidationSummary as a9, type VideoBlock as aa, buildVariantById as ab, defaultPalette as ac, extraAlertVariant as ad, legacyInputVariants as ae, type BlockKitchenComposeOnlyProps as b, type BlockKitchenProps as c, type BlockKitchenSendProps as d, type BrandPreset as e, type BrandTheme as f, type BrandTokens as g, type BuilderBlock as h, type CarouselBlock as i, type CartesianChart as j, type ChannelOption as k, type Chart as l, type ChartAxisConfig as m, type ChartDataPoint as n, type ChartSeries as o, type ChartType as p, type ContainerBlock as q, type ContainerChildBlock as r, type ContextActionsBlock as s, type ContextActionsElement as t, type CustomEmoji as u, type EditingConfig as v, type FeedbackButtonsElement as w, type IconButtonIcon as x, type InputBlock as y, type LoadResult as z };
1260
+ export { type SupportedBlockType as $, type AlertBlock as A, type BlockKitchenBaseProps as B, type CardBlock as C, type DataVisualizationBlock as D, type EditableVia as E, type FeedbackButtonSubobject as F, type LoadingConfig as G, type HeaderLevel as H, type IconButtonElement as I, type PaletteVariant as J, type PieChart as K, type LoadMessageInput as L, type MarkdownBlock as M, type PieChartSegment as N, type PlanBlock as O, type PaletteSection as P, type PreviewHooks as Q, type PreviewSurface as R, type PreviewTheme as S, type PrimaryActionConfig as T, type PrimaryActionContext as U, type RecentMessage as V, type SendAsUserStatus as W, type SendExtrasContext as X, type SendPayload as Y, type SendResult as Z, type SupportedBlock as _, type AlertLevel as a, type SupportedHeaderBlock as a0, type TableBlock as a1, type TableCell as a2, type TableColumnSetting as a3, type TaskCardBlock as a4, type TaskCardStatus as a5, type Template as a6, type UpdatePayload as a7, type UpdateResult as a8, type UrlSourceElement as a9, type ValidationSummary as aa, type VideoBlock as ab, buildVariantById as ac, defaultPalette as ad, extraAlertVariant as ae, legacyInputVariants as af, type BlockKitchenComposeOnlyProps as b, type BlockKitchenProps as c, type BlockKitchenSendProps as d, type BrandPreset as e, type BrandTheme as f, type BrandTokens as g, type BuilderBlock as h, type CarouselBlock as i, type CartesianChart as j, type ChannelOption as k, type Chart as l, type ChartAxisConfig as m, type ChartDataPoint as n, type ChartSeries as o, type ChartType as p, type ContainerBlock as q, type ContainerChildBlock as r, type ContextActionsBlock as s, type ContextActionsElement as t, type CustomEmoji as u, type FeedbackButtonsElement as v, type IconButtonIcon as w, type InputBlock as x, type LoadResult as y, type LoadedMessage as z };
@@ -1,3 +1,3 @@
1
- export { P as PaletteSection, G as PaletteVariant, Z as SupportedBlock, _ as SupportedBlockType, ab as buildVariantById, ac as defaultPalette, ad as extraAlertVariant, ae as legacyInputVariants } from './palette-Duj901T6.cjs';
1
+ export { P as PaletteSection, J as PaletteVariant, _ as SupportedBlock, $ as SupportedBlockType, ac as buildVariantById, ad as defaultPalette, ae as extraAlertVariant, af as legacyInputVariants } from './palette-C8sPJuXx.cjs';
2
2
  import 'react';
3
3
  import 'slack-web-api-client';
package/dist/palette.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { P as PaletteSection, G as PaletteVariant, Z as SupportedBlock, _ as SupportedBlockType, ab as buildVariantById, ac as defaultPalette, ad as extraAlertVariant, ae as legacyInputVariants } from './palette-Duj901T6.js';
1
+ export { P as PaletteSection, J as PaletteVariant, _ as SupportedBlock, $ as SupportedBlockType, ac as buildVariantById, ad as defaultPalette, ae as extraAlertVariant, af as legacyInputVariants } from './palette-C8sPJuXx.js';
2
2
  import 'react';
3
3
  import 'slack-web-api-client';