implements PLN-0061 shell running lifecycle integration

This commit is contained in:
bQUARKz 2026-05-15 11:47:26 +01:00
parent d929f8e2f1
commit c2698e6a22
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
5 changed files with 112 additions and 16 deletions

View File

@ -175,10 +175,13 @@ mod tests {
use prometeu_bytecode::model::{BytecodeModule, FunctionMeta, SyscallDecl}; use prometeu_bytecode::model::{BytecodeModule, FunctionMeta, SyscallDecl};
use prometeu_drivers::hardware::Hardware; use prometeu_drivers::hardware::Hardware;
use prometeu_hal::cartridge::{AppMode, AssetsPayloadSource}; use prometeu_hal::cartridge::{AppMode, AssetsPayloadSource};
use prometeu_hal::color::Color;
use prometeu_hal::primitives::Rect;
use prometeu_hal::syscalls::caps; use prometeu_hal::syscalls::caps;
use prometeu_system::CrashReport; use prometeu_system::CrashReport;
use prometeu_system::process::ProcessState; use prometeu_system::process::ProcessState;
use prometeu_system::task::TaskState; use prometeu_system::task::{TaskId, TaskState};
use prometeu_system::windows::WindowOwner;
fn halting_program() -> Vec<u8> { fn halting_program() -> Vec<u8> {
let code = assemble("HALT").expect("assemble"); let code = assemble("HALT").expect("assemble");
@ -262,6 +265,34 @@ mod tests {
} }
} }
fn load_shell_running_firmware() -> (Firmware, Hardware, InputSignals, TaskId) {
let mut firmware = Firmware::new(None);
let mut hardware = Hardware::new();
let signals = InputSignals::default();
firmware.load_cartridge(valid_cartridge(AppMode::Shell));
firmware.tick(&signals, &mut hardware);
let task_id = match &firmware.state {
FirmwareState::ShellRunning(step) => step.task_id,
other => panic!("expected ShellRunning state, got {:?}", other),
};
(firmware, hardware, signals, task_id)
}
fn focus_window(firmware: &mut Firmware, owner: WindowOwner) {
let mut windows = firmware.os.windows();
windows.remove_all_windows();
let id = windows.add_window(
"Test Window".to_string(),
owner,
Rect { x: 0, y: 0, w: 10, h: 10 },
Color::WHITE,
);
windows.set_focus(id);
}
#[test] #[test]
fn load_cartridge_transitions_to_app_crashes_when_vm_init_fails() { fn load_cartridge_transitions_to_app_crashes_when_vm_init_fails() {
let mut firmware = Firmware::new(None); let mut firmware = Firmware::new(None);
@ -362,14 +393,79 @@ mod tests {
firmware.load_cartridge(valid_cartridge(AppMode::Shell)); firmware.load_cartridge(valid_cartridge(AppMode::Shell));
firmware.tick(&signals, &mut hardware); firmware.tick(&signals, &mut hardware);
let task_id = match &firmware.state {
FirmwareState::ShellRunning(step) => step.task_id,
other => panic!("expected ShellRunning state, got {:?}", other),
};
let tick_index_before_system_update = firmware.os.vm().tick_index(); let tick_index_before_system_update = firmware.os.vm().tick_index();
firmware.tick(&signals, &mut hardware); firmware.tick(&signals, &mut hardware);
assert!(matches!(firmware.state, FirmwareState::ShellRunning(_))); assert!(matches!(firmware.state, FirmwareState::ShellRunning(_)));
assert!(firmware.os.windows().focused_window_belongs_to_task(task_id));
assert_eq!(firmware.os.vm().tick_index(), tick_index_before_system_update + 1); assert_eq!(firmware.os.vm().tick_index(), tick_index_before_system_update + 1);
} }
#[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();
firmware.os.windows().remove_all_windows();
firmware.tick(&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_for_focused_hub_window() {
let (mut firmware, mut hardware, signals, task_id) = load_shell_running_firmware();
focus_window(&mut firmware, WindowOwner::Hub);
firmware.tick(&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_for_focused_overlay_window() {
let (mut firmware, mut hardware, signals, task_id) = load_shell_running_firmware();
focus_window(&mut firmware, WindowOwner::Overlay);
firmware.tick(&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_for_other_task_window() {
let (mut firmware, mut hardware, signals, task_id) = load_shell_running_firmware();
focus_window(&mut firmware, WindowOwner::Task(TaskId(task_id.0 + 1)));
firmware.tick(&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] #[test]
fn load_cartridge_routes_game_apps_to_game_running() { fn load_cartridge_routes_game_apps_to_game_running() {
let mut firmware = Firmware::new(None); let mut firmware = Firmware::new(None);
@ -380,5 +476,6 @@ mod tests {
firmware.tick(&signals, &mut hardware); firmware.tick(&signals, &mut hardware);
assert!(matches!(firmware.state, FirmwareState::GameRunning(_))); assert!(matches!(firmware.state, FirmwareState::GameRunning(_)));
assert_eq!(firmware.os.windows().window_count(), 0);
} }
} }

View File

@ -66,10 +66,13 @@ impl ShellRunningStep {
return Some(FirmwareState::AppCrashes(AppCrashesStep { report })); return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
} }
if !outcome.focused_window_active { if !ctx.os.windows().focused_window_belongs_to_task(self.task_id) {
ctx.os.info( ctx.os.info(
LogSource::Hub, LogSource::Hub,
format!("Closing shell task {:?}: no focused window active", self.task_id), format!(
"Closing shell task {:?}: focused window does not belong to task",
self.task_id,
),
); );
if let Err(error) = ctx.os.lifecycle().close_task(self.task_id) { if let Err(error) = ctx.os.lifecycle().close_task(self.task_id) {
ctx.os.error( ctx.os.error(

View File

@ -11,7 +11,6 @@ pub struct PrometeuHub;
pub struct SystemProfileUpdate { pub struct SystemProfileUpdate {
pub crash: Option<CrashReport>, pub crash: Option<CrashReport>,
pub focused_window_active: bool,
} }
impl Default for PrometeuHub { impl Default for PrometeuHub {
@ -103,9 +102,6 @@ impl PrometeuHub {
self.render(os, hw); self.render(os, hw);
hw.gfx_mut().present(); hw.gfx_mut().present();
SystemProfileUpdate { SystemProfileUpdate { crash }
crash,
focused_window_active: os.windows().focused_window().is_some(),
}
} }
} }

View File

@ -1,5 +1,5 @@
{"type":"meta","next_id":{"DSC":36,"AGD":36,"DEC":28,"PLN":62,"LSN":44,"CLSN":1}} {"type":"meta","next_id":{"DSC":36,"AGD":36,"DEC":28,"PLN":62,"LSN":44,"CLSN":1}}
{"type":"discussion","id":"DSC-0035","status":"in_progress","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[{"id":"AGD-0035","file":"AGD-0035-agenda-task-owned-shell-windows.md","status":"accepted","created_at":"2026-05-15","updated_at":"2026-05-15"}],"decisions":[{"id":"DEC-0027","file":"DEC-0027-decision-task-owned-shell-windows.md","status":"accepted","created_at":"2026-05-15","updated_at":"2026-05-15","ref_agenda":"AGD-0035"}],"plans":[{"id":"PLN-0059","file":"PLN-0059-plan-task-window-contract-specs.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0027"]},{"id":"PLN-0060","file":"PLN-0060-plan-focused-window-ownership-api.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0027"]},{"id":"PLN-0061","file":"PLN-0061-plan-shell-running-lifecycle-integration.md","status":"open","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0027"]}],"lessons":[]} {"type":"discussion","id":"DSC-0035","status":"in_progress","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[{"id":"AGD-0035","file":"AGD-0035-agenda-task-owned-shell-windows.md","status":"accepted","created_at":"2026-05-15","updated_at":"2026-05-15"}],"decisions":[{"id":"DEC-0027","file":"DEC-0027-decision-task-owned-shell-windows.md","status":"accepted","created_at":"2026-05-15","updated_at":"2026-05-15","ref_agenda":"AGD-0035"}],"plans":[{"id":"PLN-0059","file":"PLN-0059-plan-task-window-contract-specs.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0027"]},{"id":"PLN-0060","file":"PLN-0060-plan-focused-window-ownership-api.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0027"]},{"id":"PLN-0061","file":"PLN-0061-plan-shell-running-lifecycle-integration.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0027"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]}
{"type":"discussion","id":"DSC-0023","status":"done","ticket":"perf-full-migration-to-atomic-telemetry","title":"Agenda - [PERF] Full Migration to Atomic Telemetry","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["perf","runtime","telemetry"],"agendas":[{"id":"AGD-0021","file":"AGD-0021-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0008","file":"DEC-0008-full-migration-to-atomic-telemetry.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0007","file":"PLN-0007-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0028","file":"discussion/lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} {"type":"discussion","id":"DSC-0023","status":"done","ticket":"perf-full-migration-to-atomic-telemetry","title":"Agenda - [PERF] Full Migration to Atomic Telemetry","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["perf","runtime","telemetry"],"agendas":[{"id":"AGD-0021","file":"AGD-0021-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0008","file":"DEC-0008-full-migration-to-atomic-telemetry.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0007","file":"PLN-0007-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0028","file":"discussion/lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
{"type":"discussion","id":"DSC-0020","status":"done","ticket":"jenkins-gitea-integration","title":"Jenkins Gitea Integration and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins","gitea"],"agendas":[{"id":"AGD-0018","file":"AGD-0018-jenkins-gitea-integration-and-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0003","file":"DEC-0003-jenkins-gitea-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0003","file":"PLN-0003-jenkins-gitea-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0021","file":"discussion/lessons/DSC-0020-jenkins-gitea-integration/LSN-0021-jenkins-gitea-integration.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]} {"type":"discussion","id":"DSC-0020","status":"done","ticket":"jenkins-gitea-integration","title":"Jenkins Gitea Integration and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins","gitea"],"agendas":[{"id":"AGD-0018","file":"AGD-0018-jenkins-gitea-integration-and-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0003","file":"DEC-0003-jenkins-gitea-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0003","file":"PLN-0003-jenkins-gitea-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0021","file":"discussion/lessons/DSC-0020-jenkins-gitea-integration/LSN-0021-jenkins-gitea-integration.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]}

View File

@ -2,9 +2,9 @@
id: PLN-0061 id: PLN-0061
ticket: task-owned-shell-windows ticket: task-owned-shell-windows
title: Plan - Shell Running Lifecycle Integration title: Plan - Shell Running Lifecycle Integration
status: open status: done
created: 2026-05-15 created: 2026-05-15
completed: completed: 2026-05-15
ref_decisions: [DEC-0027] ref_decisions: [DEC-0027]
tags: [runtime, os, task, window-manager, shell, lifecycle, firmware, tests] tags: [runtime, os, task, window-manager, shell, lifecycle, firmware, tests]
--- ---
@ -100,15 +100,15 @@ Included:
## Criterios de Aceite ## Criterios de Aceite
- [ ] `ShellRunningStep` no longer uses `focused_window_active` as its - [x] `ShellRunningStep` no longer uses `focused_window_active` as its
continuation contract. continuation contract.
- [ ] `ShellRunningStep` uses - [x] `ShellRunningStep` uses
`os.windows().focused_window_belongs_to_task(self.task_id)`. `os.windows().focused_window_belongs_to_task(self.task_id)`.
- [ ] A false predicate closes the task through lifecycle before returning - [x] A false predicate closes the task through lifecycle before returning
`HubHome`. `HubHome`.
- [ ] `Hub`, `Overlay`, absent focus, and another task's focused window all - [x] `Hub`, `Overlay`, absent focus, and another task's focused window all
fail the predicate for the current shell task. fail the predicate for the current shell task.
- [ ] Game-running behavior is unchanged and remains outside `WindowManager`. - [x] Game-running behavior is unchanged and remains outside `WindowManager`.
## Tests / Validacao ## Tests / Validacao