Metadata-Version: 2.4
Name: nide
Version: 2026.7.18
Summary: A bound volume (Finnish: nide) - assemble markdown text files into gfm-plus, html, and pdf channel outputs.
Author-email: Stefan Hagen <stefan@hagen.link>
Maintainer-email: Stefan Hagen <stefan@hagen.link>
License-Expression: MIT
Project-URL: Documentation, https://codes.dilettant.life/docs/nide
Keywords: developer-tools,documentation,markdown
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: defusedxml>=0.7.1
Requires-Dist: jsonschema>=4.26.0
Requires-Dist: kaava>=2026.6.20
Requires-Dist: python-jsonpath>=2.0.2
Requires-Dist: PyYAML>=6.0.3
Requires-Dist: vyyhti>=2026.6.21
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Provides-Extra: manifest
Requires-Dist: blake3; extra == "manifest"
Requires-Dist: pyhanko; extra == "manifest"
Requires-Dist: tallipoika; extra == "manifest"
Dynamic: license-file

# nide

A bound volume (Finnish: nide) — assemble Markdown source files into gfm-plus, html, pdf, and typst channel outputs.

Requires Python 3.11 or later.

## Install

Install the core package:

```
pip install nide
```

To record and verify source-tree provenance with `nide manifest` and `nide validate`,
install the optional manifest dependency group:

```
pip install nide[manifest]
```

This adds three packages:

| Package      | Purpose                                                                            |
|:-------------|:-----------------------------------------------------------------------------------|
| `tallipoika` | RFC 8785 canonical JSON — required for `nide manifest` generation                  |
| `blake3`     | BLAKE3 hashing — sha256-only fallback if absent; both hashes recorded when present |
| `pyHanko`    | PDF XMP injection — used by `nide manifest --inject-pdf` to write Dublin Core metadata as XMP |

`nide manifest --summary` and `nide validate` read an existing `build/manifest.json`
and do **not** require `tallipoika`.  They can run in environments where only the core
package is installed, provided the manifest was generated elsewhere.

## Manual

The [man page](docs/nide.1) provides the CLI reference (`man nide` after placing the file on your `MANPATH`):

```sh
mkdir -p ~/.local/share/man/man1
cp docs/nide.1 ~/.local/share/man/man1/
```

## Quickstart

Run `nide` with no arguments for a usage summary, or `nide --version` to check the installed version.
For a dedicated feature walkthrough you can follow in minutes visit [quickstart](quickstart/README.md).
A step-by-step guided build of a document pipeline is provided in the [tutorial](tutorial/README.md).
Some commented real-world configurations are provided in the [example](example/README.md) documentation.

## Pipeline

Two-pass workflow:

```
nide harvest              →  build/section-*.json, build/example-*.json

nide assemble             →  build/ir.json          (Pass 1: channel-neutral IR)
nide render -t html       →  build/html.md, build/toc-mint.json
pandoc                    →  build/tmp.html
nide inject               →  build/injected.html
tidy                      →  delivery HTML

nide render -t gfm-plus   →  build/gfm-plus.md

nide render -t typst      →  build/typst.md, build/nide-columns.lua
pandoc -t typst           →  build/typst.typ
typst compile             →  delivery PDF           (recommended PDF path)

nide render -t pdf        →  build/pdf.md           (legacy: liitos/LaTeX path)
liitos                    →  delivery PDF

nide manifest             →  build/manifest.json + embedded blocks in channel outputs
nide validate             →  six-check integrity report (zero exit on pass)
```

The compat one-pass form combines Pass 1 + Pass 2 into a single command
(`nide assemble -t html|gfm-plus|pdf`) for existing workflows.

## Commands

`nide` has ten subcommands.  Every name accepts any unambiguous prefix —
`nide a` = assemble, `nide r` = render, `nide m` = manifest, `nide val` = validate, `nide ej` = eject, `nide ex` = explain, etc.

**`nide assemble`** (Pass 1) splices a list of Markdown source files into a single
channel-neutral intermediate representation (IR) and writes it to `build/ir.json`:

- Citation definition files are expanded into formatted inline citation entries.
- Glossary content is captured per source file for channel-specific rendering.
- Section headings are numbered and stored with all metadata needed by any channel.
- Cross-references (`[sec](#label)`, `[cite](#label)`, `[tab](#label)`,
  `[fig](#label)`, `[eg](#label)`) are resolved from JSON look-up tables produced
  by `nide harvest`.
- Standalone local image references (`![alt](path)` as the entire line) are
  resolved to absolute paths relative to their source file's directory and stored
  as `ImageNode` entries in the IR.  Remote URLs pass through verbatim.

When called with `-t CHANNEL`, `nide assemble` also runs Pass 2 immediately
(compat shortcut).

**`nide render`** (Pass 2) reads `build/ir.json` and renders one or more channels.
Accepts a comma-separated list: `nide render -t gfm-plus,html,typst,pdf`.

Before writing any channel output, `nide render` copies all locally-resolved images
to `build/images/` and rewrites image references in all channels.  No Makefile `cp`
step is required — the image tree is managed automatically.  A bundled `missing.svg`
placeholder is installed when any image could not be found at assembly time.

- `html`: numbers headings with inline anchors; expands glossary into `<dl>` elements;
  writes `build/html.md` and `build/toc-mint.json`.
- `gfm-plus`: numbers headings with inline anchors; passes glossary through verbatim;
  writes `build/gfm-plus.md`.
- `typst`: converts headings, code blocks, anchors, and cross-references to raw typst
  blocks; writes `build/typst.md` and `build/nide-columns.lua`.
  Recommended path for PDF output: `pandoc -t typst … | typst compile`.
- `pdf` *(deprecated)*: attaches the nide-generated pandoc heading attribute `{#label}` to every
  heading so hyperref destination names match the cross-reference LUT; prepends
  `\phantomsection` before unnumbered (annex/appendix) section headings; replaces
  the remote logo URL with the local path; writes `build/pdf.md`.
  Use the typst channel instead for new workflows.

**`nide inject`** post-processes the HTML file that pandoc has produced from
nide's `build/html.md`, applying:

- Custom HTML table of contents (built from `build/toc-mint.json`).
- `<title>` element replaced with the value of `html-title` from config.
- Base and skin CSS inlined; `<link>` tags removed.
- Remote logo URL replaced with a local data URL.
- Image `src` attributes optionally replaced with base64 data URLs.

The result is written to `build/injected.html`.

**`nide harvest`** scans the Markdown sources and writes JSON look-up table (LUT)
files for section and example cross-references.  Run it before `nide assemble`
whenever source files change.

**`nide explain`** performs a dry-run walkthrough without writing any output files.
It prints config values, binder contents with exist/missing tags, channel exclusions,
auxiliary source file status, and LUT file presence.  Useful for diagnosing why an
assembly produces unexpected output.

**`nide doctor [--fix]`** scans the project layout and reports findings at ERROR or
WARN level: missing directories, binder file, source files, first-authored-section
heading, auxiliary sources, and LUT files.  With `--fix`, it creates missing
directories and runs `nide harvest` to generate absent LUT files.

**`nide eject`** emits a fully-commented `nide.yaml` template to stdout.
Use `-o PATH` to write it to a file; add `-f` to overwrite an existing file.

**`nide manifest [-c PATH]`** records source-tree provenance after all channel renders are
complete.  It hashes every binder source file, the config file, and all channel output files,
detects the active CCS (git, fossil, or none), records branch/revision/per-file dates,
captures toolchain versions, and writes `build/manifest.json`.  It also appends a canonical
JSON block to each channel output file (HTML comment for GFM-plus and HTML; `/* … */` for
typst); the operation is idempotent.  Requires `pip install nide[manifest]` for the
`tallipoika` canonical-JSON dependency.

**`nide manifest --summary [--format text|markdown|yaml]`** reads an existing
`build/manifest.json` and prints a human-readable rendering to stdout.  Does not require
tallipoika.  Default format is `text` (aligned); `markdown` produces pipe tables; `yaml`
produces a PyYAML human-readable subset.

**`nide validate [--strict] [--quiet]`** reads `build/manifest.json` and runs six integrity
checks: manifest self-hash, per-source-file content hashes, config hash, binder composition
hash, per-channel output hashes (embedded blocks stripped before hashing), and cross-channel
source-state consistency.  Exits 0 (all pass), 1 (any fail), or 2 (usage error / manifest
not found).  `--strict` treats WARN as failure; `--quiet` suppresses per-check output.

**`nide version`** (also `-V` / `--version`) prints the installed version and exits.

Four output channels are supported:

| Channel    | Output (`nide render`)                              | Intended consumer      |
|:-----------|:----------------------------------------------------|:-----------------------|
| `html`     | `build/html.md` + `build/toc-mint.json`             | pandoc → `nide inject` |
| `gfm-plus` | `build/gfm-plus.md`                                 | direct delivery        |
| `typst`    | `build/typst.md` + `build/nide-columns.lua`         | pandoc → typst compile |
| `pdf`      | `build/pdf.md` *(deprecated — use `typst` instead)* | `liitos`               |

IR output (`nide assemble`): `build/ir.json`.

## Configuration

All settings are read from `etc/nide.yaml` (override with `-c`).
Every key is optional; the built-in defaults are listed below.
Run `nide eject` to emit a fully-commented template with every key.

Settings are grouped under six top-level YAML keys.

### `assembly:` — source file handling

```yaml
assembly:
  binder: bind.txt                      # binder filename (in etc-path)
  source-path: src                      # directory containing source .md files
  build-path: build                     # output directory
  etc-path: etc                         # directory holding this config and LUT files
  first-authored-section: '# Scope'    # heading where body numbering starts
  binder-ignores:
    gfm-plus: []                        # filenames to skip for GFM-plus channel
    html: []                            # filenames to skip for HTML channel
    typst: []                           # filenames to skip for typst channel
    pdf: []                             # filenames to skip for PDF channel (deprecated)
  delete-when:
    - contains: '<mark title="Ephemeral region marking">'
      delete:
        - '<mark title="Ephemeral region marking">'
        - '</mark>'
  output-paths:
    ir: build/ir.json
    gfm-plus: build/gfm-plus.md
    html: build/html.md
    typst: build/typst.md
    pdf: build/pdf.md
```

### `structure:` — document anatomy

```yaml
structure:
  section-type-prefixes: [Annex, Appendix]   # prefixes that suppress auto-numbering
```

Headings whose text starts with one of these prefixes (plus a space) are treated
as special sections.  The letter designator is spelled out in the source file.

### `enumeration:` — counters and look-up tables

```yaml
enumeration:
  sections:
    l1-dot: true               # trailing dot on level-1 labels (1., 2., …)
    sub-dot: false             # trailing dot on level 2–6 labels
    reference-style: number    # [sec] expansion: number | number-title | section-sign-number
    toc-template:              # 5 entries, one per heading level
      - '$sec_cnt_disp$ [$text$](#$label$)  '
      - "\t$sec_cnt_disp$ [$text$](#$label$)  "
      - "\t\t$sec_cnt_disp$ [$text$](#$label$)  "
      - "\t\t\t$sec_cnt_disp$ [$text$](#$label$)  "
      - "\t\t\t\t$sec_cnt_disp$ [$text$](#$label$)  "
    lut:
      label-to-display: section-label-to-display.json
      display-to-label: section-display-to-label.json
      display-to-text:  section-display-to-text.json
  examples:
    track: false               # enable *Example N: scanning and look-up tables
    global-start: 4321         # global counter offset (must exceed local count)
    lut:
      local-to-global: example-local-to-global.json
      global-to-local: example-global-to-local.json
```

When `l1-dot: true` (default), nide injects `\renewcommand{\thesection}` into
the PDF channel output so LaTeX section numbers match nide's numbering.

### `references:` — cross-reference configuration

```yaml
references:
  citations:
    sources: []                                           # citation source filenames
    skip-prefixes: ['#']                                  # lines bypassing the state machine
    keyword: cite                                         # inline trigger: [cite](#label)
    cite-template: "\\[[$code$](#$label$)\\]"            # inline ref expansion template
    entry-template: '**\[**<span id="$label$" class="anchor"></span>**$code$\]** $text$'
  sections:
    keyword: sec                                          # inline trigger: [sec](#label)
  glossary:
    sources: []                                           # glossary source filenames
  tables:
    keyword: tab                                          # inline trigger: [tab](#label)
    display-prefix: Table                                 # expands to 'Table N'
  figures:
    keyword: fig                                          # inline trigger: [fig](#label)
    display-prefix: Figure                                # expands to 'Figure N'
```

Table anchors use pandoc table-caption syntax: `Table: Caption{#tab:label}`.
Figure anchors use pandoc image attributes: `![alt](path){#fig:label}`.
Both `cite-template` and `entry-template` support `$code$`, `$label$`, and (for
`entry-template`) `$text$` placeholders.

### `channels:` — channel-specific settings

```yaml
channels:
  html:
    title: 'No Title Given'    # <title> element written by nide inject
    inject:
      css-base: ''             # path to base CSS file (empty = skip)
      css-skin: ''             # path to skin CSS file
      images: false            # inline local images as base64 data URLs
      images-path: build/data-url
      intro-trigger: ''        # HTML prefix marking the first authored heading
      logo-data-url: ''        # path to logo data URL .txt file (empty = skip)
      logo-remote-url: ''      # remote logo URL to replace
      toc-trigger: '<h1 id="table-of-contents'
  typst:
    toc-depth: 2               # outline depth (0 = suppress outline)
    toc-dots: false            # dot-leader fill in outline entries
    toc-title: 'Table of Contents'
    code-size: small           # monospace font size: normal | small | footnote
    page-break-l1: false       # weak #pagebreak() before each non-preamble L1 heading
    list-of-figures: false     # emit List of Figures outline after TOC
    list-of-tables: false      # emit List of Tables outline after TOC (LOF first)
    watermark: ''              # diagonal page-background watermark text; '' = none
  pdf:                         # deprecated — use typst channel instead
    logo-local-path: images/OASISLogo-v3.0.png
    logo-remote-url: 'https://docs.oasis-open.org/templates/OASISLogo-v3.0.png'
```

Both `toc-trigger` and `intro-trigger` must be non-empty for TOC replacement to
occur.

### `metadata:` — Dublin Core document metadata

```yaml
metadata:
  title: ''           # dc:title (lang-alt in XMP)
  creator: ''         # dc:creator
  publisher: ''       # dc:publisher (rdf:Alt)
  description: ''     # dc:description
  subject: ''         # dc:subject, comma-separated tags (rdf:Bag)
  date: ''            # publication/creation date in ISO 8601 (e.g. 2026-07-16)
  type: ''            # dc:type (rdf:Bag), e.g. 'Standard'
  identifier: ''      # dc:identifier, persistent URI/DOI
  language: ''        # dc:language (rdf:Bag), BCP 47 tag, e.g. 'en'
  rights: ''          # dc:rights (rdf:Alt), rights statement or license URI
  version: ''         # dcterms:hasVersion, e.g. '1.0'
  status: ''          # dcterms:abstract, e.g. 'DRAFT' or 'Approved'
```

All fields are optional and default to empty string.  Non-empty fields are written
into `build/manifest.json` under the `metadata` key after each `nide manifest` run.
When the HTML channel output (`build/html.md`) is present, non-empty fields are also
injected as `<meta name="DC.*">` / `<meta name="DCTERMS.*">` tags into `<head>` by
the manifest embedding pass (DCMI-HTML convention; `subject` is comma-split into one
tag per term).  `nide manifest --inject-pdf PATH` maps non-empty fields to the
corresponding XMP namespaces and writes `<stem>-injected.pdf` alongside the source
PDF (requires `pip install nide[manifest]` for pyHanko).

For `channels.typst.code-size`, the valid values and their em sizes are: `normal` (1 em),
`small` (0.85 em), `footnote` (0.75 em).  The same names are valid for the
`\tablefontsize=` body directive.

## Options

### Global options

```
nide [-V|--version]
nide version
```

| Option            | Description                      |
|:------------------|:---------------------------------|
| `-V`, `--version` | Print version string and exit    |
| `version`         | Subcommand alias for `--version` |

Subcommand names accept any unambiguous prefix: `nide a` = assemble,
`nide r` = render, `nide ej` = eject, `nide ex` = explain, etc.
An unknown or ambiguous subcommand prints an error to stderr and exits with code 2.

### `nide assemble`

```
nide assemble [-d|--debug] [-c PATH|--config PATH] [-t CHANNEL|--target CHANNEL]
```

| Option       | Default         | Description                                                                          |
|:-------------|:----------------|:-------------------------------------------------------------------------------------|
| `-t CHANNEL` | (IR only)       | Compat: build IR + render `CHANNEL` in one step (`gfm-plus`, `html`, `typst`, `pdf`) |
| `-c PATH`    | `etc/nide.yaml` | Path to the assembly configuration file                                              |
| `-d`         | off             | Debug mode: write per-stage snapshot files                                           |

Without `-t`, writes only the channel-neutral IR to `build/ir.json`.

### `nide render`

```
nide render -t CHANNEL[,CHANNEL…] [-c PATH|--config PATH] [-d|--debug]
```

| Option           | Default         | Description                                                 |
|:-----------------|:----------------|:------------------------------------------------------------|
| `-t CHANNEL[,…]` | (required)      | Channels to render: `gfm-plus`, `html`, `typst`, `pdf`; CSV |
| `-c PATH`        | `etc/nide.yaml` | Path to the assembly configuration file                     |
| `-d`             | off             | Enable debug-level logging                                  |

Reads `build/ir.json` (or the path in `output-paths.ir`).  Run `nide assemble` first.

### `nide inject`

```
nide inject [-c PATH|--config PATH]
```

| Option    | Default         | Description                             |
|:----------|:----------------|:----------------------------------------|
| `-c PATH` | `etc/nide.yaml` | Path to the assembly configuration file |

### `nide harvest`

```
nide harvest [-c PATH|--config PATH]
```

| Option    | Default         | Description                             |
|:----------|:----------------|:----------------------------------------|
| `-c PATH` | `etc/nide.yaml` | Path to the assembly configuration file |

### `nide explain`

```
nide explain [-c PATH|--config PATH] [-t CHANNEL|--target CHANNEL]
```

| Option       | Default         | Description                                    |
|:-------------|:----------------|:-----------------------------------------------|
| `-c PATH`    | `etc/nide.yaml` | Path to the assembly configuration file        |
| `-t CHANNEL` | (all channels)  | Limit channel-exclusion display to one channel |

### `nide doctor`

```
nide doctor [-c PATH|--config PATH] [--fix]
```

| Option    | Default         | Description                                       |
|:----------|:----------------|:--------------------------------------------------|
| `-c PATH` | `etc/nide.yaml` | Path to the assembly configuration file           |
| `--fix`   | off             | Create missing directories and run `nide harvest` |

Exit code is 1 if any ERROR-level check fails; 0 otherwise (warnings alone → 0).

### `nide eject`

```
nide eject [-o PATH|--output PATH] [-f|--force]
```

| Option    | Default | Description                                    |
|:----------|:--------|:-----------------------------------------------|
| `-o PATH` | stdout  | Write the template to `PATH` instead of stdout |
| `-f`      | off     | Overwrite `PATH` if it already exists          |

### `nide manifest`

```
nide manifest [-c PATH|--config PATH] [--summary [--format text|markdown|yaml]]
nide manifest [-c PATH|--config PATH] --inject-pdf PATH
```

| Option               | Default         | Description                                                                          |
|:---------------------|:----------------|:-------------------------------------------------------------------------------------|
| `-c PATH`            | `etc/nide.yaml` | Path to the assembly configuration file                                              |
| `--summary`          | off             | Read and print existing manifest; do not regenerate                                  |
| `--format FORMAT`    | `text`          | Summary format: `text`, `markdown`, or `yaml`                                        |
| `--inject-pdf PATH`  | (none)          | Inject `metadata:` fields as XMP into PDF; writes `<stem>-injected.pdf`; needs pyHanko |

Without `--summary`, `nide manifest` generates (or regenerates) `build/manifest.json`
and embeds blocks in channel outputs.  Requires the `tallipoika` package
(`pip install nide[manifest]`).

With `--summary`, reads the existing manifest and prints to stdout; no tallipoika needed.

`--inject-pdf` writes Dublin Core XMP metadata from the `metadata:` config section
into the PDF.  Exit 1 if pyHanko is absent; exit 2 if the PDF is not found.

### `nide validate`

```
nide validate [-c PATH|--config PATH] [--strict] [--quiet]
```

| Option     | Default         | Description                                              |
|:-----------|:----------------|:---------------------------------------------------------|
| `-c PATH`  | `etc/nide.yaml` | Path to the assembly configuration file                  |
| `--strict` | off             | Treat WARN-level findings as failures (exit 1)           |
| `--quiet`  | off             | Suppress per-check output; only exit code is informative |

Exit code 0: all six checks pass.  Exit code 1: at least one check fails
(or WARN when `--strict`).  Exit code 2: usage error or `build/manifest.json`
not found.

### Environment

| Variable             | Effect                                                              |
|:---------------------|:--------------------------------------------------------------------|
| `NIDE_CONFIG`        | Default config path when `-c` is not given (all subcommands)        |
| `NIDE_DEBUG`         | Enable debug output; equivalent to `-d` (assemble and render modes) |
| `DUMP_LUT`           | Write back section LUT files to `etc-path` after assembly           |
| `SOURCE_DATE_EPOCH`  | Unix timestamp (seconds since epoch) used by `nide diff --mode repro` to normalise build timestamps; see below |

#### `SOURCE_DATE_EPOCH` and reproducible builds

`SOURCE_DATE_EPOCH` is a [reproducible-builds.org](https://reproducible-builds.org/docs/source-date-epoch/) standard.
When set, `nide diff` in `repro` mode replaces recognised build timestamps — ISO date/datetime values
in `<meta content="…">` attributes and, when `+xmp` is active, in the XMP block — with the canonical UTC
string `YYYY-MM-DDTHH:MM:SSZ` derived from the epoch value, rather than the generic placeholder `[timestamp]`.
This makes two builds of the same source compare as identical regardless of when they were run.

Authors typically anchor the variable to their last VCS commit before building:

```bash
# git
export SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)

# Fossil
export SOURCE_DATE_EPOCH=$(fossil info tip | awk '/^uuid:/{print $NF}' | xargs fossil time -R . | awk 'NR==1{print $1" "$2}' | date -f - +%s 2>/dev/null)
```

Or set it once in the project Makefile so every `make html pdf` is automatically anchored:

```makefile
SOURCE_DATE_EPOCH ?= $(shell git log -1 --format=%ct)
export SOURCE_DATE_EPOCH
```

Without the variable, `repro` mode still suppresses timestamp noise — timestamps become `[timestamp]`
rather than a real date — so setting it is an opt-in for commit-anchored, bit-reproducible builds.

## Dependencies

Core (always installed):

- [jsonschema](https://pypi.org/project/jsonschema/) — JSON Schema validation
- [kaava](https://pypi.org/project/kaava/) — typed configuration from YAML
- [python-jsonpath](https://pypi.org/project/python-jsonpath/) — JSONPath expression scanning
- [PyYAML](https://pypi.org/project/PyYAML/) — YAML parsing
- [vyyhti](https://pypi.org/project/vyyhti/) — Markdown embedding scanner

Optional — manifest provenance (`pip install nide[manifest]`):

- [tallipoika](https://pypi.org/project/tallipoika/) — RFC 8785 canonical JSON (required for `nide manifest` generation)
- [blake3](https://pypi.org/project/blake3/) — BLAKE3 hashing (enhancement; sha256-only fallback if absent)
- [pyHanko](https://pypi.org/project/pyhanko/) — PDF XMP injection; used by `nide manifest --inject-pdf`

## Design and requirements

| Document                            | Identifier  | File                                            |
|:------------------------------------|:------------|:------------------------------------------------|
| Software Requirements Specification | NID-SRS-001 | [requirements/srs/](requirements/srs/README.md) |
| Software Design Description         | NID-SDD-001 | [design/sdd/](design/sdd/README.md)             |

Both documents follow the MIL-STD-498 DID structure and are rendered into
the documentation site alongside the quickstart and tutorial.

## Bug Tracker

Any feature requests or bug reports shall go to the [todos of nide](https://todo.sr.ht/~sthagen/nide).

## Primary Source repository

The main source of `nide` is on a mountain in Central Switzerland under
configuration control ([fossil](https://fossil-scm.org/)).

## Contributions

If you like to share small changes under the repositories license please kindly
do so by sending a patchset.
You can send such a patchset per email using [git send-email](https://git-send-email.io).

## Support

Please kindly submit issues at https://todo.sr.ht/~sthagen/nide or write plain
text email to ~sthagen/nide@lists.sr.ht to support.
Thanks.

## Security Policy

See `SECURITY.md` for the security policy.

## Changes

See `docs/changes.md` for the release history.

## Coverage

The test suite maintains high branch coverage (≥99%).
The HTML report (if generated) is in `site/coverage/`.

## SBOM

Runtime dependency information is published in `docs/sbom/` in SPDX 3.0 (JSON-LD)
and CycloneDX 1.6 (JSON) formats.
See `docs/sbom/README.md` for the component inventory and validation guide.
