Norq

Slack Channel Conformance Spec

Conformance tests for the Norq Slack compiler. The compiler takes Markdown input and produces Slack Block Kit JSON — a JSON array of block objects.

Each example block specifies input markdown and one or more expected output fragments. A fragment is a substring that must appear in the serialized Block Kit JSON array string. The test runner compiles each input with serde_json::Value::Null as data and checks that every fragment appears in serde_json::to_string(&out.blocks).

Format:

  • Opening fence: 32 backticks followed by example channel=slack
  • Input markdown (first section, before the first . separator)
  • . separator line
  • One or more expected fragments, each separated by . lines
  • Closing fence: 32 backticks

Paragraphs

Basic paragraph

A plain paragraph compiles to a section block with a mrkdwn text object. The section type and mrkdwn sub-type are both required by Block Kit.

Hello from Slack.
.
"type":"section"
.
"type":"mrkdwn"
.
Hello from Slack.

Multiple paragraphs produce multiple section blocks

Each paragraph in the input compiles to its own section block. Paragraphs are not batched together (unlike the email compiler).

First paragraph.
 
Second paragraph.
.
"text":"First paragraph."
.
"text":"Second paragraph."

Headings

Markdown heading compiles to a header block

A markdown heading (#) compiles to a header block with plain_text. Slack header blocks render as large, bold text at the top of a message section.

# Build Passed
.
"type":"header"
.
"type":"plain_text"
.
"text":"Build Passed"

All heading levels (#, ##, ###, etc.) produce the same header block type — Slack Block Kit has only one header block with no level distinction.

## Subsection Title
.
"type":"header"
.
"text":"Subsection Title"

Directives

:::header compiles to a header block

The :::header directive also emits a header block with plain_text. It is equivalent to a markdown heading as far as Block Kit output is concerned.

:::header
Deployment Complete
:::
.
"type":"header"
.
"type":"plain_text"
.
"text":"Deployment Complete"

:::action compiles to an actions block with a button

The :::action directive emits an actions block. It contains a single button element with style: "primary". Only the first link found inside the directive body is extracted — additional links are silently ignored.

:::action
[View Report](https://example.com/report)
:::
.
"type":"actions"
.
"style":"primary"
.
"text":"View Report"
.
"url":"https://example.com/report"

When the directive contains multiple links, only the first link produces a button. The second link is not emitted.

:::action
[Primary](https://example.com/primary) [Secondary](https://example.com/secondary)
:::
.
"url":"https://example.com/primary"

The style field is always "primary" regardless of any variant parameters on the directive.

The :::footer directive emits a context block. Context blocks render as small text at the bottom of a message. The element inside the context block uses the mrkdwn type.

:::footer
Sent by Acme Notifications · [Unsubscribe](https://example.com/unsub)
:::
.
"type":"context"
.
"type":"mrkdwn"
.
Sent by Acme Notifications

:::callout compiles to a section with an emoji prefix

The :::callout directive emits a section block. The content is prefixed with an :information_source: emoji shortcode (rendered by Slack as ℹ️).

:::callout
Your free trial expires in 3 days.
:::
.
"type":"section"
.
":information_source: Your free trial expires in 3 days."

:::highlight compiles to a bold section

The :::highlight directive emits a section block where the entire content is wrapped in Slack bold markers (*...*).

:::highlight
Action required: verify your email address.
:::
.
"type":"section"
.
"text":"*Action required: verify your email address.*"

:::fields compiles to a section with a fields array

The :::fields directive parses lines of the form Key: Value and emits a section block with a fields array. Each field is a mrkdwn object where the key is bolded: *Key:* Value.

:::fields
Order ID: ORD-1234
Status: Shipped
:::
.
"type":"section"
.
"text":"*Order ID:* ORD-1234"
.
"text":"*Status:* Shipped"

Inline Formatting

Bold text

**bold** in the source compiles to Slack mrkdwn *bold* (single asterisks). Slack does not use double asterisks for bold.

This is **important** text.
.
*important*

Italic text

_italic_ (or *italic*) in the source compiles to Slack mrkdwn _italic_ (underscores). The mrkdwn italic marker is always underscore regardless of which Markdown italic syntax is used in the source.

This is _emphasized_ text.
.
_emphasized_

Strikethrough text

~~struck~~ in the source compiles to Slack mrkdwn ~struck~ (single tildes). Slack uses single tildes while Markdown uses double.

This feature is ~~deprecated~~ removed.
.
~deprecated~

Inline code

Inline code `code` compiles to backtick-wrapped text, which Slack renders in monospace.

Run `npm install` to start.
.
`npm install`

Markdown links [label](url) compile to Slack mrkdwn link syntax <url|label>.

Visit [our documentation](https://docs.example.com) for details.
.
<https://docs.example.com|our documentation>

Block-Level Elements

Thematic break compiles to a divider block

A thematic break (--- or ***) compiles to a {"type":"divider"} block.

Before the divider.
 
---
 
After the divider.
.
"type":"divider"

A standalone ![alt](url) in Markdown is parsed as an inline image inside a paragraph. The Slack compiler renders inline images as mrkdwn link syntax <url|alt> inside a section block. This preserves the clickability while being compatible with Block Kit's text-only section type.

![Product screenshot](https://example.com/screenshot.png)
.
"type":"section"
.
<https://example.com/screenshot.png|Product screenshot>

When alt text is empty, the image renders as <url> without the label portion.

![](https://example.com/hero.png)
.
"type":"section"
.
<https://example.com/hero.png>

:::media directive produces an image block

To render a native Slack image block (which displays the image inline), wrap the image in a :::media directive. The image block includes image_url and alt_text fields.

:::media
![Product screenshot](https://example.com/screenshot.png)
:::
.
"type":"image"
.
"image_url":"https://example.com/screenshot.png"
.
"alt_text":"Product screenshot"

Unordered list compiles to a mrkdwn section

An unordered list compiles to a single section block. Each item is prefixed with a dash (-) and the items are joined with newlines.

- First item
- Second item
- Third item
.
"type":"section"
.
- First item\n- Second item\n- Third item

Ordered list compiles to a mrkdwn section

An ordered list compiles to a single section block. Each item is prefixed with its 1-based index followed by a period.

1. Step one
2. Step two
3. Step three
.
1. Step one\n2. Step two\n3. Step three

Blockquote compiles to a mrkdwn section with > prefix

A blockquote compiles to a section block where each inner paragraph is prefixed with > in the mrkdwn text.

> This is important context.
.
"type":"section"
.
"> This is important context."

Code block compiles to a mrkdwn section with triple backticks

A fenced code block compiles to a section block. The content is wrapped in triple backtick fences in the mrkdwn string (Slack renders this as a monospace code block). Language hints are ignored — Slack does not support syntax highlighting.

```
git push origin main
```
.
"type":"section"
.
"text":"```\ngit push origin main\n```"

Expression Interpolation

Data expressions resolve to their values

Template expressions {{path}} are resolved against the data object and interpolated into the mrkdwn text. Special characters in the resolved value (&, <, >) are escaped to &amp;, &lt;, &gt; respectively to prevent Slack from mis-parsing the mrkdwn.

Your order {{order_id}} has shipped.
.
"type":"mrkdwn"
.
Your order  has shipped.

(The order_id path resolves to empty string when data is null, so only the surrounding text appears in the output.)