diff --git a/crates/console/prometeu-firmware/src/firmware/firmware.rs b/crates/console/prometeu-firmware/src/firmware/firmware.rs index b60811ed..0f2caa15 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware.rs @@ -171,6 +171,7 @@ impl Firmware { #[cfg(test)] mod tests { use super::*; + use crate::firmware::firmware_state::HubHomeStep; use prometeu_bytecode::assembler::assemble; use prometeu_bytecode::model::{BytecodeModule, FunctionMeta, SyscallDecl}; use prometeu_drivers::hardware::Hardware; @@ -281,6 +282,14 @@ mod tests { (firmware, hardware, signals, task_id) } + fn hub_home_firmware() -> (Firmware, Hardware) { + let mut firmware = Firmware::new(None); + firmware.state = FirmwareState::HubHome(HubHomeStep); + firmware.state_initialized = false; + + (firmware, Hardware::new()) + } + fn focus_window(firmware: &mut Firmware, owner: WindowOwner) { let mut windows = firmware.os.windows(); windows.remove_all_windows(); @@ -406,6 +415,65 @@ mod tests { assert_eq!(firmware.os.vm().tick_index(), tick_index_before_system_update + 1); } + #[test] + fn hub_home_launches_shell_a_as_native_shell_task_window() { + let (mut firmware, mut hardware) = hub_home_firmware(); + let signals = InputSignals { a_signal: true, ..Default::default() }; + + firmware.tick(&signals, &mut hardware); + + let task_id = match &firmware.state { + FirmwareState::ShellRunning(step) => step.task_id, + other => panic!("expected ShellRunning state, got {:?}", other), + }; + + assert!(firmware.os.windows().focused_window_belongs_to_task(task_id)); + assert_eq!(firmware.os.windows().window_count(), 1); + assert_eq!(firmware.os.windows().windows()[0].title, "ShellA"); + assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Foreground)); + assert_eq!( + firmware.os.lifecycle().process_state_for_task(task_id), + Ok(ProcessState::Running) + ); + } + + #[test] + fn hub_home_launches_shell_b_as_native_shell_task_window() { + let (mut firmware, mut hardware) = hub_home_firmware(); + let signals = InputSignals { b_signal: true, ..Default::default() }; + + firmware.tick(&signals, &mut hardware); + + let task_id = match &firmware.state { + FirmwareState::ShellRunning(step) => step.task_id, + other => panic!("expected ShellRunning state, got {:?}", other), + }; + + assert!(firmware.os.windows().focused_window_belongs_to_task(task_id)); + assert_eq!(firmware.os.windows().window_count(), 1); + assert_eq!(firmware.os.windows().windows()[0].title, "ShellB"); + assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Foreground)); + assert_eq!( + firmware.os.lifecycle().process_state_for_task(task_id), + Ok(ProcessState::Running) + ); + } + + #[test] + fn shell_running_start_close_uses_lifecycle_and_returns_hub_home() { + let (mut firmware, mut hardware, _signals, task_id) = load_shell_running_firmware(); + let close_signals = InputSignals { start_signal: true, ..Default::default() }; + + firmware.tick(&close_signals, &mut hardware); + + assert!(matches!(firmware.state, FirmwareState::HubHome(_))); + assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed)); + assert_eq!( + firmware.os.lifecycle().process_state_for_task(task_id), + Ok(ProcessState::Stopped) + ); + } + #[test] fn shell_running_closes_task_and_returns_hub_home_without_focused_window() { let (mut firmware, mut hardware, signals, task_id) = load_shell_running_firmware(); diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs index fb37a924..25daa8c1 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs @@ -1,6 +1,9 @@ -use crate::firmware::firmware_state::{AppCrashesStep, FirmwareState}; +use crate::firmware::firmware_state::{AppCrashesStep, FirmwareState, ShellRunningStep}; use crate::firmware::prometeu_context::PrometeuContext; use prometeu_hal::log::{LogLevel, LogSource}; +use prometeu_hal::primitives::Rect; +use prometeu_system::SystemProfileAction; +use prometeu_system::windows::WindowOwner; #[derive(Debug, Clone)] pub struct HubHomeStep; @@ -16,6 +19,18 @@ impl HubHomeStep { return Some(FirmwareState::AppCrashes(AppCrashesStep { report })); } + if let Some(SystemProfileAction::LaunchNativeShell(app)) = outcome.action { + let task_id = ctx.os.sessions().create_native_shell_task(app.app_id(), app.title()); + let id = ctx.os.windows().add_window( + app.title().to_string(), + WindowOwner::Task(task_id), + Rect { x: 40, y: 20, w: 400, h: 220 }, + app.color(), + ); + ctx.os.windows().set_focus(id); + return Some(FirmwareState::ShellRunning(ShellRunningStep::new(task_id))); + } + None } diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_shell_running.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_shell_running.rs index 9a150073..57b65486 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_shell_running.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_shell_running.rs @@ -1,8 +1,8 @@ use crate::firmware::firmware_state::{AppCrashesStep, FirmwareState, HubHomeStep}; use crate::firmware::prometeu_context::PrometeuContext; use prometeu_hal::log::LogSource; -use prometeu_system::CrashReport; use prometeu_system::task::{TaskId, TaskState}; +use prometeu_system::{CrashReport, SystemProfileAction}; #[derive(Debug, Clone)] pub struct ShellRunningStep { @@ -66,6 +66,16 @@ impl ShellRunningStep { return Some(FirmwareState::AppCrashes(AppCrashesStep { report })); } + if outcome.action == Some(SystemProfileAction::CloseShell) { + if let Err(error) = ctx.os.lifecycle().close_task(self.task_id) { + ctx.os.error( + LogSource::Hub, + format!("Failed to close shell task {:?}: {:?}", self.task_id, error), + ); + } + return Some(FirmwareState::HubHome(HubHomeStep)); + } + if !ctx.os.windows().focused_window_belongs_to_task(self.task_id) { ctx.os.info( LogSource::Hub, diff --git a/crates/console/prometeu-system/src/lib.rs b/crates/console/prometeu-system/src/lib.rs index 1bbecddc..4c9b08e7 100644 --- a/crates/console/prometeu-system/src/lib.rs +++ b/crates/console/prometeu-system/src/lib.rs @@ -5,7 +5,7 @@ mod services; pub use crash_report::CrashReport; pub use os::{LifecycleError, LifecycleOperation, SystemOS}; -pub use programs::PrometeuHub; +pub use programs::{NativeShellApp, PrometeuHub, SystemProfileAction, SystemProfileUpdate}; pub use services::fs; pub use services::process; pub use services::task; diff --git a/crates/console/prometeu-system/src/programs/mod.rs b/crates/console/prometeu-system/src/programs/mod.rs index 1a48ee4a..916c4fb1 100644 --- a/crates/console/prometeu-system/src/programs/mod.rs +++ b/crates/console/prometeu-system/src/programs/mod.rs @@ -1,3 +1,3 @@ mod prometeu_hub; -pub use prometeu_hub::PrometeuHub; +pub use prometeu_hub::{NativeShellApp, PrometeuHub, SystemProfileAction, SystemProfileUpdate}; diff --git a/crates/console/prometeu-system/src/programs/prometeu_hub/mod.rs b/crates/console/prometeu-system/src/programs/prometeu_hub/mod.rs index 02d05735..21d08e2d 100644 --- a/crates/console/prometeu-system/src/programs/prometeu_hub/mod.rs +++ b/crates/console/prometeu-system/src/programs/prometeu_hub/mod.rs @@ -1,4 +1,4 @@ #[allow(clippy::module_inception)] mod prometeu_hub; -pub use prometeu_hub::PrometeuHub; +pub use prometeu_hub::{NativeShellApp, PrometeuHub, SystemProfileAction, SystemProfileUpdate}; diff --git a/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs b/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs index 2828113f..c66d17d0 100644 --- a/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs +++ b/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs @@ -1,16 +1,49 @@ -use crate::windows::WindowOwner; use crate::{CrashReport, SystemOS}; use prometeu_hal::color::Color; -use prometeu_hal::log::{LogLevel, LogSource}; -use prometeu_hal::primitives::Rect; use prometeu_hal::{HardwareBridge, InputSignals}; use prometeu_vm::VirtualMachine; /// PrometeuHub: Launcher and system UI environment. pub struct PrometeuHub; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum NativeShellApp { + ShellA, + ShellB, +} + +impl NativeShellApp { + pub fn app_id(self) -> u32 { + match self { + Self::ShellA => 10_001, + Self::ShellB => 10_002, + } + } + + pub fn title(self) -> &'static str { + match self { + Self::ShellA => "ShellA", + Self::ShellB => "ShellB", + } + } + + pub fn color(self) -> Color { + match self { + Self::ShellA => Color::GREEN, + Self::ShellB => Color::BLUE, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SystemProfileAction { + LaunchNativeShell(NativeShellApp), + CloseShell, +} + pub struct SystemProfileUpdate { pub crash: Option, + pub action: Option, } impl Default for PrometeuHub { @@ -28,43 +61,19 @@ impl PrometeuHub { // Initializes the Window System and lists apps } - pub fn gui_update(&mut self, os: &mut SystemOS, hw: &mut dyn HardwareBridge) { + pub fn gui_update( + &mut self, + _os: &mut SystemOS, + hw: &mut dyn HardwareBridge, + ) -> Option { hw.gfx_mut().clear(Color::BLACK); - let mut next_window = None; - if hw.pad().a().pressed { - os.log(LogLevel::Debug, LogSource::Hub, 0, "window A opened".to_string()); - next_window = Some(( - "Green Window".to_string(), - Rect { x: 0, y: 0, w: 160, h: 90 }, - Color::GREEN, - )); + Some(SystemProfileAction::LaunchNativeShell(NativeShellApp::ShellA)) } else if hw.pad().b().pressed { - os.log(LogLevel::Debug, LogSource::Hub, 0, "window B opened".to_string()); - next_window = Some(( - "Indigo Window".to_string(), - Rect { x: 160, y: 0, w: 160, h: 90 }, - Color::INDIGO, - )); - } else if hw.pad().x().pressed { - os.log(LogLevel::Debug, LogSource::Hub, 0, "window X opened".to_string()); - next_window = Some(( - "Yellow Window".to_string(), - Rect { x: 0, y: 90, w: 160, h: 90 }, - Color::YELLOW, - )); - } else if hw.pad().y().pressed { - os.log(LogLevel::Debug, LogSource::Hub, 0, "window Y opened".to_string()); - next_window = - Some(("Red Window".to_string(), Rect { x: 160, y: 90, w: 160, h: 90 }, Color::RED)); - } - - if let Some((title, rect, color)) = next_window { - let mut window = os.windows(); - window.remove_all_windows(); - let id = window.add_window(title, WindowOwner::Hub, rect, color); - window.set_focus(id); + Some(SystemProfileAction::LaunchNativeShell(NativeShellApp::ShellB)) + } else { + None } } @@ -88,12 +97,11 @@ impl PrometeuHub { hw: &mut dyn HardwareBridge, ) -> SystemProfileUpdate { let mut crash = None; + let mut action = self.gui_update(os, hw); - self.gui_update(os, hw); - - if let Some(focused_id) = os.windows().focused_window() { + if os.windows().focused_window().is_some() { if hw.pad().start().down { - os.windows().close_window(focused_id); + action = Some(SystemProfileAction::CloseShell); } else { crash = os.vm().tick(vm, signals, hw); } @@ -102,6 +110,6 @@ impl PrometeuHub { self.render(os, hw); hw.gfx_mut().present(); - SystemProfileUpdate { crash } + SystemProfileUpdate { crash, action } } } diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 9ece927b..8584c727 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -34,4 +34,4 @@ {"type":"discussion","id":"DSC-0031","status":"done","ticket":"runtime-mode-separation-game-system","title":"Agenda - Runtime Mode Separation: Game and System","created_at":"2026-05-11","updated_at":"2026-05-14","tags":["runtime","firmware","hub","system-apps","game-mode","scheduler"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0040","file":"discussion/lessons/DSC-0031-runtime-mode-separation-game-system/LSN-0040-system-pipeline-separation.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14"}]} {"type":"discussion","id":"DSC-0032","status":"done","ticket":"system-os-lifecycle-process-task-contract","title":"Agenda - SystemOS Lifecycle, Process and Task Contract","created_at":"2026-05-14","updated_at":"2026-05-15","tags":["runtime","os","lifecycle","process","task","shell","firmware"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0041","file":"discussion/lessons/DSC-0032-system-os-lifecycle-process-task-contract/LSN-0041-systemos-lifecycle-authority.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0033","status":"done","ticket":"system-os-service-ownership-and-module-layout","title":"Agenda - SystemOS Service Ownership and Module Layout","created_at":"2026-05-14","updated_at":"2026-05-15","tags":["runtime","os","services","module-layout","vm","window-manager","logging"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0042","file":"discussion/lessons/DSC-0033-system-os-service-ownership-and-module-layout/LSN-0042-systemos-service-ownership-boundary.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} -{"type":"discussion","id":"DSC-0036","status":"in_progress","ticket":"prometeu-hub-ui-direction","title":"Agenda - Prometeu Hub UI Direction","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["hub","ui","shell","system-apps","lifecycle","design-system"],"agendas":[{"id":"AGD-0036","file":"AGD-0036-prometeu-hub-ui-direction.md","status":"accepted","created_at":"2026-05-15","updated_at":"2026-05-15"}],"decisions":[{"id":"DEC-0028","file":"DEC-0028-prometeu-hub-initial-retro-shell-ui-slice.md","status":"accepted","created_at":"2026-05-15","updated_at":"2026-05-15","ref_agenda":"AGD-0036"}],"plans":[{"id":"PLN-0062","file":"PLN-0062-native-shell-launch-and-lifecycle-wiring.md","status":"open","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0028"]},{"id":"PLN-0063","file":"PLN-0063-hub-mouse-input-and-click-routing.md","status":"open","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0028"]},{"id":"PLN-0064","file":"PLN-0064-retro-hub-home-and-fake-shell-ui.md","status":"open","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0028"]},{"id":"PLN-0065","file":"PLN-0065-pixel-operator-font-integration.md","status":"open","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0028"]},{"id":"PLN-0066","file":"PLN-0066-hub-ui-slice-validation-and-lesson.md","status":"open","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0028"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0036","status":"in_progress","ticket":"prometeu-hub-ui-direction","title":"Agenda - Prometeu Hub UI Direction","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["hub","ui","shell","system-apps","lifecycle","design-system"],"agendas":[{"id":"AGD-0036","file":"AGD-0036-prometeu-hub-ui-direction.md","status":"accepted","created_at":"2026-05-15","updated_at":"2026-05-15"}],"decisions":[{"id":"DEC-0028","file":"DEC-0028-prometeu-hub-initial-retro-shell-ui-slice.md","status":"accepted","created_at":"2026-05-15","updated_at":"2026-05-15","ref_agenda":"AGD-0036"}],"plans":[{"id":"PLN-0062","file":"PLN-0062-native-shell-launch-and-lifecycle-wiring.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0028"]},{"id":"PLN-0063","file":"PLN-0063-hub-mouse-input-and-click-routing.md","status":"open","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0028"]},{"id":"PLN-0064","file":"PLN-0064-retro-hub-home-and-fake-shell-ui.md","status":"open","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0028"]},{"id":"PLN-0065","file":"PLN-0065-pixel-operator-font-integration.md","status":"open","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0028"]},{"id":"PLN-0066","file":"PLN-0066-hub-ui-slice-validation-and-lesson.md","status":"open","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0028"]}],"lessons":[]} diff --git a/discussion/workflow/plans/PLN-0062-native-shell-launch-and-lifecycle-wiring.md b/discussion/workflow/plans/PLN-0062-native-shell-launch-and-lifecycle-wiring.md index 852d7155..12e98a9d 100644 --- a/discussion/workflow/plans/PLN-0062-native-shell-launch-and-lifecycle-wiring.md +++ b/discussion/workflow/plans/PLN-0062-native-shell-launch-and-lifecycle-wiring.md @@ -2,7 +2,7 @@ id: PLN-0062 ticket: prometeu-hub-ui-direction title: Native Shell Launch and Lifecycle Wiring -status: open +status: done created: 2026-05-15 ref_decisions: [DEC-0028] tags: [hub, ui, shell, system-apps, lifecycle, design-system] @@ -82,13 +82,13 @@ Make the launch/close path architecturally correct before the visual slice depen ## Acceptance Criteria -- [ ] Launching `ShellA` creates a native Shell task/process. -- [ ] Launching `ShellB` creates a native Shell task/process. -- [ ] Each launched Shell app receives a focused `WindowOwner::Task(task_id)` window. -- [ ] Closing the Shell app closes lifecycle state through `os.lifecycle().close_task(task_id)`. -- [ ] Closing the Shell app returns firmware to `HubHome`. -- [ ] Hub visual code does not directly mutate task/process managers. -- [ ] Existing shell liveness behavior remains intact. +- [x] Launching `ShellA` creates a native Shell task/process. +- [x] Launching `ShellB` creates a native Shell task/process. +- [x] Each launched Shell app receives a focused `WindowOwner::Task(task_id)` window. +- [x] Closing the Shell app closes lifecycle state through `os.lifecycle().close_task(task_id)`. +- [x] Closing the Shell app returns firmware to `HubHome`. +- [x] Hub visual code does not directly mutate task/process managers. +- [x] Existing shell liveness behavior remains intact. ## Tests