Norq

Push Channel Conformance Spec

Conformance tests for the Norq push notification compiler. The push compiler takes Markdown input (with optional YAML frontmatter) and produces a PushOutput with title, body, image, action_url, and platforms fields.

Each example block specifies input markdown and one or more expected output fragments (substrings that must appear in the compiled output).

Format:

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

The test runner serializes push output as <title>\n<body>. Fragments can match either the title or body field.


Title Resolution

Push notifications have a single title string. The compiler resolves it from four sources in priority order:

  1. :::header directive content (highest priority)
  2. First Markdown heading (# Heading)
  3. Frontmatter title: field
  4. Empty string (when no source is found)

This priority order means :::header always wins over a heading, and a heading always wins over frontmatter title:.

Priority 1: :::header directive

:::header content becomes the title. Any frontmatter title: field or Markdown heading is ignored.

---
title: Ignored Title
---
 
:::header
New Message
:::
 
You have a new message.
.
New Message

:::header with an expression in its content resolves the expression with the current data. With null data, unresolvable paths produce empty strings, so the static text portion is what the title contains.

:::header
Order Update
:::
 
Your package is ready for pickup.
.
Order Update

Priority 2: First Markdown heading

When no :::header is present, the first heading (any level) becomes the title. The heading text is excluded from the body.

# Account Alert
 
Your account needs attention.
.
Account Alert

The heading is skipped in the body because it was consumed as the title. Only the paragraph appears in the body.

# Package Delivered
 
Your package arrived at the door.
.
Package Delivered
.
Your package arrived at the door.

Priority 3: Frontmatter title:

When neither :::header nor a heading is present, the frontmatter title: field becomes the title.

---
title: Order Shipped
---
 
Your order is on its way.
.
Order Shipped

Priority 4: Empty title

When no :::header, heading, or frontmatter title: is present, the title is an empty string.

Just a body paragraph with no title source.
.
Just a body paragraph with no title source.

Body Text

The body is plain text assembled from all content nodes except those consumed as the title source. Inline formatting markers (bold, italic) are stripped to plain text.

Paragraphs

Each paragraph contributes its text to the body. Consecutive paragraphs are joined with \n.

Your package is on its way!
.
Your package is on its way!

Multiple paragraphs

---
title: Notification
---
 
First line of info.
 
Second line of info.
.
First line of info.
.
Second line of info.

Inline bold stripped

Inline formatting is stripped; only the plain text is kept.

Your order **#12345** has shipped.
.
Your order #12345 has shipped.

Body truncated at 200 characters

The body is truncated to approximately 200 characters. If the text exceeds 200 characters, it is cut at the last space before the limit and ... is appended.

---
title: Long Notification
---
 
This is a very long body text that goes well beyond the two hundred character limit enforced by the push notification compiler so that it will definitely be truncated with an ellipsis at the end when compiled.
.
...

Directives in Body

:::header excluded from body

:::header sets the title and is not included in the body.

:::header
Notification Title
:::
 
This is the body text.
.
This is the body text.

:::footer is silently ignored in push output. Its content does not appear in the title or body.

Your order is confirmed.
 
:::footer
You received this because you placed an order.
:::
.
Your order is confirmed.

:::action excluded from body

:::action content sets action_url but does not contribute to the body text. The link label and URL do not appear in the body.

Your package arrived.
 
:::action
[View Details](https://example.com/order/123)
:::
.
Your package arrived.

:::highlight — star prefix in body

:::highlight content is prefixed with the ⭐ star emoji (U+2B50) and included in the body.

:::highlight
Flash sale ends tonight
:::
.
⭐ Flash sale ends tonight

:::callout — warning prefix in body

:::callout content is prefixed with the ⚠ warning emoji (U+26A0) and included in the body.

:::callout
Your password expires in 3 days
:::
.
⚠ Your password expires in 3 days

Lists in Body

Unordered list

Unordered list items are prefixed with - and joined with \n.

---
title: Order Summary
---
 
- Widget A
- Widget B
.
- Widget A
.
- Widget B

Ordered list

Ordered list items are prefixed with N. (1-based).

---
title: Steps
---
 
1. Open the app
2. Tap notifications
.
1. Open the app
.
2. Tap notifications

Emoji Shortcodes

Emoji shortcodes are expanded in both the title and body. The title shortcodes are expanded in the extract_title step; body shortcodes are expanded in extract_body.

:::header
Launch :rocket:
:::
 
Deployment started.
.
Launch 🚀
Hello :wave: welcome!
.
Hello 👋 welcome!

Hero Image

:::hero sets the image field of PushOutput. The image field is not included in the <title>\n<body> test runner output, so it is tested only via unit tests in push.rs. The hero directive content does not appear in the body.


Platform Frontmatter

The frontmatter keys ios, android, and web are extracted into the platforms field of PushOutput. They do not appear in the body. Platform configuration is tested via unit tests in push.rs.


Blockquote

Blockquote paragraph children are rendered as plain text in the body. The > marker is not emitted.

---
title: Quote
---
 
> This is an important message.
.
This is an important message.