# VisiMark — design-decision catalogue

The language ships **sixteen functions and a fixed operator set**
([`visimark-design.md` §4](visimark-design.md#4-syntax)). This file is the register of
every proposed change to the language or its tooling — a mapper, operator, or
aggregate (sections A–D); a language feature (section E); a tooling or process
change (section F) — and the decision taken on it. It exists so the same request
is not re-argued from scratch, and so a "no" has a citable reason. The filename
is historical; the register is not vocabulary-only.

## Requesting an addition

Every request goes on a template in
[`.github/ISSUE_TEMPLATE/`](../.github/ISSUE_TEMPLATE/). The template picks the
review track and decides, up front, whether the request earns a row here at all:

| Template | What it is for | Lands as |
|----------|----------------|----------|
| **Vocabulary request** | One mapper, operator, aggregate, or sorting rule per issue | A row in **A–D** |
| **Language feature** | What a document *means* — syntax, evaluation, the finding set, the format, write-back | A row in **E** |
| **Tooling, CLI, or process** | What a *machine* sees — the command surface, CI, releasing, the workflow, the extension, repo layout | A row in **F** |
| **Bug report** | The tool does something it does not claim to do | No row — unless the fix must *decide* unspecified behaviour, which promotes it to **E** or **F** |
| **Site, playground, or docs** | The landing page, the playground, the tutorial, the examples, the reference docs | No row |
| **Project direction or outreach** | Positioning, prioritisation, launches, audiences | **No row, ever** — there is no constraint to judge it against |

**The line between E and F is who consumes the change.** A document's meaning is
section E. A command's invocation, exit code, output format and `--json` shape
are section F, because a CI job depends on them and no document does — which is
why `--json` ([#60](https://github.com/michal-niedzwiedzki/visimark/issues/60))
is filed as `tooling`. A proposal that moves both is split into two issues at
review time rather than decided as one.

An issue too thin to assess is sent back for the missing fields before review.
The process from issue to deciding comment is
[`issue-runbook.md`](issue-runbook.md).

A vocabulary request is judged against the design doc's constraints, not against Excel:

1. **It fits the shape system.** A mapper is scalar → scalar; a reducer is
   vector → scalar and takes a bare column reference, never an expression; there
   is no vector → vector ([§4](visimark-design.md#4-syntax), "Shape: map and reduce").
2. **Its value is document-local.** No locale, no clock, no network, no ambient
   data, no config (constraint 4). A value that genuinely comes from outside
   belongs in an input column where a human writes it down, dated and diffable.
3. **It adds no ambiguity.** No implicit coercion whose result depends on
   context; nothing a reader could read two ways (constraint 3).
4. **A real document needs it.** Per [§14](visimark-design.md#14-deferred) a primitive with no motivating document
   is deferred by default — "nothing in either worked example repeats" is why
   user-defined functions are deferred, and the same bar applies here. Paste the
   table and its `vmark` block into the issue.
5. **Its precision behaviour is stated.** The result's decimal width either
   derives exactly from its operands, comes from an argument, or does not follow
   at all — in which case a binding using the primitive must declare
   `precision N` ([§7](visimark-design.md#7-numeric-semantics)). A primitive
   whose width nobody has thought about is not assessable, and the request
   template asks for it.

A general issue (sections E–F) is judged against the same design doc: [§1](visimark-design.md#1-purpose)
scope and non-goals, the four [§2](visimark-design.md#2-constraints-that-shaped-the-design)
constraints and the no-plugin rule, the shape system where it touches
expressions, and diffability ([§9](visimark-design.md#9-write-back)). Criterion 4
above — a real document needs it — applies to every section.

The maintainer records the outcome in the tables below: the **Request** column
links the issue, the **Status** column links the comment that decided it. The
process for getting from an issue to that comment is
[`issue-runbook.md`](issue-runbook.md).

Once a primitive is implemented its row leaves its section table and is
condensed into the [Shipped register](#shipped) at the foot of this file — the
section tables stay a register of open questions and standing decisions, not an
ever-growing changelog.

## Status values

| Value | Meaning |
|-------|---------|
| `NEW` | Requested and catalogued; not yet decided. |
| `APPROVED` | Accepted and tracked to implementation. Stays in its section table until the implementation PR merges. |
| `UNRELEASED` | Implemented and merged to `master`; ships in the next tagged release — not yet on npm or the Marketplace. Listed in the [Shipped register](#shipped) with an empty **Released** cell; **Landed** links the implementing PR. |
| `SHIPPED` | In a published release, and a [`visimark-design.md` §4](visimark-design.md#4-syntax) row. Listed in the [Shipped register](#shipped) with **Released** linking the release; the **Decision** link holds the full reasoning its section-table row once carried. |
| `DEFERRED` | Plausible, but no motivating document yet, or it waits on another decision. Not a "no". |
| `REJECTED` | Declined on a constraint. The linked comment is the reason; reopen only with new information. |

```mermaid
stateDiagram-v2
  direction LR
  [*] --> NEW: /issue-review catalogues
  NEW --> APPROVED: /issue-decide
  NEW --> DEFERRED: /issue-decide
  NEW --> REJECTED: /issue-decide
  APPROVED --> UNRELEASED: impl PR merges
  UNRELEASED --> SHIPPED: release tag
  DEFERRED --> APPROVED: reopen with new information
  DEFERRED --> REJECTED: reopen with new information
  REJECTED --> APPROVED: reopen with new information
  REJECTED --> DEFERRED: reopen with new information
```

The seed entries below were catalogued from the **2026-09-06** design discussion
and predate the issue tracker, so their **Request** cells are empty.

---

## A. Mappers (scalar → scalar)

Run once per row inside a column rule, or once for a scalar binding. Result is a
number, a date, or a string — never a boolean ([§4](visimark-design.md#4-syntax)).

| Name | What it does | Pros | Cons | Request | Status |
|------|--------------|------|------|---------|--------|
| `TEXT(n, places)` | Format a number as a string with exactly `places` decimals: `TEXT(5.5, 2)` → `"5.50"`. | The safe half of string concatenation — an explicit format instead of a guessed one. Cheap, map-shaped. | One more name in the did-you-mean space. Only useful alongside `&`. | — | `DEFERRED` |
| `YEAR(d)` / `MONTH(d)` / `DAY(d)` | Integer field of a date. | Pure, config-free, unambiguous (date → integer). Pairs with `&` for reference numbers. | Thin on its own without `&`. | — | `DEFERRED` |
| `TRUNC(x, places)` | Drop decimals past `places` without rounding. | Distinct from `ROUND`; occasionally the correct operation (tax floors). | Overlaps `ROUND`; easy to reach for by mistake. | — | `DEFERRED` |
| `DATE(y, m, d)` | Build a date from numeric parts. | — | You would otherwise write the literal `2026-09-03`; computed components are rare. | — | `DEFERRED` |
| `COALESCE(a, b, …)` | First non-blank argument. | Real tables have optional columns (override price, ad-hoc discount) with no clean path today. | Introduces a **`blank` in-flight value** — a fourth kind beside number/date/string/boolean. `blank + 5` must be defined, and any `blank → 0` is a silent guess (constraint 3). The single change that most "becomes Excel". | — | `DEFERRED` |
| `LEN(s)` / `LEFT` / `RIGHT` / `MID` / `FIND` | String length and substring extraction. | Prefix / embedded-code extraction. | Index base (0 vs 1) is an off-by-one farm; Unicode unit policy (code point vs grapheme) is a real correctness question; `FIND` returns a position that then feeds arithmetic, multiplying the surface. Neither worked example needs it. | — | `DEFERRED` |
| `SIN` / `COS` / `TAN` / `LN` / `LOG` / `EXP` / `PI()` | Transcendental math. | Genuine structural / mechanical calculations. | Computed in binary float internally, which dents the "re-add it on a calculator" promise ([§7](visimark-design.md#7-numeric-semantics)); radians-vs-degrees is a constraint-3 trap; `PI()` is non-terminating, reopening the precision question. | — | `DEFERRED` |
| `TODAY()` / `NOW()` | Current date / time. | The most-requested Excel date function. | **Violates constraint 4 (no clock)** — the same commit must evaluate identically forever. A due date is `Issued + 30` with `Issued` an input column. | — | `REJECTED` |
| `WORKDAY(d, n)` / `NETWORKDAYS(a, b)` | Business-day arithmetic. | Business-day due dates. | A holiday calendar is ambient data (constraint 4) unless holidays are an input column; "weekend = Sat/Sun" is itself a locale assumption (constraint 3). | — | `REJECTED` |
| `BITAND` / `BITOR` / `XOR` / shifts | Bitwise integer operations. | — | No plausible quote, invoice, budget, or structural-engineering document; an integer-only island in a decimal-number language. | — | `REJECTED` |

## B. Operators

The current set is `+ - * / ^`, comparison `== != < <= > >=`, and `and or not`
([§4](visimark-design.md#4-syntax)). `=` is binding only; `%` is postfix-only so `23%` is never ambiguous.

| Name | What it does | Pros | Cons | Request | Status |
|------|--------------|------|------|---------|--------|
| `&` | Joins strings end to end: `"INV-" & YEAR(d)` → `"INV-2026"`. Dates join as their ISO text. | Computed string columns already exist and are tested (`IF(…, "late", "current")` in the drift example). `&` is a free character — no clash with table `\|`, and the language has no `&&` (it spells them `and` / `or`). | A **number** operand raises "what precision does a number have with no binding to infer from" (constraint 3). Must either refuse numeric operands or require `TEXT(n, places)` — a coercion rule to write into [§4](visimark-design.md#4-syntax). | — | `DEFERRED` |
| `s[a:b]` slice | Substring by position as an operator rather than `MID`. | Terser than a function call. | Same index-base and Unicode-unit problems as the `MID` / `LEFT` family in Section A. | — | `DEFERRED` |
| `%` as infix modulo | `a % b` for remainder. | — | `%` is postfix-only by design so `23%` is unambiguous ([§4](visimark-design.md#4-syntax)). `MOD(x, y)` is the remainder. | — | `REJECTED` (settled in [§4](visimark-design.md#4-syntax)) |
| `//` integer division | Divide and floor in one operator. | — | Adds a second division operator with its own rounding rule; `TRUNC(a / b, 0)` or `MOD` already express it. | — | `REJECTED` |

## C. Reducers (vector → scalar)

Collapse one column to one value. A reducer has one column parameter, a bare
column reference, and its other parameters are scalars — `SUM(Price * Qty)` is
refused so every intermediate is a column the reader can see
([§4](visimark-design.md#4-syntax)). `NPV(rate, flows)` is the first reducer
with a scalar parameter. The current set is `SUM MIN MAX AVG COUNT NPV IRR`.

That last rule has a consequence worth stating plainly: because `SUM(IF(x > 0,
1, 0))` is not expressible, **there is today no way to count or total a subset of
a column without materialising a visible column of 1/0 or masked values** that
pollutes the very table the reader audits. The predicate aggregates below are
the minimal, principled fill for that gap — and "grow the vocabulary in the
engine" is what [§14](visimark-design.md#14-deferred) prescribes over letting a document reach outside itself.

| Name | What it does | Pros | Cons | Request | Status |
|------|--------------|------|------|---------|--------|
| `COUNTNONBLANK(col)` | Number of rows whose cell is non-empty. | "How many line items have a delivered-date filled in" — real. The predicate is evaluated **inside** the reducer and never emits a `blank` that flows anywhere, so it is safe where `COALESCE` is not. Works on any column type. | Needs a defined notion of an empty input cell — which already exists. A different axis (presence) from the sign predicates. | — | `DEFERRED` |
| `COUNTPOSITIVE(col)` / `COUNTNEGATIVE(col)` | Count rows by the sign of a numeric cell. | Dodges `COUNTIF`'s criteria-string mini-language entirely. Variance and reconciliation columns are the use case. | Numbers only (error on a date column — one extra type check each). `COUNTZERO` is their complement against `COUNT`. | — | `DEFERRED` |
| `SUMPOSITIVE(col)` / `SUMNEGATIVE(col)` | Total the positive / negative cells of a column. | Splitting a ledger into debits and credits is **more common than counting** the entries; identical shape and cost to the count variants. | Numbers only. | — | `DEFERRED` |
| `COUNTZERO(col)` / `COUNTNONZERO(col)` | Count rows equal to / not equal to zero. | Completes the sign partition. | Derivable as complements of the above and `COUNT`; ship only if a real document reads better with them named. | — | `DEFERRED` |
| `MEDIAN(col)` / `MODE(col)` | Middle / most-frequent value. | Reporting tables. | An even-count median interpolates to a value not printed in the column, denting the recompute story ([§7](visimark-design.md#7-numeric-semantics)). Rare in quotes and invoices. | — | `DEFERRED` |
| `PRODUCT(col)` | Multiply a column together. | Compounding factors. | Niche; `^` covers most compounding, and a runaway product overflows precision fast. | — | `DEFERRED` |
| User-defined reducers | `NAME(col) = …` in a block. | — | See below. | — | `REJECTED` |

### User-defined reducers

Rejected, and tied to the spec:

- A user-defined reducer needs **vector parameters**, and the language
  deliberately has none — [§14](visimark-design.md#14-deferred) says so outright ("Users cannot define a reduce,
  because doing so would require vector parameters, and the language does not
  have them").
- Expressing a fold means exposing init / step / combine — a sub-language whose
  logic is **not visible arithmetic**, which breaks the [§7](visimark-design.md#7-numeric-semantics) property that a
  reader re-adding the column on a calculator gets the same answer.
- It reopens the macro / plugin hole constraint 4 closed on purpose: a Markdown
  file in a pull request selecting fold logic that CI then executes.
- The named predicate aggregates in this section are the pressure-relief valve.
  They cover the realistic conditional-aggregate cases as readable primitives
  everyone can run, which is exactly what [§14](visimark-design.md#14-deferred) tells you to do instead.

Custom **collectors** — a user-supplied way to accumulate — are the same feature
wearing a different hat and are rejected for the same reasons.

## D. Sorting rules

Row order is currently the author's and the tool never touches it ([§9](visimark-design.md#9-write-back)). Whether
a sheet may declare a sort order is a language question, not a vocabulary one —
it changes no expression shape — so it is tracked here rather than in Sections
A–C. **Discussed 2026-09-06; no issue open yet; not a proposal until one is.**

**Shape of the idea.** A `by` clause on the fence info line, next to the sheet
id:

````markdown
```vmark #lines by Net, Name
```
````

`fmt` would reorder the owned table's rows — input cells and computed cells
together — by those columns, ties broken left to right and then by document
order. remark already exposes the whole info string as `node.meta`, and sheet
identity is already parsed out of it ([§16](visimark-design.md#16-renderer-verification)), so `#id by a, b` is a small parser
extension, not a new surface.

| | |
|---|---|
| **Pros** | Payment schedules want date order; line-item tables sometimes want alphabetical order — real, recurring. The key sits in the document text, so constraint 4 holds and a reviewer sees the ordering rule. The result is deterministic given the data. Two people adding rows on separate branches converge on the same final order, so the order stops being a fact of edit history and merge conflicts on the table drop. No new value type and no new expression shape — it is a table-level directive, nearly free in the type system. ISO dates already sort lexically ([§5](visimark-design.md#5-dates)), so a date key costs nothing. |
| **Cons** | **It writes to human-owned rows.** [§9](visimark-design.md#9-write-back) says the tool owns computed cells and anchored values and that "everything else … is human territory and is never touched," with `fmt --fix-dates` as *the* single exception, gated behind a flag. Reordering moves input cells — the same category of breach — so `by` has to be opt-in by the same logic, or it quietly redefines the ownership model. **The first `fmt` after adding `by` is a whole-table reshuffle diff** — exactly what the offset splicer exists to avoid ([§9](visimark-design.md#9-write-back), [§13](visimark-design.md#13-testing): "a one-cell change touches one line"). One-time, but loud. **A key that is a computed column** forces `fmt` to evaluate before it can sort, so a sheet carrying a `CYCLE` or `TYPE` error cannot be ordered — another "skipped because upstream" branch to specify and test, alongside the `UNIT` one. **String order must be pinned** — code-point order is unambiguous and config-free but is not "alphabetical" (`"Banana"` sorts before `"apple"`); that is a sentence in [§5](visimark-design.md#5-dates)-land, not a silent choice. **`by` on a table-less sheet** (`#terms`, `#recon`) is a new `SHEET` error. And it is the first behaviour that looks like presentation, which [§1](visimark-design.md#1-purpose) lists as a non-goal; it invites "then can I group, subtotal, hide a column" — the Excel gravity the format is built to resist. |
| **Cost** | **M**, roughly 2–4 days: parse the clause; sort in `write/` after evaluation; plus the tie-break rule, per-type ordering, descending, the skip-if-unevaluable branch, the table-less error, and a golden-file test proving the one-time reshuffle followed by a byte-stable second `fmt`. |

**Descending.** Worth having — "largest line items first", "most recent payment
first" are ordinary asks, and omitting it just sends people back to hand-ordering,
which the feature was meant to end. `by Net desc, Name` reads cleanly and costs
almost nothing once a key list is parsed: a `desc` (and optional explicit `asc`)
keyword per key. `by -Net` is rejected on sight — it reads as arithmetic and
collides conceptually with unary minus.

**If it is built**, the gate is [§9](visimark-design.md#9-write-back)'s: declaring `by` is the author explicitly
handing row order to the tool, and `fmt` should reorder only when asked
(`fmt --sort`), or at minimum the format doc must state loudly that a sheet with
`by` has tool-managed rows. Syntax `by Col, Col` with per-key `desc` is the
shape to specify. A decision moves this section from "discussed" to a real
proposal with a `Status`.

## E. Language features

Changes to the syntax, the evaluation model, the finding set, the format, or
write-back — anything that alters **what a document means**. Not a single mapper
/ operator / reducer (those are sections A–C) and not a sorting rule (section
D). **Not the CLI surface**, either: options, exit codes and output formats are
consumed by build pipelines rather than by documents, so they are section F.
Judged against
[§1](visimark-design.md#1-purpose) scope, the four
[§2](visimark-design.md#2-constraints-that-shaped-the-design) constraints and the
no-plugin rule, the shape system ([§4](visimark-design.md#4-syntax)) where it
touches expressions, and diffability ([§9](visimark-design.md#9-write-back)). A
`REJECTED` or `DEFERRED` row is reopened only with new information.

| Feature | What it changes | Pros | Cons | Request | Status |
|---------|-----------------|------|------|---------|--------|
| Generated artifacts (`chart` statements) | A `chart <name> as pie\|bar of <cols> labelled <col> [aspect w:h]` statement in a `vmark` block. The author states the artifact's path in an ordinary Markdown image carrying an anchor; `fmt` writes a deterministic SVG there and `check` proves it current by rendering and comparing bytes. Introduces a **third category of tool-owned output** — generated artifacts — plus an `ARTIFACT` finding, and widens `STALE` to cover an artifact that is out of date or absent. | A visualisation stops being a second source of truth: one dataset, regenerated and CI-verified like every other derived value, and readable on GitHub by anyone without VisiMark installed. Constraint 4 holds fully — no clock, network, ambient fonts or config, and a closed built-in engine set. The document states the path, mirroring the anchor model, so there is no naming convention, no normalisation and no collision analysis. Byte comparison replaces any checksum, giving exact invalidation with no algorithm anyone can depend on. Forbidding formulas in declarations keeps the calculation model in the language. | Requires clarifying [§1](visimark-design.md#1-purpose)'s "no presentation layer" non-goal, naming a third owned category in [§9](visimark-design.md#9-write-back), and scoping [§13](visimark-design.md#13-testing)'s one-line-diff guarantee to documents. `check` proves an artifact's provenance, never that the picture depicts the data faithfully — a limit new in kind, accepted as downstream of the primary mechanics. Author-chosen paths need a hard gate (relative, contained, no symlink escape, lowercase `.svg`, refuse to overwrite anything not VisiMark-marked). Cost is **L** — a rendering engine plus artifact writing, path validation and staleness. Greyscale discrimination weakens past ~5 series. | [#36](https://github.com/michal-niedzwiedzki/visimark/issues/36) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/36#issuecomment-5575507072) |
| Whole-sheet materialization anchor for imports (`<!--vmark=sheet-->`) | A sheet-level anchor form that inserts/regenerates a full GFM table from an imported sheet's data directly in the document body, preserving the anchor as the update point. | Makes an otherwise-invisible imported CSV visible to an unmodified Markdown renderer without transferring file ownership; reuses the existing import integrity stamp unchanged. | Inlines a vector with no reduce step — a third vector consumer the shape system ([§4](visimark-design.md#4-syntax)) doesn't define, and one anchors have never done (today: scalar text only, [§3](visimark-design.md#3-document-model)). No proposed marker delimits the generated table's end (constraint 3). Directly reverses why generated artifacts are kept out of the document ([§13](visimark-design.md#13-testing)) and why imports exist at all ([§19](visimark-design.md#19-declared-local-data-imports)) — both settled to avoid exactly this kind of whole-table inline diff. | [#75](https://github.com/michal-niedzwiedzki/visimark/issues/75) | [REJECTED](https://github.com/michal-niedzwiedzki/visimark/issues/75#issuecomment-5655549317) |

`assert` statements, generated artifacts (`chart`), the sheet-id /
anchor-comment grammar hardening and declared local data imports shipped, and
scenario parameters landed unreleased — see the [Shipped register](#shipped).

## F. Tooling and process

The CLI surface — commands, options, exit codes, the stdout/stderr split, the
`--json` shape — plus CI, releasing, the review workflow, the command / skill
set, the editor extension, repo layout, and the structure of the design docs.
Not a change to what a document means, but everything a machine downstream of
`visimark` depends on, and so still a standing decision worth a citable reason.

A section F row states its **machine contract**: which exit codes are involved,
what each stream carries, and whether an existing CI job or script behaves
differently afterwards. That last question is section F's breaking-change test,
the way "does a document that passes `check` today still pass?" is section E's.

| Change | What it changes | Pros | Cons | Request | Status |
|--------|-----------------|------|------|---------|--------|
| Shared build across the document phases | An engine and API change, not a playground one. `fmt`, `eval`, `explain` and `check` each call `locate` + `build` + `check` over the same source text from scratch, so any caller that wants more than one of them pays for the document several times. Proposes an entry point that builds once and hands the built document to each phase — the same shape as the reader injection in [`browser-fs-port-plan.md`](design/browser-fs-port-plan.md), which turned an API that took a path into one that takes a port. | Measured, not guessed: the playground pays four full passes per typing-settle, 2,262 ms on a 2,000-row document for the three-call pass and nearer 2.9 s with the quest's STALE check — see [`playground-pipeline-cost-plan.md`](design/playground-pipeline-cost-plan.md) for the table. It is the largest single lever the playground has, and the CLI pays the same cost every time a command runs two phases. The phases already agree on what a built document is, so nothing new has to be invented. | Touches the engine's public surface, which is the reason review §2.10 refused to do it inside a playground change and the follow-up review's §2.13 asked for it to be tracked here instead of inside a `playground-`prefixed design doc. No motivating document needs it: every document the playground ships checks in 12–20 ms, so the win is real only for pasted input far larger than anything in the repository. That is the [§14](visimark-design.md#14-deferred) bar, and this does not clear it yet. | [review §2.13](reviews/2026-09-20-playground-followup.md) | `NEW` |
| Reposition around verifiable numbers (growth programme) | Not a change to the language. Proposes leading with "make numbers in Markdown verifiable" rather than "spreadsheet mechanics for Markdown", and sequencing growth work — README and landing-page rewrite, a browser playground, marketplace publishing, five worked examples, a "why not a spreadsheet" comparison, a concentrated launch — ahead of further language work. | The positioning is [§1](visimark-design.md#1-purpose)'s own framing put more sharply — auditable in review, enforceable in CI — and drifts toward no non-goal. The browser playground is a real gap: the site's `eval` demos are prerendered, so a visitor cannot edit an input and watch a value go stale. Identifies the drift invoice as the strongest asset the project already has. | Fifteen proposals in one issue with no common verdict — approving it approves a launch plan, rejecting it rejects a wanted playground. Most of the bundle has no [§2](visimark-design.md#2-constraints-that-shaped-the-design) constraint to be judged against, so the register is the wrong instrument for it. Two premises had lapsed: marketplace publishing is already automated in `release.yml` and its failure already diagnosed in [`releasing.md`](releasing.md), and the landing page gained the Explore section in [#40](https://github.com/michal-niedzwiedzki/visimark/pull/40). Its prioritisation argument does not reopen charts ([#36](https://github.com/michal-niedzwiedzki/visimark/issues/36), merged) or `Σ` ([#43](https://github.com/michal-niedzwiedzki/visimark/issues/43)) — growth impact is not new information about the constraint that decided either. | [#45](https://github.com/michal-niedzwiedzki/visimark/issues/45) | [DEFERRED](https://github.com/michal-niedzwiedzki/visimark/issues/45#issuecomment-5588945953) |
---

## Shipped

Additions that have been approved and implemented — a vocabulary primitive
(now a [`visimark-design.md` §4](visimark-design.md#4-syntax) row), a language
feature (specified in the design doc section it changed), or a tooling / process
change. That document, not this one, is where the behaviour is specified. The
**Decision** link holds the full reasoning the row carried while it was under
review in sections A–F.

A row enters this table `UNRELEASED` — merged to `master`, **Released** empty —
and is promoted to `SHIPPED` with the release link when the next `vX.Y.Z` tag
ships it (see [`releasing.md`](releasing.md)). Its request issue is closed at
the same time.

| Name | Kind | Request | Landed | Released | Decision |
|------|------|---------|--------|----------|----------|
| Declared domain on `param` | language feature | [#241](https://github.com/michal-niedzwiedzki/visimark/issues/241) | [#246](https://github.com/michal-niedzwiedzki/visimark/pull/246) | [v0.1.8](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.8) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/241#issuecomment-5821299735) |
| Record the browser bundle's size as a checked ceiling | tooling | [#191](https://github.com/michal-niedzwiedzki/visimark/issues/191) | [#198](https://github.com/michal-niedzwiedzki/visimark/pull/198) | [v0.1.8](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.8) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/191#issuecomment-5796346439) |
| Cross-host equivalence check over the worked-example corpus | tooling | [#189](https://github.com/michal-niedzwiedzki/visimark/issues/189) | [#195](https://github.com/michal-niedzwiedzki/visimark/pull/195) | [v0.1.8](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.8) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/189#issuecomment-5793796093) |
| Prove the published package is importable, not just installable | tooling | [#190](https://github.com/michal-niedzwiedzki/visimark/issues/190) | [#196](https://github.com/michal-niedzwiedzki/visimark/pull/196) | [v0.1.8](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.8) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/190#issuecomment-5794137088) |
| Pin the markdownlint restoration contract, and report analyze() failures once | tooling | [#173](https://github.com/michal-niedzwiedzki/visimark/issues/173) | [#182](https://github.com/michal-niedzwiedzki/visimark/pull/182) | [v0.1.8](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.8) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/173#issuecomment-5786173572) |
| An MCP server exposing the engine to agents | tooling | [#169](https://github.com/michal-niedzwiedzki/visimark/issues/169) | [#183](https://github.com/michal-niedzwiedzki/visimark/pull/183) | [v0.1.8](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.8) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/169#issuecomment-5786228235) |
| Enforce the Node support policy (current LTS + latest stable, both blocking) | tooling | [#184](https://github.com/michal-niedzwiedzki/visimark/issues/184) | [#186](https://github.com/michal-niedzwiedzki/visimark/pull/186) | [v0.1.8](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.8) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/184#issuecomment-5790907466) |
| Bun/Node parity as an always-on rule | tooling | [#171](https://github.com/michal-niedzwiedzki/visimark/issues/171) | [#175](https://github.com/michal-niedzwiedzki/visimark/pull/175) | [v0.1.8](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.8) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/171#issuecomment-5785197844) |
| `fmt --no-artifacts` | tooling | [#168](https://github.com/michal-niedzwiedzki/visimark/issues/168) | [#178](https://github.com/michal-niedzwiedzki/visimark/pull/178) | [v0.1.8](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.8) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/168#issuecomment-5785369119) |
| `IRR(flows)` | reducer | [#158](https://github.com/michal-niedzwiedzki/visimark/issues/158) | [#167](https://github.com/michal-niedzwiedzki/visimark/pull/167) | [v0.1.8](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.8) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/158#issuecomment-5783540649) |
| `NPV(rate, flows)` | reducer | [#157](https://github.com/michal-niedzwiedzki/visimark/issues/157) | [#165](https://github.com/michal-niedzwiedzki/visimark/pull/165) | [v0.1.8](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.8) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/157#issuecomment-5782258164) |
| `PMT(rate, nper, pv)` | mapper | [#156](https://github.com/michal-niedzwiedzki/visimark/issues/156) | [#161](https://github.com/michal-niedzwiedzki/visimark/pull/161) | [v0.1.8](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.8) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/156#issuecomment-5781382455) |
| `EOMONTH(d, months)` | mapper | [#6](https://github.com/michal-niedzwiedzki/visimark/issues/6) | [#8](https://github.com/michal-niedzwiedzki/visimark/pull/8) | [v0.1.2](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.2) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/6#issuecomment-5559247913) |
| `SQRT(x)` | mapper | [#18](https://github.com/michal-niedzwiedzki/visimark/issues/18) | [#20](https://github.com/michal-niedzwiedzki/visimark/pull/20) | [v0.1.2](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.2) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/18#issuecomment-5560992652) |
| `assert` statements | language feature | [#27](https://github.com/michal-niedzwiedzki/visimark/issues/27) | [#32](https://github.com/michal-niedzwiedzki/visimark/pull/32) | [v0.1.2](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.2) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/27#issuecomment-5570701059) |
| Runtime-portable CLI launcher | tooling | [#29](https://github.com/michal-niedzwiedzki/visimark/issues/29) | [#34](https://github.com/michal-niedzwiedzki/visimark/pull/34) | [v0.1.2](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.2) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/29#issuecomment-5572442105) |
| Generated artifacts (`chart` statements) | language feature | [#36](https://github.com/michal-niedzwiedzki/visimark/issues/36) | [#39](https://github.com/michal-niedzwiedzki/visimark/pull/39) | [v0.1.2](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.2) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/36#issuecomment-5575507072) |
| Sheet-id / anchor-comment grammar hardening | language feature | [#38](https://github.com/michal-niedzwiedzki/visimark/issues/38) | [#48](https://github.com/michal-niedzwiedzki/visimark/pull/48) | [v0.1.2](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.2) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/38#issuecomment-5588230949) |
| `Σ` / `∑` alias for `SUM` | language feature | [#43](https://github.com/michal-niedzwiedzki/visimark/issues/43) | [#49](https://github.com/michal-niedzwiedzki/visimark/pull/49) | [v0.1.2](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.2) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/43#issuecomment-5588631404) |
| `FLOOR(number, significance)` | mapper | [#53](https://github.com/michal-niedzwiedzki/visimark/issues/53) | [#56](https://github.com/michal-niedzwiedzki/visimark/pull/56) | [v0.1.2](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.2) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/53#issuecomment-5598663415) |
| `CEILING(number, significance)` | mapper | [#54](https://github.com/michal-niedzwiedzki/visimark/issues/54) | [#59](https://github.com/michal-niedzwiedzki/visimark/pull/59) | [v0.1.2](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.2) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/54#issuecomment-5600795875) |
| `--json` on all document CLI commands | tooling | [#60](https://github.com/michal-niedzwiedzki/visimark/issues/60) | [#62](https://github.com/michal-niedzwiedzki/visimark/pull/62) | [v0.1.2](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.2) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/60#issuecomment-5609768148) |
| Declared local data imports with integrity stamps | language feature | [#66](https://github.com/michal-niedzwiedzki/visimark/issues/66) | [#68](https://github.com/michal-niedzwiedzki/visimark/pull/68) | [v0.1.3](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.3) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/66#issuecomment-5626597963) |
| `unlabelled` clause for headerless CSV imports | language feature | [#70](https://github.com/michal-niedzwiedzki/visimark/issues/70) | [#72](https://github.com/michal-niedzwiedzki/visimark/pull/72) | [v0.1.3](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.3) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/70#issuecomment-5637476576) |
| Human-readable column references and aliases (quoted headers, `is`) | language feature | [#86](https://github.com/michal-niedzwiedzki/visimark/issues/86) | [#88](https://github.com/michal-niedzwiedzki/visimark/pull/88) | [v0.1.3](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.3) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/86#issuecomment-5668665880) |
| Scenario parameters (`param`, `eval --scenario`) | language feature | [#119](https://github.com/michal-niedzwiedzki/visimark/issues/119) | [#123](https://github.com/michal-niedzwiedzki/visimark/pull/123) | [v0.1.6](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.6) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/119#issuecomment-5740581923) |
| Refuse unrecognised and misplaced CLI options | tooling | [#121](https://github.com/michal-niedzwiedzki/visimark/issues/121) | [#135](https://github.com/michal-niedzwiedzki/visimark/pull/135) | [v0.1.7](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.7) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/121#issuecomment-5758855875) |
| Division by zero is `TYPE` | language feature | [#122](https://github.com/michal-niedzwiedzki/visimark/issues/122) | [#126](https://github.com/michal-niedzwiedzki/visimark/pull/126) | [v0.1.6](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.6) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/122#issuecomment-5746529695) |
| Prose notation for unary vocabulary (`\|x\|`, `⌊x⌋`, `⌈x⌉`, `√(x)`) | language feature | [#64](https://github.com/michal-niedzwiedzki/visimark/issues/64) | [#136](https://github.com/michal-niedzwiedzki/visimark/pull/136) | [v0.1.7](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.7) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/64#issuecomment-5759705769) |
| Extension changelog entry for every release | tooling | [#131](https://github.com/michal-niedzwiedzki/visimark/issues/131) | [#138](https://github.com/michal-niedzwiedzki/visimark/pull/138) | [v0.1.7](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.7) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/131#issuecomment-5760479248) |
| Presentation-only `%` display sigil on prose anchors | language feature | [#140](https://github.com/michal-niedzwiedzki/visimark/issues/140) | [#144](https://github.com/michal-niedzwiedzki/visimark/pull/144) | [v0.1.7](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.7) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/140#issuecomment-5775547789) |
| A pre-commit hook for the pre-commit framework | tooling | [#149](https://github.com/michal-niedzwiedzki/visimark/issues/149) | [#151](https://github.com/michal-niedzwiedzki/visimark/pull/151) | [v0.1.8](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.8) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/149#issuecomment-5780522480) |
| A `remark`/`unified` plugin wrapping `visimark check` (`remark-lint-visimark`) | tooling | [#152](https://github.com/michal-niedzwiedzki/visimark/issues/152) | [#159](https://github.com/michal-niedzwiedzki/visimark/pull/159) | [v0.1.8](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.8) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/152#issuecomment-5781287493) |
| A `markdownlint` custom rule wrapping `visimark check` (`markdownlint-rule-visimark`) | tooling | [#153](https://github.com/michal-niedzwiedzki/visimark/issues/153) | [#164](https://github.com/michal-niedzwiedzki/visimark/pull/164) | [v0.1.8](https://github.com/michal-niedzwiedzki/visimark/releases/tag/v0.1.8) | [APPROVED](https://github.com/michal-niedzwiedzki/visimark/issues/153#issuecomment-5782250622) |

<!--vmark:no-formulas-->
