Contributing
slackblocks is a monorepo containing nine 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, and Elixir.
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 v2 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.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
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
python3 rust/bin/check-package.py --toolchain stable
python3 rust/bin/check-package.py --toolchain 1.85.0 --lower-bounds
python3 rust/bin/check-package.py --toolchain 1.85.0 --json-features
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.
All nine 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 nine 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. 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.
Use LanguageContent with Python, TypeScript, Go, Java, CSharp, Ruby, Rust, PHP, and Elixir 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.