--- id: DEC-0027 ticket: task-owned-shell-windows title: Decision - Task-Owned Shell Windows status: accepted created: 2026-05-15 accepted: 2026-05-15 ref_agenda: AGD-0035 tags: [runtime, os, task, window-manager, shell, lifecycle] --- # Decision - Task-Owned Shell Windows ## Status Accepted on 2026-05-15. This decision is normative and ready to drive plans. ## Contexto O Prometeu OS ja separa lifecycle tecnico e presenca navegavel: ```text Process = execucao tecnica Task = presenca navegavel Window = presenca visual da task ``` `SystemOS` e a autoridade semantica para coordenar `TaskState` e `ProcessState`. `TaskManager`, `ProcessManager` e `WindowManager` continuam como storage/servicos de dominio, acessados por facades. `WindowOwner` ja representa `Hub`, `Task(TaskId)` e `Overlay`. Shell apps podem criar janelas com `WindowOwner::Task(task_id)`. Games continuam fora do `WindowManager`: eles sao sessoes fullscreen conduzidas por `GameRunningStep`, nao janelas. Antes desta decision, `ShellRunningStep` dependia de um sinal generico, `focused_window_active`, que apenas informava se existia alguma janela focada. Esse contrato nao garantia que a janela focada pertencia a task que o `ShellRunningStep` estava conduzindo. ## Decisao `ShellRunningStep` MUST continuar rodando uma Shell task somente quando as duas condicoes abaixo forem verdadeiras: ```text TaskState::Foreground + focused window owner == WindowOwner::Task(task_id) ``` O predicado de janela MUST ser exposto pela `WindowFacade` como: ```text os.windows().focused_window_belongs_to_task(task_id) ``` Esse predicado MUST consultar o foco global do `WindowManager` e responder se a janela atualmente focada pertence a `WindowOwner::Task(task_id)`. Quando `ShellRunningStep` detectar que a Shell task foreground nao possui a janela focada elegivel, ele MUST fechar a task por lifecycle antes de retornar ao Hub: ```text Shell task perde sua janela focada elegivel -> os.lifecycle().close_task(task_id) -> TaskState::Closed -> ProcessState::Stopped -> FirmwareState::HubHome ``` O firmware MUST NOT apenas retornar ao Hub sem acionar `os.lifecycle().close_task(task_id)`, porque isso deixaria a task/processo dependente de cleanup implicito. ## Rationale O modelo do OS precisa preservar a relacao direta entre task navegavel e janela visual. Se uma Shell task esta em foreground, sua continuidade precisa depender da janela visual da propria task, nao da existencia de qualquer janela focada. `focused_window_belongs_to_task(task_id)` e o nome escolhido porque o foco e global no `WindowManager`. A pergunta nao e se a task possui um subsistema de foco proprio, mas se o foco global atual pertence aquela task. A consulta pertence a `WindowFacade` porque ela responde sobre janela, foco e ownership. Transicoes de estado continuam pertencendo a `LifecycleFacade`. `ShellRunningStep` coordena os dois dominios no nivel firmware: ```text WindowFacade responde perguntas sobre janelas, foco e ownership LifecycleFacade altera TaskState e ProcessState ShellRunningStep coordena a decisao firmware-level entre janela e lifecycle ``` Essa separacao preserva o desenho recente de `SystemOS`: ownership e mediacao continuam centralizados, mas cada facade responde pelo dominio certo. ## Alternativas Descartadas ### Manter `focused_window_active` Descartado como contrato final porque o booleano generico nao codifica ownership da task. Ele permitiria que uma Shell task continuasse por causa de uma janela pertencente ao `Hub`, a uma `Overlay`, ou a outra task futura. ### Usar "task possui alguma janela ativa" Descartado para v1 porque abre semantica de overlays, multiplas janelas, minimizacao, app switcher e background. Essa direcao pode ser reavaliada em agenda futura, mas e maior que o contrato necessario agora. ### Lifecycle fecha janelas, mas janelas nunca afetam lifecycle Descartado para Shell apps foreground porque permitiria uma Shell task foreground sem presenca visual propria. Isso contradiz o contrato `Window = presenca visual da task` para apps Shell. ## Invariantes / Contrato - `Process` MUST representar execucao tecnica. - `Task` MUST representar presenca navegavel. - `Window` MUST representar presenca visual de Shell/system tasks quando essas tasks sao modeladas pelo `WindowManager`. - Shell app windows MUST usar `WindowOwner::Task(TaskId)`. - `Hub` MUST NOT ser tratado como janela de Shell task. - `Overlay` MUST NOT ser tratado como janela de Shell task nesta v1. - Games MUST NOT ser modelados como `WindowManager` windows. - Games MUST continuar como sessoes fullscreen conduzidas por `GameRunningStep`. - `ShellRunningStep` MUST NOT usar `focused_window_active` como contrato de continuidade da sua task. - `ShellRunningStep` MUST consultar `os.windows().focused_window_belongs_to_task(task_id)` para verificar ownership da janela focada. - Quando a consulta retornar falso para uma Shell task foreground, `ShellRunningStep` MUST chamar `os.lifecycle().close_task(task_id)` e retornar `FirmwareState::HubHome`. - `WindowFacade` MUST responder perguntas de foco, janela e ownership. - `LifecycleFacade` MUST executar mudancas de `TaskState` e `ProcessState`. - Uma futura politica de overlay/modal MUST ser decidida em agenda propria se overlays passarem a roubar foco ou preservar task subjacente. ## Impactos ### Spec Os specs canonicos MUST documentar o contrato: ```text Process = execucao tecnica Task = presenca navegavel Window = presenca visual da task ``` Eles tambem MUST documentar que games nao sao janelas, que games rodam via `GameRunningStep`, que Shell apps usam `WindowOwner::Task(TaskId)`, e que v1 de `ShellRunningStep` depende de foco pertencente a propria task. ### Runtime / System `WindowFacade` precisa expor `focused_window_belongs_to_task(task_id)`. `WindowManager` pode continuar sendo o storage de foco e janelas, mas a API publica consumida pelo firmware deve passar pela facade. ### Firmware `ShellRunningStep` deve substituir o uso de `focused_window_active` pela nova consulta em `os.windows()`. Quando o predicado for falso, deve fechar a task via `os.lifecycle().close_task(task_id)` e retornar `HubHome`. `GameRunningStep` nao muda por esta decision. ### Host Sem impacto direto esperado. O host nao deve precisar conhecer o contrato Task-Window para Shell task liveness. ### Firmware / Hub O outcome generico `focused_window_active` deixa de ser o contrato de continuidade de `ShellRunningStep`. Ele pode permanecer como detalhe interno ou ser removido em plano posterior, desde que nao seja usado como predicado de liveness da Shell task. ### Tooling Sem impacto direto esperado alem de testes. ## Propagacao Necessaria - Criar plano de execucao a partir desta decision antes de editar specs ou codigo. - Atualizar specs canonicos em ingles com o contrato Task-Window. - Adicionar `focused_window_belongs_to_task(task_id)` em `WindowFacade` e, se fizer sentido para teste unitario, em `WindowManager`. - Alterar `ShellRunningStep` para consultar a nova API e fechar lifecycle antes de retornar `HubHome`. - Ajustar ou remover o uso de `focused_window_active` no caminho de Shell running. - Adicionar testes para: - janela focada da propria task permite continuidade; - janela focada de outro owner fecha a Shell task; - ausencia de foco fecha a Shell task; - fechamento passa por `LifecycleFacade` e para o processo; - games continuam fora do `WindowManager`. ## Fora de Escopo - overlay/modal policy; - background services; - minimizacao; - app switcher; - dock; - multiplas janelas complexas; - redesign da Shell; - transformar games em janelas; - reabrir o contrato geral de `Process`, `Task` e lifecycle. ## Referencias - Agenda: `AGD-0035` - Discussao: `DSC-0035` - Licao relacionada: `LSN-0040` System Pipeline Separation - Licao relacionada: `LSN-0041` SystemOS Lifecycle Authority - Licao relacionada: `LSN-0042` SystemOS Service Ownership Boundary - Licao relacionada: `LSN-0043` SystemOS Domain Facades ## Revision Log - 2026-05-15: Draft inicial criado a partir de `AGD-0035`.