# Principles

These are not style preferences. Each one closes a failure that has already happened in a project
like this one, and each one has a price that is paid on purpose.

## One source, two faces

Every Markdown story has a **typed JSON twin**: the same content, validated by the format's schema,
convertible in both directions and tested in both directions. Neither is the original.

_Why._ A human wants to write prose and read a diff. A machine — an agent, a CMS, a script — wants a
structure it can validate. Pick one and you lose the other; keep a converter you do not test and the
two drift within a month.

_The price._ Every block, directive and attribute has to exist twice, in the parser and in the
schema, and a round-trip test runs over every story in the repository. A format cannot be
half-added.

## One type system

Shared types — the story, the scene, attributes, configuration, diagnostics, translations — are
defined **once**, in the core, derived from the zod schemas. A format package never redefines one.

_Why._ The second definition is never a copy for long. It gains a field, loses a constraint, and
then two parts of the same program disagree about what a scene is.

_The price._ The core holds more than feels natural, and moving a type is a change that ripples.

## One stack

No dependency that duplicates one already present. No second way of doing something that already has
a way.

_Why._ A monorepo with two bundlers, two test runners or two date libraries spends its maintenance
budget on the seams between them.

_The price._ Sometimes the slightly worse tool wins because it is already there.

## Content is not trusted

Raw HTML in a Markdown story is sanitized by default. A directive cannot raise its own privileges —
it cannot lower the diagram security level, inject props React treats specially, or forge the
mounting of a component. MDX is the exception and is treated as code, never sanitized, because that
is what it is.

_Why._ A story often comes from somewhere else: a CMS, a pull request, an agent reading a PDF. The
author of a story is not always the owner of the site that renders it.

_The price._ A deliberate escape hatch has to be configured, and an `.mdx` file has to be reviewed
like code.

## Nothing is published quietly

A release is manual and approved by a human. An agent never publishes. Every bundled asset and every
non-permissive dependency is listed before it is merged, with its licence.

_Why._ This repository imports code written by someone else, and it embeds fonts and generated
images. Attribution that is added later is attribution that is wrong in between.

## Accessibility is a diagnostic, not a lint rule

A diagram without an accessible name, a slide too dense to read, an image without a description:
these produce diagnostics that travel with the story and are reported by whatever builds it — the
command line _and_ a site build.

_Why._ A warning nobody sees is a warning that does not exist. This rule found a real defect the day
it was enforced: a diagram in one of the example decks had never had an accessible title, and site
builds had been swallowing the message for weeks.

## The site framework is reached through its plugins

The formats reach the site framework through its plugin interface, `@recit/site-stories`, rather
than by changing its core.

_Why._ A plugin is easier to test and to move than a change scattered through the framework, and the
published packages stay independent of a framework that is never published.

_The price._ Some things are done the long way, through a plugin, when a three-line change would do.
