Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ jobs:
- name: Verify immutable historical snapshots
run: pnpm --filter @slackblocks/docs check:legacy-snapshots

- name: Verify every released version has a docs snapshot
run: pnpm --filter @slackblocks/docs check:release-snapshots

- name: Verify documentation generation is clean
run: >-
git diff --exit-code --
Expand Down
32 changes: 27 additions & 5 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,44 @@ These must be in place before the workflows can publish:

## Coordinated release procedure

1. Bump the version in **both** manifests on one branch:
The docs site serves the current package version live (`lastVersion:
"current"`) and lists every earlier release as a frozen snapshot in the version
dropdown. Each release therefore has to freeze the version it is moving *off*
before the new version takes over as current — otherwise that version vanishes
from the dropdown. CI enforces this: the `check:release-snapshots` guard fails
if any released `python/v*` tag other than the current package version is
missing from `docs/versions.json` (or the legacy manifest).

1. On one branch, freeze the **outgoing** docs version — the value currently in
`python/pyproject.toml`, before you bump it — so it survives as a dropdown
entry once the new version becomes current:

```sh
pnpm --filter @slackblocks/docs generate
pnpm --filter @slackblocks/docs exec docusaurus docs:version <outgoing>
```

`generate` populates the gitignored API reference so the snapshot matches a
real build; `docs:version` then copies `docs/docs` into
`docs/versioned_docs/version-<outgoing>` and prepends `<outgoing>` to
`docs/versions.json`. (The very first monorepo release is the exception: its
outgoing `2.0.0` is a legacy version already frozen in the manifest.)
2. Bump the version in **both** manifests on the same branch:
- `python/pyproject.toml` (`project.version`)
- `typescript/package.json` (`version`)
2. Add a `## [X.Y.Z] — YYYY-MM-DD` section to both `python/CHANGELOG.md` and
3. Add a `## [X.Y.Z] — YYYY-MM-DD` section to both `python/CHANGELOG.md` and
`typescript/CHANGELOG.md`. The publish workflows extract this section for
the GitHub Release notes.
3. Merge to `master` and wait for CI to pass.
4. Tag and push, Python first (either order works; the guards only require
4. Merge to `master` and wait for CI to pass.
5. Tag and push, Python first (either order works; the guards only require
the two manifests to agree):

```sh
git tag python/vX.Y.Z && git push origin python/vX.Y.Z
git tag ts/vX.Y.Z && git push origin ts/vX.Y.Z
```

5. Each workflow publishes to its registry and then creates a GitHub Release
6. Each workflow publishes to its registry and then creates a GitHub Release
for its tag with the changelog section as notes.

## Partial-failure recovery
Expand Down
1 change: 1 addition & 0 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"generate:python": "UV_CACHE_DIR=../.uv-cache uv run --project ../python --group docs python scripts/generate_python_reference.py",
"generate:legacy": "UV_CACHE_DIR=../.uv-cache uv run --project ../python --group docs python scripts/port_legacy_docs.py generate",
"check:legacy-snapshots": "UV_CACHE_DIR=../.uv-cache uv run --project ../python --group docs python scripts/port_legacy_docs.py check",
"check:release-snapshots": "node scripts/check_release_snapshots.mjs",
"check:typescript-api": "node scripts/check_typescript_api_docs.mjs",
"check:typescript-api-rendering": "node scripts/check_typescript_api_rendering.mjs",
"check:legacy-contract": "node scripts/check_legacy_docs.mjs",
Expand Down
97 changes: 97 additions & 0 deletions docs/scripts/check_release_snapshots.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/usr/bin/env node

// Every released version must be reachable from the docs site: the current
// package version is served live (lastVersion: "current"), and every earlier
// release must have a frozen snapshot in the version dropdown. That snapshot is
// recorded either in docs/versions.json (Docusaurus-era releases, cut with
// `docusaurus docs:version`) or in docs/legacy/manifest.json (the pre-monorepo
// versions the legacy porter freezes). A release that ships without cutting its
// docs version leaves a gap — this guard fails so the gap is caught in CI
// rather than discovered by a reader.

import { execFileSync } from "node:child_process";
import { readFileSync } from "node:fs";
import { join, resolve } from "node:path";
import { fileURLToPath } from "node:url";

const docsRoot = resolve(fileURLToPath(new URL("..", import.meta.url)));

function currentVersion() {
const pyproject = readFileSync(
join(docsRoot, "..", "python", "pyproject.toml"),
"utf8",
);
const match = pyproject.match(/^version = "([^"]+)"$/m);
if (!match) throw new Error("Could not read the Python package version");
return match[1];
}

function snapshottedVersions() {
const versions = new Set(
JSON.parse(readFileSync(join(docsRoot, "versions.json"), "utf8")),
);
const manifest = JSON.parse(
readFileSync(join(docsRoot, "legacy", "manifest.json"), "utf8"),
);
for (const entry of manifest.versions) versions.add(entry.version);
return versions;
}

// Python and TypeScript are released together under the same version number, so
// the `python/v*` tags are the authoritative list of released versions. Plain
// `v*` tags belong to the pre-monorepo 1.x/2.0 line and are covered by the
// legacy manifest instead.
function releasedVersions() {
let output;
try {
output = execFileSync("git", ["tag", "--list", "python/v*"], {
cwd: docsRoot,
encoding: "utf8",
});
} catch (error) {
console.warn(
`Could not enumerate release tags (${error.message}); skipping the ` +
"released-snapshot guard. This should only happen outside CI, where " +
"docs.yml checks out with fetch-depth: 0.",
);
return null;
}
return [
...new Set(
output
.split("\n")
.map((line) => line.trim())
.filter(Boolean)
.map((tag) => tag.replace(/^python\/v/, "")),
),
];
}

const current = currentVersion();
const released = releasedVersions();

if (released === null) {
process.exit(0);
}

const snapshotted = snapshottedVersions();
const missing = released
.filter((version) => version !== current)
.filter((version) => !snapshotted.has(version))
.sort();

if (missing.length > 0) {
console.error(
`Released version(s) without a frozen docs snapshot: ${missing.join(", ")}.\n` +
"Freeze each one with `docusaurus docs:version <version>` (see RELEASING.md) " +
"so it appears in the version dropdown, or confirm it belongs in the legacy " +
"manifest.",
);
process.exitCode = 1;
} else {
const checked = released.filter((version) => version !== current).length;
console.log(
`Every released version has a docs snapshot ` +
`(${checked} checked; ${current} is the live current version).`,
);
}
79 changes: 79 additions & 0 deletions docs/versioned_docs/version-2.1.0/contributing.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
sidebar_position: 5
---

# Contributing

`slackblocks` is a monorepo containing two handwritten implementations and one shared conformance contract. A feature is complete when its wire output and validation category agree in Python and TypeScript.

## 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.
- `docs/` contains this Docusaurus site and executable examples.

## Set up the workspace

Python development uses [uv](https://docs.astral.sh/uv/):

```bash
cd python
uv sync --all-groups
```

JavaScript development uses the pnpm workspace from the repository root:

```bash
pnpm install --frozen-lockfile
```

## Run the checks

From `python/`:

```bash
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:

```bash
pnpm --filter @nicklambourne/slackblocks typecheck
pnpm --filter @nicklambourne/slackblocks test
pnpm --filter @nicklambourne/slackblocks check:package
pnpm --filter @slackblocks/docs build
```

The Python and TypeScript conformance suites both exercise every ID in `spec/manifest.json`. A checked-in `conformance/skiplist.txt` may document an intentional language gap, but unknown failures and stale skips fail the suite.

## Add or change a Block Kit feature

1. Add or update canonical JSON in `spec/fixtures/valid/`, register it in `spec/manifest.json`, and link the exact official Slack reference used to validate it.
2. Map every new JSON-producing capability to at least one valid fixture in `spec/coverage.json`.
3. Add invalid behavior to `spec/fixtures/invalid/manifest.json` when the feature introduces a validation rule. Every scalar leaf in `spec/limits.json` must have a matching invalid case.
4. Update `spec/limits.json` for shared scalar constraints.
5. Implement the feature idiomatically in both packages, or record a reason in the affected skip list.
6. Add language-native tests and update the relevant guide or executable example.

When introducing a cross-language API, serialized JSON and validation outcomes are shared; public naming and construction style are language-native.

## Documentation

Run the local site from the repository root:

```bash
pnpm --filter @slackblocks/docs start
```

Narrative pages live in `docs/docs/`. Python and TypeScript API pages are generated during every docs build. Reusable examples live one-per-file under `docs/examples/python/` and `docs/examples/typescript/`; tests execute them and compare their output with `docs/examples/section_hello.json`.

Use `LanguageContent` with `Python` and `TypeScript` 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.
135 changes: 135 additions & 0 deletions docs/versioned_docs/version-2.1.0/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
---
sidebar_label: Welcome
sidebar_position: 1
---

import LanguageContent, { Python, TypeScript } from '@site/src/components/LanguageContent';

# Welcome to `slackblocks`!

<p align="center">
<img width="30%" src="/slackblocks/img/sb.png" alt="slackblocks logo" />
</p>

`slackblocks` builds Slack messages with the [Block Kit API](https://api.slack.com/block-kit) in Python and TypeScript — without writing JSON by hand.

It exists because Block Kit JSON is verbose, easy to get subtly wrong, and unpleasant to maintain in source control. `slackblocks` gives you:

- **Typed, idiomatic APIs** for Python and TypeScript.
- **Validation as you build** — character limits, required fields, mutually-exclusive options, and element-type restrictions are enforced at construction time, so you find out *before* hitting Slack's API.
- **Drop-in compatibility** with the official Slack SDKs and their plain JSON payloads.
- **Zero runtime dependencies.**

## Quick Start

Ready to build your first message? The [Quick Start guide](quick-start) takes you from installation to a validated Slack payload using the language selected in the top navigation.

## Components

The [Slack Block Kit API](https://api.slack.com/block-kit) defines several resource types (all defined in JSON) that work together to build block-based messages.

<LanguageContent>
<Python>

The Python package mirrors that hierarchy with classes.

### Objects

[Objects](reference/python/objects) (e.g. [`Text`](reference/python/objects#text), [`Option`](reference/python/objects#option), [`Confirm`](reference/python/objects#confirm)) are the lowest-level primitives — small composable pieces that populate [Elements](reference/python/elements) and [Blocks](reference/python/blocks).

For convenience, [`PlainText`](reference/python/objects#plaintext) and [`Markdown`](reference/python/objects#markdown) are thin subclasses of `Text` that you can use anywhere a `Text` is expected — `PlainText("Hi", emoji=True)` reads more naturally than `Text("Hi", type_=TextType.PLAINTEXT, emoji=True)`.

### Elements

[Elements](reference/python/elements) are typically interactive UI controls that go *inside* blocks. The [`CheckboxGroup`](reference/python/elements#checkboxgroup) element, for instance, takes one or more [`Option`](reference/python/objects#option) items and presents a checkbox menu.

### Blocks

[Blocks](reference/python/blocks) are the core visual unit of a message. Different block classes produce different UI:

- [`SectionBlock`](reference/python/blocks#sectionblock) — a chunk of markdown text, optionally with an accessory element.
- [`HeaderBlock`](reference/python/blocks#headerblock) — a large bold title.
- [`DividerBlock`](reference/python/blocks#dividerblock) — a visual separator (like an HTML `<hr>`).
- [`MarkdownBlock`](reference/python/blocks#markdownblock) — GitHub-flavored Markdown (Slack 2024+; richer formatting than `mrkdwn`).
- [`RichTextBlock`](reference/python/blocks#richtextblock) — formatted text with inline styling, lists, code blocks, and quotes.
- [`ActionsBlock`](reference/python/blocks#actionsblock) — a row of interactive elements like buttons or menus.
- [`AlertBlock`](reference/python/blocks#alertblock) — a severity-labelled notice for a modal.
- [`CardBlock`](reference/python/blocks#cardblock) and [`CarouselBlock`](reference/python/blocks#carouselblock) — compact linked content, individually or in a scrolling collection.
- [`ContainerBlock`](reference/python/blocks#containerblock) — a titled group of related child blocks.
- [`ContextActionsBlock`](reference/python/blocks#contextactionsblock) — feedback and icon controls beside contextual content.
- [`DataTableBlock`](reference/python/blocks#datatableblock) and [`DataVisualizationBlock`](reference/python/blocks#datavisualizationblock) — structured data as a table or chart.
- [`PlanBlock`](reference/python/blocks#planblock) and [`TaskCardBlock`](reference/python/blocks#taskcardblock) — plans, task state, rich output, and sources.
- [`VideoBlock`](reference/python/blocks#videoblock) — embed a video from a Slack-supported provider.
- [`ImageBlock`](reference/python/blocks#imageblock), [`ContextBlock`](reference/python/blocks#contextblock), [`InputBlock`](reference/python/blocks#inputblock), [`FileBlock`](reference/python/blocks#fileblock), [`TableBlock`](reference/python/blocks#tableblock).

See [Using Blocks](usage/using_blocks) for examples of all block types side-by-side with their JSON output and Slack rendering.

### Messages

[Messages](reference/python/messages) are a convenience wrapper around blocks that can be unpacked directly into the Slack SDK's `chat_postMessage` (and friends).

- [`Message`](reference/python/messages#message) — a normal channel message.
- [`WebhookMessage`](reference/python/messages#webhookmessage) — for incoming webhooks.
- [`MessageResponse`](reference/python/messages#messageresponse) — replies to slash commands and interactions.

### Views

[Views](reference/python/views) are an alternative usage of blocks that build custom UI surfaces in Slack — modal dialogs and the App Home tab — typically used by interactive Slack apps.

### Utilities

- [`block_kit_builder_url(payload, team_id=None)`](reference/python/builder#block_kit_builder_url) — turn any block, list of blocks, message, or view into a [Block Kit Builder](https://app.slack.com/block-kit-builder) URL for browser-based preview.
- [`Block.from_dict(data)`](reference/python/blocks#block) — parse a Slack-shaped block payload back into a `slackblocks` object. Per-class `from_dict` parsers are also available on every composition object.

</Python>
<TypeScript>

The TypeScript package exposes data-first factories that return typed, Slack-shaped objects.

### Composition objects

Factories such as [`plainText`](reference/typescript/objects#plaintext), [`mrkdwn`](reference/typescript/objects#mrkdwn), and [`option`](reference/typescript/objects#option) create the small values used throughout Block Kit payloads.

### Elements

Interactive controls are built with factories such as [`button`](reference/typescript/elements#button), [`checkboxes`](reference/typescript/elements#checkboxes), [`staticSelect`](reference/typescript/elements#staticselect), and [`datePicker`](reference/typescript/elements#datepicker).

### Blocks

Every block factory follows the same `<kind>Block` naming pattern. Alongside the familiar [`sectionBlock`](reference/typescript/blocks#sectionblock), [`actionsBlock`](reference/typescript/blocks#actionsblock), and [`videoBlock`](reference/typescript/blocks#videoblock), the API includes [`alertBlock`](reference/typescript/blocks#alertblock), [`cardBlock`](reference/typescript/blocks#cardblock), [`carouselBlock`](reference/typescript/blocks#carouselblock), [`containerBlock`](reference/typescript/blocks#containerblock), [`contextActionsBlock`](reference/typescript/blocks#contextactionsblock), [`dataTableBlock`](reference/typescript/blocks#datatableblock), [`dataVisualizationBlock`](reference/typescript/blocks#datavisualizationblock), [`planBlock`](reference/typescript/blocks#planblock), and [`taskCardBlock`](reference/typescript/blocks#taskcardblock).

### Messages and views

Use [`message`](reference/typescript/messages#message), [`webhookMessage`](reference/typescript/messages#webhookmessage), and [`messageResponse`](reference/typescript/messages#messageresponse) for message payloads. Use [`modal`](reference/typescript/views#modal) and [`homeTab`](reference/typescript/views#hometab) for Slack views.

### Utilities

- [`blockKitBuilderUrl`](reference/typescript/utilities#blockkitbuilderurl) creates a browser preview URL.
- [`validate`](reference/typescript/utilities#validate) and [`assertValid`](reference/typescript/utilities#assertvalid) validate existing Slack-shaped data.

</TypeScript>
</LanguageContent>

## Guides

<LanguageContent>
<Python>

- [Installation](usage/installation)
- [Using Blocks](usage/using_blocks)
- [Sending Messages](usage/sending_messages)
- [Recipe Book](usage/cookbook) — complete end-to-end recipes for common message patterns.
- [Troubleshooting & FAQ](usage/troubleshooting)
- [Migrating from 1.x to 2.x](usage/migration) — upgrade guide for `1.x` users.
- [Contributing](contributing) — developing and contributing to `slackblocks` itself.

</Python>
<TypeScript>

- [Installation](usage/installation)
- [Sending Messages](usage/sending_messages)
- [Recipe Book](usage/cookbook) — complete end-to-end recipes for common message patterns.
- [Contributing](contributing) — developing and contributing to `slackblocks` itself.

</TypeScript>
</LanguageContent>
Loading