feat: caller-customized send — compose-only mode, send-dialog extras, exported primitives - #138
Merged
StephenTangCook merged 3 commits intoJul 17, 2026
Conversation
Split BlockKitchenProps into a shared base plus an all-or-nothing send
integration union: provide loadChannels/loadSendAsUserStatus/onSend for
the built-in send flow, or omit all three and the builder renders no send
button, becoming a pure editor whose host owns the send flow. Partial
wiring is a type error (and a runtime console warning for JS consumers);
`editing` requires the trio since chat.update is channel-bound.
Add onValidationChange, reporting { valid, errorCount, errors } whenever
the draft's verdict changes — the exact verdict the issues sheet shows,
validated against the message surface — so a host-rendered CTA can gate
on validity without re-running the validator and risking drift.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J2JGjyg1LC9RxdjBFR8oeJ
Contributor
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
block-kitchen | ae21df3 | Commit Preview URL Branch Preview URL |
Jul 17 2026, 03:39 AM |
…end primitives Three extension points layered on compose-only mode, so callers can customize the moment of commitment at any grain: - renderSendExtras (send mode): host-defined fields rendered inside the built-in send dialog below the channel/identity pickers; values collected via setExtras arrive on onSend as payload.extras. The key is only present when the slot is wired, so existing onSend handlers are unaffected. The extras object is dialog-owned and resets on open. - primaryAction (compose-only mode): host-owned button rendered in the toolbar slot where "Review & send" normally sits (e.g. "Save template" for a message drafter). onClick receives the current draft plus the same validation verdict onValidationChange reports; disableWhenInvalid opts into Send-style gating (default off). - exported send-flow primitives: SendDialog (+ SendDialogProps), useSlackSignIn, SlackSignInButton — so a bespoke send flow built on compose-only mode doesn't rebuild channel loading, OAuth polling, and sending states from scratch. Both new props follow the union's all-or-nothing discipline: wrong-branch use is a type error, with mount-time console.warn guards for untyped consumers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Av2NwEDuB6sw2Q6ab35bH
StephenTangCook
marked this pull request as ready for review
July 17, 2026 03:26
…udience-boundary-uykjuy Brings the branch up to date with main (0.9.5 → 0.9.9 line): dependency bumps, the JSON-drawer Done button, the data_visualization preview-image sanitize fix, and the bk-utilities style-scoping step. No conflicts. Applies Biome's it.each formatting to three test files: main's Biome bump (2.4.16 → 2.5.4, #155) reformats multi-line it.each arrays onto one line. The files are otherwise untouched by this branch; the reformat just keeps `biome check` green under the newer version.
Merged
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes the send integration customizable at every grain, layered on the original compose-only change:
loadChannels/loadSendAsUserStatus/onSendfor the built-in send flow (unchanged), or omit all three and the builder renders no send button — a pure Block Kit editor whose host app owns the send flow.onValidationChangelets a host-rendered CTA gate on the exact validation verdict the issues sheet shows.primaryAction(compose-only mode): a host-owned button in the toolbar slot where "Review & send" normally sits — e.g. "Save template" for a message drafter.onClickreceives the current draft (builder-native format) plus the validation verdict;disableWhenInvalidopts into Send-style gating (default off — a drafter usually allows committing a WIP draft).renderSendExtras(send mode): host-defined fields rendered inside the built-in send dialog below the channel/identity pickers. Values collected viasetExtrasarrive ononSendaspayload.extras— for "the built-in flow plus a cross-post toggle / custom sender name" without rebuilding the dialog.SendDialog(+SendDialogProps),useSlackSignIn,SlackSignInButton— so a bespoke send flow built on compose-only mode doesn't rebuild channel loading, OAuth polling, and sending states from scratch.Why
When composing is one step in a flow the host app owns (audience selection, scheduling, multi-channel fan-out), the moment of commitment belongs outside the builder — but previously the send trio was required and the "Review & send" button couldn't be removed, forcing either a dual-CTA UX or CSS hacks. The host also had no access to the builder's validation verdict, so an external CTA would have to re-run the validator and risk drifting from what the issues sheet displays.
The follow-up layer closes the gap between "all" (built-in flow, fixed fields) and "nothing" (compose-only): customizing which identity sends, custom sender names, or extra toggles like cross-posting previously meant abandoning the entire built-in dialog. Now each need maps to a grain: extras slot → built-in UX + host fields;
primaryAction→ compose-only with a toolbar-native CTA; exported primitives → fully bespoke flows without rebuilding the machinery.Design choices:
BlockKitchenPropsisBlockKitchenBaseProps & (BlockKitchenSendProps | BlockKitchenComposeOnlyProps); the compose-only branch pins the trio (andediting) toundefined. Wiring only some of the three is a type error rather than a silently dead send button. Runtimeconsole.warns cover untyped JS consumers. Fully backward compatible for existing consumers.renderSendExtraslives on the send branch,primaryActionon the compose-only branch, each pinned?: undefinedon the other — wrong-branch use is a type error, with mount-time warnings for untyped consumers.payload.extrasonly exists when the slot is wired, so existingonSendhandlers never see a new key. The extras object is dialog-owned: it resets on open alongside the channel/identity pickers, andsetExtras(patch)shallow-merges —onSendstays a pure data-in callback instead of relying on host closures.editingrequires the trio.chat.updateis inherently bound to a channel + timestamp, so the update flow only exists alongside the send integration.onValidationChangefires on verdict change only (piggybacking the builder's debounced validation pass, deduped against the last-notified verdict), always scoped to themessagesurface — the same verdict the issues chip/sheet displays. The sameValidationSummaryis whatprimaryAction.onClickreceives.New exports:
SendDialog,useSlackSignIn,SlackSignInButton; typesValidationSummary,BlockKitchenBaseProps,BlockKitchenSendProps,BlockKitchenComposeOnlyProps,SendDialogProps,SendExtrasContext,PrimaryActionConfig,PrimaryActionContext. README gains "Compose-only mode", "Keeping the CTA in the toolbar", "Building a bespoke send flow from the exported primitives", and "Extending the send dialog" sections.Test plan
pnpm typecheckpnpm lintpnpm test(390 tests, 31 files — includes browser-mode Storybook tests)pnpm buildpnpm demo:build(the demo consumes../srcdirectly, so this is a real-consumer compile check)Tests in
test/block-kitchen-compose-only.test.tsx:onValidationChangereports{ valid: true, errorCount: 0, errors: [] }for a valid draft and a non-empty error list for an invalid oneprimaryActionrenders in the toolbar; click receives{ blocks, validation }; enabled for an invalid draft by default;disableWhenInvaliddisables iteditingwithout the trio warns and is ignored;primaryActionalongside the trio warns and the built-in send button wins;renderSendExtraswithout the trio warns@ts-expect-errorassertions (enforced bypnpm typecheck) pin the all-or-nothing union, including both new wrong-branch combinationsNew
test/send-dialog-extras.test.tsx:channelId/sendAsUsercontext (null before channels load)setExtrasvalues arrive onpayload.extras;{}when wired but unused; the key is absent when the slot isn't wiredtest/public-api.test.tspins the entry-point exports (components + send-flow primitives) so a rename/dropped export fails in CI, not in a consumer's build.Notes for reviewer
BlockKitchendestructures through a widening cast: TS's correlated-union narrowing otherwise flags the runtime guards as "always true" (TS2774), and the guards must exist for untyped consumers anyway. The cast pullsprimaryActionfrom the compose-only branch (Pick) rather than the send branch'sundefinedpin, which would collapse the intersection toundefined.Meta) because arg inference collapses the props union tonever.Toolbargains ashowSendprop (defaulttrue) and a resolvedprimaryActionslot; both are no-ops for every existing usage.SendDialogPropsis a new exported interface (previously an inline type) since the dialog is now public API.🤖 Generated with Claude Code
https://claude.ai/code/session_019Av2NwEDuB6sw2Q6ab35bH