From 2154dba080e35b3baa0028914e7b542a2234cf12 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 15 May 2026 11:43:04 +0100 Subject: [PATCH 1/5] Task-Owned Shell Windows --- discussion/index.ndjson | 3 +- ...GD-0035-agenda-task-owned-shell-windows.md | 432 ++++++++++++++++++ ...-0027-decision-task-owned-shell-windows.md | 238 ++++++++++ ...LN-0059-plan-task-window-contract-specs.md | 116 +++++ ...-0060-plan-focused-window-ownership-api.md | 105 +++++ ...lan-shell-running-lifecycle-integration.md | 128 ++++++ 6 files changed, 1021 insertions(+), 1 deletion(-) create mode 100644 discussion/workflow/agendas/AGD-0035-agenda-task-owned-shell-windows.md create mode 100644 discussion/workflow/decisions/DEC-0027-decision-task-owned-shell-windows.md create mode 100644 discussion/workflow/plans/PLN-0059-plan-task-window-contract-specs.md create mode 100644 discussion/workflow/plans/PLN-0060-plan-focused-window-ownership-api.md create mode 100644 discussion/workflow/plans/PLN-0061-plan-shell-running-lifecycle-integration.md diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 419f40a8..3874ab01 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,4 +1,5 @@ -{"type":"meta","next_id":{"DSC":35,"AGD":35,"DEC":27,"PLN":59,"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":"open","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":"open","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-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-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"}]} diff --git a/discussion/workflow/agendas/AGD-0035-agenda-task-owned-shell-windows.md b/discussion/workflow/agendas/AGD-0035-agenda-task-owned-shell-windows.md new file mode 100644 index 00000000..cd3ee222 --- /dev/null +++ b/discussion/workflow/agendas/AGD-0035-agenda-task-owned-shell-windows.md @@ -0,0 +1,432 @@ +--- +id: AGD-0035 +ticket: task-owned-shell-windows +title: Agenda - Task-Owned Shell Windows +status: accepted +created: 2026-05-15 +resolved: 2026-05-15 +decision: DEC-0027 +tags: [runtime, os, task, window-manager, shell, lifecycle] +--- + +# Agenda - Task-Owned Shell Windows + +## Contexto + +O Prometeu OS ja possui `SystemOS` como autoridade semantica para coordenar +lifecycle entre `Task` e `Process`, com facades por dominio para evitar que o +firmware manipule storage interno diretamente. + +O modelo atual separa: + +```text +Process = execucao tecnica +Task = presenca navegavel +Window = presenca visual da task +``` + +O `WindowOwner` ja representa `Hub`, `Task(TaskId)` e `Overlay`. Shell apps ja +conseguem criar janelas associadas a uma task, e games continuam fora do +`WindowManager`: eles sao sessoes fullscreen conduzidas por `GameRunningStep`, +nao janelas. + +Hoje, porem, `ShellRunningStep` ainda decide se continua rodando a partir de um +sinal generico: `focused_window_active`. Esse sinal vem do resultado do update +do Hub e apenas indica se existe alguma janela focada/ativa, sem confirmar que +essa janela pertence a task que o `ShellRunningStep` esta conduzindo. + +## Problema + +O contrato de liveness de uma Shell task ainda esta acoplado a presenca visual +generica, nao a presenca visual da propria task. + +Isso deixa uma lacuna conceitual: + +- uma task pode estar em `Foreground`, mas a janela focada pode pertencer a + outro owner; +- uma janela pode estar focada, mas nao representar a task que esta rodando no + `ShellRunningStep`; +- o firmware precisa inferir continuidade por um booleano generico em vez de + perguntar ao OS: "esta task ainda possui uma janela ativa/focada?". + +Se `Task` e a unidade de navegacao, e `Window` e a presenca visual dessa task, +o fluxo de Shell precisa saber se a presenca visual pertence a mesma task. + +## Pontos Criticos + +1. Autoridade do contrato. + A pergunta deve provavelmente passar por `SystemOS` ou por uma facade de + windows/lifecycle, nao por logica espalhada no Hub. O `WindowManager` pode + armazenar e consultar janelas, mas a semantica de "task continua viva" deve + permanecer coerente com o modelo de lifecycle. + +2. Foco versus atividade. + A decisao precisa separar: + - "a task possui alguma janela"; + - "a task possui uma janela ativa"; + - "a task possui a janela atualmente focada". + + Para o `ShellRunningStep`, o criterio candidato e mais forte: a janela + focada deve pertencer a propria task. + +3. Encerramento da task. + Se uma Shell task perde sua unica janela ativa/focada, o sistema precisa + decidir se isso fecha a task, suspende a task, retorna ao Hub, ou representa + erro. Para esta agenda, o foco e o contrato Task-Window; politicas futuras + de background ficam fora. + +4. Separacao entre Shell e Game. + Games nao devem virar `WindowOwner::Task`. A presenca visual de game + continua sendo a propria sessao fullscreen do `GameRunningStep`. O contrato + aqui vale para Shell/system apps representados por janelas. + +5. Multiplicidade futura sem resolver agora. + O contrato deve ser simples o bastante para uma janela por Shell task hoje, + mas nao deve bloquear uma evolucao posterior para multiplas janelas. A + agenda nao deve decidir dock, app switcher, services em background, + minimizacao, ou redesign da Shell. + +## Opcoes + +### Opcao A - Manter `focused_window_active` como criterio generico + +**Abordagem:** `ShellRunningStep` continua dependendo do outcome generico do +Hub, que informa apenas se existe alguma janela focada/ativa. + +**Vantagens:** + +- menor mudanca imediata; +- preserva o fluxo atual de retorno ao Hub; +- evita adicionar API nova no `WindowManager` agora. + +**Riscos / Custos:** + +- nao expressa o modelo `Task = presenca navegavel` e `Window = presenca visual + da task`; +- permite continuidade baseada em janela de outro owner; +- mantem uma inferencia de lifecycle fora do contrato direto do OS; +- tende a piorar quando houver mais janelas ou owners. + +**Leitura:** opcao aceitavel apenas como estado transitorio, nao como contrato +final. + +### Opcao B - `ShellRunningStep` depende de janela focada pertencente a propria task + +**Abordagem:** expor uma consulta do tipo +`focused_window_belongs_to_task(task_id)` ou +`task_has_focused_window(task_id)`. O `ShellRunningStep` continua somente se a +task estiver `Foreground` e a janela focada tiver `WindowOwner::Task(task_id)`. + +**Vantagens:** + +- alinha diretamente `Task` e `Window`; +- remove a ambiguidade do booleano generico; +- torna o encerramento de Shell task explicavel: sem janela focada da propria + task, nao ha presenca visual ativa para aquela task; +- funciona bem para o modelo atual de uma Shell task em foreground. + +**Riscos / Custos:** + +- pode ser forte demais se o futuro permitir task com janela existente mas + temporariamente sem foco; +- exige definir se perda de foco para `Hub` ou `Overlay` fecha a task ou apenas + pausa o fluxo; +- precisa tomar cuidado para overlays nao roubarem foco de forma que fechem a + task acidentalmente. + +**Leitura:** e a opcao mais coerente para o contrato imediato, desde que +overlays e Hub sejam tratados explicitamente. + +### Opcao C - `ShellRunningStep` depende de qualquer janela ativa pertencente a propria task + +**Abordagem:** expor uma consulta do tipo `task_has_active_window(task_id)`. +Foco global continua separado: a task segue viva enquanto pelo menos uma janela +aberta/ativa pertence a ela, mesmo que outra superficie esteja focada. + +**Vantagens:** + +- prepara melhor um futuro com overlays, app switcher ou multiplas janelas; +- evita fechar task por perda temporaria de foco; +- separa "existencia visual da task" de "janela no topo". + +**Riscos / Custos:** + +- exige definir imediatamente o que significa "active" alem de `has_focus`; +- pode preservar tasks sem presenca navegavel real se a janela existir mas nao + estiver selecionada; +- abre portas para semantica de background/minimizacao, que esta fora do escopo + desta agenda. + +**Leitura:** boa direcao futura, mas talvez maior que o contrato necessario +agora. + +### Opcao D - Lifecycle fecha janelas; janelas nunca fecham lifecycle + +**Abordagem:** `ShellRunningStep` ignora foco/janelas para continuidade; a task +so fecha quando o lifecycle manda fechar. O `WindowManager` seria apenas +reflexo visual da task, nao sinal de liveness. + +**Vantagens:** + +- preserva autoridade total do lifecycle; +- evita que bugs de foco encerrem task; +- simples de raciocinar em termos de `TaskState`. + +**Riscos / Custos:** + +- contradiz a intencao de `Window = presenca visual da task` para Shell apps; +- pode manter uma Shell task foreground sem janela visivel; +- empurra para outro mecanismo a deteccao de fechamento pelo usuario. + +**Leitura:** defensavel para services/background no futuro, mas desalinhada +com Shell apps visuais neste escopo. + +## Sugestao / Recomendacao + +A recomendacao consolidada e seguir a Opcao B como contrato v1: + +```text +ShellRunningStep continua somente quando: +1. sua Task esta em Foreground; +2. a janela focada pertence a WindowOwner::Task(task_id) da propria task. +``` + +Isso troca o sinal generico `focused_window_active` por uma pergunta explicita +ao `WindowFacade`: + +```text +os.windows().focused_window_belongs_to_task(task_id) +``` + +Esse nome comunica que o `WindowManager` possui um foco global, e que a +pergunta e se esse foco global pertence a task informada. O nome +`task_has_focused_window` continua sendo conceitualmente aceitavel, mas pode +soar como se cada task tivesse um foco proprio. + +O comportamento v1 recomendado e: + +```text +if !ctx.os.windows().focused_window_belongs_to_task(self.task_id) { + ctx.os.lifecycle().close_task(self.task_id)?; + return Some(FirmwareState::HubHome(HubHomeStep)); +} +``` + +O contrato deve declarar tambem: + +- `Hub` e `Overlay` nao sao Shell task windows; +- games nao entram no `WindowManager`; +- a ausencia de janela focada da propria Shell task fecha ou encerra o fluxo da + Shell task conforme a politica atual, sem introduzir background service ou + minimizacao nesta decisao; +- multiplas janelas, dock, app switcher, background services e redesign da + Shell ficam fora desta agenda. + +## Perguntas em Aberto + +- [x] O criterio v1 deve ser "janela focada pertence a task" ou "task possui + alguma janela ativa"? +- [x] O nome da API deve falar em foco (`task_has_focused_window`) ou em + pertencimento do foco global (`focused_window_belongs_to_task`)? +- [x] Quando uma `Overlay` esta focada, ela deve preservar a Shell task + subjacente ou isso ainda nao existe no modelo atual? +- [x] Fechar a ultima janela da Shell task deve chamar `close_task(task_id)` + diretamente ou apenas retornar ao Hub e deixar lifecycle resolver? +- [x] A consulta deve morar no `WindowFacade`, em uma facade de lifecycle, ou em + uma facade composta de `SystemOS`? +- [x] A decision posterior precisa alterar somente firmware/system, ou tambem + documentar esse contrato nos specs canonicos? + +## Respostas Consolidadas + +### Criterio v1 + +O criterio v1 e "janela focada pertence a task". + +`ShellRunningStep` continua somente quando: + +```text +TaskState::Foreground ++ +focused window owner == WindowOwner::Task(task_id) +``` + +A alternativa "task possui alguma janela ativa" fica reservada para um futuro +com overlays, multiplas janelas, minimizacao ou app switcher. Ela e mais ampla +do que o contrato necessario agora. + +Nesta versao, Shell apps sao foreground e visualmente representados por uma +janela focada. Se a janela focada nao pertence a task, a task nao deve +continuar executando. + +### Nome e local da API + +O nome recomendado e: + +```text +focused_window_belongs_to_task(task_id) +``` + +A API deve morar em `WindowFacade`: + +```text +os.windows().focused_window_belongs_to_task(task_id) +``` + +Motivo: a pergunta e sobre foco, janela e `WindowOwner`, nao sobre transicao de +lifecycle. + +A separacao de responsabilidades fica: + +```text +WindowFacade + responde perguntas sobre janelas, foco e ownership + +LifecycleFacade + altera TaskState e ProcessState + +ShellRunningStep + coordena a decisao firmware-level entre janela e lifecycle +``` + +Se esse padrao se repetir, uma facade composta como `sessions()` pode surgir no +futuro, mas ela nao e necessaria para este contrato v1. + +### Overlays + +Overlay policy ainda nao existe no modelo atual. + +Para v1: + +```text +Hub nao e janela da task. +Overlay nao e janela da task. +Game nao e WindowManager window. +ShellRunningStep exige foco em WindowOwner::Task(task_id). +``` + +Se uma overlay puder roubar foco, isso precisa ser tratado em uma agenda +propria de overlay/modal policy. Esta decisao nao deve tentar preservar, +pausar, ou modelar Shell task sob overlay. + +Possiveis direcoes futuras, fora deste escopo: + +- overlay modal preserva a task subjacente; +- overlay nao-modal nao rouba foco; +- overlay possui owner/contexto proprio. + +### Fechamento da Shell task + +Quando a Shell task perde sua janela focada elegivel, o firmware deve chamar: + +```text +os.lifecycle().close_task(task_id) +``` + +e entao retornar para `HubHome`. + +Nao basta apenas retornar ao Hub, porque isso deixaria task/processo dependente +de cleanup implicito. + +Contrato v1: + +```text +Shell task perde sua janela focada elegivel + -> lifecycle.close_task(task_id) + -> TaskState::Closed + -> ProcessState::Stopped + -> FirmwareState::HubHome +``` + +O fechamento visual da Shell task precisa passar pelo lifecycle do `SystemOS`. + +### Propagacao para specs + +A decision posterior deve alterar firmware/system e tambem documentar o +contrato nos specs canonicos. + +O contrato arquitetural a preservar e: + +```text +Process = execucao tecnica +Task = presenca navegavel +Window = presenca visual da task +``` + +Tambem deve ficar documentado: + +- games nao sao janelas; +- games rodam como sessoes fullscreen via `GameRunningStep`; +- Shell apps possuem janelas no `WindowManager`; +- Shell app windows usam `WindowOwner::Task(TaskId)`; +- `Hub` e `Overlay` nao sao janelas de Shell task; +- na v1, `ShellRunningStep` so continua se a janela focada pertence a sua + propria task. + +Isso evita que trabalho futuro transforme game em window ou reintroduza +`focused_window_active` como contrato generico. + +## Fora de Escopo + +- dock; +- app switcher; +- background services; +- minimizacao; +- multiplas janelas complexas; +- redesign da Shell; +- transformar games em janelas; +- mudar o modelo fullscreen de `GameRunningStep`; +- reabrir o contrato geral de `Process`, `Task` e lifecycle ja consolidado. + +## Criterio para Encerrar + +Esta agenda pode virar decision quando houver resposta explicita para: + +1. qual predicado `ShellRunningStep` usa para continuar; +2. onde a API de consulta Task-Window mora; +3. como `Hub`, `Overlay` e `GameRunningStep` ficam excluidos ou tratados; +4. qual comportamento ocorre quando a Shell task perde sua janela elegivel; +5. quais specs ou codigos precisam ser propagados depois da decision. + +## Discussion + +A discussao convergiu para um contrato v1 pequeno, focado em Shell task +foreground com janela focada propria. + +Resumo da direcao: + +```text +AGD-0035 segue a Opcao B. + +Predicado: + focused_window_belongs_to_task(task_id) + +Facade: + os.windows() + +Comportamento: + se falso em ShellRunningStep: + os.lifecycle().close_task(task_id) + retorna HubHome + +Fora de escopo: + overlay policy + background + minimizacao + app switcher + game windows +``` + +Casos de background, overlay policy, minimizacao, app switcher e multiplas +janelas devem ficar para agendas futuras. + +## Resolution + +Aceita em 2026-05-15 e promovida para `DEC-0027`. + +A decision deve seguir a Opcao B: `ShellRunningStep` continua somente quando a +Shell task esta em `TaskState::Foreground` e a janela focada pertence a +`WindowOwner::Task(task_id)`. A API normativa e +`os.windows().focused_window_belongs_to_task(task_id)`. Quando o predicado for +falso, `ShellRunningStep` deve fechar a task via +`os.lifecycle().close_task(task_id)` e retornar `HubHome`. diff --git a/discussion/workflow/decisions/DEC-0027-decision-task-owned-shell-windows.md b/discussion/workflow/decisions/DEC-0027-decision-task-owned-shell-windows.md new file mode 100644 index 00000000..ae507865 --- /dev/null +++ b/discussion/workflow/decisions/DEC-0027-decision-task-owned-shell-windows.md @@ -0,0 +1,238 @@ +--- +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`. diff --git a/discussion/workflow/plans/PLN-0059-plan-task-window-contract-specs.md b/discussion/workflow/plans/PLN-0059-plan-task-window-contract-specs.md new file mode 100644 index 00000000..a81f0e05 --- /dev/null +++ b/discussion/workflow/plans/PLN-0059-plan-task-window-contract-specs.md @@ -0,0 +1,116 @@ +--- +id: PLN-0059 +ticket: task-owned-shell-windows +title: Plan - Task Window Contract Specs +status: open +created: 2026-05-15 +completed: +ref_decisions: [DEC-0027] +tags: [runtime, os, task, window-manager, shell, lifecycle, specs] +--- + +# Plan - Task Window Contract Specs + +## Briefing + +Document the task-owned shell window contract from `DEC-0027` in the canonical +runtime specs before code execution changes the observable model. + +## Decisions de Origem + +- `DEC-0027` - Task-Owned Shell Windows + +## Alvo + +The canonical specs must state that: + +- `Process` is technical execution. +- `Task` is navigable presence. +- `Window` is visual presence for shell/system tasks managed by the + `WindowManager`. +- Game cartridges are not `WindowManager` windows and continue as fullscreen + `GameRunningStep` sessions. +- Shell app windows use `WindowOwner::Task(TaskId)`. +- `Hub` and `Overlay` are not shell task windows. +- v1 `ShellRunningStep` continues only while the focused window belongs to its + own task. + +## Escopo + +Included: + +- Update [docs/specs/runtime/12-firmware-pos-and-prometeuhub.md](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/docs/specs/runtime/12-firmware-pos-and-prometeuhub.md) with the contract. +- Add or adjust terminology in the same file if the existing terms section is + the best local home. +- Link the contract to firmware states, AppMode behavior, and Hub/system window + responsibilities. + +## Fora de Escopo + +- Code changes in `crates/`. +- Overlay/modal policy. +- Background services, minimization, app switcher, or dock behavior. +- Multiple-window semantics beyond preserving future compatibility. +- Rewriting unrelated firmware spec sections. + +## Plano de Execucao + +1. Locate the normative spec home. + Use `docs/specs/runtime/12-firmware-pos-and-prometeuhub.md` as the primary + target because it already owns POS, Hub, AppMode, and firmware states. + +2. Extend the terms section. + Add concise definitions for `Process`, `Task`, `Window`, `WindowOwner`, and + shell task windows if they are not already present. + +3. Add a Task-Owned Shell Windows section. + State the v1 predicate explicitly: + + ```text + TaskState::Foreground + + + focused window owner == WindowOwner::Task(task_id) + ``` + + State that the runtime API is + `os.windows().focused_window_belongs_to_task(task_id)`. + +4. Clarify AppMode separation. + Under `Game`, state that games are fullscreen firmware sessions and must not + be modeled as `WindowManager` windows. Under `System`, state that shell apps + are represented through task-owned windows. + +5. Clarify lifecycle consequence. + State that when a shell task loses its eligible focused window, + `ShellRunningStep` must close the task through `SystemOS` lifecycle and then + return to Hub. + +6. Keep excluded topics explicit. + Add a short note that overlay policy, minimization, app switcher, dock, and + background services are future contracts, not part of v1. + +## Criterios de Aceite + +- [ ] The spec describes the `Process` / `Task` / `Window` model in English. +- [ ] The spec states that games are not `WindowManager` windows. +- [ ] The spec states that shell app windows use `WindowOwner::Task(TaskId)`. +- [ ] The spec states the v1 `ShellRunningStep` focused-window predicate. +- [ ] The spec states that loss of the eligible focused window closes the task + through lifecycle before returning to Hub. +- [ ] The spec does not introduce overlay, background, minimization, app + switcher, dock, or multiple-window policy. + +## Tests / Validacao + +- Run `discussion validate`. +- Review the changed spec for English-only normative text. +- Confirm every normative point from `DEC-0027` has a corresponding spec line. + +## Riscos + +- The spec may become too implementation-specific if it describes current Rust + call sites instead of the contract. +- The spec may accidentally imply overlay/modal behavior. Keep overlay + behavior explicitly out of scope. +- Future work may need a dedicated OS/window spec chapter; this plan keeps the + change local to the existing firmware/Hub spec. diff --git a/discussion/workflow/plans/PLN-0060-plan-focused-window-ownership-api.md b/discussion/workflow/plans/PLN-0060-plan-focused-window-ownership-api.md new file mode 100644 index 00000000..0d8b38d6 --- /dev/null +++ b/discussion/workflow/plans/PLN-0060-plan-focused-window-ownership-api.md @@ -0,0 +1,105 @@ +--- +id: PLN-0060 +ticket: task-owned-shell-windows +title: Plan - Focused Window Ownership API +status: open +created: 2026-05-15 +completed: +ref_decisions: [DEC-0027] +tags: [runtime, os, task, window-manager, shell, lifecycle, code] +--- + +# Plan - Focused Window Ownership API + +## Briefing + +Add the OS/window query required by `DEC-0027`: +`os.windows().focused_window_belongs_to_task(task_id)`. + +This plan owns the system-side API and tests. It does not change firmware +control flow; that is covered by `PLN-0061`. + +## Decisions de Origem + +- `DEC-0027` - Task-Owned Shell Windows + +## Alvo + +`WindowFacade` must expose a focused-window ownership predicate that answers +whether the global focused window belongs to a specific `TaskId`. + +## Escopo + +Included: + +- Add a focused ownership query to + [window.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-system/src/os/facades/window.rs). +- Add a helper in + [window_manager.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-system/src/services/windows/window_manager.rs) if it keeps the facade thin and testable. +- Add unit tests for focused-window ownership in the `prometeu-system` crate. + +## Fora de Escopo + +- Changing `ShellRunningStep`. +- Removing `focused_window_active` from `PrometeuHubOutcome`. +- Spec edits. +- Overlay/modal policy or active-window semantics. +- Multiple-window policy beyond querying the single global focused window. + +## Plano de Execucao + +1. Add `WindowManager::focused_window_belongs_to_task`. + In `crates/console/prometeu-system/src/services/windows/window_manager.rs`, + implement a method that: + - returns `false` when `focused` is `None`; + - finds the focused window by `WindowId`; + - returns `true` only when its owner is `WindowOwner::Task(task_id)`; + - returns `false` for `Hub`, `Overlay`, another task id, or stale focus. + +2. Add `WindowFacade::focused_window_belongs_to_task`. + In `crates/console/prometeu-system/src/os/facades/window.rs`, expose the + public facade method and delegate to the manager/helper. + +3. Keep existing facade methods intact. + Do not remove `focused_window()` or `windows()` in this plan. Existing call + sites may still need them until `PLN-0061` updates firmware. + +4. Add tests in `window_manager.rs`. + Cover: + - no focused window returns `false`; + - focused `WindowOwner::Task(task_id)` returns `true`; + - focused `WindowOwner::Task(other_task)` returns `false`; + - focused `WindowOwner::Hub` returns `false`; + - focused `WindowOwner::Overlay` returns `false`; + - stale focused id after window removal returns `false`. + +5. Add facade-level coverage if existing `SystemOS` tests provide a convenient + pattern. + If facade-level setup would duplicate manager coverage heavily, keep the + unit coverage at the manager level and rely on the facade as a direct + delegation. + +## Criterios de Aceite + +- [ ] `os.windows().focused_window_belongs_to_task(task_id)` compiles. +- [ ] The predicate returns `true` only for the currently focused + `WindowOwner::Task(task_id)`. +- [ ] `Hub`, `Overlay`, absent focus, stale focus, and other task owners return + `false`. +- [ ] No firmware behavior changes are included in this plan. + +## Tests / Validacao + +- Run `cargo test -p prometeu-system`. +- Run any narrower test command available for the window manager module if the + crate supports it. +- Run `discussion validate`. + +## Riscos + +- Directly scanning `windows` by focused id is simple but can hide stale focus + bugs. Tests must lock stale focus behavior to `false`. +- Adding only a facade method without manager coverage would make the contract + harder to verify. Prefer manager tests. +- Borrowing through `WindowFacade` must remain short-lived and compatible with + existing `SystemOS` facade patterns. diff --git a/discussion/workflow/plans/PLN-0061-plan-shell-running-lifecycle-integration.md b/discussion/workflow/plans/PLN-0061-plan-shell-running-lifecycle-integration.md new file mode 100644 index 00000000..91abc07a --- /dev/null +++ b/discussion/workflow/plans/PLN-0061-plan-shell-running-lifecycle-integration.md @@ -0,0 +1,128 @@ +--- +id: PLN-0061 +ticket: task-owned-shell-windows +title: Plan - Shell Running Lifecycle Integration +status: open +created: 2026-05-15 +completed: +ref_decisions: [DEC-0027] +tags: [runtime, os, task, window-manager, shell, lifecycle, firmware, tests] +--- + +# Plan - Shell Running Lifecycle Integration + +## Briefing + +Integrate the accepted task-owned window contract into `ShellRunningStep`. +`ShellRunningStep` must stop using generic focused-window presence as the +liveness contract and must close the shell task through lifecycle when the +focused window no longer belongs to that task. + +## Decisions de Origem + +- `DEC-0027` - Task-Owned Shell Windows + +## Alvo + +`ShellRunningStep` continues only when: + +```text +TaskState::Foreground ++ +os.windows().focused_window_belongs_to_task(task_id) +``` + +When that predicate is false, the step must call +`os.lifecycle().close_task(task_id)` and return `FirmwareState::HubHome`. + +## Escopo + +Included: + +- Update + [firmware_step_shell_running.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-firmware/src/firmware/firmware_step_shell_running.rs). +- Adjust the shell-running path in + [prometeu_hub.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs) only if `focused_window_active` becomes unused or misleading. +- Add firmware/system tests proving lifecycle closure and Hub return behavior. +- Preserve `GameRunningStep` behavior. + +## Fora de Escopo + +- Spec edits; covered by `PLN-0059`. +- Adding the window ownership API; covered by `PLN-0060`. +- Overlay/modal policy. +- Background, minimization, app switcher, dock, or multiple-window behavior. +- Treating games as windows. + +## Plano de Execucao + +1. Depend on `PLN-0060`. + Do not update `ShellRunningStep` until + `os.windows().focused_window_belongs_to_task(task_id)` exists. + +2. Replace the liveness predicate in `ShellRunningStep`. + In `firmware_step_shell_running.rs`, keep the existing `TaskState` + validation. After `ctx.hub.update_shell_profile(...)`, replace the + `outcome.focused_window_active` check with: + + ```text + !ctx.os.windows().focused_window_belongs_to_task(self.task_id) + ``` + +3. Preserve crash handling order. + Keep VM crash handling before window-liveness closure so a crash reported by + the shell update remains a crash, not a normal close. + +4. Close through lifecycle before returning Hub. + When the focused-window ownership predicate is false: + - log the closure with task id; + - call `ctx.os.lifecycle().close_task(self.task_id)`; + - log lifecycle close errors; + - return `Some(FirmwareState::HubHome(HubHomeStep))`. + +5. De-emphasize or remove `focused_window_active`. + If `SystemProfileUpdate.focused_window_active` has no remaining call sites, + remove it from `SystemProfileUpdate` and its construction. If another + legitimate caller remains, keep it but do not use it for shell task + liveness. + +6. Add or update tests. + Use existing firmware tests in + [firmware.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-firmware/src/firmware/firmware.rs) + or a nearby test module. Cover: + - focused task-owned window keeps the shell path running; + - no focused window closes task and returns `HubHome`; + - focused `Hub` window closes task and returns `HubHome`; + - focused `Overlay` window closes task and returns `HubHome` in v1; + - focused window for another task closes task and returns `HubHome`; + - lifecycle close changes task to `Closed` and process to `Stopped`; + - `GameRunningStep` remains independent from `WindowManager`. + +## Criterios de Aceite + +- [ ] `ShellRunningStep` no longer uses `focused_window_active` as its + continuation contract. +- [ ] `ShellRunningStep` uses + `os.windows().focused_window_belongs_to_task(self.task_id)`. +- [ ] A false predicate closes the task through lifecycle before returning + `HubHome`. +- [ ] `Hub`, `Overlay`, absent focus, and another task's focused window all + fail the predicate for the current shell task. +- [ ] Game-running behavior is unchanged and remains outside `WindowManager`. + +## Tests / Validacao + +- Run `cargo test -p prometeu-system`. +- Run `cargo test -p prometeu-firmware`. +- Run a workspace or targeted build command used by this repository if the two + crate tests are not sufficient. +- Run `discussion validate`. + +## Riscos + +- Current Hub update behavior may close or replace windows during the same tick; + tests need to account for the final focused window after `update_shell_profile`. +- Removing `focused_window_active` may require small cleanup in system tests or + callers. +- The v1 overlay behavior is intentionally strict. Do not add overlay + preservation logic in this plan. From 32b838f9c4758c865ddd08da08c40c47b95903e1 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 15 May 2026 11:44:19 +0100 Subject: [PATCH 2/5] implements PLN-0059 task window specs --- discussion/index.ndjson | 2 +- ...LN-0059-plan-task-window-contract-specs.md | 16 ++--- .../12-firmware-pos-and-prometeuhub.md | 71 +++++++++++++++++-- 3 files changed, 75 insertions(+), 14 deletions(-) diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 3874ab01..22674046 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,5 +1,5 @@ {"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":"open","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":"open","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":"open","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-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-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"}]} diff --git a/discussion/workflow/plans/PLN-0059-plan-task-window-contract-specs.md b/discussion/workflow/plans/PLN-0059-plan-task-window-contract-specs.md index a81f0e05..0a247675 100644 --- a/discussion/workflow/plans/PLN-0059-plan-task-window-contract-specs.md +++ b/discussion/workflow/plans/PLN-0059-plan-task-window-contract-specs.md @@ -2,9 +2,9 @@ id: PLN-0059 ticket: task-owned-shell-windows title: Plan - Task Window Contract Specs -status: open +status: done created: 2026-05-15 -completed: +completed: 2026-05-15 ref_decisions: [DEC-0027] tags: [runtime, os, task, window-manager, shell, lifecycle, specs] --- @@ -91,13 +91,13 @@ Included: ## Criterios de Aceite -- [ ] The spec describes the `Process` / `Task` / `Window` model in English. -- [ ] The spec states that games are not `WindowManager` windows. -- [ ] The spec states that shell app windows use `WindowOwner::Task(TaskId)`. -- [ ] The spec states the v1 `ShellRunningStep` focused-window predicate. -- [ ] The spec states that loss of the eligible focused window closes the task +- [x] The spec describes the `Process` / `Task` / `Window` model in English. +- [x] The spec states that games are not `WindowManager` windows. +- [x] The spec states that shell app windows use `WindowOwner::Task(TaskId)`. +- [x] The spec states the v1 `ShellRunningStep` focused-window predicate. +- [x] The spec states that loss of the eligible focused window closes the task through lifecycle before returning to Hub. -- [ ] The spec does not introduce overlay, background, minimization, app +- [x] The spec does not introduce overlay, background, minimization, app switcher, dock, or multiple-window policy. ## Tests / Validacao diff --git a/docs/specs/runtime/12-firmware-pos-and-prometeuhub.md b/docs/specs/runtime/12-firmware-pos-and-prometeuhub.md index 93a832bd..a4b90b86 100644 --- a/docs/specs/runtime/12-firmware-pos-and-prometeuhub.md +++ b/docs/specs/runtime/12-firmware-pos-and-prometeuhub.md @@ -31,6 +31,14 @@ The VM does not own the machine lifecycle. Firmware does. - **AppMode**: cartridge mode, currently `Game` or `System`. - **Logical frame**: execution unit completed when the app reaches `FRAME_SYNC`. - **Host tick**: the host-driven outer update tick. +- **Process**: technical execution owned by POS lifecycle. +- **Task**: navigable presence associated with an app flow. +- **Window**: visual presence for a shell/system task when that task is + represented by the system window model. +- **WindowOwner**: ownership marker for a system window. The v1 owners are + `Hub`, `Task(TaskId)`, and `Overlay`. +- **Shell task window**: a `WindowManager` window whose owner is + `WindowOwner::Task(TaskId)`. ## 3 POS Responsibilities @@ -69,6 +77,8 @@ Game-mode cartridges: - transition firmware into the game-running state; - run as the active app flow; - present through the main frame path. +- run as fullscreen firmware sessions; +- are not `WindowManager` windows. ### System @@ -77,8 +87,59 @@ System-mode cartridges: - are initialized by POS; - are integrated into the Hub/window environment; - do not replace the firmware state with the game-running path. +- are represented as shell task windows when they expose a visual shell + presence. -## 6 Cartridge Load Flow +## 6 Task-Owned Shell Windows + +The shell window contract separates execution, navigation, and visual +presence: + +```text +Process = technical execution +Task = navigable presence +Window = visual presence of the task +``` + +For shell/system apps managed by the window model, the visible shell presence +must be a `WindowManager` window owned by `WindowOwner::Task(TaskId)`. + +`Hub` windows are not shell task windows. `Overlay` windows are not shell task +windows in this v1 contract. Overlay/modal policy, minimization, app switching, +dock behavior, background services, and complex multi-window semantics are +future contracts and are not defined here. + +Game cartridges must not be represented as `WindowManager` windows. They remain +fullscreen sessions driven by `GameRunningStep`. + +For v1, `ShellRunningStep` continues a shell task only while both conditions are +true: + +```text +TaskState::Foreground ++ +focused window owner == WindowOwner::Task(task_id) +``` + +The runtime query for the focused-window ownership predicate is: + +```text +os.windows().focused_window_belongs_to_task(task_id) +``` + +If a foreground shell task loses its eligible focused window, +`ShellRunningStep` must close the task through `SystemOS` lifecycle before +returning to Hub: + +```text +Shell task loses its eligible focused window + -> os.lifecycle().close_task(task_id) + -> TaskState::Closed + -> ProcessState::Stopped + -> FirmwareState::HubHome +``` + +## 7 Cartridge Load Flow Current high-level flow: @@ -93,7 +154,7 @@ If VM initialization fails, firmware transitions to the crash path. POS selects the cartridge and its execution context, but the cartridge's initial callable is not chosen by firmware metadata. Execution starts from the cartridge boot protocol defined in [`13-cartridge.md`](13-cartridge.md), currently `func_id = 0`. -## 7 Firmware States +## 8 Firmware States The current firmware state model includes: @@ -107,7 +168,7 @@ The current firmware state model includes: These states express machine orchestration above the VM. They are not guest-visible bytecode states. -## 8 Execution Contract +## 9 Execution Contract For game-mode execution, firmware/runtime coordination preserves: @@ -118,7 +179,7 @@ For game-mode execution, firmware/runtime coordination preserves: This keeps the machine model deterministic and observable. -## 9 Crash Handling +## 10 Crash Handling Firmware owns terminal fault presentation. @@ -130,7 +191,7 @@ When a terminal app fault occurs: Crash handling is outside the guest VM execution model. -## 10 Relationship to Other Specs +## 11 Relationship to Other Specs - [`02-vm-instruction-set.md`](02-vm-instruction-set.md) defines the VM subsystem run by firmware. - [`09-events-and-concurrency.md`](09-events-and-concurrency.md) defines the frame-boundary model used by firmware. From d929f8e2f1cf526489ff68b266d2fe5a1bbfe69a Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 15 May 2026 11:45:32 +0100 Subject: [PATCH 3/5] implements PLN-0060 focused window ownership API --- .../prometeu-system/src/os/facades/window.rs | 5 + .../src/services/windows/window_manager.rs | 99 +++++++++++++++++++ discussion/index.ndjson | 2 +- ...-0060-plan-focused-window-ownership-api.md | 12 +-- 4 files changed, 111 insertions(+), 7 deletions(-) diff --git a/crates/console/prometeu-system/src/os/facades/window.rs b/crates/console/prometeu-system/src/os/facades/window.rs index 5e6c8c90..db08e9a3 100644 --- a/crates/console/prometeu-system/src/os/facades/window.rs +++ b/crates/console/prometeu-system/src/os/facades/window.rs @@ -1,4 +1,5 @@ use crate::os::SystemOS; +use crate::task::TaskId; use crate::windows::{Window, WindowId, WindowOwner}; use prometeu_hal::color::Color; use prometeu_hal::primitives::Rect; @@ -26,6 +27,10 @@ impl<'a> WindowFacade<'a> { self.os.window_manager.focused } + pub fn focused_window_belongs_to_task(&self, task_id: TaskId) -> bool { + self.os.window_manager.focused_window_belongs_to_task(task_id) + } + pub fn close_window(&mut self, id: WindowId) { self.os.window_manager.remove_window(id); } diff --git a/crates/console/prometeu-system/src/services/windows/window_manager.rs b/crates/console/prometeu-system/src/services/windows/window_manager.rs index 51099ecd..70e475fe 100644 --- a/crates/console/prometeu-system/src/services/windows/window_manager.rs +++ b/crates/console/prometeu-system/src/services/windows/window_manager.rs @@ -1,4 +1,5 @@ use crate::services::windows::window::{Window, WindowId}; +use crate::task::TaskId; use crate::windows::WindowOwner; use prometeu_hal::color::Color; use prometeu_hal::primitives::Rect; @@ -51,6 +52,17 @@ impl WindowManager { window.has_focus = window.id == id; } } + + pub fn focused_window_belongs_to_task(&self, task_id: TaskId) -> bool { + let Some(focused_id) = self.focused else { + return false; + }; + + self.windows + .iter() + .find(|window| window.id == focused_id) + .is_some_and(|window| window.owner == WindowOwner::Task(task_id)) + } } #[cfg(test)] @@ -103,4 +115,91 @@ mod tests { assert_eq!(wm.windows.len(), 0); assert_eq!(wm.focused, None); } + + #[test] + fn focused_window_belongs_to_task_returns_false_without_focus() { + let mut wm = WindowManager::new(); + wm.add_window( + "Task Window".to_string(), + WindowOwner::Task(crate::task::TaskId(1)), + Rect { x: 0, y: 0, w: 10, h: 10 }, + Color::WHITE, + ); + + assert!(!wm.focused_window_belongs_to_task(crate::task::TaskId(1))); + } + + #[test] + fn focused_window_belongs_to_task_matches_focused_task_owner() { + let mut wm = WindowManager::new(); + let id = wm.add_window( + "Task Window".to_string(), + WindowOwner::Task(crate::task::TaskId(1)), + Rect { x: 0, y: 0, w: 10, h: 10 }, + Color::WHITE, + ); + + wm.set_focus(id); + + assert!(wm.focused_window_belongs_to_task(crate::task::TaskId(1))); + } + + #[test] + fn focused_window_belongs_to_task_rejects_other_task_owner() { + let mut wm = WindowManager::new(); + let id = wm.add_window( + "Task Window".to_string(), + WindowOwner::Task(crate::task::TaskId(2)), + Rect { x: 0, y: 0, w: 10, h: 10 }, + Color::WHITE, + ); + + wm.set_focus(id); + + assert!(!wm.focused_window_belongs_to_task(crate::task::TaskId(1))); + } + + #[test] + fn focused_window_belongs_to_task_rejects_hub_owner() { + let mut wm = WindowManager::new(); + let id = wm.add_window( + "Hub Window".to_string(), + WindowOwner::Hub, + Rect { x: 0, y: 0, w: 10, h: 10 }, + Color::WHITE, + ); + + wm.set_focus(id); + + assert!(!wm.focused_window_belongs_to_task(crate::task::TaskId(1))); + } + + #[test] + fn focused_window_belongs_to_task_rejects_overlay_owner() { + let mut wm = WindowManager::new(); + let id = wm.add_window( + "Overlay Window".to_string(), + WindowOwner::Overlay, + Rect { x: 0, y: 0, w: 10, h: 10 }, + Color::WHITE, + ); + + wm.set_focus(id); + + assert!(!wm.focused_window_belongs_to_task(crate::task::TaskId(1))); + } + + #[test] + fn focused_window_belongs_to_task_rejects_stale_focus() { + let mut wm = WindowManager::new(); + wm.add_window( + "Task Window".to_string(), + WindowOwner::Task(crate::task::TaskId(1)), + Rect { x: 0, y: 0, w: 10, h: 10 }, + Color::WHITE, + ); + wm.focused = Some(WindowId(999)); + + assert!(!wm.focused_window_belongs_to_task(crate::task::TaskId(1))); + } } diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 22674046..8a419f18 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,5 +1,5 @@ {"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":"open","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":"open","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-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"}]} diff --git a/discussion/workflow/plans/PLN-0060-plan-focused-window-ownership-api.md b/discussion/workflow/plans/PLN-0060-plan-focused-window-ownership-api.md index 0d8b38d6..0f624c43 100644 --- a/discussion/workflow/plans/PLN-0060-plan-focused-window-ownership-api.md +++ b/discussion/workflow/plans/PLN-0060-plan-focused-window-ownership-api.md @@ -2,9 +2,9 @@ id: PLN-0060 ticket: task-owned-shell-windows title: Plan - Focused Window Ownership API -status: open +status: done created: 2026-05-15 -completed: +completed: 2026-05-15 ref_decisions: [DEC-0027] tags: [runtime, os, task, window-manager, shell, lifecycle, code] --- @@ -81,12 +81,12 @@ Included: ## Criterios de Aceite -- [ ] `os.windows().focused_window_belongs_to_task(task_id)` compiles. -- [ ] The predicate returns `true` only for the currently focused +- [x] `os.windows().focused_window_belongs_to_task(task_id)` compiles. +- [x] The predicate returns `true` only for the currently focused `WindowOwner::Task(task_id)`. -- [ ] `Hub`, `Overlay`, absent focus, stale focus, and other task owners return +- [x] `Hub`, `Overlay`, absent focus, stale focus, and other task owners return `false`. -- [ ] No firmware behavior changes are included in this plan. +- [x] No firmware behavior changes are included in this plan. ## Tests / Validacao From c2698e6a225a911f7c3ea4b919ca8304bf0b18a3 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 15 May 2026 11:47:26 +0100 Subject: [PATCH 4/5] implements PLN-0061 shell running lifecycle integration --- .../src/firmware/firmware.rs | 99 ++++++++++++++++++- .../firmware/firmware_step_shell_running.rs | 7 +- .../src/programs/prometeu_hub/prometeu_hub.rs | 6 +- discussion/index.ndjson | 2 +- ...lan-shell-running-lifecycle-integration.md | 14 +-- 5 files changed, 112 insertions(+), 16 deletions(-) diff --git a/crates/console/prometeu-firmware/src/firmware/firmware.rs b/crates/console/prometeu-firmware/src/firmware/firmware.rs index 3bb60111..b60811ed 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware.rs @@ -175,10 +175,13 @@ mod tests { use prometeu_bytecode::model::{BytecodeModule, FunctionMeta, SyscallDecl}; use prometeu_drivers::hardware::Hardware; use prometeu_hal::cartridge::{AppMode, AssetsPayloadSource}; + use prometeu_hal::color::Color; + use prometeu_hal::primitives::Rect; use prometeu_hal::syscalls::caps; use prometeu_system::CrashReport; 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 { 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] fn load_cartridge_transitions_to_app_crashes_when_vm_init_fails() { let mut firmware = Firmware::new(None); @@ -362,14 +393,79 @@ mod tests { 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), + }; let tick_index_before_system_update = firmware.os.vm().tick_index(); firmware.tick(&signals, &mut hardware); 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); } + #[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] fn load_cartridge_routes_game_apps_to_game_running() { let mut firmware = Firmware::new(None); @@ -380,5 +476,6 @@ mod tests { firmware.tick(&signals, &mut hardware); assert!(matches!(firmware.state, FirmwareState::GameRunning(_))); + assert_eq!(firmware.os.windows().window_count(), 0); } } 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 94339e96..9a150073 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 @@ -66,10 +66,13 @@ impl ShellRunningStep { 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( 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) { ctx.os.error( 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 8c219df7..2828113f 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 @@ -11,7 +11,6 @@ pub struct PrometeuHub; pub struct SystemProfileUpdate { pub crash: Option, - pub focused_window_active: bool, } impl Default for PrometeuHub { @@ -103,9 +102,6 @@ impl PrometeuHub { self.render(os, hw); hw.gfx_mut().present(); - SystemProfileUpdate { - crash, - focused_window_active: os.windows().focused_window().is_some(), - } + SystemProfileUpdate { crash } } } diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 8a419f18..93123ad9 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,5 +1,5 @@ {"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-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"}]} diff --git a/discussion/workflow/plans/PLN-0061-plan-shell-running-lifecycle-integration.md b/discussion/workflow/plans/PLN-0061-plan-shell-running-lifecycle-integration.md index 91abc07a..413468cf 100644 --- a/discussion/workflow/plans/PLN-0061-plan-shell-running-lifecycle-integration.md +++ b/discussion/workflow/plans/PLN-0061-plan-shell-running-lifecycle-integration.md @@ -2,9 +2,9 @@ id: PLN-0061 ticket: task-owned-shell-windows title: Plan - Shell Running Lifecycle Integration -status: open +status: done created: 2026-05-15 -completed: +completed: 2026-05-15 ref_decisions: [DEC-0027] tags: [runtime, os, task, window-manager, shell, lifecycle, firmware, tests] --- @@ -100,15 +100,15 @@ Included: ## Criterios de Aceite -- [ ] `ShellRunningStep` no longer uses `focused_window_active` as its +- [x] `ShellRunningStep` no longer uses `focused_window_active` as its continuation contract. -- [ ] `ShellRunningStep` uses +- [x] `ShellRunningStep` uses `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`. -- [ ] `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. -- [ ] Game-running behavior is unchanged and remains outside `WindowManager`. +- [x] Game-running behavior is unchanged and remains outside `WindowManager`. ## Tests / Validacao From 82a338042680083468310e7f01de29ea9376254d Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 15 May 2026 15:51:41 +0100 Subject: [PATCH 5/5] housekeeping --- discussion/index.ndjson | 4 +- ...ask-window-liveness-belongs-to-the-task.md | 112 +++++ ...GD-0035-agenda-task-owned-shell-windows.md | 432 ------------------ ...-0027-decision-task-owned-shell-windows.md | 238 ---------- ...LN-0059-plan-task-window-contract-specs.md | 116 ----- ...-0060-plan-focused-window-ownership-api.md | 105 ----- ...lan-shell-running-lifecycle-integration.md | 128 ------ 7 files changed, 114 insertions(+), 1021 deletions(-) create mode 100644 discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md delete mode 100644 discussion/workflow/agendas/AGD-0035-agenda-task-owned-shell-windows.md delete mode 100644 discussion/workflow/decisions/DEC-0027-decision-task-owned-shell-windows.md delete mode 100644 discussion/workflow/plans/PLN-0059-plan-task-window-contract-specs.md delete mode 100644 discussion/workflow/plans/PLN-0060-plan-focused-window-ownership-api.md delete mode 100644 discussion/workflow/plans/PLN-0061-plan-shell-running-lifecycle-integration.md diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 93123ad9..ba91153e 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,5 +1,5 @@ -{"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":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0027"]}],"lessons":[]} +{"type":"meta","next_id":{"DSC":36,"AGD":36,"DEC":28,"PLN":62,"LSN":45,"CLSN":1}} +{"type":"discussion","id":"DSC-0035","status":"done","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":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.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-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"}]} diff --git a/discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md b/discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md new file mode 100644 index 00000000..c6dadf5d --- /dev/null +++ b/discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md @@ -0,0 +1,112 @@ +--- +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. diff --git a/discussion/workflow/agendas/AGD-0035-agenda-task-owned-shell-windows.md b/discussion/workflow/agendas/AGD-0035-agenda-task-owned-shell-windows.md deleted file mode 100644 index cd3ee222..00000000 --- a/discussion/workflow/agendas/AGD-0035-agenda-task-owned-shell-windows.md +++ /dev/null @@ -1,432 +0,0 @@ ---- -id: AGD-0035 -ticket: task-owned-shell-windows -title: Agenda - Task-Owned Shell Windows -status: accepted -created: 2026-05-15 -resolved: 2026-05-15 -decision: DEC-0027 -tags: [runtime, os, task, window-manager, shell, lifecycle] ---- - -# Agenda - Task-Owned Shell Windows - -## Contexto - -O Prometeu OS ja possui `SystemOS` como autoridade semantica para coordenar -lifecycle entre `Task` e `Process`, com facades por dominio para evitar que o -firmware manipule storage interno diretamente. - -O modelo atual separa: - -```text -Process = execucao tecnica -Task = presenca navegavel -Window = presenca visual da task -``` - -O `WindowOwner` ja representa `Hub`, `Task(TaskId)` e `Overlay`. Shell apps ja -conseguem criar janelas associadas a uma task, e games continuam fora do -`WindowManager`: eles sao sessoes fullscreen conduzidas por `GameRunningStep`, -nao janelas. - -Hoje, porem, `ShellRunningStep` ainda decide se continua rodando a partir de um -sinal generico: `focused_window_active`. Esse sinal vem do resultado do update -do Hub e apenas indica se existe alguma janela focada/ativa, sem confirmar que -essa janela pertence a task que o `ShellRunningStep` esta conduzindo. - -## Problema - -O contrato de liveness de uma Shell task ainda esta acoplado a presenca visual -generica, nao a presenca visual da propria task. - -Isso deixa uma lacuna conceitual: - -- uma task pode estar em `Foreground`, mas a janela focada pode pertencer a - outro owner; -- uma janela pode estar focada, mas nao representar a task que esta rodando no - `ShellRunningStep`; -- o firmware precisa inferir continuidade por um booleano generico em vez de - perguntar ao OS: "esta task ainda possui uma janela ativa/focada?". - -Se `Task` e a unidade de navegacao, e `Window` e a presenca visual dessa task, -o fluxo de Shell precisa saber se a presenca visual pertence a mesma task. - -## Pontos Criticos - -1. Autoridade do contrato. - A pergunta deve provavelmente passar por `SystemOS` ou por uma facade de - windows/lifecycle, nao por logica espalhada no Hub. O `WindowManager` pode - armazenar e consultar janelas, mas a semantica de "task continua viva" deve - permanecer coerente com o modelo de lifecycle. - -2. Foco versus atividade. - A decisao precisa separar: - - "a task possui alguma janela"; - - "a task possui uma janela ativa"; - - "a task possui a janela atualmente focada". - - Para o `ShellRunningStep`, o criterio candidato e mais forte: a janela - focada deve pertencer a propria task. - -3. Encerramento da task. - Se uma Shell task perde sua unica janela ativa/focada, o sistema precisa - decidir se isso fecha a task, suspende a task, retorna ao Hub, ou representa - erro. Para esta agenda, o foco e o contrato Task-Window; politicas futuras - de background ficam fora. - -4. Separacao entre Shell e Game. - Games nao devem virar `WindowOwner::Task`. A presenca visual de game - continua sendo a propria sessao fullscreen do `GameRunningStep`. O contrato - aqui vale para Shell/system apps representados por janelas. - -5. Multiplicidade futura sem resolver agora. - O contrato deve ser simples o bastante para uma janela por Shell task hoje, - mas nao deve bloquear uma evolucao posterior para multiplas janelas. A - agenda nao deve decidir dock, app switcher, services em background, - minimizacao, ou redesign da Shell. - -## Opcoes - -### Opcao A - Manter `focused_window_active` como criterio generico - -**Abordagem:** `ShellRunningStep` continua dependendo do outcome generico do -Hub, que informa apenas se existe alguma janela focada/ativa. - -**Vantagens:** - -- menor mudanca imediata; -- preserva o fluxo atual de retorno ao Hub; -- evita adicionar API nova no `WindowManager` agora. - -**Riscos / Custos:** - -- nao expressa o modelo `Task = presenca navegavel` e `Window = presenca visual - da task`; -- permite continuidade baseada em janela de outro owner; -- mantem uma inferencia de lifecycle fora do contrato direto do OS; -- tende a piorar quando houver mais janelas ou owners. - -**Leitura:** opcao aceitavel apenas como estado transitorio, nao como contrato -final. - -### Opcao B - `ShellRunningStep` depende de janela focada pertencente a propria task - -**Abordagem:** expor uma consulta do tipo -`focused_window_belongs_to_task(task_id)` ou -`task_has_focused_window(task_id)`. O `ShellRunningStep` continua somente se a -task estiver `Foreground` e a janela focada tiver `WindowOwner::Task(task_id)`. - -**Vantagens:** - -- alinha diretamente `Task` e `Window`; -- remove a ambiguidade do booleano generico; -- torna o encerramento de Shell task explicavel: sem janela focada da propria - task, nao ha presenca visual ativa para aquela task; -- funciona bem para o modelo atual de uma Shell task em foreground. - -**Riscos / Custos:** - -- pode ser forte demais se o futuro permitir task com janela existente mas - temporariamente sem foco; -- exige definir se perda de foco para `Hub` ou `Overlay` fecha a task ou apenas - pausa o fluxo; -- precisa tomar cuidado para overlays nao roubarem foco de forma que fechem a - task acidentalmente. - -**Leitura:** e a opcao mais coerente para o contrato imediato, desde que -overlays e Hub sejam tratados explicitamente. - -### Opcao C - `ShellRunningStep` depende de qualquer janela ativa pertencente a propria task - -**Abordagem:** expor uma consulta do tipo `task_has_active_window(task_id)`. -Foco global continua separado: a task segue viva enquanto pelo menos uma janela -aberta/ativa pertence a ela, mesmo que outra superficie esteja focada. - -**Vantagens:** - -- prepara melhor um futuro com overlays, app switcher ou multiplas janelas; -- evita fechar task por perda temporaria de foco; -- separa "existencia visual da task" de "janela no topo". - -**Riscos / Custos:** - -- exige definir imediatamente o que significa "active" alem de `has_focus`; -- pode preservar tasks sem presenca navegavel real se a janela existir mas nao - estiver selecionada; -- abre portas para semantica de background/minimizacao, que esta fora do escopo - desta agenda. - -**Leitura:** boa direcao futura, mas talvez maior que o contrato necessario -agora. - -### Opcao D - Lifecycle fecha janelas; janelas nunca fecham lifecycle - -**Abordagem:** `ShellRunningStep` ignora foco/janelas para continuidade; a task -so fecha quando o lifecycle manda fechar. O `WindowManager` seria apenas -reflexo visual da task, nao sinal de liveness. - -**Vantagens:** - -- preserva autoridade total do lifecycle; -- evita que bugs de foco encerrem task; -- simples de raciocinar em termos de `TaskState`. - -**Riscos / Custos:** - -- contradiz a intencao de `Window = presenca visual da task` para Shell apps; -- pode manter uma Shell task foreground sem janela visivel; -- empurra para outro mecanismo a deteccao de fechamento pelo usuario. - -**Leitura:** defensavel para services/background no futuro, mas desalinhada -com Shell apps visuais neste escopo. - -## Sugestao / Recomendacao - -A recomendacao consolidada e seguir a Opcao B como contrato v1: - -```text -ShellRunningStep continua somente quando: -1. sua Task esta em Foreground; -2. a janela focada pertence a WindowOwner::Task(task_id) da propria task. -``` - -Isso troca o sinal generico `focused_window_active` por uma pergunta explicita -ao `WindowFacade`: - -```text -os.windows().focused_window_belongs_to_task(task_id) -``` - -Esse nome comunica que o `WindowManager` possui um foco global, e que a -pergunta e se esse foco global pertence a task informada. O nome -`task_has_focused_window` continua sendo conceitualmente aceitavel, mas pode -soar como se cada task tivesse um foco proprio. - -O comportamento v1 recomendado e: - -```text -if !ctx.os.windows().focused_window_belongs_to_task(self.task_id) { - ctx.os.lifecycle().close_task(self.task_id)?; - return Some(FirmwareState::HubHome(HubHomeStep)); -} -``` - -O contrato deve declarar tambem: - -- `Hub` e `Overlay` nao sao Shell task windows; -- games nao entram no `WindowManager`; -- a ausencia de janela focada da propria Shell task fecha ou encerra o fluxo da - Shell task conforme a politica atual, sem introduzir background service ou - minimizacao nesta decisao; -- multiplas janelas, dock, app switcher, background services e redesign da - Shell ficam fora desta agenda. - -## Perguntas em Aberto - -- [x] O criterio v1 deve ser "janela focada pertence a task" ou "task possui - alguma janela ativa"? -- [x] O nome da API deve falar em foco (`task_has_focused_window`) ou em - pertencimento do foco global (`focused_window_belongs_to_task`)? -- [x] Quando uma `Overlay` esta focada, ela deve preservar a Shell task - subjacente ou isso ainda nao existe no modelo atual? -- [x] Fechar a ultima janela da Shell task deve chamar `close_task(task_id)` - diretamente ou apenas retornar ao Hub e deixar lifecycle resolver? -- [x] A consulta deve morar no `WindowFacade`, em uma facade de lifecycle, ou em - uma facade composta de `SystemOS`? -- [x] A decision posterior precisa alterar somente firmware/system, ou tambem - documentar esse contrato nos specs canonicos? - -## Respostas Consolidadas - -### Criterio v1 - -O criterio v1 e "janela focada pertence a task". - -`ShellRunningStep` continua somente quando: - -```text -TaskState::Foreground -+ -focused window owner == WindowOwner::Task(task_id) -``` - -A alternativa "task possui alguma janela ativa" fica reservada para um futuro -com overlays, multiplas janelas, minimizacao ou app switcher. Ela e mais ampla -do que o contrato necessario agora. - -Nesta versao, Shell apps sao foreground e visualmente representados por uma -janela focada. Se a janela focada nao pertence a task, a task nao deve -continuar executando. - -### Nome e local da API - -O nome recomendado e: - -```text -focused_window_belongs_to_task(task_id) -``` - -A API deve morar em `WindowFacade`: - -```text -os.windows().focused_window_belongs_to_task(task_id) -``` - -Motivo: a pergunta e sobre foco, janela e `WindowOwner`, nao sobre transicao de -lifecycle. - -A separacao de responsabilidades fica: - -```text -WindowFacade - responde perguntas sobre janelas, foco e ownership - -LifecycleFacade - altera TaskState e ProcessState - -ShellRunningStep - coordena a decisao firmware-level entre janela e lifecycle -``` - -Se esse padrao se repetir, uma facade composta como `sessions()` pode surgir no -futuro, mas ela nao e necessaria para este contrato v1. - -### Overlays - -Overlay policy ainda nao existe no modelo atual. - -Para v1: - -```text -Hub nao e janela da task. -Overlay nao e janela da task. -Game nao e WindowManager window. -ShellRunningStep exige foco em WindowOwner::Task(task_id). -``` - -Se uma overlay puder roubar foco, isso precisa ser tratado em uma agenda -propria de overlay/modal policy. Esta decisao nao deve tentar preservar, -pausar, ou modelar Shell task sob overlay. - -Possiveis direcoes futuras, fora deste escopo: - -- overlay modal preserva a task subjacente; -- overlay nao-modal nao rouba foco; -- overlay possui owner/contexto proprio. - -### Fechamento da Shell task - -Quando a Shell task perde sua janela focada elegivel, o firmware deve chamar: - -```text -os.lifecycle().close_task(task_id) -``` - -e entao retornar para `HubHome`. - -Nao basta apenas retornar ao Hub, porque isso deixaria task/processo dependente -de cleanup implicito. - -Contrato v1: - -```text -Shell task perde sua janela focada elegivel - -> lifecycle.close_task(task_id) - -> TaskState::Closed - -> ProcessState::Stopped - -> FirmwareState::HubHome -``` - -O fechamento visual da Shell task precisa passar pelo lifecycle do `SystemOS`. - -### Propagacao para specs - -A decision posterior deve alterar firmware/system e tambem documentar o -contrato nos specs canonicos. - -O contrato arquitetural a preservar e: - -```text -Process = execucao tecnica -Task = presenca navegavel -Window = presenca visual da task -``` - -Tambem deve ficar documentado: - -- games nao sao janelas; -- games rodam como sessoes fullscreen via `GameRunningStep`; -- Shell apps possuem janelas no `WindowManager`; -- Shell app windows usam `WindowOwner::Task(TaskId)`; -- `Hub` e `Overlay` nao sao janelas de Shell task; -- na v1, `ShellRunningStep` so continua se a janela focada pertence a sua - propria task. - -Isso evita que trabalho futuro transforme game em window ou reintroduza -`focused_window_active` como contrato generico. - -## Fora de Escopo - -- dock; -- app switcher; -- background services; -- minimizacao; -- multiplas janelas complexas; -- redesign da Shell; -- transformar games em janelas; -- mudar o modelo fullscreen de `GameRunningStep`; -- reabrir o contrato geral de `Process`, `Task` e lifecycle ja consolidado. - -## Criterio para Encerrar - -Esta agenda pode virar decision quando houver resposta explicita para: - -1. qual predicado `ShellRunningStep` usa para continuar; -2. onde a API de consulta Task-Window mora; -3. como `Hub`, `Overlay` e `GameRunningStep` ficam excluidos ou tratados; -4. qual comportamento ocorre quando a Shell task perde sua janela elegivel; -5. quais specs ou codigos precisam ser propagados depois da decision. - -## Discussion - -A discussao convergiu para um contrato v1 pequeno, focado em Shell task -foreground com janela focada propria. - -Resumo da direcao: - -```text -AGD-0035 segue a Opcao B. - -Predicado: - focused_window_belongs_to_task(task_id) - -Facade: - os.windows() - -Comportamento: - se falso em ShellRunningStep: - os.lifecycle().close_task(task_id) - retorna HubHome - -Fora de escopo: - overlay policy - background - minimizacao - app switcher - game windows -``` - -Casos de background, overlay policy, minimizacao, app switcher e multiplas -janelas devem ficar para agendas futuras. - -## Resolution - -Aceita em 2026-05-15 e promovida para `DEC-0027`. - -A decision deve seguir a Opcao B: `ShellRunningStep` continua somente quando a -Shell task esta em `TaskState::Foreground` e a janela focada pertence a -`WindowOwner::Task(task_id)`. A API normativa e -`os.windows().focused_window_belongs_to_task(task_id)`. Quando o predicado for -falso, `ShellRunningStep` deve fechar a task via -`os.lifecycle().close_task(task_id)` e retornar `HubHome`. diff --git a/discussion/workflow/decisions/DEC-0027-decision-task-owned-shell-windows.md b/discussion/workflow/decisions/DEC-0027-decision-task-owned-shell-windows.md deleted file mode 100644 index ae507865..00000000 --- a/discussion/workflow/decisions/DEC-0027-decision-task-owned-shell-windows.md +++ /dev/null @@ -1,238 +0,0 @@ ---- -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`. diff --git a/discussion/workflow/plans/PLN-0059-plan-task-window-contract-specs.md b/discussion/workflow/plans/PLN-0059-plan-task-window-contract-specs.md deleted file mode 100644 index 0a247675..00000000 --- a/discussion/workflow/plans/PLN-0059-plan-task-window-contract-specs.md +++ /dev/null @@ -1,116 +0,0 @@ ---- -id: PLN-0059 -ticket: task-owned-shell-windows -title: Plan - Task Window Contract Specs -status: done -created: 2026-05-15 -completed: 2026-05-15 -ref_decisions: [DEC-0027] -tags: [runtime, os, task, window-manager, shell, lifecycle, specs] ---- - -# Plan - Task Window Contract Specs - -## Briefing - -Document the task-owned shell window contract from `DEC-0027` in the canonical -runtime specs before code execution changes the observable model. - -## Decisions de Origem - -- `DEC-0027` - Task-Owned Shell Windows - -## Alvo - -The canonical specs must state that: - -- `Process` is technical execution. -- `Task` is navigable presence. -- `Window` is visual presence for shell/system tasks managed by the - `WindowManager`. -- Game cartridges are not `WindowManager` windows and continue as fullscreen - `GameRunningStep` sessions. -- Shell app windows use `WindowOwner::Task(TaskId)`. -- `Hub` and `Overlay` are not shell task windows. -- v1 `ShellRunningStep` continues only while the focused window belongs to its - own task. - -## Escopo - -Included: - -- Update [docs/specs/runtime/12-firmware-pos-and-prometeuhub.md](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/docs/specs/runtime/12-firmware-pos-and-prometeuhub.md) with the contract. -- Add or adjust terminology in the same file if the existing terms section is - the best local home. -- Link the contract to firmware states, AppMode behavior, and Hub/system window - responsibilities. - -## Fora de Escopo - -- Code changes in `crates/`. -- Overlay/modal policy. -- Background services, minimization, app switcher, or dock behavior. -- Multiple-window semantics beyond preserving future compatibility. -- Rewriting unrelated firmware spec sections. - -## Plano de Execucao - -1. Locate the normative spec home. - Use `docs/specs/runtime/12-firmware-pos-and-prometeuhub.md` as the primary - target because it already owns POS, Hub, AppMode, and firmware states. - -2. Extend the terms section. - Add concise definitions for `Process`, `Task`, `Window`, `WindowOwner`, and - shell task windows if they are not already present. - -3. Add a Task-Owned Shell Windows section. - State the v1 predicate explicitly: - - ```text - TaskState::Foreground - + - focused window owner == WindowOwner::Task(task_id) - ``` - - State that the runtime API is - `os.windows().focused_window_belongs_to_task(task_id)`. - -4. Clarify AppMode separation. - Under `Game`, state that games are fullscreen firmware sessions and must not - be modeled as `WindowManager` windows. Under `System`, state that shell apps - are represented through task-owned windows. - -5. Clarify lifecycle consequence. - State that when a shell task loses its eligible focused window, - `ShellRunningStep` must close the task through `SystemOS` lifecycle and then - return to Hub. - -6. Keep excluded topics explicit. - Add a short note that overlay policy, minimization, app switcher, dock, and - background services are future contracts, not part of v1. - -## Criterios de Aceite - -- [x] The spec describes the `Process` / `Task` / `Window` model in English. -- [x] The spec states that games are not `WindowManager` windows. -- [x] The spec states that shell app windows use `WindowOwner::Task(TaskId)`. -- [x] The spec states the v1 `ShellRunningStep` focused-window predicate. -- [x] The spec states that loss of the eligible focused window closes the task - through lifecycle before returning to Hub. -- [x] The spec does not introduce overlay, background, minimization, app - switcher, dock, or multiple-window policy. - -## Tests / Validacao - -- Run `discussion validate`. -- Review the changed spec for English-only normative text. -- Confirm every normative point from `DEC-0027` has a corresponding spec line. - -## Riscos - -- The spec may become too implementation-specific if it describes current Rust - call sites instead of the contract. -- The spec may accidentally imply overlay/modal behavior. Keep overlay - behavior explicitly out of scope. -- Future work may need a dedicated OS/window spec chapter; this plan keeps the - change local to the existing firmware/Hub spec. diff --git a/discussion/workflow/plans/PLN-0060-plan-focused-window-ownership-api.md b/discussion/workflow/plans/PLN-0060-plan-focused-window-ownership-api.md deleted file mode 100644 index 0f624c43..00000000 --- a/discussion/workflow/plans/PLN-0060-plan-focused-window-ownership-api.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -id: PLN-0060 -ticket: task-owned-shell-windows -title: Plan - Focused Window Ownership API -status: done -created: 2026-05-15 -completed: 2026-05-15 -ref_decisions: [DEC-0027] -tags: [runtime, os, task, window-manager, shell, lifecycle, code] ---- - -# Plan - Focused Window Ownership API - -## Briefing - -Add the OS/window query required by `DEC-0027`: -`os.windows().focused_window_belongs_to_task(task_id)`. - -This plan owns the system-side API and tests. It does not change firmware -control flow; that is covered by `PLN-0061`. - -## Decisions de Origem - -- `DEC-0027` - Task-Owned Shell Windows - -## Alvo - -`WindowFacade` must expose a focused-window ownership predicate that answers -whether the global focused window belongs to a specific `TaskId`. - -## Escopo - -Included: - -- Add a focused ownership query to - [window.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-system/src/os/facades/window.rs). -- Add a helper in - [window_manager.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-system/src/services/windows/window_manager.rs) if it keeps the facade thin and testable. -- Add unit tests for focused-window ownership in the `prometeu-system` crate. - -## Fora de Escopo - -- Changing `ShellRunningStep`. -- Removing `focused_window_active` from `PrometeuHubOutcome`. -- Spec edits. -- Overlay/modal policy or active-window semantics. -- Multiple-window policy beyond querying the single global focused window. - -## Plano de Execucao - -1. Add `WindowManager::focused_window_belongs_to_task`. - In `crates/console/prometeu-system/src/services/windows/window_manager.rs`, - implement a method that: - - returns `false` when `focused` is `None`; - - finds the focused window by `WindowId`; - - returns `true` only when its owner is `WindowOwner::Task(task_id)`; - - returns `false` for `Hub`, `Overlay`, another task id, or stale focus. - -2. Add `WindowFacade::focused_window_belongs_to_task`. - In `crates/console/prometeu-system/src/os/facades/window.rs`, expose the - public facade method and delegate to the manager/helper. - -3. Keep existing facade methods intact. - Do not remove `focused_window()` or `windows()` in this plan. Existing call - sites may still need them until `PLN-0061` updates firmware. - -4. Add tests in `window_manager.rs`. - Cover: - - no focused window returns `false`; - - focused `WindowOwner::Task(task_id)` returns `true`; - - focused `WindowOwner::Task(other_task)` returns `false`; - - focused `WindowOwner::Hub` returns `false`; - - focused `WindowOwner::Overlay` returns `false`; - - stale focused id after window removal returns `false`. - -5. Add facade-level coverage if existing `SystemOS` tests provide a convenient - pattern. - If facade-level setup would duplicate manager coverage heavily, keep the - unit coverage at the manager level and rely on the facade as a direct - delegation. - -## Criterios de Aceite - -- [x] `os.windows().focused_window_belongs_to_task(task_id)` compiles. -- [x] The predicate returns `true` only for the currently focused - `WindowOwner::Task(task_id)`. -- [x] `Hub`, `Overlay`, absent focus, stale focus, and other task owners return - `false`. -- [x] No firmware behavior changes are included in this plan. - -## Tests / Validacao - -- Run `cargo test -p prometeu-system`. -- Run any narrower test command available for the window manager module if the - crate supports it. -- Run `discussion validate`. - -## Riscos - -- Directly scanning `windows` by focused id is simple but can hide stale focus - bugs. Tests must lock stale focus behavior to `false`. -- Adding only a facade method without manager coverage would make the contract - harder to verify. Prefer manager tests. -- Borrowing through `WindowFacade` must remain short-lived and compatible with - existing `SystemOS` facade patterns. diff --git a/discussion/workflow/plans/PLN-0061-plan-shell-running-lifecycle-integration.md b/discussion/workflow/plans/PLN-0061-plan-shell-running-lifecycle-integration.md deleted file mode 100644 index 413468cf..00000000 --- a/discussion/workflow/plans/PLN-0061-plan-shell-running-lifecycle-integration.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -id: PLN-0061 -ticket: task-owned-shell-windows -title: Plan - Shell Running Lifecycle Integration -status: done -created: 2026-05-15 -completed: 2026-05-15 -ref_decisions: [DEC-0027] -tags: [runtime, os, task, window-manager, shell, lifecycle, firmware, tests] ---- - -# Plan - Shell Running Lifecycle Integration - -## Briefing - -Integrate the accepted task-owned window contract into `ShellRunningStep`. -`ShellRunningStep` must stop using generic focused-window presence as the -liveness contract and must close the shell task through lifecycle when the -focused window no longer belongs to that task. - -## Decisions de Origem - -- `DEC-0027` - Task-Owned Shell Windows - -## Alvo - -`ShellRunningStep` continues only when: - -```text -TaskState::Foreground -+ -os.windows().focused_window_belongs_to_task(task_id) -``` - -When that predicate is false, the step must call -`os.lifecycle().close_task(task_id)` and return `FirmwareState::HubHome`. - -## Escopo - -Included: - -- Update - [firmware_step_shell_running.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-firmware/src/firmware/firmware_step_shell_running.rs). -- Adjust the shell-running path in - [prometeu_hub.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs) only if `focused_window_active` becomes unused or misleading. -- Add firmware/system tests proving lifecycle closure and Hub return behavior. -- Preserve `GameRunningStep` behavior. - -## Fora de Escopo - -- Spec edits; covered by `PLN-0059`. -- Adding the window ownership API; covered by `PLN-0060`. -- Overlay/modal policy. -- Background, minimization, app switcher, dock, or multiple-window behavior. -- Treating games as windows. - -## Plano de Execucao - -1. Depend on `PLN-0060`. - Do not update `ShellRunningStep` until - `os.windows().focused_window_belongs_to_task(task_id)` exists. - -2. Replace the liveness predicate in `ShellRunningStep`. - In `firmware_step_shell_running.rs`, keep the existing `TaskState` - validation. After `ctx.hub.update_shell_profile(...)`, replace the - `outcome.focused_window_active` check with: - - ```text - !ctx.os.windows().focused_window_belongs_to_task(self.task_id) - ``` - -3. Preserve crash handling order. - Keep VM crash handling before window-liveness closure so a crash reported by - the shell update remains a crash, not a normal close. - -4. Close through lifecycle before returning Hub. - When the focused-window ownership predicate is false: - - log the closure with task id; - - call `ctx.os.lifecycle().close_task(self.task_id)`; - - log lifecycle close errors; - - return `Some(FirmwareState::HubHome(HubHomeStep))`. - -5. De-emphasize or remove `focused_window_active`. - If `SystemProfileUpdate.focused_window_active` has no remaining call sites, - remove it from `SystemProfileUpdate` and its construction. If another - legitimate caller remains, keep it but do not use it for shell task - liveness. - -6. Add or update tests. - Use existing firmware tests in - [firmware.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-firmware/src/firmware/firmware.rs) - or a nearby test module. Cover: - - focused task-owned window keeps the shell path running; - - no focused window closes task and returns `HubHome`; - - focused `Hub` window closes task and returns `HubHome`; - - focused `Overlay` window closes task and returns `HubHome` in v1; - - focused window for another task closes task and returns `HubHome`; - - lifecycle close changes task to `Closed` and process to `Stopped`; - - `GameRunningStep` remains independent from `WindowManager`. - -## Criterios de Aceite - -- [x] `ShellRunningStep` no longer uses `focused_window_active` as its - continuation contract. -- [x] `ShellRunningStep` uses - `os.windows().focused_window_belongs_to_task(self.task_id)`. -- [x] A false predicate closes the task through lifecycle before returning - `HubHome`. -- [x] `Hub`, `Overlay`, absent focus, and another task's focused window all - fail the predicate for the current shell task. -- [x] Game-running behavior is unchanged and remains outside `WindowManager`. - -## Tests / Validacao - -- Run `cargo test -p prometeu-system`. -- Run `cargo test -p prometeu-firmware`. -- Run a workspace or targeted build command used by this repository if the two - crate tests are not sufficient. -- Run `discussion validate`. - -## Riscos - -- Current Hub update behavior may close or replace windows during the same tick; - tests need to account for the final focused window after `update_shell_profile`. -- Removing `focused_window_active` may require small cleanup in system tests or - callers. -- The v1 overlay behavior is intentionally strict. Do not add overlay - preservation logic in this plan.