Skip to content
inflatable-cookiePublic

About

Generic Nightfire TypeScript and Svelte runtime

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Nightfire

Nightfire is the standalone generic block-content system for Rust and TypeScript/Svelte. It owns the durable value envelope, blocks, strategies, registries, normalization, validation, hashing, block IDs, media locators, markdown rendering, and generic editors. Product schemas, product blocks, and application integrations stay in consumers.

The repository keeps the TypeScript/Svelte implementation under ts/, the Rust crate nightfire under rust/, and shared wire fixtures at the root. 0.1.0 is published on npm and tagged v0.1.0.

Install

After the first dual-language release, TypeScript consumers use:

bun add github:inflatable-cookie/nightfire#<commit>

Rust consumers use the same immutable repository tag:

nightfire = { git = "ssh://git@github.com/inflatable-cookie/nightfire.git", tag = "v0.5.0" }

Svelte 5 is a peer dependency. @inflatable-cookie/poodle-svelte is an optional peer for editor entry points only. A renderer-only consumer does not install Poodle:

import { NightfireRenderer } from "@inflatable-cookie/nightfire/renderer";
import "@inflatable-cookie/nightfire/render-registrations";

Consumers using the editor must add Poodle and load the default editor styling; renderers carry no appearance and need none of it:

import "@inflatable-cookie/nightfire/styles.css";

The stylesheet is the editor's default appearance layer, not a renderer theme. Its declared --nightfire-* names are the public override surface: consumers can re-declare a name in their own scope without a fork, build step, or Nightfire dependency. The six app-shaped names (color-surface, color-surface-secondary, color-danger, color-field-bg, button-chip-padding-block and button-chip-padding-inline) are intentionally retained as stable editor-chrome API names.

To align Nightfire's editor defaults with Poodle's variables, map them on the wrapper that contains the editor:

.nightfire-theme {
  --nightfire-color-danger: var(--poodle-color-status-danger);
  --nightfire-color-border-subtle: var(--poodle-color-border-default);
}

Keep this mapping wrapper-scoped; a :root override depends on stylesheet load order. The mapping is a consumer recipe, not a Poodle dependency.

Imports

Use the narrowest export for the job:

import type { NightfireValue } from "@inflatable-cookie/nightfire/types";
import { prepareNightfireForSave } from "@inflatable-cookie/nightfire/validation";
import { NightfireRenderer } from "@inflatable-cookie/nightfire/renderer";
import { renderRichTextHtml } from "@inflatable-cookie/nightfire/rich-text";

core and validation have no Svelte runtime edge. Registry modules use Svelte only for type declarations. Importing renderer does not load editors, registration side effects, or Poodle. Editor and renderer registration modules remain explicit opt-ins. Editors need Poodle installed; renderers do not.

The Rust crate exposes the matching value, block, strategy, registry, validation, hashing, block-ID, and media-locator contracts. Both languages use the shared versioned fixtures under fixtures/wire/.

JSON Schema 2020-12 documents for the generic wire mechanics and every declared core block payload ship under schemas/. Their stable mechanics identifiers are nightfire.value@1, nightfire.block@1, nightfire.registry@1, and nightfire.strategy@1. References stay relative so consumers can copy and validate the tree offline.

The wire shape is:

interface NightfireValue {
  schema: string;
  blocks: Array<{
    id?: string;
    type: string;
    version: string;
    data: Record<string, unknown>;
  }>;
}

Saving validates versions, drops invalid blocks, and assigns missing nf_ UUIDv7-style block IDs. Unknown versions fail closed. Legacy v1 sibling block envelopes are rejected rather than silently migrated.

Markdown is parsed by marked and sanitized by isomorphic-dompurify before entering Svelte {@html}. The malicious fixture suite exercises SSR, browser, and a Tauri-shaped WebView environment.

Rich text and consumer inline nodes

rich_text documents render through one public entry point that composes any schema-scoped consumer inline-node renderers and sanitizes the result:

import {
  registerRichTextInlineNodeRenderer,
  renderRichTextHtml
} from "@inflatable-cookie/nightfire/rich-text";

registerRichTextInlineNodeRenderer("acme.maths", "math_inline", ({
  node,
  escapeText,
  escapeAttribute
}) => {
  const latex = String(node.attrs.latex ?? "");
  return `<span class="acme-math" data-latex="${escapeAttribute(latex)}">${escapeText(latex)}</span>`;
});

const html = renderRichTextHtml(storedDocument, { schema: "acme.maths" });

NightfireRenderer and the registered rich_text block renderer pass the value's schema on, including through nested item_list items, so a registration applies wherever the document sits.

  • Registration is scoped to one schema. There is no wildcard, and no registration answers for another schema.
  • An admitted built-in node type cannot be overridden; registering one throws.
  • The last registration for a schema and type wins. unregisterRichTextInlineNodeRenderer removes it and restores the fallback.
  • Marks compose around the returned HTML. The renderer also receives the node's attributes, its rendered childrenHtml (empty for a leaf), and escapeText/escapeAttribute helpers.
  • An unregistered leaf, or a renderer that throws or returns a non-string, renders a visible escaped fallback — <span data-nightfire-inline-node="math_inline" data-nightfire-inline-fallback="unregistered">math_inline</span>. An unknown container keeps its children instead.
  • The result is sanitized and ready for {@html}; the raw serializer is not public. Representative HTML maths markup survives, but arbitrary MathML or SVG is not promised.

This is rendering only. The rich-text editor still follows Poodle's closed vocabulary, does not round-trip a consumer node, and emptiness still treats an unknown leaf as no content. Typesetting and styles for registered output are the consumer's; Nightfire ships no maths markup and no appearance.

Development

effigy tasks
effigy test --plan
effigy qa

Project documentation

Start at docs/README.md. It routes to the package vision, architecture, contracts, and plan. Dependency extraction evidence lives in PROVENANCE.md; contribution rules live in CONTRIBUTING.md.

Dependencies

  • @inflatable-cookie/poodle-svelte (optional peer): generic field, control, markdown, and rich-text editor components. Not a renderer dependency.
  • svelte (peer): component runtime supplied by the consumer.
  • marked: retained markdown parsing behavior.
  • isomorphic-dompurify: one sanitizer contract across DOM and SSR runtimes.
  • serde and serde_json: retained Rust wire serialization and JSON payloads.
  • blake3: retained deterministic block-data hashing.
  • uuid: retained UUIDv7 block ID generation and serialization.
  • thiserror: retained typed media-locator and validation errors.

Test-only packages provide Bun types, TypeScript/Svelte checking, jsdom, and DOM component assertions. The completed repository must contain no Underlay, SvelteKit, Vite, bits-ui, lucide-svelte, zod, or smol-toml dependency in either language tranche.

License

MIT. See LICENSE.

About

Generic Nightfire TypeScript and Svelte runtime

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages