The Story Model
The StoryModel class is the root data structure for every StoryCAD outline. Understanding how it organizes elements is essential for working with the API.
Overview
A StoryModel contains three things:
- A flat element collection (
StoryElementCollection): every story element stored in a dictionary keyed by GUID - Two tree views:
ExplorerView(for authoring) andNarratorView(for reading order) - A trash view:
TrashViewfor soft-deleted elements
StoryModel
├── StoryElements ← flat dictionary of all elements (keyed by GUID)
├── ExplorerView ← tree: Overview → Problems, Characters, Scenes, Settings, Folders
├── NarratorView ← tree: Sections containing Scenes in reading order
└── TrashView ← tree: soft-deleted elements
The Element Collection
StoryElementCollection extends ObservableCollection<StoryElement> and automatically maintains:
StoryElementGuids: aDictionary<Guid, StoryElement>for O(1) lookup by GUIDCharacters,Settings,Problems,Scenes: filtered sub-collections by type
When you call GetAllElements(), you receive the full StoryElementCollection. When you call GetElement(guid), the API looks up the element in StoryElementGuids.
// Get every element
var all = api.GetAllElements().Payload;
// Get one element by GUID
var element = api.GetElement(someGuid).Payload;
// Get all elements of a type
var characters = api.GetElementsByType(StoryItemType.Character).Payload;
# Get every element
all_elements = sc.get_all_elements()
# Get one element by handle (returns its full state as a JSON string)
element = sc.get_element(some_element)
# Get all elements of a type
characters = sc.get_elements_by_type(sc.item_type.Character)
GUID-Based Cross-Referencing
Elements reference each other through GUIDs, not object references. This is the most important concept for API consumers.
For example, a ProblemModel has:
public Guid Protagonist { get; set; } // GUID of a CharacterModel
public Guid Antagonist { get; set; } // GUID of a CharacterModel
# A Problem's reference fields are GUID strings (C#-type concept).
# Read them from the element's JSON state:
detail = json.loads(sc.get_element(problem))
protagonist_guid = detail.get("Protagonist") # GUID of a Character
antagonist_guid = detail.get("Antagonist") # GUID of a Character
A SceneModel has:
public Guid Setting { get; set; } // GUID of a SettingModel
public Guid ViewpointCharacter { get; set; } // GUID of a CharacterModel
public Guid Protagonist { get; set; } // GUID of a CharacterModel
public Guid Antagonist { get; set; } // GUID of a CharacterModel
public List<Guid> CastMembers { get; set; } // GUIDs of CharacterModels
# A Scene's reference fields are GUID strings (C#-type concept).
# Read them from the element's JSON state:
detail = json.loads(sc.get_element(scene))
setting_guid = detail.get("Setting") # GUID of a Setting
viewpoint_guid = detail.get("ViewpointCharacter") # GUID of a Character
protagonist_guid = detail.get("Protagonist") # GUID of a Character
antagonist_guid = detail.get("Antagonist") # GUID of a Character
cast_member_guids = detail.get("CastMembers") # list of Character GUIDs
Why GUIDs instead of object references? The GUID-based design enables:
- JSON serialization without circular references
- Stable references that survive save/load cycles
- Multiple views of the same element (ExplorerView and NarratorView can reference the same Scene)
- Safe deletion (removing an element doesn’t break the object graph)
An unassigned reference uses Guid.Empty. For example, a new Scene’s Setting property is Guid.Empty until a Setting is assigned.
Resolving References
To follow a GUID reference, look up the target element:
// Get a scene
var sceneResult = api.GetElement(sceneGuid);
// The scene's Setting field is a GUID string: parse it and look up the setting
var settingGuid = /* extract from scene data */;
var settingResult = api.GetElement(settingGuid);
# Get a scene's full state as JSON
scene_data = json.loads(sc.get_element(scene))
# The scene's Setting field is a GUID string: extract it and look up the setting
setting_guid = scene_data.get("Setting")
setting_data = json.loads(sc.get_element(setting_guid))
To find all elements that reference a given element, use SearchForReferences:
// Find everything that references this character
var refs = api.SearchForReferences(characterGuid).Payload;
// Returns: scenes where they appear, problems where they're protagonist/antagonist, etc.
# Find everything that references this character
refs = sc.search_for_references(character)
# Returns: scenes where they appear, problems where they're protagonist/antagonist, etc.
The Explorer View
The ExplorerView is the authoring tree. Its root is always the StoryOverview element (the story’s title node). All other elements are children or descendants of the overview.
Overview: "My Novel"
├── Problem: "The Central Conflict"
├── Character: "Alex"
├── Character: "Jordan"
├── Folder: "Act 1"
│ ├── Scene: "Opening"
│ └── Scene: "Inciting Incident"
├── Setting: "The City"
└── StoryWorld: "Story World"
Rules:
- The overview is always the first (root) node
- Folders organize elements in the Explorer View
- Any element type except Section can be a child of the overview or a folder
- The TrashCan is a separate root node in
TrashView
The Narrator View
The NarratorView organizes scenes in reading order using Section elements (chapters, acts, parts):
Section: "Part 1"
├── Section: "Chapter 1"
│ ├── Scene: "Opening"
│ └── Scene: "Inciting Incident"
└── Section: "Chapter 2"
└── Scene: "Rising Action"
Rules:
- Only Section and Scene elements belong in the Narrator View
- Sections can nest (Part → Chapter → Scene)
- Scenes in the Narrator View reference the same
StoryElementas in the Explorer View
Singleton Elements
Three element types have at most one instance per story:
| Type | Purpose | Always present? |
|---|---|---|
StoryOverview |
Story metadata (title, author, premise) | Yes, root of ExplorerView |
TrashCan |
Container for soft-deleted elements | Yes, root of TrashView |
StoryWorld |
Worldbuilding data | No, optional, created on demand |
Access the overview via GetElementsByType(StoryItemType.StoryOverview). Access the StoryWorld via GetStoryWorld().
The Changed Flag
StoryModel.Changed is a dirty bit. It becomes true whenever an element is added, removed, moved, or has a property updated. The API sets this automatically; you don’t need to manage it. It’s used internally by auto-save.
File Format
Story outlines are saved as .stbx files (JSON). The serialized format contains:
- All
StoryElementinstances (polymorphic, with type discriminator) - Flattened tree structures (
FlattenedExplorerView,FlattenedNarratorView,FlattenedTrashView) as lists of(Uuid, ParentUuid)pairs - Version metadata (
CreatedVersion,LastVersion)
The tree views are reconstructed from the flattened lists on load.
// Save
await api.WriteOutline("my-story.stbx");
// Load
await api.OpenOutline("my-story.stbx");
# Save
sc.write_outline("my-story.stbx")
# Load
sc.open_outline("my-story.stbx")