Files
storybook/docs/advanced/23-best-practices.md
Sienna Meridian Satterwhite 47fafdc2bf feat(lang): complete extends to modifies keyword migration
This commit completes the migration started in the previous commit,
updating all remaining files:

- Lexer: Changed token from Extends to Modifies
- Parser: Updated lalrpop grammar rules and AST field names
- AST: Renamed Schedule.extends field to modifies
- Grammar: Updated tree-sitter grammar.js
- Tree-sitter: Regenerated parser.c and node-types.json
- Examples: Updated baker-family work schedules
- Tests: Updated schedule composition tests and corpus
- Docs: Updated all reference documentation and tutorials
- Validation: Updated error messages and validation logic
- Package: Bumped version to 0.3.1 in all package manifests

All 554 tests pass.
2026-02-16 22:55:04 +00:00

7.6 KiB

Best Practices

This chapter compiles best practices for writing clear, maintainable, and effective Storybook code. These guidelines apply across all declaration types and project sizes.

Naming Conventions

Declarations

Use PascalCase for all declaration names:

character MasterBaker { }          // Good
species DomesticCat { }            // Good
behavior GuardPatrol { }           // Good

character master_baker { }         // Avoid
behavior guard-patrol { }         // Invalid (no hyphens)

Fields

Use snake_case for field names:

character Martha {
    skill_level: 0.95            // Good
    emotional_state: focused     // Good
    years_experience: 22         // Good

    skillLevel: 0.95             // Avoid (camelCase)
}

Behavior Tree Labels

Use snake_case for node labels, with descriptive names:

choose survival_instinct {       // Good
    then fight_response { }      // Good
    then flight_response { }     // Good
}

choose s1 {                      // Avoid (meaningless)
    then a { }                   // Avoid
}

Enum Variants

Use PascalCase or snake_case consistently within an enum:

// PascalCase (good for short names)
enum Size { Tiny, Small, Normal, Large, Huge }

// snake_case (good for compound names)
enum GovernmentStyle {
    absolute_tyranny,
    constitutional_monarchy,
    direct_democracy
}

File Organization

Separate Schema from World

Keep reusable type definitions separate from instance data:

project/
  schema/           # Reusable across stories
    core_enums.sb   # Enum definitions
    templates.sb    # Template definitions
    beings.sb       # Species definitions
  world/            # Specific to this story
    characters/     # Character instances
    behaviors/      # Behavior trees
    relationships/  # Relationship instances
    locations/      # Location instances

One Concern per File

Group related declarations, but avoid putting unrelated things together:

// characters/bakery_staff.sb - Good: related characters together
character Martha { }
character Jane { }
character Elena { }

// everything.sb - Avoid: everything in one file
character Martha { }
behavior BakeRoutine { }
schedule DailyRoutine { }
relationship Partnership { }

Explicit Imports

Prefer explicit imports over wildcards:

// Good: clear what is being used
use schema::core_enums::{SkillLevel, Specialty};
use schema::beings::Human;

// Avoid: unclear dependencies
use schema::core_enums::*;
use schema::beings::*;

Character Design

Use Species for Identity, Templates for Traits

// Species: ontological identity
species Human { lifespan: 70 }

// Templates: compositional traits
template Warrior { strength: 10..20 }
template Scholar { intelligence: 15..20 }

// Character: combines identity and traits
character Aragorn: Human from Warrior {
    strength: 18
}

Document with Prose Blocks

character Martha: Human {
    age: 34

    ---backstory
    Martha learned to bake from her grandmother, starting at
    age twelve. She now runs the most popular bakery in town.
    ---

    ---personality
    Meticulous and patient, with an unwavering commitment to
    quality. Tough but fair with her staff.
    ---
}

Prefer Flat Inheritance

Avoid deep species hierarchies. Two or three levels is usually enough:

// Good: shallow
species Mammal { warm_blooded: true }
species Human includes Mammal { sapient: true }

// Avoid: too deep
species Being { }
species LivingBeing includes Being { }
species Animal includes LivingBeing { }
species Vertebrate includes Animal { }
species Mammal includes Vertebrate { }
species Human includes Mammal { }

Behavior Tree Design

Name Every Composite Node

// Good: self-documenting
choose daily_priority {
    then handle_emergency { }
    then do_work { }
    then relax { }
}

// Avoid: anonymous nodes
choose {
    then { }
    then { }
}

Keep Trees Shallow

Extract deep subtrees into separate behaviors:

// Good: flat with includes
behavior Main {
    choose mode {
        include CombatBehavior
        include ExplorationBehavior
        include SocialBehavior
    }
}

// Avoid: deeply nested
behavior Main {
    choose {
        then { choose { then { choose { then { Action } } } } }
    }
}

Use Decorators for Control Flow

// Good: decorator handles timing
cooldown(30s) { CastSpell }
timeout(10s) { SolvePuzzle }
retry(3) { PickLock }

// Avoid: manual timing in actions
CheckCooldownTimer
IfCooldownReady { CastSpell }

Expression Writing

Use Parentheses for Clarity

// Good: explicit grouping
on (health < 50 or is_poisoned) and has_antidote -> healing

// Risky: relies on precedence knowledge
on health < 50 or is_poisoned and has_antidote -> healing

Break Complex Conditions into Multiple Transitions

// Good: separate transitions, easy to read
state combat {
    on health < 20 and not has_potion -> desperate
    on surrounded and not has_escape -> desperate
    on enemy_count > 10 -> desperate
}

// Avoid: one massive condition
state combat {
    on (health < 20 and not has_potion) or (surrounded and not has_escape) or enemy_count > 10 -> desperate
}

Use is for Enum Comparisons

// Good: reads naturally
on status is active -> active_state
on skill_level is master -> teach_others

// Works but less readable
on status == active -> active_state

Schedule Design

Use Named Blocks for Override Support

// Good: named blocks can be overridden
schedule Base {
    block work { 09:00 - 17:00, action: standard_work }
}

schedule Variant modifies Base {
    block work { 05:00 - 13:00, action: early_work }
}
schedule DailyRoutine {
    // Morning
    block wake { 06:00 - 07:00, action: morning_routine }
    block breakfast { 07:00 - 08:00, action: eat }

    // Work
    block commute { 08:00 - 09:00, action: travel }
    block work { 09:00 - 17:00, action: work }

    // Evening
    block leisure { 18:00 - 22:00, action: relax }
    block sleep { 22:00 - 06:00, action: sleep }
}

Relationship Design

Use Roles for Clarity

// Good: roles clarify the relationship
relationship Marriage {
    Martha as spouse
    David as spouse
    bond: 0.9
}

// Less clear without roles
relationship Marriage {
    Martha
    David
    bond: 0.9
}

Use Perspectives for Asymmetry

// Good: captures different viewpoints
relationship TeacherStudent {
    Gandalf as teacher self { patience: 0.8 } other { potential: 0.9 }
    Frodo as student self { motivation: 0.7 } other { admiration: 0.95 }
    bond: 0.85
}

General Principles

  1. Readability over brevity: Storybook code should read like a narrative, not a puzzle.

  2. Explicit over implicit: Say what you mean. Use named nodes, explicit imports, and clear field names.

  3. Flat over deep: Shallow hierarchies, short behavior trees, and focused files are easier to maintain.

  4. Composition over inheritance: Prefer combining templates over building deep species hierarchies.

  5. Document with prose: Prose blocks are a feature, not clutter. Use them to explain intent alongside data.

  6. One concept per declaration: Each behavior tree, life arc, or schedule should have a single clear purpose.

Cross-References