@rohal12/spindle
v0.64.11
Published
A Preact-based story format for Twine 2.
Maintainers
Readme
Spindle
A modern Twine 2 story format built with Preact. Variables, macros, saves, settings, widgets, and full CommonMark markdown — all using a concise curly-brace syntax.
Install
npm install @rohal12/spindleThe package (the compiler entry point, @rohal12/spindle/tooling, @rohal12/spindle/headless and @rohal12/spindle/play) needs Node.js 24 or later. Compiled stories run in current browsers.
Features
- Curly-brace macro syntax:
{if $health > 0}...{/if},{set $name = "Hero"} - Story and temporary variables with dot notation
- Full CommonMark markdown (GFM tables, strikethrough)
- Form inputs: textbox, numberbox, checkbox, radio, listbox, cycle
- Save system with playthroughs, quick save/load, and export/import
- Persistent settings (toggle, list, range)
- Reusable widgets
window.StoryJavaScript API for scripting- StoryVariables passage for strict variable declarations
Usage with twee-ts
import * as spindle from '@rohal12/spindle';
import { compile } from '@rohal12/twee-ts';
const result = await compile({
sources: ['src/'],
format: spindle,
});Spindle’s optional compiler/play integration requires twee-ts 2.x.
Or install both and let twee-ts auto-discover the format via the twine-story-format keyword.
Documentation
Full docs at rohal12.github.io/spindle:
- Markup — Links, variables, macros, HTML, markdown
- Macros — Complete macro reference
- Variables — Story and temporary variables
- Special Passages — StoryInit, StoryVariables, StoryInterface
- Saves — Save system
- Settings — Toggle, list, and range settings
- Story API — The
window.StoryJavaScript API - Widgets — Reusable content blocks
- npm Package — Packaging guide
- Twine Editor Extensions — Highlighting, references and a toolbar in Twine's passage editor
Development
bun install
bun run test # run tests
bun run build # build format
bun run preview # build + compile dev story
bun run docs:dev # local docs dev server
bun run docs:build # build docs for deployment
bun run duplication # code duplication checks (CPD needs PMD_BIN)Code duplication
CI measures duplication in src/ with three tools, each a separate check:
jscpd and PMD CPD (exact copies) and fallow (copies with renamed
identifiers). A pull request fails when it adds duplication compared with
main or exceeds duplication-budget.json. The settings are in
.jscpd.json and .fallowrc.json; scripts/duplication.ts runs the
checks and compares against a base (--base main). Locally, CPD needs
PMD 7 with PMD_BIN set to its bin/pmd.
Before adding a helper, parser or hook, look for the one that already does the job and extend it; most fixes then land in one place. The shared modules:
| Job | Module |
| -------------------------------------------------------- | ------------------------------------------------------------------- |
| Name-keyed storage, own-key reads (hasOwn, ownValue) | src/utils/namespace.ts |
| Case-insensitive macro/widget names | src/utils/macro-names.ts |
| Clone, equality, structural diff and merge | src/structural.ts |
| Macro arguments: declared parameters, ctx.args | src/components/macros/macro-args.ts |
| Quoting, escapeRegExp, splitting, string scanning | src/components/macros/arg-utils.ts |
| JavaScript scanning (findCodeEnd, lexJs) | src/js-lexer.ts |
| Which declaration a variable names (Bindings) | src/js-bindings.ts |
| Sigils and scopes (SIGIL_SCOPES, SCOPE_SIGILS) | src/markup/tokens.ts |
| Passage markup (parseMarkup, tokenizeMarkup) | src/markup/parse.ts (grammar: src/markup/spindle.peggy) |
| Where code in {…} ends (closeBrace, rawBodyEnd) | src/markup/code-end.ts |
| Which closer closes which opener (pairMarkup, the AST) | src/markup/pair.ts (buildAst in src/markup/ast.ts) |
| Code, passage names and markup text in a passage | src/code-check.ts (passagePiecesOf) |
| Declarations of StoryVariables (parseDeclarations) | src/story-variables.ts (literals: src/static-literal.ts) |
| Reading story state / render contexts in components | src/hooks/use-story-fields.ts, src/hooks/use-render-options.ts |
| Save storage operations | src/saves/storage.ts (createBackend) |
| Listeners to notify (createListeners) | src/utils/listeners.ts |
| Checking plain data handed in (checkObject, matchers) | src/utils/field-checks.ts (checkActionMatcher: action registry) |
License
This is free and unencumbered software released into the public domain. See UNLICENSE.
