Skip to content

Conversation

@cloutiertyler
Copy link
Contributor

Description of Changes

Two documentation improvements:

  1. Reducers documentation: Clarified that using global/static variables in reducers is undefined behavior, not just "values won't persist". Added six specific reasons why this is undefined:

    • Fresh execution environments
    • Module updates
    • Concurrent execution
    • Crash recovery
    • Non-transactional updates
    • Replay safety
  2. Access permissions documentation: Replaced the "Combining Both Techniques" example that used indexes on Option fields (which SpacetimeDB doesn't support) with a working example that filters by a required department field instead.

API and ABI breaking changes

None. Documentation only.

Expected complexity level and risk

1 - Documentation changes only.

Testing

  • Verify the reducers warning is clear and accurate
  • Verify the access permissions example compiles and makes sense

@cloutiertyler cloutiertyler changed the title Tyler/claude docs 6 Small docs improvement Jan 20, 2026
… non-persistent

Explains the multiple reasons why relying on module-level state is
undefined: fresh instances, module updates, concurrent execution,
crash recovery, non-transactional updates, and replay safety.
- Change generate output path from ../client-unity/Assets/autogen to ../Assets/autogen
- Fix Rust directory references from server-rust to spacetimedb to match what spacetime init creates
- Update inline storage recommendations to reflect ~100MB capability
- Add pricing context ($1/GB) for choosing between inline and external storage
- Fix Unity tutorial generate command path (../Assets/autogen not ../client-unity/Assets/autogen)
- Fix Rust directory references from server-rust to spacetimedb
Strip volatile fields that change every run:
- Timestamps (started_at, finished_at)
- Local paths (work_dir_golden, work_dir_llm)
- Temp database names (golden_db, llm_db)
- Server port numbers from scorer notes

Also clear llm_output for passing tests (keep for failures for debugging).
Add documentation for calling between reducers and procedures:
- Add "Calling Reducers from Procedures" section showing how to invoke
  reducer functions within withTx blocks
- Add "Scheduling Procedures" section explaining how reducers can
  schedule procedures via schedule tables
- Add tip to schedule-tables.md clarifying procedure scheduling
- Wrap code snippets inside reducer functions
- Fix TypeScript: ctx.timestamp.microseconds → ctx.timestamp.microsSinceUnixEpoch
- Fix Rust: use std::time::Duration instead of spacetimedb::Duration

All examples verified to compile in TypeScript, Rust, and C#.
Add note explaining that the `pub` modifier on Rust table structs
follows normal Rust visibility rules and has no effect on SpacetimeDB
client visibility.
- Add "Uploading to S3 from a Procedure" section to file-storage docs
- Add "Pre-signed URL Flow" alternative pattern
- Replace all "C# procedures coming soon" with working examples
- Update procedures.md, reducers.md, access-permissions.md, cheat-sheet.md

All code examples verified to compile in TypeScript, Rust, and C#.
@cloutiertyler
Copy link
Contributor Author

/update-llm-benchmark

@clockwork-labs-bot
Copy link
Collaborator

LLM Benchmark Results (ci-quickfix)

Language Mode Category Tests Passed Task Pass %
Rust rustdoc_json basics 25/27 83.3% ⬆️ +38.9%
Rust rustdoc_json schema 23/34 65.3% ⬆️ +38.8%
Rust rustdoc_json total 48/61 75.2% ⬆️ +38.9%
Rust docs basics 5/27 11.1%
Rust docs schema 8/34 20.5%
Rust docs total 13/61 15.4%
C# docs basics 24/27 91.7% ⬆️ +30.6%
C# docs schema 25/34 73.7% ⬆️ +42.2%
C# docs total 49/61 83.5% ⬆️ +35.8%

Compared against master branch baseline

Generated at: 2026-01-21T14:48:14.116Z

Failure Analysis (click to expand)

Benchmark Failure Analysis

Generated from: /__w/SpacetimeDB/SpacetimeDB/tools/xtask-llm-benchmark/../../docs/llms/docs-benchmark-details.json

Summary

  • Total failures analyzed: 35

SpacetimeDB Benchmark Analysis

Rust / rustdoc_json Failures

Compile/Publish Errors (2 failures)

t_002_scheduled_table & t_017_scheduled_columns

  1. The generated code:

    use spacetimedb::{table, reducer, ReducerContext, Table, ScheduleAt};
    
    #[table(name = tick_timer, schedule(reducer = tick, column = scheduled_at))]
    pub struct TickTimer {
        #[primary_key]
        #[auto_inc]
        scheduled_id: u64,
        scheduled_at: ScheduleAt,
    }
    
    #[reducer(init)]
    pub fn init(ctx: &ReducerContext) {
        ctx.db.tick_timer().insert(TickTimer {
            scheduled_id: 0,
            scheduled_at: ScheduleAt::repeat_micros(50_000),
        });
    }
  2. The golden example:

    use spacetimedb::{reducer, table, ReducerContext, ScheduleAt, Table};
    use std::time::Duration;
    
    #[table(name = tick_timer, scheduled(tick))]
    pub struct TickTimer {
        #[primary_key]
        #[auto_inc]
        pub scheduled_id: u64,
        pub scheduled_at: ScheduleAt,
    }
    
    #[reducer]
    pub fn tick(_ctx: &ReducerContext, _schedule: TickTimer) {
    }
    
    #[reducer(init)]
    pub fn init(ctx: &ReducerContext) {
        let every_50ms: ScheduleAt = Duration::from_millis(50).into();
        ctx.db.tick_timer().insert(TickTimer {
            scheduled_id: 0,
            scheduled_at: every_50ms,
        });
    }
  3. The error: publish_error: spacetime publish failed (exit=1)

  4. Explain the difference: The generated code uses repeat_micros(50_000) which is incorrect, whereas the expected code uses Duration::from_millis(50) along with .into() to convert it to ScheduleAt. Additionally, the #[reducer(scheduled)] is omitted in the generated code, which is necessary for scheduling.

  5. Root cause: The documentation is unclear about the specific format and function usages for ScheduleAt, and the importance of tagged attributes for reducer functions.

  6. Recommendation: Update the documentation to clearly explain:

    • Usage of ScheduleAt and its initialization methods.
    • The correct annotations like #[reducer(scheduled)].
    • Improve the examples in the documentation to reflect accurate usage.

Other Failures (6 failures)

1. t_003_struct_in_table

  1. The generated code:

    use spacetimedb::{ReducerContext, Table, SpacetimeType};
    
    #[derive(SpacetimeType)]
    pub struct Position {
        x: i32,
        y: i32,
    }
    
    #[spacetimedb::table(name = entity)]
    pub struct Entity {
        #[primary_key]
        id: i32,
        pos: Position,
    }
    
    #[spacetimedb::reducer]
    pub fn add_entity(ctx: &ReducerContext, id: i32, x: i32, y: i32) {
        let pos = Position { x, y };
        ctx.db.entity().insert(Entity { id, pos });
    }
  2. The golden example:

    use spacetimedb::{table, SpacetimeType};
    
    #[derive(SpacetimeType, Clone, Debug)]
    pub struct Position {
        pub x: i32,
        pub y: i32,
    }
    
    #[table(name = entity)]
    pub struct Entity {
        #[primary_key]
        pub id: i32,
        pub pos: Position,
    }
  3. The error: schema_parity: reducers differ - expected [], got ["add_entity()"]

  4. Explain the difference: The generated code adds a reducer that is not present in the expected output. The primary issue is the omission of pub keywords, which results in reduced visibility.

  5. Root cause: The documentation lacks emphasis on struct visibility, which is critical for serialization and deserialization.

  6. Recommendation: Update the documentation to:

    • Highlight the importance of using pub to expose struct fields.
    • Provide more examples with a focus on accessibility modifiers.

2. t_013_spacetime_sum_type

  1. The generated code:

    use spacetimedb::{table, reducer, ReducerContext, Table, SpacetimeType};
    
    #[derive(SpacetimeType)]
    pub struct Rect {
        width: i32,
        height: i32,
    }
    
    #[derive(SpacetimeType)]
    pub enum Shape {
        Circle(i32),
        Rectangle(Rect),
    }
    
    #[table(name = result)]
    pub struct ResultRow {
        #[primary_key]
        id: i32,
        value: Shape,
    }
    
    #[reducer]
    pub fn set_circle(ctx: &ReducerContext, id: i32, radius: i32) {
        ctx.db.result().insert(ResultRow { id, value: Shape::Circle(radius) });
    }
  2. The golden example:

    use spacetimedb::{reducer, table, ReducerContext, SpacetimeType, Table};
    
    #[derive(SpacetimeType, Clone, Debug)]
    pub struct Rect {
        pub width: i32,
        pub height: i32,
    }
    
    #[derive(SpacetimeType, Clone, Debug)]
    pub enum Shape {
        Circle(i32),
        Rectangle(Rect),
    }
    
    #[table(name = result)]
    pub struct ResultRow {
        #[primary_key]
        pub id: i32,
        pub value: Shape,
    }
    
    #[reducer]
    pub fn set_circle(ctx: &ReducerContext, id: i32, radius: i32) {
        ctx.db.result().insert(ResultRow { id, value: Shape::Circle(radius) });
    }
  3. The error: sum_type_row_parity: spacetime sql failed: Error: no such table: result.

  4. Explain the difference: The generated code is missing pub for the struct fields, which is essential for the serialization framework. The enum lacks the Clone and Debug traits that are significant for data handling.

  5. Root cause: The documentation should clarify the necessity for fields to be public in cases of serialization in SpacetimeDB, especially within types used in tables.

  6. Recommendation: Revise the documentation to include:

    • The roles of public fields in struct serialization.
    • The importance of implementing common traits such as Clone and Debug for enum variants.

3. t_015_product_type_columns

  1. The generated code:

    use spacetimedb::{table, reducer, ReducerContext, Table, SpacetimeType};
    
    #[derive(SpacetimeType)]
    pub struct Address {
        street: String,
        zip: i32,
    }
    
    #[derive(SpacetimeType)]
    pub struct Position {
        x: i32,
        y: i32,
    }
    
    #[table(name = profile)]
    pub struct Profile {
        #[primary_key]
        id: i32,
        home: Address,
        work: Address,
        pos: Position,
    }
    
    #[reducer]
    pub fn seed(ctx: &ReducerContext) {
        let table = ctx.db.profile();
        if table.id().find(1).is_none() {
            table.insert(Profile {
                id: 1,
                home: Address {
                    street: "1 Main".to_string(),
                    zip: 11111,
                },
                work: Address {
                    street: "2 Broad".to_string(),
                    zip: 22222,
                },
                pos: Position { x: 7, y: 9 },
            });
        }
    }
  2. The golden example:

    use spacetimedb::{reducer, table, ReducerContext, SpacetimeType, Table};
    
    #[derive(SpacetimeType, Clone, Debug)]
    pub struct Address {
        pub street: String,
        pub zip: i32,
    }
    
    #[derive(SpacetimeType, Clone, Debug)]
    pub struct Position {
        pub x: i32,
        pub y: i32,
    }
    
    #[table(name = profile)]
    pub struct Profile {
        #[primary_key]
        pub id: i32,
        pub home: Address,
        pub work: Address,
        pub pos: Position,
    }
    
    #[reducer]
    pub fn seed(ctx: &ReducerContext) {
        ctx.db.profile().insert(Profile {
            id: 1,
            home: Address { street: "1 Main".into(), zip: 11111 },
            work: Address { street: "2 Broad".into(), zip: 22222 },
            pos: Position { x: 7, y: 9 },
        });
    }
  3. The error: product_type_columns_row_parity: spacetime sql failed: Error: no such table: profile.

  4. Explain the difference: Again, generated code does not have pub declarations for struct fields, compromising field visibility. The insert logic in the reduction does not take advantage of .into().

  5. Root cause: Lack of emphasis in the documentation regarding the critical need for field visibility, alongside practical examples of struct initialization.

  6. Recommendation: Update the documentation to:

    • Stress the requirements for table schemas regarding field visibility.
    • Provide thorough initialization examples in the context of reducers with various types.

Summary

Across the variations of failures, common issues arise from:

  • Omission of pub visibility for struct fields.
  • Missing specific annotations like #[reducer(scheduled)].
  • Clearer understanding and examples of ScheduleAt usage.

Next Steps: Immediate updates to the documentation should reflect these observations, improving clarity and usability for developers.

clockwork-labs-bot and others added 2 commits January 21, 2026 14:49
- Add crash recovery implementation details (4096 batch size, 128-bit counter)
- Add multi-language examples for crash recovery behavior
- Update link in transactions-atomicity.md to point to auto-increment page
- Remove appendix.md as its only content is now in auto-increment.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants