Skip to content

feat: add blocks preview command to open blocks in Block Kit Builder - #620

Merged
WilliamBergamin merged 32 commits into
mainfrom
bill-bkb-open-command
Jul 29, 2026
Merged

feat: add blocks preview command to open blocks in Block Kit Builder#620
WilliamBergamin merged 32 commits into
mainfrom
bill-bkb-open-command

Conversation

@WilliamBergamin

@WilliamBergamin WilliamBergamin commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Changelog

Add the slack blocks preview command to open Block Kit blocks in the Block Kit Builder.

Summary

This adds a new blocks preview command. It takes a set of Block Kit blocks and opens them in the Block Kit Builder in a web browser.

Blocks are passed with the --blocks flag. The value is a JSON array of blocks or a JSON object with a blocks array. Pass - to read the blocks from standard input.

The command is hidden from the help output unless an AI coding tool is detected. It is meant to be used by AI tools that generate Block Kit layouts.

Preview

(No UI change. The command prints the Builder URL and opens it in the browser.)

Testing

Build the CLI first:

make build

Then run the examples below with SLACK_DISABLE_TELEMETRY=true.

Expected to work:

  1. Preview a bare blocks array passed as a flag value.

    ./bin/slack blocks preview --blocks '[{"type":"divider"}]'
    
  2. Preview a full blocks object passed as a flag value.

    ./bin/slack blocks preview --blocks '{"blocks":[{"type":"section","text":{"type":"mrkdwn","text":"Hello"}}]}'
    
  3. Preview blocks read from a file on standard input.

    echo '[{"type":"divider"}]' > blocks.json
    ./bin/slack blocks preview --blocks - < blocks.json
    

Expected to error:

  1. Pass a value that is not valid JSON. This returns an unable_to_parse_json error.

    ./bin/slack blocks preview --blocks 'not json'
    
  2. Pass valid JSON that is not a blocks payload. This returns an invalid_blocks error.

    ./bin/slack blocks preview --blocks '{"foo":"bar"}'
    

Unit tests:

make test testdir=cmd/blocks

Notes

  • The command is gated on AI-tool detection, so it stays hidden from the normal help output and docs.
  • The URL path segment /block-kit-builder/{id}/builder was verified in an authenticated browser.

Requirements

WilliamBergamin and others added 17 commits July 20, 2026 14:43
Design for reworking `slack blocks preview` input handling (--blocks flag
with `-` stdin sentinel) and folding in the seven code-review findings.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Baseline commit of the in-progress `slack blocks preview` feature before
the input-rework and review-fix tasks. Opens Block Kit blocks in the
Block Kit Builder, gated behind the block-kit-builder experiment.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Adds Os.Stdin() and IOStreams.IsStdinTTY(), mirroring Stdout()/IsTTY(),
so callers can distinguish piped/redirected stdin from an interactive
terminal even when stdout is still a TTY.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Replaces the positional argument with a --blocks flag, supports the -
stdin sentinel and auto-detected piped stdin, and errors on a bare
interactive invocation instead of blocking forever on stdin.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Reading blocks from stdin consumes the pipe, so the interactive team
picker cannot prompt. Detect stdin input with more than one auth and no
--team flag and return an actionable error instead of failing on EOF.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
An org-grid workspace install has an enterprise ID but is not an
enterprise install; key off IsEnterpriseInstall so those installs open
the Builder in the workspace context.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Guard against empty or scheme-less hosts that would otherwise yield a
malformed URL, and wrap the url.Parse error with a structured code.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Hide the blocks command while gated behind the block-kit-builder
experiment and drop its generated doc pages, which docgen omits for
hidden commands.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Surface and allow the blocks command only when the CLI is invoked by an AI
coding tool, reusing the existing useragent.GetAIAgent detection. Replaces the
block-kit-builder experiment: the parent command is hidden from help/docs unless
an AI tool is detected, and preview's PreRunE now returns command_unavailable for
non-AI callers. Removes the BlockKitBuilder experiment.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
These agent-workflow planning artifacts under docs/superpowers/ are not part of
the shipped product and are unreferenced. Remove them from the branch.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
…tools

The blocks preview command was gated two ways on AI-coding-tool detection:
visibility (parent command Hidden) and execution (preview PreRunE returning
command_unavailable for non-AI callers). Drop the execution gate so anyone can
run the command, while keeping the visibility gate so it stays hidden from help
and docs unless an AI coding tool is detected.

Remove the now-orphaned command_unavailable error code (added by the same commit
that introduced the gate, used nowhere else) and regenerate errors.md.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Remove the auto-detected stdin pipe from blocks preview so blocks are read
from standard input only via the explicit --blocks - sentinel, matching the
prevailing convention of comparable tools (kubectl -f -, gh --input -). This
drops the one place the command diverged from those tools.

Fix the file-input example to use the sentinel form and correct the inaccurate
"piped from a file" wording (< is redirection, not a pipe), and update the
Long description and remediation text to match the new behavior.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Remove comments that restate self-explanatory function names and bodies,
and trim the rest to the non-obvious rationale. Comments-only change.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
- Guard `--blocks -` with IsStdinTTY() so an interactive terminal returns
  a clear error instead of blocking forever on stdin.
- Return ErrCredentialsNotFound when no teams are logged in, instead of
  showing an empty team prompt.
- Preserve the user's JSON key order in the Block Kit Builder URL by
  compacting the original input instead of re-marshalling the parsed value.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
@WilliamBergamin WilliamBergamin self-assigned this Jul 21, 2026
@WilliamBergamin WilliamBergamin added enhancement M-T: A feature request for new functionality semver:minor Use on pull requests to describe the release version increment labels Jul 21, 2026
@WilliamBergamin
WilliamBergamin marked this pull request as ready for review July 21, 2026 22:20
@WilliamBergamin
WilliamBergamin requested review from a team as code owners July 21, 2026 22:20
@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.47619% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.04%. Comparing base (0e3135c) to head (5874f18).

Files with missing lines Patch % Lines
cmd/blocks/preview.go 90.90% 7 Missing and 5 partials ⚠️
internal/iostreams/reader.go 66.66% 1 Missing and 1 partial ⚠️
internal/slackdeps/os.go 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #620      +/-   ##
==========================================
+ Coverage   71.91%   72.04%   +0.13%     
==========================================
  Files         231      233       +2     
  Lines       19492    19659     +167     
==========================================
+ Hits        14018    14164     +146     
- Misses       4243     4254      +11     
- Partials     1231     1241      +10     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>

@lukegalbraithrussell lukegalbraithrussell left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving as docs/ code owner

@zimeg zimeg added the changelog Use on updates to be included in the release notes label Jul 28, 2026
@zimeg zimeg added this to the Next Release milestone Jul 28, 2026

@zimeg zimeg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@WilliamBergamin Awesome changes to see 🧱 🔍

I'm approving now with solid confidences but do hope some of the changes following are interesting! Nothing is blocking 🔨 FWIW! Comments of discussion include:

  • Making this command unhidden: This command is nice to surface next release!
  • Wording around Block Kit Builder: Quibbles on wording for polish if it reads well?
  • Missing authentications: Are default teams for BKB alright to use?
  • Refactor of utilities and inlines: JSON and reading STDIN might be good to move to internal packages?
  • Error handling: Refactors might find kind errors wrapped between package!

🚢 Please feel free to add what seems most useful! I like the direction this goes! 🧭

Comment thread internal/slackdeps/os_mock.go Outdated
Comment on lines +58 to +59
m.On("Stdout").Return(os.Stdout)
m.On("Stdin").Return(os.Stdin)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧮 quibble: Alphabetical swap of "stdin" and "stdout" here and later in the mocks might be nice for later but no blocker!

Stdout() File

// Stdin returns the file descriptor for stdin
Stdin() File

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🌟 praise: Thanks for introducing standard input operations!

Comment thread cmd/blocks/blocks.go
Comment on lines +24 to +25
// aiAgentFunc is a package variable so it can be stubbed in tests.
var aiAgentFunc = useragent.GetAIAgent

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// aiAgentFunc is a package variable so it can be stubbed in tests.
var aiAgentFunc = useragent.GetAIAgent

🪓 thought: I'd be alright making this command visible to standard use either now or later? This command seems so useful to casual block kit building IMHO!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My aim here was to make it available only to AI tools for now since it would let us iterate on the command and introduce breaking changes until we decide to make it visible to everyone

Comment thread cmd/blocks/blocks.go Outdated
Comment thread cmd/blocks/blocks.go Outdated
Comment thread cmd/blocks/preview.go
Comment on lines +125 to +126
case flagChanged && flagValue == "-":
return readStdinBlocks(clients)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
case flagChanged && flagValue == "-":
return readStdinBlocks(clients)
case !flagChanged:
return readStdinBlocks(clients)

⚙️ question: Would an omitted --blocks flag be meaningful instead of the "-" value for gathering stdin inputs? I find the commands afterwards clean, but might not be familiar with all hopes for the command!

$ slack blocks preview < blocks.json

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this was and pattern I considered originally but it creates weird edge cases with flags, like how do we properly handle

$ slack blocks preview --team TA1B2C3 < blocks.json

Maybe if we support no flags with the following then we don't support flags with this type of pattern 🤔

Comment thread cmd/blocks/preview.go
return "", true, missingBlocksError()
}
return input, true, nil
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚂 suggestion: We can perhaps separate the input itself and mixing errors from command details with moving this function to:

internal/iostreams/reader.go

Comment thread cmd/blocks/preview.go
Comment thread cmd/blocks/preview.go Outdated
Comment on lines +193 to +200
func teamOrEnterpriseID(auth *types.SlackAuth) string {
if auth.IsEnterpriseInstall {
return auth.EnterpriseID
}
return auth.TeamID
}

func buildBlockKitBuilderURL(apiHost string, id string, blocksJSON string) (string, error) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👁️‍🗨️ suggestion: We might pass auth to this buildBlockKitBuilderURL function to avoid added variables IMHO

Comment thread cmd/blocks/preview.go Outdated
WilliamBergamin and others added 12 commits July 29, 2026 06:48
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Previously the preview command auto-scoped the Block Kit Builder URL to
the sole logged-in team, and silently ignored credential read failures
when --team was set. Now the builder is team-less unless --team is passed
explicitly, and a failure to read authentications for a --team lookup is
returned instead of swallowed.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>

@zimeg zimeg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@WilliamBergamin Thanks for landing these changes! I think it moves things in an evermore better direction and agree that we can iterate while keeping this command hidden if needed 🚢 💨

Comment thread cmd/blocks/preview.go
Comment on lines +55 to +58
{
Meaning: "Preview blocks read from a redirect and scoped to a team",
Command: "blocks preview --team T0123456 --blocks - < blocks.json",
},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 praise: Thanks for including this as an example! In ongoing iteration this might be a preferred pattern for me to use.

@WilliamBergamin
WilliamBergamin merged commit b6ea8e7 into main Jul 29, 2026
10 checks passed
@WilliamBergamin
WilliamBergamin deleted the bill-bkb-open-command branch July 29, 2026 18:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog Use on updates to be included in the release notes enhancement M-T: A feature request for new functionality semver:minor Use on pull requests to describe the release version increment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants