--- id: LSN-0044 ticket: task-owned-shell-windows title: Task Window Liveness Belongs to the Task created: 2026-05-15 tags: [runtime, os, task, window-manager, shell, lifecycle] --- # Task Window Liveness Belongs to the Task ## Context The Prometeu OS model separates execution, navigation, and visual presence: ```text Process = technical execution Task = navigable presence Window = visual presence of the task ``` Before `DSC-0035`, shell execution used a generic `focused_window_active` signal. That signal only answered whether any window was focused. It did not prove that the focused window represented the shell task currently driven by `ShellRunningStep`. The implemented contract replaced that generic signal with a task-owned window predicate: ```text os.windows().focused_window_belongs_to_task(task_id) ``` ## Key Decisions ### Shell liveness requires the task's own focused window **What:** `ShellRunningStep` continues only when the task is foreground and the global focused window is owned by `WindowOwner::Task(task_id)`. **Why:** A shell task in foreground must have its own visual presence. Any focused window is not enough; the focused window must belong to the same task. **Trade-offs:** This v1 rule is strict. It does not preserve a shell task under focused overlays, minimized windows, background execution, or app switcher state. Those behaviors need their own explicit contracts. ### Window ownership is queried through the window facade **What:** `WindowFacade` exposes `focused_window_belongs_to_task(task_id)`. **Why:** The question is about the global focused window and its owner. It is a window-domain query, not a lifecycle transition. **Trade-offs:** Firmware still coordinates across domains: it asks the window facade for ownership and then calls lifecycle when the predicate is false. ### Lifecycle still owns state transitions **What:** When the shell task loses its eligible focused window, `ShellRunningStep` closes the task through `os.lifecycle().close_task(task_id)` before returning to `HubHome`. **Why:** Returning to Hub without lifecycle closure would leave task and process state dependent on implicit cleanup. **Trade-offs:** A visual close becomes a lifecycle event for foreground shell apps. That is correct for v1 shell apps, but it must not be generalized to background services without a separate decision. ## Patterns and Algorithms Use this split when OS behavior crosses services: ```text WindowFacade answers questions about windows, focus, and ownership LifecycleFacade changes TaskState and ProcessState Firmware step coordinates policy between the two domains ``` The useful pattern is not "windows close tasks". The pattern is: 1. derive a visual-presence predicate from the window domain; 2. let the firmware step decide what the predicate means in that state; 3. perform lifecycle transitions through `SystemOS` lifecycle. ## Pitfalls - Do not use a generic "some window is focused" signal as a task liveness contract. - Do not treat `Hub` or `Overlay` windows as shell task windows. - Do not model game cartridges as `WindowManager` windows. Games remain fullscreen `GameRunningStep` sessions. - Do not add overlay preservation, minimization, app switcher, or background service behavior as incidental side effects of shell liveness. - Do not return to Hub without closing the shell task through lifecycle. ## Takeaways - Task-owned shell windows bind shell liveness to the task's own visual presence. - Ownership predicates belong in the window facade; state mutation belongs in lifecycle. - Strict v1 semantics are easier to evolve than an implicit "active window" concept. - Future overlay or background behavior should be introduced by explicit policy, not by weakening the shell liveness predicate.