docs: PR #131 review — lifecycle ordering contract (item 9)
Documents the ImplantApp lifecycle ordering and the nav-stack state guarantee at each hook: - Class-level docstring on implant_app.gd describes on_install, on_open, on_close, and on_insert_deactivated: when each fires, what nav state subclasses can rely on, and what is safe to do (construct + register_screen in on_install; data refresh + read nav.current() in on_open; pause timers in on_close; no close_app manual call in on_insert_deactivated — call super or replicate the guard). - Arch doc gains a "Lifecycle hooks" subsection under ImplantApp base class with a four-row contract table plus explanatory notes on two load-bearing invariants: why on_install sees an empty stack (bottom-up _ready order, no open signal yet); why on_close must not push/pop (would destroy preserved position on reopen). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -184,6 +184,21 @@ func handle_intent(_action: String, _params: Dictionary) -> void: pass # future
|
||||
- `_current_screen_id` bookkeeping. The base owns it.
|
||||
- Enter/leave dispatch. The default `_on_screen_changed` drives it; screens that need enter/leave implement those methods, the base invokes them via `has_method` tolerance.
|
||||
|
||||
### Lifecycle hooks
|
||||
|
||||
The four hooks fire in a defined order with a defined nav-stack guarantee at each point. Subclasses can rely on this contract without inspecting `HudGroups` state directly.
|
||||
|
||||
| Hook | When it fires | Nav stack state when it fires | Intended use |
|
||||
|------|--------------|-------------------------------|--------------|
|
||||
| `on_install()` | Once, from `_ready()`, after nav is created | **Empty.** No screens have been pushed yet. | Construct screens; call `register_screen(id, screen)`; call `nav.set_default("...")`. Wiring only — do not read `nav.current()`. |
|
||||
| `on_open(mode)` | Every activation (FULLSCREEN or INSERT) | **Non-empty.** Base ensures: `preserves_state=false` → `reset_to_default()` called; `preserves_state=true` and stack was empty → `push_default()` called; `preserves_state=true` and stack non-empty → untouched. | Data refresh; announce current screen; start animations. `nav.current()` is safe here. |
|
||||
| `on_close()` | Every deactivation (GAMEPLAY or different app takes focus) | Stack preserved — base does not mutate it. | Pause timers; stop high-frequency feeds; save scroll position. Do **not** push or pop — the stack survives for the next `on_open`. |
|
||||
| `on_insert_deactivated()` | SnapshotConsumers calls this when server drops insert state | Whatever `on_close()` left it (if the app was already closed) or the live state (if the app is still open) | Base default: close if active in INSERT mode, no-op otherwise. FULLSCREEN apps override. Do not call `close_app()` manually — call `super()` or replicate the guard. |
|
||||
|
||||
**Why `on_install` sees an empty stack:** `_ready()` fires bottom-up — children before parents. The base's `_ready()` creates `nav` and then calls `on_install()` synchronously. No `HudGroups.app_changed` signal has fired yet (that comes from `open_app()`, which requires the game to be running). Subclasses that call `nav.current()` in `on_install` always see `""` — which is always wrong. The right pattern is to call `nav.set_default("my_first_screen")` in `on_install` and let the base push it on the first `on_open`.
|
||||
|
||||
**Why `on_close` must not push/pop:** The nav stack is the in-flight navigation position that survives across close/reopen cycles (when `preserves_state=true`). Mutating it in `on_close` destroys the user's position. If an app needs to reset navigation on close, set `preserves_state=false` in the manifest instead — the base handles the reset at the top of `on_open`, which is the right moment.
|
||||
|
||||
## `ImplantNavStack` — intra-app navigation
|
||||
|
||||
Apps push and pop screens. The stack is owned by `ImplantApp` (one per app instance — no global nav state).
|
||||
|
||||
Reference in New Issue
Block a user