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.
This commit is contained in:
Calic
2026-06-11 03:10:56 +02:00
parent 89b7b060d1
commit a49aa3ef2b
2 changed files with 18 additions and 8 deletions

View File

@@ -207,7 +207,6 @@ function init(ctx)
bg_texture = nil, -- L.10 loads it bg_texture = nil, -- L.10 loads it
default_teaser_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() 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 carousel_state = {}, -- per-module-id state, L.5 populates
manifest_cache = {}, -- L.3 populates manifest_cache = {}, -- L.3 populates
launcher_dir = MODULE_DIR, -- L.4: detail.lua dofiles markdown.lua via this 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() 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 / -- L.7: Conflict-modal takes priority over STATE_LIST input. esc /
-- Cancel close the modal back to STATE_LIST; "Launch anyway" stays -- Cancel close the modal back to STATE_LIST; "Launch anyway" stays
-- a no-op until engine.module.switch_module_supports_fallback ships. -- 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 if input.was_action_pressed("ui_back") then
ctx_panels.modal_open = nil
state = fsm.next(state, "esc") state = fsm.next(state, "esc")
ctx_panels.modal_def = nil
elseif input.was_action_pressed("ui_click") then elseif input.was_action_pressed("ui_click") then
local id = modal_panel.handle_click(ctx_panels.modal_def, mx, my) local id = modal_panel.handle_click(ctx_panels.modal_def, mx, my)
if id == "cancel" then if id == "cancel" then
ctx_panels.modal_open = nil
state = fsm.next(state, "cancel") state = fsm.next(state, "cancel")
ctx_panels.modal_def = nil
elseif id == "launch_anyway" then elseif id == "launch_anyway" then
local mid = ctx_panels.modal_def.module_id local mid = ctx_panels.modal_def.module_id
ctx_panels.modal_open = nil
state = fsm.next(state, "launch_anyway") state = fsm.next(state, "launch_anyway")
ctx_panels.modal_def = nil
if engine.module.switch_module_supports_fallback then if engine.module.switch_module_supports_fallback then
engine.switch_module(mid, { mode = "fallback" }) engine.switch_module(mid, { mode = "fallback" })
end end
@@ -396,7 +404,7 @@ function render(ctx)
detail_panel.render(ctx_panels) detail_panel.render(ctx_panels)
-- L.7: Conflict modal renders above the panels (backdrop dims them). -- 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) modal_panel.render(ctx_panels.modal_def, screen_w, screen_h)
end end

View File

@@ -155,7 +155,7 @@ function M.handle_click(ctx, mx, my)
body = { body = {
ctx.t("conflict_body_line1"), ctx.t("conflict_body_line1"),
string.format(ctx.t("conflict_body_line2_fmt"), 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 = { buttons = {
{ id = "cancel", label = ctx.t("cancel") }, { id = "cancel", label = ctx.t("cancel") },
@@ -164,7 +164,9 @@ function M.handle_click(ctx, mx, my)
}, },
module_id = entry.id, 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 end
return true return true
end end