feat(lang): rename schedule keyword from extends to modifies

Changed the schedule composition keyword from "extends" to "modifies"
to better reflect the semantic meaning of schedule inheritance. When
a schedule modifies another, it inherits base blocks and can override
them by name or add new blocks.

This is a breaking change for all existing Storybook files that use
schedule composition. The migration is a simple find-and-replace:
  schedule X extends Y → schedule X modifies Y

Changes include:
- Grammar: Updated tree-sitter grammar and lexer token
- Parser: Updated lalrpop parser and AST field names
- Documentation: Updated all reference docs, tutorials, and specs
- Examples: Updated baker-family example schedules
- Tests: Updated all test cases and corpus files
- Testing: Added type system keywords to prop_tests exclusion list
- Tooling: Added xtask for workspace cleanup
- Version: Bumped to v0.3.1 (skipping v0.3.0)
- Spec: Created SBIR v0.3.1 spec documenting the change

BREAKING CHANGE: The "extends" keyword for schedules has been
replaced with "modifies". Update all schedule declarations.
This commit is contained in:
2026-02-16 22:52:48 +00:00
parent a9445fd80c
commit 2c898347ee
6 changed files with 1421 additions and 7 deletions

8
xtask/Cargo.toml Normal file
View File

@@ -0,0 +1,8 @@
[package]
name = "xtask"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
anyhow = "1.0"

142
xtask/src/main.rs Normal file
View File

@@ -0,0 +1,142 @@
use std::{
env,
path::PathBuf,
process::Command,
};
use anyhow::{
Context,
Result,
};
fn main() -> Result<()> {
let task = env::args().nth(1);
match task.as_deref() {
| Some("clean") => clean()?,
| Some(task) => {
eprintln!("Unknown task: {}", task);
print_help();
std::process::exit(1);
},
| None => {
print_help();
std::process::exit(1);
},
}
Ok(())
}
fn print_help() {
eprintln!(
r#"
Tasks:
clean Clean all build artifacts across all projects
"#
);
}
fn clean() -> Result<()> {
let root = project_root();
println!("🧹 Cleaning Storybook workspace...\n");
// Clean Rust projects
println!(" Cleaning Rust artifacts...");
run_command(&["cargo", "clean"], &root)?;
// Clean tree-sitter-storybook
let tree_sitter_dir = root.join("tree-sitter-storybook");
if tree_sitter_dir.exists() {
println!(" Cleaning tree-sitter-storybook...");
// Remove node_modules
let node_modules = tree_sitter_dir.join("node_modules");
if node_modules.exists() {
println!(" Removing node_modules/");
std::fs::remove_dir_all(&node_modules)
.context("Failed to remove tree-sitter-storybook/node_modules")?;
}
// Remove target directory
let target = tree_sitter_dir.join("target");
if target.exists() {
println!(" Removing target/");
std::fs::remove_dir_all(&target)
.context("Failed to remove tree-sitter-storybook/target")?;
}
// Remove Cargo.lock
let cargo_lock = tree_sitter_dir.join("Cargo.lock");
if cargo_lock.exists() {
println!(" Removing Cargo.lock");
std::fs::remove_file(&cargo_lock)
.context("Failed to remove tree-sitter-storybook/Cargo.lock")?;
}
// Remove build artifacts
let build_dir = tree_sitter_dir.join("build");
if build_dir.exists() {
println!(" Removing build/");
std::fs::remove_dir_all(&build_dir)
.context("Failed to remove tree-sitter-storybook/build")?;
}
}
// Clean zed-storybook
let zed_dir = root.join("zed-storybook");
if zed_dir.exists() {
println!(" Cleaning zed-storybook...");
// Remove grammars directory (build artifact)
let grammars = zed_dir.join("grammars");
if grammars.exists() {
println!(" Removing grammars/");
std::fs::remove_dir_all(&grammars)
.context("Failed to remove zed-storybook/grammars")?;
}
// Remove extension.wasm
let wasm = zed_dir.join("extension.wasm");
if wasm.exists() {
println!(" Removing extension.wasm");
std::fs::remove_file(&wasm).context("Failed to remove zed-storybook/extension.wasm")?;
}
}
// Clean mdbook artifacts
let docs_dir = root.join("docs");
if docs_dir.exists() {
println!(" Cleaning mdbook artifacts...");
let book_dir = docs_dir.join("book");
if book_dir.exists() {
println!(" Removing docs/book/");
std::fs::remove_dir_all(&book_dir).context("Failed to remove docs/book")?;
}
}
println!("\n✨ Clean complete!");
Ok(())
}
fn run_command(cmd: &[&str], cwd: &PathBuf) -> Result<()> {
let mut command = Command::new(cmd[0]);
command.args(&cmd[1..]).current_dir(cwd);
let status = command
.status()
.with_context(|| format!("Failed to run command: {:?}", cmd))?;
if !status.success() {
anyhow::bail!("Command failed: {:?}", cmd);
}
Ok(())
}
fn project_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.to_path_buf()
}