Skip to the document

Documents

A Plicine document is a plain .md file: CommonMark, with an optional block of YAML frontmatter at the top. Nothing about it is proprietary. Open one in any text editor, in GitHub, in Obsidian, and you'll see readable text.

Frontmatter

A document can start with a ----delimited block of YAML:

---
title: Lorem ipsum proposal
theme: acme
client: Lorem Ipsum Ltd
page: A4
---

Plicine reads a handful of these keys itself: title, theme, page (A4 or Letter), layout (infinite or paginated; see Writing and editing for what the two look like), blocks (pins, see How blocks work), cover (true adds the theme's cover page) and link. Anything else you put in frontmatter, client, reference, date, whatever a theme's templates want, is simply passed through and available to those templates. The exact shape is in vault.json and frontmatter.

If the title key is missing, Plicine uses the document's first level-1 heading instead, and falls back to the filename if there's no heading either. An INDEX.md is its folder's page, so it falls back to the folder's name, and the one at the vault root to the vault's title (Vaults).

The sidebar's Rename writes the new title in whichever of those places the title came from: the title key, or else the first level-1 heading, whose text becomes what was typed. A file named after its title follows it. "Named after" sets aside capitals, accents, hyphens, underscores and punctuation, so getting-started.md is named after "Getting started" and cli.md isn't named after "CLI reference". The new file name is written the way the old one was: getting-started.md retitled "Quick start: part 1" becomes quick-start-part-1.md, meeting_notes.md stays in snake case, and Plan.md retitled "Budget plan" becomes Budget plan.md, with anything a file name can't hold (\ / : * ? " < > |) left out. The file stays put when that name is taken, when it was called something else to begin with, and when it's an INDEX.md. A document with neither a title key nor a level-1 heading is titled by its file name, so there Rename renames the file. Rename file only ever renames the file. Either way, a file that gets a new name is a move, so the links to it are rewritten (Links between documents).

Block fences

A fenced code block whose name matches an installed block is rendered by that block, and its body is read as YAML rather than shown as code:

```quote-table
currency: GBP
vat: 0.20
rows:
  - { item: Discovery workshop, qty: 1, unit: 1200 }
```

The YAML body becomes the block's props. This is the whole contract: the document holds data, in a shape the reader can see and edit directly, and the block package holds the logic that turns that data into a table, a diagram or whatever else it draws. See How blocks work for what a block is made of.

Language fences stay code

A fence only turns into a block when its name matches something installed. Otherwise, if the name is a recognised programming language or file format, ts, python, json, bash and so on, it renders as an ordinary code sample, exactly as it would anywhere else:

```ts
const total = rows.reduce((sum, row) => sum + row.qty * row.unit, 0);
```

This is why a fence named ts is never treated as executable: ts is one of the most common fences in ordinary markdown, including the documentation you're reading, and turning every code sample into a running block would be a trap. Plicine's own inline block fence is deliberately named ts-block instead, so it can't collide with a plain code sample.

Unknown names get a warning

If a fence's name isn't a known code language and doesn't match any installed block, it still renders (as plain code, so nothing is lost), but the app marks it with an "Unknown block" badge and a warning explaining that no installed block matches that name. This is usually a typo, a block that hasn't been packed into the vault yet, or a document written against a block your vault doesn't have installed.

Callouts

A quote whose first line is [!type] is a callout: a coloured box with an icon and a title, the same syntax Obsidian uses and GitHub's alerts share. The rest of the line is the title (inline markdown, optional; the type's name when left out), and the rest of the quote is the body, which can hold anything a quote can: paragraphs, lists, code, another callout.

> [!warning] Check the dates
> The **holiday block** runs from 15 December to 1 January.
> - No site visits
> - Support by email only

Check the dates

The holiday block runs from 15 December to 1 January.

  • No site visits
  • Support by email only

The types are Obsidian's: note, abstract, info, todo, tip, success, question, warning, failure, danger, bug, example and quote, with the same aliases (tldr, hint, caution, error and so on) and a note style for anything unknown. [!type]+ makes the callout collapsible and open, [!type]- collapsible and closed; the box then becomes a <details> element, and print shows it as written. In the app, clicking a folded callout's title opens or closes it, and clicking its body opens the editor. A quote that doesn't start with [!type] is an ordinary quote.

The callout core block is the same thing as YAML, for the edit form and the add-block picker; see Core blocks. Both forms go through the same rule, so they look identical.

An ordinary markdown link to another .md file in the vault, [pricing](../pricing.md), works as a normal relative link. Inside the app it navigates to that document without a page reload; on GitHub or in a plain editor it's just a relative link to another file in the repository, and behaves exactly as you'd expect there too. Links to anything else (a URL, an anchor, a non-markdown file) are left alone. It's the same inside a block's markdown, a card or a columns item say: the link is relative to the document the block is in.

[[Pricing]] works too, the way it does in Obsidian: name the file, not the path. Both forms render the same link, and both navigate inside the app. In a PDF a link to another document is printed as text, since the PDF holds only the one document; Exporting from the command line has the rule.

See [[Pricing]] for the day rates, or [[guides/Writing a block|how to write a block]].
Jump to [[Pricing#Retainers]], or to [[#Images]] further down this page.

What goes in the brackets is a file name, a partial path or a full one, with .md optional. Plicine looks for it in the linking document's own folder first, then from the vault root, then anywhere in the vault: the nearest match wins, and case doesn't matter. After a | comes the text to show; a # names a heading, which reads as "Pricing > Retainers" and lands on that heading. Inside a table the pipe needs escaping, [[Pricing\|prices]], since an unescaped one would end the cell.

Reading this in the app, here are both kinds side by side: vaults resolves to the vaults page, while a page nobody has written doesn't.

A link that matches no file in the vault is shown in grey with a dotted underline rather than as a working link, so a typo or a document you haven't written yet is visible while you read. plicine validate reports those as warnings, so a vault full of links can be checked in CI. Unlike Obsidian, clicking one doesn't create the document.

Move a document or a folder in the app's sidebar and the links to it are rewritten to reach it where it is now (Finding your way around). That covers both kinds, [[Pricing]] and [rates](guides/pricing.md), a [label]: path.md definition, and either of them in the markdown inside a block. Only the part of the link that names the file changes: its text, its #heading and its |alias stay as you wrote them, a link that still finds the document, as [[Pricing]] does when only the folder changed, isn't touched at all, and the file keeps its line endings. The document that moved is looked after too, since a link or a picture written from its old folder (../assets/chart.png, say) has to be written from the new one. What looks like a link in a code sample is an example of one, and stays as it is. A file moved or renamed outside Plicine leaves its links pointing at the old name.

An image in the vault's assets/ folder can be embedded the same way, and a number after the pipe sets its width in pixels:

![[sample-chart.svg|420]]

Embedding a document (![[Pricing]]) isn't supported: it renders as an ordinary link to that document rather than pulling its text in.

Images

Images are referenced the normal markdown way, with a path relative to the document. From proposals/quote.md, that means going up a level:

![Bar chart with four bars rising from left to right](../assets/sample-chart.svg)

It's the same inside a block's markdown, a card or a columns item say: the path is relative to the document the block is in.

They can also go through the image core block, which adds a caption, width and alignment. Its src is relative to the vault root, not the document:

```image
src: assets/sample-chart.svg
alt: Bar chart with four bars rising from left to right
caption: Images come from the vault's assets folder.
```

Plicine only ever serves files from the vault's assets/ folder, so that's where images should live.

Line breaks

A newline inside a paragraph is a line break. Press Enter once in the in-place editor and the next line stays in the same paragraph, on its own line; leave a blank line and you start a new paragraph, which becomes an element of its own once saved. Strict CommonMark folds a single newline into a space, which suits prose hard-wrapped in a text editor but makes an inline editor feel broken: what you typed on two lines comes back on one. Plicine does what GitHub comments and Obsidian do instead. The flip side is that a paragraph hard-wrapped in another editor shows those wraps, so keep each paragraph on one line. A backslash or two spaces at the end of a line still force a break the CommonMark way.

Raw HTML is escaped

Plicine's markdown renderer doesn't interpret raw HTML written in a document; it's escaped and shown as literal text rather than executed. This keeps a document safe to render even if it came from a folder you don't fully trust, but it does mean you can't rely on hand-written <div>s or <script> tags doing anything, the way they might in an editor that renders HTML directly. If you need something markdown can't express, that's what a block is for.

The one comment Plicine reads is a line holding only <!-- {width="wide"} --> or <!-- {width="full"} -->, directly above a table, image or paragraph, which makes that element wider than the text. It isn't shown. Core blocks has the details; any other comment is escaped like the rest of raw HTML.

Data lives in the document, logic lives in the block

The one deliberate exception to "documents hold no code" is the ts-block fence, which lets you write TypeScript directly in a document instead of packaging it as a block. It's still treated as code, though: it goes through the same compile step and the same approval prompt as any packaged block, so it's an explicit choice rather than something that runs silently. Everything else in a document, frontmatter and block bodies alike, is plain data.

Graceful degradation elsewhere

Because a document is just CommonMark and YAML, it still makes sense when Plicine isn't the one reading it:

  • On GitHub, frontmatter shows as a small table of keys and values above the document.
  • A block fence like ```quote-table renders as a plain code block showing the YAML, since GitHub doesn't know what a quote-table is.
  • Relative links between documents and images still work, because the underlying markdown is untouched, only the app's own renderer rewrites them for in-app navigation.
  • [[links]] are the one piece of syntax GitHub doesn't know: it shows the brackets as written. Obsidian resolves them the same way Plicine does, so a vault opened in both behaves consistently; use ordinary markdown links in a document whose main audience reads it on GitHub.
  • Raw HTML, unlike inside the app, is rendered by GitHub rather than escaped, so it's worth avoiding it in a document you expect people to read both ways.

A document written for Plicine should always be readable as "just a markdown file with some YAML in it", because that's exactly what it is.