Norq
Spec/0.1-alpha/msteams

MS Teams Channel Conformance Spec

Conformance tests for the Norq MS Teams (Adaptive Card) compiler. The compiler takes Markdown input and produces an Adaptive Card JSON object.

Each example block specifies input markdown and one or more expected output fragments. A fragment is a substring that must appear in the serialized Adaptive Card JSON. 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.card).

Format:

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

Adaptive Card Envelope

Every output is wrapped in an Adaptive Card object

All MS Teams output is a single JSON object of type AdaptiveCard. The object always includes the $schema, version, body, and actions fields. The version is 1.5.

Hello from MS Teams.
.
"type":"AdaptiveCard"
.
"$schema":"http://adaptivecards.io/schemas/adaptive-card.json"
.
"version":"1.5"
.
"body":
.
"actions":

Paragraphs

Basic paragraph compiles to a TextBlock

A plain paragraph compiles to a TextBlock element in the body array. The element always includes "wrap":true to allow line wrapping in the Teams client.

Hello from MS Teams.
.
"type":"TextBlock"
.
"text":"Hello from MS Teams."
.
"wrap":true

Multiple paragraphs produce multiple TextBlock elements

Each paragraph in the input compiles to its own TextBlock in the body array. Paragraphs are not batched.

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

Headings

Markdown heading compiles to a large bold TextBlock

A markdown heading (#) compiles to a TextBlock with "size":"Large" and "weight":"Bolder". All heading levels produce the same Large/Bolder TextBlock — Adaptive Cards have no heading hierarchy beyond size and weight.

# Deployment Alert
.
"size":"Large"
.
"weight":"Bolder"
.
"text":"Deployment Alert"

All heading levels (##, ###, etc.) compile to the same Large/Bolder TextBlock.

## Section Header
.
"size":"Large"
.
"weight":"Bolder"
.
"text":"Section Header"

Directives

:::header compiles to an ExtraLarge bold TextBlock

The :::header directive emits a TextBlock with "size":"ExtraLarge" and "weight":"Bolder". This is larger than a markdown heading, intended for the top-level notification title.

:::header
Deployment Complete
:::
.
"size":"ExtraLarge"
.
"weight":"Bolder"
.
"text":"Deployment Complete"

The :::footer directive emits a TextBlock with "isSubtle":true and "size":"Small". Teams renders subtle text in a muted color, appropriate for footers and fine print.

:::footer
Sent by Acme Notifications
:::
.
"isSubtle":true
.
"size":"Small"
.
"text":"Sent by Acme Notifications"

:::action compiles to Action.OpenUrl in the actions array

The :::action directive extracts all links from its body and emits one Action.OpenUrl object per link into the card's top-level actions array (not the body). Each action has type, title, and url fields.

:::action
[Open Dashboard](https://example.com/dashboard)
:::
.
"type":"Action.OpenUrl"
.
"title":"Open Dashboard"
.
"url":"https://example.com/dashboard"

Unlike Slack's :::action (which only extracts the first link), the Teams :::action emits an Action.OpenUrl for every link in the directive body.

:::action
[View Order](https://example.com/order) [Track Shipment](https://example.com/track)
:::
.
"title":"View Order"
.
"title":"Track Shipment"

:::callout compiles to an accent Container

The :::callout directive emits a Container element with "style":"accent". The content is wrapped in a TextBlock inside the container's items array.

:::callout
Your free trial expires in 3 days.
:::
.
"type":"Container"
.
"style":"accent"
.
"text":"Your free trial expires in 3 days."

:::highlight compiles to an attention Container

The :::highlight directive emits a Container element with "style":"attention". The content is wrapped in a TextBlock inside the container's items array.

:::highlight
Action required: verify your email address.
:::
.
"type":"Container"
.
"style":"attention"
.
"text":"Action required: verify your email address."

:::fields compiles to a FactSet

The :::fields directive parses lines of the form Key: Value and emits a FactSet element. Each fact has a title (the key) and value (the value after the colon).

:::fields
Order ID: ORD-1234
Status: Shipped
:::
.
"type":"FactSet"
.
"title":"Order ID"
.
"value":"ORD-1234"
.
"title":"Status"
.
"value":"Shipped"

Inline Formatting

Bold text

**bold** in the source compiles to **bold** (double asterisks) in the Adaptive Card text string. Teams renders double-asterisk markdown as bold.

This is **critical** information.
.
**critical**

Italic text

_italic_ (or *italic*) in the source compiles to _italic_ (single underscores) in the Adaptive Card text string.

This is _important_ context.
.
_important_

Strikethrough text

Adaptive Cards do not support strikethrough formatting. ~~struck~~ in the source is rendered as plain text with the strikethrough markers removed.

This is ~~deprecated~~ removed.
.
"text":"This is deprecated removed."

Markdown links [label](url) compile to Adaptive Card link syntax [label](url), which Teams renders as clickable hyperlinks inside a TextBlock.

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

Block-Level Elements

Thematic break compiles to a separator TextBlock

A thematic break (---) compiles to a TextBlock with "separator":true. The separator flag tells Teams to draw a horizontal rule above the element.

Before the break.
 
---
 
After the break.
.
"separator":true

Image compiles to an Image element

A standalone ![alt](url) in Markdown — when parsed as a block-level image — compiles to an Image element with url and optionally altText.

:::media
![Product screenshot](https://example.com/screenshot.png)
:::
.
"type":"Image"
.
"url":"https://example.com/screenshot.png"
.
"altText":"Product screenshot"

Unordered list compiles to a TextBlock with dash-prefixed lines

An unordered list compiles to a single TextBlock. Each item is prefixed with - and items are joined with newlines.

- First item
- Second item
.
"type":"TextBlock"
.
- First item

Ordered list compiles to a TextBlock with numbered lines

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

1. Step one
2. Step two
.
"type":"TextBlock"
.
1. Step one

Blockquote compiles to an emphasis Container

A blockquote compiles to a Container element with "style":"emphasis". The content is wrapped in a TextBlock inside the container's items array.

> This is important context.
.
"type":"Container"
.
"style":"emphasis"
.
"text":"This is important context."

Code block compiles to a monospace TextBlock

A fenced code block compiles to a TextBlock with "fontType":"Monospace".

```
git push origin main
```
.
"fontType":"Monospace"
.
"text":"git push origin main"

Expression Interpolation

Data expressions resolve to their values

Template expressions {{path}} are resolved against the data object and interpolated into the TextBlock text. When data is null, unresolved expressions produce an empty string.

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