Skip to content

feat: theme selector + accordion world map#12

Merged
StrangeNoob merged 4 commits into
mainfrom
feature/theme-selector-accordion-map
Jul 3, 2026
Merged

feat: theme selector + accordion world map#12
StrangeNoob merged 4 commits into
mainfrom
feature/theme-selector-accordion-map

Conversation

@StrangeNoob

@StrangeNoob StrangeNoob commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Implements Turn 3 of the nvim-quest UI.dc.html redesign brief plus an in-game theme picker built from the brief's Turn 2 style directions.

What changed

1. Accordion world map (worldmap.go)

The flat 29-lesson map overflowed 80×24. It's now an accordion:

  • Cleared and locked acts collapse to a single summary row (✓ ACT I · … ★ 24/24 · cleared, · ACT IV · … locked · clear act iii) — stars stay visible so replay value shows.
  • Only the current act expands, with per-lesson tri-stars (★★★ / ☆☆☆).
  • j/k move · enter plays a lesson or expands a collapsed act · tab folds/unfolds · esc title.
  • Always fits 24 rows regardless of catalog size (a test asserts ≤22 lines for every act).

State: mapIdx (a lesson index) is replaced by openAct + mapSel (selection over visible rows); a focusLesson helper positions the cursor for callers arriving from the title / results / room screens.

2. Theme selector

Classic (the original look) · Synthwave · Charm · Phosphor, from a new Theme entry in the title menu with live preview. A Theme is a per-act palette plus base role colors; applyTheme reassigns the shared style vars in place, so every renderer re-skins with no call-site changes. The choice persists in progress.json (new optional field — no version bump, so old saves load fine and default to Classic) and is preserved across a progress reset.

3. Content sync

  • Finale: "The Grid Core is broken" → "The Archivist is catalogued", palette Act III → Act IV brass.
  • roman(4)"IV" so an Act IV boss-clear reads "ACT IV COMPLETE".

Scope / ceiling

Themes re-skin colors only (reads clearly as synthwave-neon / charm-pastel / phosphor-green). They do not rebuild each theme's structural chrome (dither banners, pill HUD, CRT reverse-video headers); Phosphor is approximated as a per-act color ramp. Full structural fidelity is a separate, larger job.

Tests

  • styles_test.go — theme switching + Classic fallback for unknown/empty names.
  • worldmap_test.go — fits-24 for every act, one-act-expanded invariant, expand-collapses-previous, summary-row status text.
  • themes_test.go — picker select persists + esc reverts the preview.
  • progress_test.go — v2 backward compat + theme round-trip.
  • Full suite green, go vet + gofmt clean.

Spec: docs/superpowers/specs/2026-07-04-theme-selector-and-accordion-map-design.md

Summary by CodeRabbit

  • New Features

    • Added a theme picker in the title menu, letting players preview and save visual themes.
    • Redesigned the world map into an accordion-style view with expandable/collapsible acts and clearer progress display.
  • Bug Fixes

    • Added folding/unfolding on the map with Tab, plus smoother navigation and selection behavior.
    • Theme choices now persist across sessions and are preserved when resetting progress.
    • Updated play instructions and endgame text to match the latest gameplay flow.

Turn 3 of the redesign brief: the flat 29-lesson map overflowed 80x24, so
rework it as an accordion — cleared/locked acts collapse to one summary row
(stars stay visible), only the current act expands, always fits 24 rows. j/k
move, enter plays a lesson or expands a collapsed act, tab folds/unfolds.

Add a theme selector built from Turn 2's directions: Classic (the original
look), Synthwave, Charm, and Phosphor. A Theme lives in styles.go as a per-act
palette plus base role colors; applyTheme reassigns the shared style vars in
place, so every renderer re-skins for free. The choice persists in
progress.json (new optional field, no version bump — old saves default to
Classic) and survives a progress reset. Picker screen lives under a new title
menu entry with live preview.

Sync the two now-stale strings the brief flagged: the finale reads "The
Archivist is catalogued" in Act IV brass (was "The Grid Core is broken"), and
roman(4) returns "IV".
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@StrangeNoob, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 21 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 78e1dcf3-097b-44e7-b672-cc88f4f1bdf9

📥 Commits

Reviewing files that changed from the base of the PR and between 79cf3ec and b8ffd16.

📒 Files selected for processing (4)
  • go.mod
  • internal/ui/themes.go
  • internal/ui/themes_test.go
  • internal/ui/title.go
📝 Walkthrough

Walkthrough

This PR adds a theme system (Theme model, registry, applyTheme, theme picker screen, and persistence via a new Progress.Theme field) and replaces the flat world map with an accordion-style layout using openAct/mapSel state and a focusLesson helper. It also fixes finale text/palette, roman(4), and updates docs/README/tests accordingly.

Changes

Theme selector and accordion map

Layer / File(s) Summary
Design spec and docs
docs/superpowers/specs/2026-07-04-theme-selector-and-accordion-map-design.md, docs/ARCHITECTURE.md, README.md
Adds design brief for theme selector/accordion map and updates architecture and how-to-play/navigation docs.
Theme model, registry, and applyTheme
internal/ui/styles.go, internal/ui/styles_test.go
Introduces Theme, themeRegistry, PrimaryStyle(), and applyTheme rebuilding shared style variables, with switching/fallback tests.
Progress theme persistence
internal/progress/progress.go, internal/progress/progress_test.go
Adds Theme field to Progress (omitempty) with round-trip and v2-compat tests.
Theme picker screen and wiring
internal/ui/app.go, internal/ui/themes.go, internal/ui/themes_test.go, internal/ui/title.go
Adds screenThemes, updateThemes/viewThemes, startup theme application, title menu entry, reset preserving theme, and tests.
Accordion world map
internal/ui/worldmap.go, internal/ui/worldmap_test.go
Replaces mapIdx-based navigation with openAct/mapSel accordion model, act/lesson aggregation, focusLesson/toggleAct, tri-star rendering, and tests.
Caller migrations and finale fixes
internal/ui/results.go, internal/ui/room.go, internal/ui/app_test.go
Updates call sites to use focusLesson, fixes finale palette/text and roman(4), and adjusts existing tests.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant TitleScreen
  participant ThemesScreen as updateThemes/viewThemes
  participant StylesModule as applyTheme
  participant Progress as progress.Save/Load

  User->>TitleScreen: select "Theme"
  TitleScreen->>ThemesScreen: switch to screenThemes, set themeIdx
  User->>ThemesScreen: j/k (preview)
  ThemesScreen->>StylesModule: applyTheme(previewName)
  User->>ThemesScreen: enter (commit)
  ThemesScreen->>StylesModule: applyTheme(selectedName)
  ThemesScreen->>Progress: Save(prog with Theme)
  ThemesScreen->>TitleScreen: switch to screenTitle
  User->>ThemesScreen: esc (cancel, alternate path)
  ThemesScreen->>StylesModule: applyTheme(savedTheme)
  ThemesScreen->>TitleScreen: switch to screenTitle
Loading
sequenceDiagram
  participant User
  participant WorldMap as updateMap/viewMap
  participant Rows as visibleRows
  participant Focus as focusLesson/toggleAct

  User->>WorldMap: j/k (move selection)
  WorldMap->>Rows: recompute visible rows for openAct
  User->>WorldMap: tab (fold/unfold act header)
  WorldMap->>Focus: toggleAct(sel)
  Focus->>Rows: update openAct, re-anchor mapSel
  User->>WorldMap: enter (open lesson if unlocked)
  WorldMap->>Focus: focusLesson(lessonIdx)
  Focus->>Rows: set openAct + mapSel to lesson row
Loading

Possibly related PRs

  • StrangeNoob/nvim-quest#2: Both PRs modify the final-boss/results UI flow in internal/ui/results.go, touching viewResults/viewFinale logic and messaging.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the two main changes: a theme selector and an accordion world map.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/theme-selector-accordion-map

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/ui/themes_test.go (1)

9-14: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test relies on external cleanup discipline rather than pinning its own starting theme.

m.themeIdx = themeIndexByName(activeThemeName) assumes the global activeThemeName is already "Classic", but nothing in this test enforces that — it depends on every other test in the package (e.g. in styles_test.go, which runs earlier alphabetically) properly restoring the theme afterward. If any test forgets to reset it, this test's assumptions (and its "j should live-preview a different theme" / persistence assertions) become order-dependent.

Pin the starting state explicitly instead of relying on it:

🔧 Proposed fix
 func TestThemePickerSelectPersistsAndCancelReverts(t *testing.T) {
 	defer applyTheme("Classic") // don't leak a theme to other tests
+	applyTheme("Classic") // ensure a known starting theme regardless of run order

 	m := newTestModel(t)
 	m.themeIdx = themeIndexByName(activeThemeName) // start on the active (Classic)
 	m.scr = screenThemes
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/ui/themes_test.go` around lines 9 - 14,
TestThemePickerSelectPersistsAndCancelReverts depends on the shared
activeThemeName being “Classic” instead of setting its own starting state.
Update this test to explicitly pin the theme before creating/configuring the
model (using applyTheme and then m.themeIdx/themeIndexByName as needed) so the
assertions in TestThemePickerSelectPersistsAndCancelReverts do not rely on
cleanup from styles_test or any other test. Keep the existing defer cleanup, but
make the initial theme setup self-contained in the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/progress/progress_test.go`:
- Line 35: The test setup in progress_test uses os.WriteFile without checking
its error, so a failed write will surface later as a confusing failure. Update
the setup around the legacy file creation in the test to handle the return value
from os.WriteFile explicitly and fail the test immediately with a clear message
if it errors, keeping the check close to the legacy and progress file
initialization.

---

Nitpick comments:
In `@internal/ui/themes_test.go`:
- Around line 9-14: TestThemePickerSelectPersistsAndCancelReverts depends on the
shared activeThemeName being “Classic” instead of setting its own starting
state. Update this test to explicitly pin the theme before creating/configuring
the model (using applyTheme and then m.themeIdx/themeIndexByName as needed) so
the assertions in TestThemePickerSelectPersistsAndCancelReverts do not rely on
cleanup from styles_test or any other test. Keep the existing defer cleanup, but
make the initial theme setup self-contained in the test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c720421c-5668-4617-b8ac-ded2b2b4b0c5

📥 Commits

Reviewing files that changed from the base of the PR and between 2e57a26 and 79cf3ec.

📒 Files selected for processing (16)
  • README.md
  • docs/ARCHITECTURE.md
  • docs/superpowers/specs/2026-07-04-theme-selector-and-accordion-map-design.md
  • internal/progress/progress.go
  • internal/progress/progress_test.go
  • internal/ui/app.go
  • internal/ui/app_test.go
  • internal/ui/results.go
  • internal/ui/room.go
  • internal/ui/styles.go
  • internal/ui/styles_test.go
  • internal/ui/themes.go
  • internal/ui/themes_test.go
  • internal/ui/title.go
  • internal/ui/worldmap.go
  • internal/ui/worldmap_test.go

// A v2 save written before the Theme field existed must still load, with
// Theme defaulting to "" (no version bump, no wipe).
legacy := filepath.Join(dir, "legacy.json")
os.WriteFile(legacy, []byte(`{"version":2,"xp":10,"level":1,"stars":{}}`), 0o644)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Unchecked error from os.WriteFile.

golangci-lint (errcheck) flags this. If the write fails, the test proceeds against a missing/incomplete file and fails with a confusing downstream error instead of a clear message.

🛠️ Proposed fix
-	os.WriteFile(legacy, []byte(`{"version":2,"xp":10,"level":1,"stars":{}}`), 0o644)
+	if err := os.WriteFile(legacy, []byte(`{"version":2,"xp":10,"level":1,"stars":{}}`), 0o644); err != nil {
+		t.Fatalf("WriteFile: %v", err)
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
os.WriteFile(legacy, []byte(`{"version":2,"xp":10,"level":1,"stars":{}}`), 0o644)
if err := os.WriteFile(legacy, []byte(`{"version":2,"xp":10,"level":1,"stars":{}}`), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
🧰 Tools
🪛 golangci-lint (2.12.2)

[error] 35-35: Error return value of os.WriteFile is not checked

(errcheck)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/progress/progress_test.go` at line 35, The test setup in
progress_test uses os.WriteFile without checking its error, so a failed write
will surface later as a confusing failure. Update the setup around the legacy
file creation in the test to handle the return value from os.WriteFile
explicitly and fail the test immediately with a clear message if it errors,
keeping the check close to the legacy and progress file initialization.

Source: Linters/SAST tools

The theme was already applied on selection (verified: the live-committed
title renders byte-identically to a restart with the same theme), but the
title logo was colored by the current act only — and act I is a shade of
green in every theme — so switching themes looked like nothing happened.

Paint the logo top-to-bottom across all four act colors so every theme
renders a distinct title, and apply the theme explicitly on commit rather
than relying on the j/k preview side-effect. Adds a regression test asserting
the four themes produce distinct title renders.
@StrangeNoob StrangeNoob merged commit a21cee6 into main Jul 3, 2026
2 checks passed
@StrangeNoob StrangeNoob deleted the feature/theme-selector-accordion-map branch July 3, 2026 21:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant