chore: bootstrap nana-story M0

This commit is contained in:
Codex
2026-07-28 12:51:00 +08:00
commit 3dacc92423
71 changed files with 6840 additions and 0 deletions
+96
View File
@@ -0,0 +1,96 @@
use nana_domain::{
AppInfo, DemoPackSummary, DOMAIN_SCHEMA_VERSION, PlayerView, ResourceBundle, ValidationIssue,
validate_bundle,
};
use serde::Serialize;
const DEMO_PLAYER_VIEW: &str = include_str!("../../fixtures/player-view/initial.json");
const DEMO_BUNDLE: &str = include_str!("../../content/nana-demo/bundle.json");
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CommandError {
code: &'static str,
message: String,
issues: Vec<ValidationIssue>,
}
impl CommandError {
fn parse(target: &str, error: &serde_json::Error) -> Self {
Self {
code: "invalid_embedded_json",
message: format!("failed to parse embedded {target}: {error}"),
issues: Vec::new(),
}
}
}
#[tauri::command]
fn get_app_info() -> AppInfo {
AppInfo {
name: "听娜娜讲故事".to_owned(),
version: env!("CARGO_PKG_VERSION").to_owned(),
contract_version: DOMAIN_SCHEMA_VERSION,
}
}
#[tauri::command]
fn get_demo_player_view() -> Result<PlayerView, CommandError> {
serde_json::from_str(DEMO_PLAYER_VIEW)
.map_err(|error| CommandError::parse("PlayerView fixture", &error))
}
#[tauri::command]
fn get_demo_pack_summary() -> Result<DemoPackSummary, CommandError> {
let bundle: ResourceBundle = serde_json::from_str(DEMO_BUNDLE)
.map_err(|error| CommandError::parse("content bundle", &error))?;
let report = validate_bundle(&bundle);
if !report.is_valid() {
return Err(CommandError {
code: "invalid_content_bundle",
message: "the embedded content bundle failed domain validation".to_owned(),
issues: report.issues,
});
}
Ok(DemoPackSummary {
id: bundle.id,
display_name: bundle.display_name,
revision: bundle.revision,
characters: bundle.characters.len(),
world_books: bundle.world_books.len(),
personas: bundle.personas.len(),
plot_modules: bundle.plot_modules.len(),
item_specs: bundle.item_specs.len(),
})
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
get_app_info,
get_demo_pack_summary,
get_demo_player_view
])
.run(tauri::generate_context!())
.expect("failed to run nana-story");
}
#[cfg(test)]
mod tests {
use super::{get_demo_pack_summary, get_demo_player_view};
#[test]
fn embedded_player_view_is_valid() {
let view = get_demo_player_view().expect("valid PlayerView fixture");
assert_eq!(view.character_name, "娜娜");
assert!(!view.beats.is_empty());
}
#[test]
fn embedded_content_bundle_is_valid() {
let summary = get_demo_pack_summary().expect("valid demo bundle");
assert_eq!(summary.characters, 1);
assert_eq!(summary.plot_modules, 1);
}
}