Story Categories
See also: Data Model · Story Engine · Page Builder
The taxonomy every authored story is filed under. Source of truth: libs/stretched-types/src/story/story-category.ts. Both the builder's picker and any catalog reader import from here, so ids, labels, and icons never drift.
The 21-id taxonomy
STORY_CATEGORY_IDS (canonical display order — also the order STORY_CATEGORY_LIST renders in):
foundation, income-career, taxes, credit, savings-investing, housing,
utilities-bills, transportation, food-dining, healthcare, insurance,
family-education, pets, lifestyle, hobbies, subscriptions, vacations,
gifts, charity, retirement-legacy, misc
StoryCategory is the union of those ids. They are persisted on stored stories and published JSON — never rename one after first use; add a new id instead and retire the old one only after a data migration.
Each id maps to a StoryCategoryInfo in STORY_CATEGORIES: display label, one-line blurb, and a Material Icons ligature icon (e.g. income-career → "Income & Career" / work). misc is the only isCatchAll: true entry and is surfaced last.
Helpers
| Function | Behaviour |
|---|---|
isStoryCategory(value) | Type guard using an own-property check (not in, which would let 'constructor' etc. pass). |
storyCategoryLabel(id) | Label with the raw id as fallback. |
orderedCategories(ids) | De-dupes, returns canonical display order, drops unknown ids — a story authored against a newer taxonomy degrades cleanly on an older client. |
Multi-category stories
Story.categories: StoryCategory[] (libs/stretched-types/src/story/story.ts) — a story can belong to several categories, so overlapping stories aren't forced into one bucket (a wedding is Gifts + Family + Lifestyle; a car loan is Transportation + Credit). See Story Engine for the rest of the Story shape.
Categories on the catalog manifest
Categories are mirrored onto the lightweight catalog summary so browsing never downloads a full story graph. StoryCatalogService.summaries (libs/stretched-components/src/dynamic-host/storage/story-catalog.ts) maps each published story to a StorySummary:
{ name: story.name, version: story.version, categories: story.categories }
StorySummary itself is defined in libs/stretched-components/src/dynamic-host/storage/story-store.ts. The catalog (what users read, via the STORY_CATALOG_SOURCE token) is deliberately distinct from the store (what admins write, via STORY_STORE) — see Story Engine.
The builder chip picker
The admin story builder (apps/stretched/src/app/pages/admin/story-builder/story-builder.component.ts, see Page Builder) renders a chip per STORY_CATEGORY_LIST entry (icon + label). Chips toggle ids in a categories signal; the selection is saved onto the story as categories and re-hydrated when a story is loaded. A "Filed under: …" summary line lists the selected labels. Behaviour is covered by the describe('categories', …) block in story-builder.component.spec.ts.
Status: categories ride the data; the web browse UI is the map
The taxonomy rides on every Story and StorySummary, and the web /stories page shows each story's primary category as its node's group label on the story map (since 2026-07-21 that page is the lib sc-story-map with the account gate — see Story Engine § the public surface; the earlier chip-filter list UI was replaced). The mobile Stories tab still lists the catalog flat. A dedicated browse-by-category view remains possible on the same data path if wanted.