Nib
A minimal, copy-paste Ink runtime for SvelteKit: one onInit callback for game-specific wiring, a tick counter to bridge non-reactive inkjs to Svelte 5 runes.
About
Integrating Ink directly into a SvelteKit project scatters BindExternalFunction calls, game-data registration, and story mechanics through the load/continue/choose plumbing; nothing is reusable and the reactive boundary is hard to reason about. Nib was extracted from The Work once the generic runtime proved separable. The fix is a single injection seam: loadStory() handles BOM stripping, the error handler, and an optional storylet stub, then calls onInit(ink) after story creation but before playback. All game-specific binding lives there and nowhere else. The reactivity problem is handled by a tick counter incremented at the end of every continue(); because the UI reads tick inside a $derived, any getVariable() call re-evaluates whenever the story advances. inkjs is a plain mutable object with no Svelte awareness; the tick counter is the minimal bridge. The full runtime is two files. Drop the directory into any SvelteKit project, write an onInit, and own the code.
Highlights
- Single onInit injection seam: game-specific BindExternalFunction calls and data registration in one callback, nowhere else.
- tick counter bridges non-reactive inkjs to Svelte 5 runes: UI reads tick inside $derived, so getVariable() re-evaluates on every continue().
- Reactive story state via $state/$derived: canContinue, currentTags, history update automatically.
- Tag parsing for CLEAR, mood:x, and class:x: CLEAR wipes history, mood: writes data-mood for CSS, bare tags become class names.
- Copy-paste distribution: two files, no package.json, designed to be owned and edited per project.
By the numbers
- Commits
- 1
- Lines added
- +343
- Lines removed
- −0
- Source files
- 254
