From a49aa3ef2b8c6b27bbf42a35dfa661c83603be9a Mon Sep 17 00:00:00 2001 From: Calic Date: Thu, 11 Jun 2026 03:10:56 +0200 Subject: [PATCH] fix(launcher): wire FSM dispatch + correct conflict-count The conflict-modal path bypassed the FSM extension landed in L.7: panels/detail.lua set ctx.modal_open directly and init.lua drove modal handling off the side-channel field, so STATE_MODAL_CONFLICT was unreachable at runtime (only the test-lib asserts exercised it). Now detail.lua marks ctx._pending_modal_open and init.lua dispatches the open_conflict event so state actually transitions through the fsm. Modal entry/exit is gated on state == STATE_MODAL_CONFLICT. Also fixes the conflict body line: r.missing doesn't exist on the dep-fetcher result; use #(errors) + #(conflicts) for the count displayed in the modal body. --- init.lua | 20 ++++++++++++++------ panels/detail.lua | 6 ++++-- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/init.lua b/init.lua index 7d83680..0bb4f2d 100644 --- a/init.lua +++ b/init.lua @@ -207,7 +207,6 @@ function init(ctx) bg_texture = nil, -- L.10 loads it default_teaser_texture = nil, -- L.10 loads it t = function(k) return k end, -- L.8 swaps in real t() - modal_open = nil, -- L.7 sets to "quit" | "conflict" carousel_state = {}, -- per-module-id state, L.5 populates manifest_cache = {}, -- L.3 populates launcher_dir = MODULE_DIR, -- L.4: detail.lua dofiles markdown.lua via this @@ -340,22 +339,31 @@ function update(ctx, dt) local mx, my = engine.input.get_mouse_pos() + -- C1: detail.lua's launch-button branch signals a conflict via + -- ctx._pending_modal_open; dispatch the FSM event here so state + -- actually transitions into STATE_MODAL_CONFLICT before the modal + -- branch below runs. + if ctx_panels._pending_modal_open == "conflict" then + state = fsm.next(state, "open_conflict") + ctx_panels._pending_modal_open = nil + end + -- L.7: Conflict-modal takes priority over STATE_LIST input. esc / -- Cancel close the modal back to STATE_LIST; "Launch anyway" stays -- a no-op until engine.module.switch_module_supports_fallback ships. - if ctx_panels.modal_open == "conflict" then + if state == fsm.STATE_MODAL_CONFLICT then if input.was_action_pressed("ui_back") then - ctx_panels.modal_open = nil state = fsm.next(state, "esc") + ctx_panels.modal_def = nil elseif input.was_action_pressed("ui_click") then local id = modal_panel.handle_click(ctx_panels.modal_def, mx, my) if id == "cancel" then - ctx_panels.modal_open = nil state = fsm.next(state, "cancel") + ctx_panels.modal_def = nil elseif id == "launch_anyway" then local mid = ctx_panels.modal_def.module_id - ctx_panels.modal_open = nil state = fsm.next(state, "launch_anyway") + ctx_panels.modal_def = nil if engine.module.switch_module_supports_fallback then engine.switch_module(mid, { mode = "fallback" }) end @@ -396,7 +404,7 @@ function render(ctx) detail_panel.render(ctx_panels) -- L.7: Conflict modal renders above the panels (backdrop dims them). - if ctx_panels.modal_open == "conflict" then + if state == fsm.STATE_MODAL_CONFLICT then modal_panel.render(ctx_panels.modal_def, screen_w, screen_h) end diff --git a/panels/detail.lua b/panels/detail.lua index 12df29e..33eb4e7 100644 --- a/panels/detail.lua +++ b/panels/detail.lua @@ -155,7 +155,7 @@ function M.handle_click(ctx, mx, my) body = { ctx.t("conflict_body_line1"), string.format(ctx.t("conflict_body_line2_fmt"), - r and #r.missing or 0), + r and (#(r.errors or {}) + #(r.conflicts or {})) or 0), }, buttons = { { id = "cancel", label = ctx.t("cancel") }, @@ -164,7 +164,9 @@ function M.handle_click(ctx, mx, my) }, module_id = entry.id, } - ctx.modal_open = "conflict" + -- C1: signal init.lua to dispatch open_conflict on the FSM + -- so state actually transitions into STATE_MODAL_CONFLICT. + ctx._pending_modal_open = "conflict" end return true end