class_name ImplantNavStack extends Node ## Intra-app navigation stack for ImplantApp subclasses (#844, D-191). ## Mutation is synchronous: screen_changed fires before push/pop/replace returns. signal screen_changed(current_screen_id: String) var _stack: Array[String] = [] var _payloads: Array[Dictionary] = [] var _default_screen_id: String = "" var _mutating: bool = false # re-entrancy guard: set while screen_changed is emitting func set_default(screen_id: String) -> void: _default_screen_id = screen_id func push(screen_id: String, payload: Dictionary = {}) -> void: if _mutating: push_error( "ImplantNavStack: nested mutation detected — use call_deferred from screen_changed handler" ) return _stack.append(screen_id) _payloads.append(payload) _mutating = true screen_changed.emit(screen_id) _mutating = false # pop() never empties the stack below the default screen — this app always # has at least one screen visible. To truly reset, use reset_to_default(). func pop() -> void: if _mutating: push_error( "ImplantNavStack: nested mutation detected — use call_deferred from screen_changed handler" ) return if _stack.is_empty(): push_warning("ImplantNavStack: pop() on empty stack") return _stack.pop_back() _payloads.pop_back() if not _stack.is_empty(): _mutating = true screen_changed.emit(_stack.back()) _mutating = false elif not _default_screen_id.is_empty(): push(_default_screen_id) func replace(screen_id: String, payload: Dictionary = {}) -> void: if _mutating: push_error( "ImplantNavStack: nested mutation detected — use call_deferred from screen_changed handler" ) return if not _stack.is_empty(): _stack.pop_back() _payloads.pop_back() _stack.append(screen_id) _payloads.append(payload) _mutating = true screen_changed.emit(screen_id) _mutating = false func reset_to_default() -> void: _stack.clear() _payloads.clear() if not _default_screen_id.is_empty(): push(_default_screen_id) func is_empty() -> bool: return _stack.is_empty() func push_default() -> void: if not _default_screen_id.is_empty(): push(_default_screen_id) func current() -> String: if _stack.is_empty(): return "" return _stack.back() func current_payload() -> Dictionary: if _payloads.is_empty(): return {} return _payloads.back()