updated AGENTS.md and pps site works again.

This commit is contained in:
Sebastian Moser
2026-10-14 01:50:08 +02:00
parent a7db7da9d2
commit 0b316ad54f
20 changed files with 1053 additions and 264 deletions

625
docs/part-system.md Normal file
View File

@@ -0,0 +1,625 @@
# Mize Part System (LLM generated guide)
## Overview
Mize is built around a **parts architecture** that allows you to compose functionality modularly. Each part is a self-contained unit that can:
- Store and manage its own state
- Expose methods to other parts
- Register itself with the Mize instance
- Define configuration options
Parts are the primary way to extend Mize's functionality. The system is designed so that parts can depend on each other, and users can enable/disable parts as needed.
---
## Core Concepts
### The MizePart Trait
All parts implement the `MizePart` trait, which provides:
```rust
pub trait MizePart: MizePartGenerated {
fn name(&self) -> &'static str; // Part identifier
fn get_mize(&mut self) -> &mut Mize; // Access to the Mize instance
fn init(&mut self, mize: &mut Mize) -> MizeResult<()>; // Initialization
fn run(&mut self, mize: &mut Mize) -> MizeResult<()>; // Execution
fn deps(&self) -> &'static [&'static str]; // Dependencies
fn opts(&self, mize: &mut Mize); // Config options
// ... async variants
}
```
### Part Registration
Parts register themselves with the Mize instance using:
- **`mize.register_part(Box::new(MyPart { ... }))`** - For parts with state and methods
- **`mize.add_name_only_part("name")`** - For simple parts that only need to be known by name (and have no state)
### Part Access
Parts can access other parts using:
```rust
// Get a part by name (returns a guard that ensures the part is returned)
let mut part = mize.get_part("part_name")?;
// Get a typed part (for parts with structs)
let mut typed_part = mize.get_part_native::<OtherPart>("other_part")?;
```
**Important**: Users must ensure that dependent parts are enabled before the parts that depend on them. There's no automatic dependency resolution.
---
## Current Implementation (Compiled-in Parts)
Currently, all parts are compiled into the `marts` crate and registered via the `compile_in_parts!` macro in the application's `main.rs`.
### Example: `apps/os/src/main.rs`
```rust
compile_in_parts! {
mize,
"cli": marts::cli,
"auth": marts::part_client_auth,
"spacetime": marts::spacetime,
"ppc.server": marts::ppc::server::part_ppc_server,
"c2vi": marts::part_c2vi,
"sci": marts::sci::part_sci,
"sci.timetrack": marts::sci::timetrack::part_timetrack,
"sci.inbox": marts::sci::inbox::part_sci_inbox,
};
```
Each entry maps a part name to a function that registers the part with the Mize instance.
### How It Works
1. The macro reads the `enable` config option (a list of part names)
2. For each enabled part, it calls the corresponding registration function
3. Each registration function creates the part and calls `mize.register_part()`
---
## Creating a New Part
### Option 1: Simple Name-Only Part
For parts that don't need state or methods:
```rust
// In your part module
pub fn part_my_simple_part(mize: &mut Mize) -> MizeResult<()> {
// Just register the name
mize.add_name_only_part("my_simple_part");
Ok(())
}
```
### Option 2: Part with State and Methods (Using `mize_part` Macro)
For parts that need to store state and expose methods:
```rust
use mize::{mize_part, Mize, MizePart, MizePartCreate, MizeResult};
#[mize_part] // or #[mize_part("custom_name")] for custom name
#[derive(Default)]
pub struct MyPart {
mize: Mize, // Required field
counter: u32, // Your custom state
api_key: String, // More state
}
// Implement MizePartCreate for parts that need custom initialization
impl MizePartCreate for MyPart {
// Optional: override create_generated for custom initialization
}
impl MizePart for MyPart {
// Optional: override name if not using the struct name
fn name(&self) -> &'static str {
"my_part" // Default would be "mypart" from struct name
}
// Optional: define config options this part needs
fn opts(&self, mize: &mut Mize) {
mize.new_opt("my_part.api_key");
mize.new_opt("my_part.counter_initial");
}
// Optional: initialization logic
fn init(&mut self, mize: &mut Mize) -> MizeResult<()> {
self.counter = mize.get_config("my_part.counter_initial")?.value_u32()?;
self.api_key = mize.get_config("my_part.api_key")?.value_string()?;
Ok(())
}
// Optional: define dependencies on other parts
fn deps(&self) -> &'static [&'static str] {
&["auth"] // This part requires "auth" to be loaded first
}
}
// The registration function
pub fn part_my_part(mize: &mut Mize) -> MizeResult<()> {
mize.register_part(Box::new(MyPart {
mize: mize.clone(),
counter: 0,
api_key: String::new(),
}))?;
Ok(())
}
// You can add methods to your part struct
impl MyPart {
pub fn increment(&mut self) -> u32 {
self.counter += 1;
self.counter
}
pub fn get_api_key(&self) -> &str {
&self.api_key
}
}
```
### Using the Part from Another Part
```rust
pub fn part_another_part(mize: &mut Mize) -> MizeResult<()> {
// Get the MyPart instance
let mut my_part = mize.get_part_native::<MyPart>("my_part")?;
// Call its methods
let count = my_part.increment();
let api_key = my_part.get_api_key();
// ... your logic
Ok(())
}
```
---
## The `mize_part` Macro
The `#[mize_part]` procedural macro simplifies part creation by automatically implementing the required traits.
### What It Generates
For a struct like:
```rust
#[mize_part]
#[derive(Default)]
pub struct MyPart {
mize: Mize,
// ... your fields
}
```
The macro generates:
1. **`MizePartGenerated` implementation** - Provides default implementations for the generated trait methods
2. **`MizePartCreateGenerated` implementation** - Provides a default `create_generated` method
3. **`MizePartCreateThisSucks` implementation** - Compatibility trait
4. **Extern "C" function** - Named `make_mize_part_<name>` for dynamic loading
### Custom Part Name
```rust
#[mize_part("custom_name")]
pub struct MyPart {
mize: Mize,
}
```
This will use "custom_name" instead of the derived "mypart".
### Requirements
- The struct **must** have a field named `mize: Mize`
- The struct should derive `Default` or implement it
---
## Configuration
### Defining Config Options
(about to be deprecad, does nothing)
Parts declare what config options they need in their `opts()` method:
```rust
impl MizePart for MyPart {
fn opts(&self, mize: &mut Mize) {
mize.new_opt("my_part.api_url");
mize.new_opt("my_part.timeout");
}
}
```
### Using Config Options
Parts retrieve config using `mize.get_config()`:
```rust
fn init(&mut self, mize: &mut Mize) -> MizeResult<()> {
let api_url = mize.get_config("my_part.api_url")?.value_string()?;
let timeout = mize.get_config("my_part.timeout")?.value_u64()?;
Ok(())
}
```
### User Configuration
Users configure parts via TOML files or environment variables. Example `config.toml`:
```toml
[my_part]
api_url = "https://api.example.com"
timeout = 30
[enable]
# List of parts to enable
my_part = true
auth = true
spacetime = true
```
Or via command line:
```bash
mize --opt my_part.api_url=https://api.example.com run
```
---
## Part Dependencies
### Declaring Dependencies
(about to be deprecad, does nothing)
Parts declare their dependencies in the `deps()` method:
```rust
impl MizePart for MyPart {
fn deps(&self) -> &'static [&'static str] {
&["auth", "spacetime"]
}
}
```
**Note**: This is currently informational only. Users must manually ensure dependencies are enabled.
### Accessing Dependencies
Use `get_part_native()` to access other parts:
```rust
fn run(&mut self, mize: &mut Mize) -> MizeResult<()> {
// Get the auth part
let mut auth = mize.get_part_native::<ClientAuth>("client_auth")?;
// Use it
let token = auth.id_token()?;
// Get spacetime
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
Ok(())
}
```
---
## Future Vision: Separate Crates
The goal is to move each part to its own crate following the naming convention **`mize-part-<name>`**. when creating a part make a new Cargo project like this already.
### Structure of a Part Crate
```
mize-part-discord/
├── Cargo.toml
└── src/
└── lib.rs
```
### Example: `mize-part-discord/Cargo.toml`
```toml
[package]
name = "mize-part-discord"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
mize = { path = "../mize" }
serenity = "0.12"
```
### Example: `mize-part-discord/src/lib.rs`
```rust
use mize::{mize_part, Mize, MizePart, MizePartCreate, MizeResult};
use serenity::Client;
#[mize_part]
#[derive(Default)]
pub struct DiscordPart {
mize: Mize,
client: Option<Client>,
}
impl MizePartCreate for DiscordPart {}
impl MizePart for DiscordPart {
fn name(&self) -> &'static str {
"discord"
}
fn opts(&self, mize: &mut Mize) {
mize.new_opt("discord.token");
}
fn init(&mut self, mize: &mut Mize) -> MizeResult<()> {
let token = mize.get_config("discord.token")?.value_string()?;
// Initialize serenity client...
Ok(())
}
}
// The registration function - THIS IS WHAT MATTERS
pub fn part_discord(mize: &mut Mize) -> MizeResult<()> {
mize.register_part(Box::new(DiscordPart {
mize: mize.clone(),
client: None,
}))?;
Ok(())
}
```
### Using Separate Part Crates
In your application's `Cargo.toml`:
```toml
[dependencies]
mize = { path = "../mize" }
mize-part-discord = { path = "../mize-part-discord" }
mize-part-auth = { path = "../mize-part-auth" }
```
In your application's `main.rs`:
```rust
compile_in_parts! {
mize,
"discord": mize_part_discord::part_discord,
"auth": mize_part_auth::part_auth,
};
```
## Complete Example: Creating a New Part
Let's create a complete example of a new part called `mize-part-greeting`.
### Step 1: Create the Crate
```bash
cargo new --lib mize-part-greeting
cd mize-part-greeting
```
### Step 2: Edit `Cargo.toml`
```toml
[package]
name = "mize-part-greeting"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
mize = { path = "../mize" }
```
### Step 3: Create the Part (`src/lib.rs`)
```rust
use mize::{mize_part, Mize, MizePart, MizePartCreate, MizeResult};
/// A simple greeting part that stores a name and can generate greetings
#[mize_part]
#[derive(Default)]
pub struct GreetingPart {
mize: Mize,
name: String,
}
impl MizePartCreate for GreetingPart {}
impl MizePart for GreetingPart {
fn name(&self) -> &'static str {
"greeting"
}
fn opts(&self, mize: &mut Mize) {
mize.new_opt("greeting.name");
mize.new_opt("greeting.formal");
}
fn init(&mut self, mize: &mut Mize) -> MizeResult<()> {
self.name = mize
.get_config("greeting.name")?
.value_string()
.unwrap_or_else(|_| "World".to_string());
Ok(())
}
}
impl GreetingPart {
/// Generate a greeting message
pub fn greet(&self) -> String {
let formal = self
.mize
.get_config("greeting.formal")
.map(|v| v.value_bool().unwrap_or(false))
.unwrap_or(false);
if formal {
format!("Good day, {}!", self.name)
} else {
format!("Hello, {}!", self.name)
}
}
/// Set the name
pub fn set_name(&mut self, name: String) {
self.name = name;
}
/// Get the current name
pub fn get_name(&self) -> &str {
&self.name
}
}
/// Registration function - this is what the compile_in_parts! macro will call
pub fn part_greeting(mize: &mut Mize) -> MizeResult<()> {
mize.register_part(Box::new(GreetingPart {
mize: mize.clone(),
name: String::new(),
}))?;
Ok(())
}
```
### Step 4: Add to Your Application
In your application's `Cargo.toml`:
```toml
[dependencies]
mize = { path = "../mize" }
mize-part-greeting = { path = "../mize-part-greeting" }
```
In your application's `main.rs`:
```rust
compile_in_parts! {
mize,
"greeting": mize_part_greeting::part_greeting,
};
```
### Step 5: Use the Part
```rust
// In another part or in your CLI
let mut greeting = mize.get_part_native::<GreetingPart>("greeting")?;
println!("{}", greeting.greet());
// Change the name
greeting.set_name("Alice".to_string());
println!("{}", greeting.greet());
```
### Step 6: Configure It
Create a `config.toml`:
```toml
[greeting]
name = "Bob"
formal = true
[enable]
greeting = true
```
Run with:
```bash
mize --config config.toml run
```
---
## Best Practices
1. **Naming**: Use `mize-part-<name>` for crate names and `part_<name>` for registration functions
2. **State**: Store part-specific state in the part struct
3. **Dependencies**: Declare dependencies in `deps()` and document them
4. **Config**: Declare all config options in `opts()`
5. **Error Handling**: Use `MizeResult` and `mize_err!` for errors
6. **Documentation**: Document your part's purpose, config options, and methods
---
## Summary
- Parts are modular units of functionality in Mize
- Each part implements the `MizePart` trait
- Use `#[mize_part]` macro for parts with state and methods
- Register parts with `mize.register_part()` or `mize.add_name_only_part()`
- Parts access config via `mize.get_config()`
- Parts access other parts via `mize.get_part_native::<T>()`
- Users enable parts via the `enable` config option
- Future: Each part will be its own crate named `mize-part-<name>`
---
## Quick Reference
### Common Patterns
**Register a part:**
```rust
mize.register_part(Box::new(MyPart { mize: mize.clone(), ... }))?;
```
**Get a part:**
```rust
let mut part = mize.get_part_native::<MyPart>("my_part")?;
```
**Get config:**
```rust
let value = mize.get_config("my_part.setting")?.value_string()?;
```
**Declare config option:**
```rust
mize.new_opt("my_part.setting");
```
**Define a part struct:**
```rust
#[mize_part]
#[derive(Default)]
pub struct MyPart {
mize: Mize,
// your fields...
}
```
**Registration function:**
```rust
pub fn part_my_part(mize: &mut Mize) -> MizeResult<()> {
mize.register_part(Box::new(MyPart { mize: mize.clone(), ... }))?;
Ok(())
}
```