strings.lua holds English defaults. ctx.t(key, ...) looks up the key,
optionally string.formats with extra args, and falls back to '[key]'
for missing entries. All panels and init.lua sweep clean of inline
literals — verified by a grep over '"[A-Z][a-z][a-z ]+"'. This sets
up v2's localization-lib-pattern (separate slice, ADR-0036 trigger)
to swap STRINGS without touching the launcher code.
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.
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.
panels/carousel.lua holds per-module state (index, timer, manual pause,
texture cache). Auto-rotate at carousel_interval_seconds (5s default,
frontmatter-overrideable). Click on overlay arrows steps and pauses
auto-rotate for 30s. Max 7 indicator dots, then 'X / N' overlay (L-Q4).
Single-teaser hides the arrows and dots. Zero-teaser falls through to
ctx.default_teaser_texture which L.10 ships.
ASCII fallback glyphs ('OK', 'WARN', 'ERR') have variable width; the
previous 40px column gap was too narrow for 'WARN', causing the id
column to wobble between modules. Widens the gutter to 48px and
right-aligns the glyph inside it so the id text always starts at a
predictable x.
panels/detail.lua now renders the selected module's title, version,
author, markdown description, and per-dep status list with OK/WARN/ERR
glyphs. Dep-list order follows manifest.module::deps[] (L-Q6 load-
order, no re-sort). Whole-list bg-tint is bubbled to ctx.list_tint so
the list panel picks up a yellow/red ambient cue for warn/error
states. Hero strip remains a placeholder until L.5.
Splits rendering into panels/list.lua and panels/detail.lua. List
implements row hit-test, scroll-state, whole-list bg-tint hook for the
dep-conflict UX (L-Q6). Detail renders an empty-state placeholder
until L.4-L.7 fill in title, description, deps, carousel, and launch
button.