fix(xtask): give mobile starters app-owned commands

Make app new --mobile write hemx-app with HEMX_APP_* release settings, app-scoped release output, and docs that name Workout only as the concrete starter flow instead of leaking Workout command identity.

req: ceremony/006
This commit is contained in:
slhx agent
2026-06-12 16:15:09 +02:00
parent 3f824a83f8
commit 79b4d1e167
2 changed files with 34 additions and 6 deletions
+2 -2
View File
@@ -14,8 +14,8 @@ this path outside the repository:
cargo run -p hemx-xtask -- app new --mobile PATH cargo run -p hemx-xtask -- app new --mobile PATH
``` ```
The created app includes the same `hemx-workout mobile-release` and The created app includes app-owned `hemx-app mobile-release` and
`hemx-workout mobile-verify` commands, plus `MOBILE_STARTER.md` naming the host, `hemx-app mobile-verify` commands, plus `MOBILE_STARTER.md` naming the host,
recovery, and release-kit boundary. req: ceremony/006 recovery, and release-kit boundary. req: ceremony/006
## When to use this path ## When to use this path
+32 -4
View File
@@ -91,7 +91,7 @@ fn run_app_new(operands: &[String]) -> ExitCode {
); );
if mobile { if mobile {
println!( println!(
"next\tHEMX_WORKOUT_ORIGIN=https://app.example.com {}/hemx-workout mobile-release", "next\tHEMX_APP_ORIGIN=https://app.example.com {}/hemx-app mobile-release",
destination.display() destination.display()
); );
} }
@@ -217,9 +217,31 @@ fn create_app_scaffold(destination: &Path) -> std::io::Result<()> {
fn create_mobile_app_scaffold(destination: &Path) -> std::io::Result<()> { fn create_mobile_app_scaffold(destination: &Path) -> std::io::Result<()> {
// req: ceremony/006 req: examples/006 // req: ceremony/006 req: examples/006
create_workout_app(destination)?; create_workout_app(destination)?;
fs::rename(
destination.join("hemx-workout"),
destination.join("hemx-app"),
)?;
for (from, to) in [
("hemx_workout_app", "hemx_app"),
("hemx-workout-app", "hemx-app"),
("workout-app", "hemx-app"),
("./hemx-workout", "./hemx-app"),
("hemx-workout", "hemx-app"),
("HEMX_WORKOUT_", "HEMX_APP_"),
("target/hemx-mobile/workout", "target/hemx-mobile/app"),
("com.hemx.workout", "com.hemx.app"),
("hemx Workout Copilot", "hemx App"),
(
"# Workout mobile external blockers",
"# hemx app mobile external blockers",
),
("workout-mobile", "app-mobile"),
] {
replace_in_tree(destination, from, to)?;
}
fs::write( fs::write(
destination.join("MOBILE_STARTER.md"), destination.join("MOBILE_STARTER.md"),
"# Mobile hemx starter\n\nThis starter is the phone-first Workout path exposed through `app new --mobile`: it has a real page/form/keyed partial/notice flow, typed host haptics/share calls returning through Rust handlers, app-owned command/event/projection recovery truth, and the same `hemx-workout mobile-release` / `hemx-workout mobile-verify` release-kit commands.\n\nIt deliberately does not add a hemx mobile framework, client store, signing secret owner, or store submission bot. req: ceremony/006 req: examples/006\n", "# Mobile hemx starter\n\nThis starter is the phone-first path exposed through `app new --mobile`: it has a real page/form/keyed partial/notice flow, typed host haptics/share calls returning through Rust handlers, app-owned command/event/projection recovery truth, and app-owned `hemx-app mobile-release` / `hemx-app mobile-verify` release-kit commands.\n\nThe starter uses the Workout product flow as a concrete first app, but its command surface, crate name, binary name, release output, and environment variables are owned by the created app. It deliberately does not add a hemx mobile framework, client store, signing secret owner, or store submission bot. req: ceremony/006 req: examples/006\n",
)?; )?;
Ok(()) Ok(())
} }
@@ -1250,17 +1272,21 @@ mod tests {
create_mobile_app_scaffold(&destination).expect("create mobile app scaffold"); create_mobile_app_scaffold(&destination).expect("create mobile app scaffold");
let mobile_readme = let mobile_readme =
fs::read_to_string(destination.join("MOBILE_STARTER.md")).expect("mobile docs"); fs::read_to_string(destination.join("MOBILE_STARTER.md")).expect("mobile docs");
let command = fs::read_to_string(destination.join("hemx-workout")).expect("command"); let command = fs::read_to_string(destination.join("hemx-app")).expect("command");
let lib_rs = fs::read_to_string(destination.join("src/lib.rs")).expect("lib rs"); let lib_rs = fs::read_to_string(destination.join("src/lib.rs")).expect("lib rs");
let template = let template =
fs::read_to_string(destination.join("templates/workout.heml")).expect("template"); fs::read_to_string(destination.join("templates/workout.heml")).expect("template");
assert!(mobile_readme.contains("phone-first Workout path")); assert!(mobile_readme.contains("phone-first path"));
assert!(mobile_readme.contains("typed host haptics/share calls")); assert!(mobile_readme.contains("typed host haptics/share calls"));
assert!(mobile_readme.contains("command/event/projection recovery truth")); assert!(mobile_readme.contains("command/event/projection recovery truth"));
assert!(mobile_readme.contains("app-owned `hemx-app mobile-release`"));
assert!(mobile_readme.contains("does not add a hemx mobile framework")); assert!(mobile_readme.contains("does not add a hemx mobile framework"));
assert!(command.contains("mobile-release")); assert!(command.contains("mobile-release"));
assert!(command.contains("mobile-verify")); assert!(command.contains("mobile-verify"));
assert!(command.contains("HEMX_APP_ORIGIN"));
assert!(command.contains("target/hemx-mobile/app"));
assert!(!command.contains("HEMX_WORKOUT_"));
assert!(command.contains("app-owned command/event/projection records")); assert!(command.contains("app-owned command/event/projection records"));
assert!(command.contains("external_blockers")); assert!(command.contains("external_blockers"));
assert!(lib_rs.contains("HostCall::Share")); assert!(lib_rs.contains("HostCall::Share"));
@@ -1269,6 +1295,8 @@ mod tests {
assert!(template.contains("<form")); assert!(template.contains("<form"));
assert!(template.contains("data-hemx-slot")); assert!(template.contains("data-hemx-slot"));
assert!(destination.join("tests/e2e.rs").exists()); assert!(destination.join("tests/e2e.rs").exists());
assert!(destination.join("hemx-app").exists());
assert!(!destination.join("hemx-workout").exists());
let _ = fs::remove_dir_all(&destination); let _ = fs::remove_dir_all(&destination);
} }