feat(launcher): launch button + dep-conflict modal

Detail-panel bottom-right Launch button: switches to the selected
module via engine.switch_module if dep-check is clean, otherwise
opens a modal listing conflicts with Cancel + 'Launch anyway'. The
latter is greyed out unless engine.module.switch_module_supports_fallback
is exposed (separate engine slice). FSM adds STATE_MODAL_CONFLICT
with transitions to/from STATE_LIST via cancel/launch_anyway/esc.

Generic panels/modal.lua takes a def-table (title/body/buttons) and
returns the clicked button id from handle_click; reusable for future
modals beyond the conflict case.
This commit is contained in:
Calic
2026-06-11 02:54:55 +02:00
parent 10d0ab6529
commit 89b7b060d1
4 changed files with 142 additions and 15 deletions

28
fsm.lua
View File

@@ -1,23 +1,23 @@
-- Sporel Launcher — pure-Lua finite state machine.
-- No engine.* calls; testable directly by lib-management.launcher-test.
local M = {}
M.STATE_LIST = "list"
M.STATE_QUIT_CONFIRM = "quit_confirm"
M.EXIT = "exit" -- sentinel: caller should engine.exit(0)
M.STATE_LIST = "list"
M.STATE_QUIT_CONFIRM = "quit_confirm"
M.STATE_MODAL_CONFLICT = "modal_conflict"
M.EXIT = "exit"
-- Events: "esc", "enter", "click_yes", "click_no", "click_module"
function M.next(state, event)
if state == M.STATE_LIST then
if event == "esc" then return M.STATE_QUIT_CONFIRM end
return M.STATE_LIST
if event == "esc" then return M.STATE_QUIT_CONFIRM end
if event == "open_conflict" then return M.STATE_MODAL_CONFLICT end
elseif state == M.STATE_QUIT_CONFIRM then
if event == "esc" then return M.STATE_LIST end
if event == "enter" then return M.EXIT end
if event == "click_yes" then return M.EXIT end
if event == "click_no" then return M.STATE_LIST end
return M.STATE_QUIT_CONFIRM
if event == "esc" then return M.STATE_LIST end
if event == "enter" then return M.EXIT end
if event == "click_yes" then return M.EXIT end
if event == "click_no" then return M.STATE_LIST end
elseif state == M.STATE_MODAL_CONFLICT then
if event == "esc" then return M.STATE_LIST end
if event == "cancel" then return M.STATE_LIST end
if event == "launch_anyway" then return M.STATE_LIST end -- side-effect
end
return state
end