Contributing
slackblocks is a monorepo containing ten language-native implementations and one shared conformance contract. A feature is complete when its wire output and validation category agree in Python, TypeScript, Go, Java, C#, Ruby, Rust, PHP, Elixir, and Haskell.
Repository layout
spec/contains the normative fixtures, validation cases, limits, and principles.python/contains the Python package and its tests.typescript/contains the ESM TypeScript package and its tests.go/contains the Go v3 module, its directslack-go/slackblock integration, and its tests.java/contains the Java 17 Maven artifact, concrete fluent builders, direct Slack Java SDK model integration, and its tests.csharp/contains the .NET 8 NuGet package, its generated immutable value types, and its tests.ruby/contains the Ruby 3.3 gem, generated immutable values and RBS signatures, and its tests.rust/contains the Rust 2024 crate (MSRV 1.85), consuming builders, checked Serde integration, and independent API/conformance tooling. Rust support begins with 2.6.0.php/contains PHP 8.2+ readonly values, backed enums, Composer packaging and isolated sending examples. PHP support begins with 2.7.0.elixir/contains immutable structs, native typespecs, checked Jason integration, Erlang consumers and isolated sending examples. Elixir support begins with the Hex release in 2.8.0.haskell/contains checked immutable values, explicit field records and Aeson JSON, with a separate HTTP application. Haskell joins the coordinated release in 3.1.0.docs/contains this Docusaurus site and executable examples.
Set up the workspace
Python development uses uv:
cd python
uv sync --all-groups
JavaScript development uses the pnpm workspace from the repository root:
pnpm install --frozen-lockfile
Run the checks
From python/:
uv run pytest test/unit test/conformance test/docs
uv run ruff check slackblocks test
uv run mypy slackblocks
uv build --clear
uv run twine check --strict dist/*
From the repository root:
pnpm --filter @nicklambourne/slackblocks typecheck
pnpm --filter @nicklambourne/slackblocks test
pnpm --filter @nicklambourne/slackblocks check:package
NODE_OPTIONS=--max-old-space-size=8192 pnpm --filter @slackblocks/docs build
From go/:
go test -race -cover ./...
go vet ./...
From java/:
./mvnw clean verify
On JDK 21 or newer the Java build also runs google-java-format, Error Prone, and NullAway; on JDK 17 it compiles and tests only. The generated Java model comes from spec/model.json, the language-neutral model also consumed by the C#, Ruby, Rust, PHP, Elixir, and Go generators. After changing that file or spec/limits.json, regenerate and format the sources from the repository root:
python3 java/generator/generate_models.py --check-go
(cd java && ./mvnw spotless:apply)
--check-go fails if the Java model and the Go builder registry expose different fields. The Go generator (go generate ./... in go/) reads the same spec/model.json and spec/limits.json to write the doc comments on every generated builder method, so a description, limit, or Slack link edited there reaches the Go and Java references together.
From csharp/, with the .NET 8 SDK or newer:
dotnet test
dotnet format --verify-no-changes
The build treats warnings as errors with the .NET analyzers enabled, and the tests compile and run every C# documentation snippet. The C# value types are generated from the same spec/model.json. After changing it, regenerate them from the repository root; CI fails when the checked-in output differs:
python3 csharp/generator/generate_models.py
From ruby/, with Ruby 3.3 or newer, Python, and Bundler:
bundle install
python3 generator/generate_models.py --check
bundle exec ruby -I test -e 'Dir[File.expand_path("test/*_test.rb")].sort.each { |path| require path }'
bundle exec standardrb
bundle exec ruby bin/coverage
bundle exec steep check --group=lib --group=consumer
Ruby's model generator also writes limits, vocabulary, RBS signatures, and API reference metadata. Run it without --check after changing spec/model.json, spec/limits.json, or spec/vocabulary.json; CI verifies the checked-in output and tests the installed gem.
From the repository root, with Rust 1.85.0 and stable, rustfmt, Clippy and Python 3.11+:
export RUSTFMT="$(rustup which --toolchain 1.85.0 rustfmt)"
python3 rust/generator/generate.py --check
rust/bin/check
python3 rust/bin/check-package.py --toolchain 1.85.0 --all-consumers
python3 rust/bin/check-package.py --toolchain stable
rustup component add llvm-tools-preview --toolchain stable
cargo +stable install cargo-llvm-cov --version 0.9.1 --locked
python3 rust/bin/check-coverage.py
Rust generation reads the shared model directly; contextual rules and helpers remain handwritten. Conformance tooling is a separate unpublished workspace package. Package checks unpack the actual Cargo archive, run its tests, and compile fresh consumers against the extracted artifact. Syn independently audits public exports/signatures and supplies native API comments to the site renderer. Guide checks compile and execute the exact README/MDX code and compare each block example with the documented JSON. Rust CI uses GitHub-provided runners.
From the repository root, with 64-bit PHP 8.2+, Composer 2 and Python 3:
(cd php && composer install && composer test && composer analyse && composer format:check)
python3 php/generator/generate.py --check
python3 php/generator/test_generate.py
python3 php/bin/check-types.py
python3 php/bin/check-package.py
(cd php/integrations && composer install && composer test)
python3 php/bin/check-integration-resolution.py lowest
python3 php/bin/check-integration-resolution.py latest
python3 docs/scripts/check_php_guide_snippets.py
PHP uses PSR-4 readonly classes, camelCase named constructors, backed enums and PHPDoc collection types. Reflection independently audits every public type and signature; every producer maps to a shared capability. The 95% handwritten line coverage gate uses Xdebug and reports generated coverage separately. See the PHP workflow for its exact coverage command. Actual Composer ZIP consumers run on GitHub-provided Linux, macOS and Windows runners. Sending adapters are examples in their own Composer project and never become core dependencies.
From elixir/, with Elixir 1.16.3, compatible OTP 26.2+, Python 3.11+,
Ruff 0.15.12, Hex 2.5.1 and Rebar3 3.27.1:
mix deps.get
mix run tools/check.exs
The full checker verifies deterministic generation, formatting, native/fixture
contracts, public exports, properties, doctests, Credo, Dialyzer and a 90%
handwritten coverage gate. It also checks actual Hex archives in independent
Mix and Rebar3 consumers, a relocated OTP release with consolidated protocols,
and isolated HTTP integrations in locked/lower/latest dependency profiles.
Use mix run tools/check.exs --profile native for another supported compiler;
format generated output only with the pinned compiler. Site examples and API
rendering are checked by the documentation build.
From haskell/, use GHC 9.6.7, Cabal 3.16.1.0, Python 3, Ormolu 0.7.7.0 and HLint 3.8:
cabal update
python bin/check
The comprehensive local command runs deterministic generation, formatting, lint,
all native/fixture suites, compile-fail controls, independent GHC export discovery,
95% handwritten expression coverage, separately resolved lower/locked/latest
core and HTTP dependencies, and actual extracted source archives with two fresh
consumers. CI covers GHC 9.6–9.14; endpoint matrices run Linux/macOS/Windows.
python3 docs/scripts/check_haskell_guide_snippets.py from the repository root
executes literal README and MDX examples. The site builds native GHC signatures
and compiled Haddock comments and verifies every public reference anchor.
Cabal is not in Dependabot's supported ecosystems.
Review Hackage release notes when updating Haskell dependencies, retain the
minimum compiler and declared lower bounds, and run both core and HTTP
lower/locked/latest profiles. Refresh the corresponding committed resolution
receipts in haskell/dependencies/ only after those checks pass. The existing
GitHub Actions updater still covers Haskell workflow actions.
All ten conformance suites exercise every ID in spec/manifest.json. Released
conformance/skiplist.txt files must be empty, and the harnesses assert this. A
temporary transition gap remains incomplete work; it is not an exception to
feature parity or permission to release.
Add or change a Block Kit feature
Follow Adding New Block Kit Features for the complete workflow: checking Slack's release/reference/validator evidence, updating the shared model and registries, implementing all native APIs, regenerating sources, testing boundaries and parent contexts, and updating docs and release metadata. The guide includes the per-language file map, generator commands, a worked example and a PR evidence checklist.
Serialized JSON and validation categories are shared; public naming and construction style remain native to each language.
Add a language implementation
Follow Adding a language implementation from the native API design checkpoint through conformance, helper and quality parity, standalone packaging, CI, documentation and publishing. It includes the repository integration map, staged acceptance gates and a completion checklist.
Documentation
Run the local site from the repository root:
pnpm --filter @slackblocks/docs start
Narrative pages live in docs/docs/. API pages for all ten languages are generated during every docs build. Java pages come from the javadoc tool, through docs/scripts/java-reference/ReferenceDoclet.java, so building the site needs JDK 17 or newer on PATH or in JAVA_HOME. C# pages come from the compiled library's XML documentation, through docs/scripts/csharp-reference/, so building the site also needs the .NET 8 SDK or newer. Ruby pages use model-derived metadata through docs/scripts/generate_ruby_reference.mjs. Rust pages use the actual public Syn syntax tree and rustdoc comments through docs/scripts/generate_rust_reference.mjs; install stable Rust plus Rust 1.85.0 for MSRV checks and pinned formatting. PHP pages reflect the actual public signatures, defaults and PHPDoc using docs/scripts/php_reference_adapter.php; install PHP 8.2+ and run composer install in php/. Elixir pages use compiled Code.fetch_docs and Code.Typespec metadata through docs/scripts/generate_elixir_reference.mjs; install Elixir 1.16.3 with compatible OTP 26.2+ and run mix deps.get in elixir/. Elixir has its own native renderer. Haskell uses docs/scripts/generate_haskell_reference.py with GHC 9.6.7 and Cabal 3.16.1.0; its signatures and comments come from GHC/Haddock, checked against an independent public export inventory. The other five render through docs/scripts/reference_pages.mjs. Reusable examples live under the matching directory in docs/examples/; tests execute them and compare their output with docs/examples/section_hello.json.
The complete production build includes current references and frozen releases.
Allow an 8 GiB Node heap with NODE_OPTIONS=--max-old-space-size=8192, as CI does;
Node's default heap can run out during client compilation. Leave additional
memory available for the native tools and operating system.
Use LanguageContent with Python, TypeScript, Go, Java, CSharp, Ruby, Rust, PHP, Elixir, and Haskell children for language-specific prose or examples. The site-level selector controls which child is shown.
Pull requests
Keep changes scoped, add a regression test for bug fixes, and include fixture changes whenever wire behavior changes. All local checks relevant to the changed package should pass before review.