List rows showed the long `summary` (name+id+description fallback chain)
and overflowed. Now each row shows the module name with a small genre-
tags subline sourced from manifest.launcher.md (meta.tags, threaded onto
the entry in init). The list also scrolls properly now (wheel dispatch +
upper clamp) — it was wired but dead without a wheel binding.
Detail panel: description + dependency list ran off the bottom of the
window. The mid-section (title/description/deps) now scrolls inside a
begin_view/end_view scissor region between the fixed hero strip and the
fixed launch button; news view scrolls too. Both panels get an inline
scrollbar. Detail scroll resets on module switch.
Depends on engine.input.get_mouse_wheel (sporel-engine). README controls/
demonstrates synced.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Solid-fill placeholders for the empty-state background (1920×1080) and
the zero-teaser hero fallback (800×450). Brand-refresh slice can drop
both files in without touching code. Loaded once on init via
engine.asset.load_texture (launcher's own sandbox).
The status_glyph helper in detail.lua returned 'OK'/'WARN'/'ERR'/'?'
literals - user-visible UI text that the Mid-Gate-B grep missed
because the pattern required a lowercase suffix. Now lives in
strings.lua alongside the other UI keys, so v2 localization-lib will
pick it up without touching detail.lua.
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.
Static 'coming soon' stub authored as Markdown with JSON-frontmatter
title. Loaded once on init, parsed via the same fm + md helpers as
modules. Live news (Gitea-API or RSS) deferred — single-source-of-
truth in the launcher repo keeps update-pace under our control.
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.
Matches the existing frontmatter.lua memoization pattern. Avoids
re-dofile'ing markdown.lua per successful load_for call (matters
once L.9 rolls out manifest.launcher.md to every module and the
init-time call count goes from 1 to N).
manifest_loader.load_for(id) reads <install>/modules/<id>/
manifest.launcher.md via engine.module.read_file, runs the JSON
frontmatter through frontmatter.parse, and pre-parses the body via
markdown.parse. Cached on the shared ctx so the detail-panel render
loop does not pay the parse cost per frame. Missing files degrade to
{_missing=true} and the list falls back to the module's name/id.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
draw_module_row, tooltip_for, and apply_update were the v0.2.0
button-list rendering helpers. The master-detail panel refactor in
41b28ff moved rendering into panels/list.lua + panels/detail.lua,
leaving these three functions with no call sites. Also drops the
now-unused icons import and ICON_GAP/ICON_STRIP_W constants that only
the deleted helpers referenced; FONT_BUTTON, COLOR_DIM and COLOR_MODAL
stay because the quit-confirm modal still uses them.
Verified by grep that none of the removed symbols have remaining
references in init.lua or sibling files. CI boot is clean:
init ok, modules=6 + BOOTING -> RUNNING -> SHUTTING_DOWN, no FATAL.
init.lua drops 443 -> 302 lines; function count drops 14 -> 11.
Replaces the centered-button list with a 1/3 list + 2/3 detail split
driven by panels/list.lua and panels/detail.lua. The shared ctx_panels
table threads UI state (selected_entry, scroll_y, modal_open, etc.)
through both panels. Existing dep + update checks remain on the module
entries; FSM and ESC-quit flow unchanged. Detail-panel content is still
a stub until L.4-L.7.
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.
The body region rebuilt from line-split included a trailing empty
entry from the final LF the parser appends to drive its gmatch. Strip
it so 'body text\n' (the on-disk content) parses to 'body text' (the
user-visible body), which is what the launcher-test asserts.
Hand-rolled parser + renderer for the launcher's description bodies.
Supports headers H1-H3, paragraphs, bullet + ordered lists, inline
bold/italic/code, inline images. No tables, blockquotes, links, or
code blocks. Font sizes per spec L-Q3 (H1=24, H2=20, H3=16, body=14).
Images resolve via engine.module.load_texture when a module_id is
provided to the renderer.
Parses manifest.launcher.md leading JSON-frontmatter (delimited by
standalone '---' lines), returns (meta, body). Reuses
lib-management.dep-fetcher._json_decode so we do not add a second JSON
decoder to the codebase. Lazy require so test-lib contexts can swap a
stub decoder via _G.LAUNCHER_FRONTMATTER_DECODER.
- Async update-check per module on init (git.start_ls_remote_tags),
polled per frame. Result classified into available / no-update /
fail / local-only via the new kind field from lib-core.git 0.2.0.
- Pre-emptive dep-check per module (depf.ensure_for_module) on init
populates a dep_check_result whose ok/conflicts/warnings/errors
drive the icon strip.
- Inline icon strip per module-row (icons.lua): rotating ASCII
spinner while pending, yellow warn for auth/network failures or
dirty deps, blue update-badge with target version (clickable to
trigger fetch+checkout), red error for dep-check conflicts, gray
L for repos that genuinely don't exist on the server. Tooltip on
hover shows the specific reason per icon.
- Click handler splits launch-vs-update by hit-testing the badge
rect before the row rect. Launch is blocked when dep-check failed.
- On init, reads engine.config("gitea_token") and forwards via
git.set_token so users can persist their token in their user-data
engine.json layer instead of using an env var.
- New deps: lib-core.git 0.1.0, lib-management.dep-fetcher 0.1.2.
Initial README following the project's module documentation structure:
Abstract paragraph, Badges as bold key:value list, Topology section
with auto-generated mermaid graph, Controls describing player input,
Demonstrates listing the Sporel features showcased, References at
the end.
Install pre-commit hook via Sporel --install-hooks=.; topology is
auto-managed by Sporel --lint --fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>