#Build Your Own Presentation
Where a theme changes how the app looks (colors,
fonts, icons), the presentation layer is how it's structured: nav
arrangement, Home's sections, library sorting/grouping, reader defaults,
player controls, and what content units are called. It lives in its own file,
presentation/<id>/presentation.json, separate from identity (brand.json)
and from per-install config (deployment.json) — see
build-your-own-theme.md for how the three
relate. Example presentation templates live in the
theme & template gallery.
#It's a runtime file, like your brand
presentation.json is served and read on every request, the same way
brand.json and theme.css are — not baked into the JavaScript bundle.
Replace it on a deployed site and the next page load reflects the new
arrangement: no rebuild, no redeploy of the app bundle. A missing key always
falls back to StoryLark's built-in default, so a presentation file written
today keeps working on every future engine version, and an unknown key is
ignored with a warning rather than breaking the page. The easiest way to
change it on a live site is a theme package
that includes a presentation.json — installed from the admin portal's
Brand & themes card or npm run import-theme, with the same validation,
version history, and one-click rollback a theme gets.
#What's configurable
Home Continue card (resume where you left off) + new-releases carousel
Library the shelf — sorting, search, cover art (list vs grouped by layout)
Book a unit's detail / chapter list
Reader read / listen / read+listen with word-synced highlighting
Now Playing full-screen audio player
Settings account, typography, theme, read-along mode, downloads
About app version, changelog, roadmap
presentation.json drives, without touching code:
| Key | Controls |
|---|---|
nav |
Tab bar position, which entries appear, their order, their labels. |
home.sections |
Which sections Home shows and in what order. |
library |
Default sort, which sorts/groupings the picker offers, list vs. grid, whether search shows. |
reader.defaultMode |
Read, Listen, or Read + Listen as the opening mode. |
player |
Skip distance, the speed-control options. |
cover.aspect |
Cover art aspect ratio across the app. |
detail |
Which of cover / author / description / chapter list / length show on a unit's detail screen. |
auth.required |
Whether an account is required to use the app. |
settings |
Which controls the Settings screen offers. |
download.mode |
Offline-download behavior. |
emptyState |
Copy shown on an empty shelf. |
about.links |
Links shown on the About screen. |
layout / nouns |
See below — the original two knobs, now part of the same file. |
Full key-by-key defaults live in storylark-core's DEFAULT_PRESENTATION; every
value above defaults to the app's existing, shipped behavior, so an empty or
partial presentation.json changes nothing you didn't explicitly set.
#layout — flat vs series
"layout": "flat" // standalone units in one flat list, no collection level
"layout": "series" // units grouped into collections
flat— every unit stands alone and shows in a single flat list (think a library of standalone short stories). No collection/grouping level. Flat manifests may still carry an optionalgrouplabel and an in-worldtimeframe("YYYY-MM") for chronological sorting.series— units are grouped into collections (think books grouped into a series). Manifest entries carryseries,seriesOrder,bookOrder, etc.
The layout value also changes an auto-download default (see the autoDownload
setting in packages/core/src/lib/types.ts): flat auto-downloads new units; series keeps a
whole collection downloaded.
#nouns — what a "unit" and "collection" are called
Every user-visible content word is pulled from presentation.json nouns —
the app never hardcodes "story", "chapter", or "book". Consumed via
packages/core/src/presentation.ts (NOUNS, countUnits()).
"nouns": {
"unit": "story", "unitPlural": "stories",
"Unit": "Story", "UnitPlural": "Stories",
"collection": null, "Collection": null
}
| Key | Meaning |
|---|---|
unit / unitPlural |
Lowercase singular/plural of one content unit ("story"/"stories", "chapter"/"chapters"). |
Unit / UnitPlural |
Capitalized forms for sentence starts / headings. |
collection / Collection |
The grouping level's name ("book", "series"), or null for a flat library. |
Example for a chaptered-book library:
"layout": "series",
"nouns": {
"unit": "chapter", "unitPlural": "chapters",
"Unit": "Chapter", "UnitPlural": "Chapters",
"collection": "book", "Collection": "Book"
}
countUnits(n) then renders "1 chapter" / "3 chapters" automatically, and
layout: "series" groups those chapters under their book.
#Content shape
The manifest and chapter schema the presentation renders is produced by the
publish pipeline. Blocks the reader knows how to render (from
packages/core/src/lib/types.ts / BlockRenderer):
paragraph (with em/strong spans) · scene-break · display-beat ·
message-block (speaker/time/text) · image · end-marker.
How markdown maps to these blocks is documented in
content-pipeline.md; how the reader renders and
highlights them is in read-along.md.
#What still means editing components
Screens themselves — their internal composition beyond the toggles in the table above, and adding an entirely new screen — aren't config-driven. The shape of a screen (nav position, sections, sort options, defaults, what's shown) is presentation data; the code that renders a screen is still the engine. If a template you want isn't expressible with the keys above, that's the line: browse existing arrangements in the gallery for ones close to what you want, retune the JSON, and see the submission guide to share your own.
Found a gap? StoryLark is open source — improve these docs on GitHub.