Skip to content

feat: full-fidelity theme skins (Phosphor / Synthwave / Charm)#13

Merged
StrangeNoob merged 6 commits into
mainfrom
feature/theme-skins-full-fidelity
Jul 3, 2026
Merged

feat: full-fidelity theme skins (Phosphor / Synthwave / Charm)#13
StrangeNoob merged 6 commits into
mainfrom
feature/theme-skins-full-fidelity

Conversation

@StrangeNoob

@StrangeNoob StrangeNoob commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Follow-up to #12. Turns the color-only themes into full mockup fidelity — the Turn 2 designs differ in structure, not just color, so the renderers became theme-driven via a Skin seam.

Architecture — the Skin interface (skin.go)

Renderers assemble semantic content (roomHeader, buffer lines, HUD segments) and hand it to activeSkin(), which owns the layout. Themes without a registered skin fall back to classicSkin and re-skin by palette only.

type Skin interface {
    Logo(lines []string) string
    RoomHeader(h roomHeader) string
    Body(act int, s string) string
    CursorCell(act int, s string) string
    FrameBuffer(act int, lines []string) string
    HUD(act int, segs []segment) string
}

Skins restyle the title logo and the room (header, buffer, HUD, cursor, objective) — the three screens the mockups actually themed. Map / welcome / stats / results stay structurally neutral (color-only).

Skins

  • classicSkin — original look, reproduced byte-for-byte (proven with a golden render diff). Every theme fell back to it before its own skin landed, so each phase was independently safe.
  • Phosphor — single-color CRT: one hue/act × 5 brightness steps, reverse-video chips, block-figlet logo, > objective, line-number gutter, !! BOSS !! + T-MINUS bar.
  • Synthwave — cyan→magenta gradient block logo, ▓▒░ dither banners, rounded box + purple line numbers, act-color block cursor, neon chip HUD, BOSS » chip + TIME bar.
  • Charm✧ nvim─quest ✧ wordmark, lowercase act i · … banners, objective, rounded act-colored borders, spaced pastel pills, pink cursor, encouraging copy.

Phasing (one commit each)

  1. Invisible Skin refactor + classicSkin (golden-diff proves zero visual change)
  2. Phosphor · 3. Synthwave · 4. Charm — each rendered against its mockup before commit.

Tests

Per-skin chrome tests assert the distinguishing glyphs ( gutter, ▓▒░, rounded , lowercase banner) appear under that theme and not under Classic; TestTitleReskinsAcrossThemes keeps the four titles distinct. Full suite green, go vet + gofmt clean.

Spec: docs/superpowers/specs/2026-07-04-theme-skins-full-fidelity-design.md

Summary by CodeRabbit

  • New Features

    • Added three fully themed visual styles: Classic, Phosphor, Synthwave, and Charm.
    • Theme changes now restyle the title screen, room header, HUD, cursor, and frame around content for a more distinctive look.
    • Updated the help text to clearly explain that themes preserve progress while changing the game’s appearance.
  • Bug Fixes

    • Improved theme rendering consistency across lesson and boss screens.
    • Refined the selected theme’s visual output to better match its intended style.

Introduce a Skin interface that owns the structural chrome — Logo, RoomHeader,
CursorCell, FrameBuffer, HUD — and route viewTitle/viewRoom through the active
skin. Renderers now assemble semantic content (roomHeader, buffer lines, HUD
segments) and let the skin draw it.

classicSkin reproduces the current look byte-for-byte (verified via a golden
render diff across title, room, room+hint, and boss+visual screens), and every
theme still falls back to it — so this commit changes nothing on screen. It's
the seam the phosphor/synthwave/charm skins plug into next.
phosphorSkin renders the Turn 2c mockup: one hue per act with five brightness
steps, hierarchy carried by luminance + reverse-video chips rather than a second
color. Reverse-chip act banner, block-figlet logo + SYS.READY bar, bright "> "
objective line, line-number gutter buffer, bg-block cursor, chip HUD, and for
bosses a "!! BOSS !!" chip + T-MINUS bar. Phosphor's palette dim shifts to green
so the color-only screens (map, stats) stay in-hue. Registered via skinRegistry;
Classic and the other themes are untouched.
synthwaveSkin renders the Turn 2a mockup: a cyan→magenta gradient block logo,
▓▒░ dither act banners, a rounded buffer box with purple line numbers, an
act-color block cursor, ▸▸ objective chevrons, neon background-chip HUD, and a
BOSS » chip + TIME bar. Adds a shared roundedBox helper (reused by charm next).
charmSkin renders the Turn 2b mockup: a ✧ nvim─quest ✧ wordmark, lowercase
"act i · the cursor dojo" banners, a ♥ objective line, rounded act-colored
buffer borders, spaced pastel pills for the HUD, a pink block cursor, and
encouraging copy ("(rude!)", "you've got this"). Reuses the shared roundedBox.
@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: 47 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: fa27160c-f539-45c5-ace8-ae67e92aa194

📥 Commits

Reviewing files that changed from the base of the PR and between bdd509f and 1ac96b2.

📒 Files selected for processing (5)
  • README.md
  • internal/ui/charm.go
  • internal/ui/charm_test.go
  • internal/ui/skin.go
  • internal/ui/synthwave.go
📝 Walkthrough

Walkthrough

This PR introduces a Skin interface and registry to abstract room chrome rendering, refactors viewRoom, buffer/selection rendering, HUD rendering, and viewTitle to route through activeSkin(), adds three new skins (Phosphor, Synthwave, Charm) with tests, adjusts Phosphor theme colors, and updates related docs.

Changes

Theme skin system

Layer / File(s) Summary
Skin interface, semantic model, and registry
internal/ui/skin.go
Defines Skin interface, roomHeader/segment/segRole types, roundedBox helper, skinRegistry, activeSkin() fallback logic, and default classicSkin implementation.
Room and title rendering wired to skins
internal/ui/room.go, internal/ui/title.go
Refactors viewRoom, buffer/selection helpers, renderHUD, and viewTitle to delegate rendering to activeSkin() instead of hardcoded lipgloss styles.
Phosphor skin and theme colors
internal/ui/phosphor.go, internal/ui/phosphor_test.go, internal/ui/styles.go
Adds CRT-style phosphorSkin (ramp colors, chips, timer bar), updates Phosphor theme's act-4 palette and Dim color, and adds chrome tests.
Synthwave skin and tests
internal/ui/synthwave.go, internal/ui/synthwave_test.go
Adds gradient-based synthwaveSkin with chip HUD/header rendering and a chrome test.
Charm skin and tests
internal/ui/charm.go, internal/ui/charm_test.go
Adds pastel pill-based charmSkin and a chrome test.
Documentation updates
README.md, docs/ARCHITECTURE.md, docs/superpowers/specs/2026-07-04-theme-skins-full-fidelity-design.md
Updates theme picker description, architecture package layout, and adds a new design spec document.

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

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant TitleView as viewTitle
  participant RoomView as viewRoom
  participant SkinRegistry as activeSkin()
  participant Skin as Active Skin

  User->>TitleView: select theme (Classic/Synthwave/Charm/Phosphor)
  TitleView->>SkinRegistry: activeSkin()
  SkinRegistry-->>TitleView: registered Skin or classicSkin fallback
  TitleView->>Skin: Logo(logoLines)
  Skin-->>TitleView: styled wordmark

  User->>RoomView: enter lesson/room
  RoomView->>SkinRegistry: activeSkin()
  SkinRegistry-->>RoomView: Skin
  RoomView->>Skin: RoomHeader(roomHeader)
  RoomView->>Skin: Body(act, text)
  RoomView->>Skin: FrameBuffer(act, bufferLines)
  RoomView->>Skin: HUD(act, segments)
  Skin-->>RoomView: rendered chrome strings
  RoomView-->>User: final rendered room view
Loading

Possibly related PRs

  • StrangeNoob/nvim-quest#2: Both PRs modify internal/ui/room.go's room-view rendering; the skin refactor must integrate with heart-message UI output added there.
  • StrangeNoob/nvim-quest#9: Both PRs change internal/ui/room.go's buffer/selection rendering path, one adding visual-mode highlighting and the other routing it through skin abstractions.
  • StrangeNoob/nvim-quest#11: Both PRs touch internal/ui/room.go's buffer/HUD rendering, with overlapping changes to visual-block highlighting and HUD indicators.
🚥 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 accurately summarizes the main change: adding full-fidelity skins for Phosphor, Synthwave, and Charm.
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-skins-full-fidelity

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 (8)
internal/ui/room.go (1)

243-258: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Skin lookup called repeatedly per render.

activeSkin() is invoked separately for RoomHeader, Body, and FrameBuffer (and again inside renderHUD). Caching it once (skin := activeSkin()) would avoid the repeated map lookups and slightly clean up the call sites, though the current cost is negligible given a tiny registry map.

🤖 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/room.go` around lines 243 - 258, The room rendering path is
calling activeSkin() multiple times per render, causing repeated lookup work and
noisier call sites. In the room.go rendering flow around roomHeader, cache the
result once (for example in the render method that builds the room output) and
reuse that same skin value for RoomHeader, Body, FrameBuffer, and the renderHUD
call instead of invoking activeSkin() repeatedly.
internal/ui/skin.go (3)

126-131: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

classicSkin.FrameBuffer mutates the input slice in place.

Unlike phosphorSkin.FrameBuffer (which allocates a new out slice), this mutates lines directly. Currently harmless since callers (bufferLines()) don't reuse the slice afterward, but it's a fragile contract for a shared interface method — a future caller that retains/reuses the buffer slice would see corrupted data.

♻️ Proposed fix
 func (classicSkin) FrameBuffer(_ int, lines []string) string {
-	for i, ln := range lines {
-		lines[i] = "  " + ln
-	}
-	return strings.Join(lines, "\n")
+	out := make([]string, len(lines))
+	for i, ln := range lines {
+		out[i] = "  " + ln
+	}
+	return strings.Join(out, "\n")
 }
🤖 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/skin.go` around lines 126 - 131, classicSkin.FrameBuffer
currently mutates the incoming lines slice in place, which makes this shared
interface method fragile compared with phosphorSkin.FrameBuffer. Update
FrameBuffer to build and return a new slice with the prefixed lines instead of
rewriting lines directly, so callers of bufferLines() or any future reuse of the
input slice won’t see unexpected modifications.

58-80: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

roundedBox can misalign the frame when content exceeds innerW.

pad is clamped to 0 when content is wider than innerW-1, but the content itself is never truncated, so the right border (bs.Right) will be pushed past its column for that row, breaking the box shape. Since buffer lines can be arbitrary code text, a long line will likely trigger this whenever the terminal/box width is narrower than the content.

Consider truncating (with an ellipsis) or wrapping content that exceeds innerW-1 before computing pad.

🤖 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/skin.go` around lines 58 - 80, The roundedBox helper is letting
overlong buffer content push the right border out of alignment because it only
clamps padding and never limits the rendered line width. Update roundedBox to
handle lines wider than innerW-1 by truncating with an ellipsis or wrapping
before computing pad, and keep the numbering path in sync so border placement
stays stable for synthwave/charm rendering.

105-120: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use fmt.Fprintf instead of WriteString(fmt.Sprintf(...)).

Static analysis flags this pattern at Line 112.

♻️ Proposed fix
 	if h.isBoss {
 		b.WriteString(dangerStyle.Render("⚔ BOSS: "+h.title) + "\n")
 		b.WriteString(dimStyle.Render(h.taunt) + "\n")
-		b.WriteString(fmt.Sprintf("%s %d:%02d · step %d/%d\n\n",
-			timerBar(h.timeLeft, h.timeTotal, 30),
-			h.timeLeft/60, h.timeLeft%60, h.step, h.steps))
+		fmt.Fprintf(&b, "%s %d:%02d · step %d/%d\n\n",
+			timerBar(h.timeLeft, h.timeTotal, 30),
+			h.timeLeft/60, h.timeLeft%60, h.step, h.steps)
🤖 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/skin.go` around lines 105 - 120, The RoomHeader method in
classicSkin is building strings with WriteString(fmt.Sprintf(...)), which static
analysis flags as unnecessary formatting allocation. Update RoomHeader to write
formatted content directly with fmt.Fprintf on the strings.Builder for each
interpolated line, keeping the existing output unchanged for both the boss and
non-boss branches.

Source: Linters/SAST tools

internal/ui/synthwave.go (1)

87-101: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

HUD doesn't differentiate roles beyond segMode.

segRole defines segRec, segPending, segHearts, segCombo, and segKeys (per internal/ui/skin.go), but synthwaveSkin.HUD only special-cases segMode; every other role renders with the identical swBoxBg/pal.Accent chip. This loses semantic distinction (e.g. recording vs. combo vs. hearts) that the other skins likely convey visually.

🤖 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/synthwave.go` around lines 87 - 101, The HUD rendering in
synthwaveSkin.HUD only distinguishes segMode, so all other segment roles are
drawn identically and lose their semantic meaning. Update the switch on s.role
in HUD to handle the additional segRole values from internal/ui/skin.go—segRec,
segPending, segHearts, segCombo, and segKeys—by choosing distinct chip
colors/styling for each, while keeping the existing fallback for unknown roles.
internal/ui/charm.go (2)

73-75: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

HUD mode label parsed from decorated display text.

segMode handling strips "-- "/" --" markers off s.text to recover the raw mode name. This silently degrades to the unstripped (and still-uppercase) string if the upstream format ever changes, coupling this skin's rendering to another layer's string formatting convention instead of a raw value carried on segment.

🤖 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/charm.go` around lines 73 - 75, The segMode branch in charm.go is
parsing the HUD label from decorated display text, which tightly couples
rendering to the upstream string format. Update the segment handling to use the
raw mode value carried on segment instead of stripping "-- " and " --" from
s.text, and keep charmPill fed with that canonical value from segMode so the
label stays correct even if the display decoration changes.

40-40: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Buffer/header width 72 duplicated as a magic literal.

Consider hoisting to a package-level const (e.g., charmBoxWidth = 72) to keep the two call sites in sync if the layout width ever changes.

Also applies to: 65-65

🤖 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/charm.go` at line 40, The fixed width value used in the Charm
header/buffer rendering is duplicated as a magic literal, so hoist it into a
package-level const and use that shared symbol in the relevant write/spacing
call sites in Charm rendering logic. Update the `spread(...)` usage in the
header builder and the other width-based call site to reference the same
constant so layout changes stay in sync.
internal/ui/charm_test.go (1)

8-27: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No coverage for boss-room chrome.

Only the non-boss RoomHeader branch is exercised. The boss branch (taunt line, charmTimeBar, "boss: " pill) is untested here, and it's exactly the path with the negative-filled panic risk flagged in charm.go. Consider adding a boss-lesson case asserting on "boss: ", "(rude!)", and the time string.

🤖 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/charm_test.go` around lines 8 - 27, Add test coverage in
TestCharmSkinChrome for the boss-room path in Charm skin, since only the regular
RoomHeader branch is currently verified. Exercise a boss lesson through
newTestModel/openLesson and assert the boss-specific chrome rendered by
activeSkin()/charmSkin, including the "boss: " pill, the taunt line like
"(rude!)", and the charmTimeBar time string. Keep the existing non-boss
assertions, but extend the test so the boss branch in charmSkin and its header
rendering are covered.
🤖 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 `@README.md`:
- Around line 84-87: Clarify the README wording around the theme picker so it
does not imply theme selection is untouched; update the copy in README.md near
the theme description to say only gameplay progress is untouched, while the
selected theme preference is still persisted. Keep the existing theme-related
description but narrow “your progress is untouched” to gameplay progress
(XP/stars/unlocks) so it matches the behavior exercised by themes_test.go and
the theme selection flow.

---

Nitpick comments:
In `@internal/ui/charm_test.go`:
- Around line 8-27: Add test coverage in TestCharmSkinChrome for the boss-room
path in Charm skin, since only the regular RoomHeader branch is currently
verified. Exercise a boss lesson through newTestModel/openLesson and assert the
boss-specific chrome rendered by activeSkin()/charmSkin, including the "boss: "
pill, the taunt line like "(rude!)", and the charmTimeBar time string. Keep the
existing non-boss assertions, but extend the test so the boss branch in
charmSkin and its header rendering are covered.

In `@internal/ui/charm.go`:
- Around line 73-75: The segMode branch in charm.go is parsing the HUD label
from decorated display text, which tightly couples rendering to the upstream
string format. Update the segment handling to use the raw mode value carried on
segment instead of stripping "-- " and " --" from s.text, and keep charmPill fed
with that canonical value from segMode so the label stays correct even if the
display decoration changes.
- Line 40: The fixed width value used in the Charm header/buffer rendering is
duplicated as a magic literal, so hoist it into a package-level const and use
that shared symbol in the relevant write/spacing call sites in Charm rendering
logic. Update the `spread(...)` usage in the header builder and the other
width-based call site to reference the same constant so layout changes stay in
sync.

In `@internal/ui/room.go`:
- Around line 243-258: The room rendering path is calling activeSkin() multiple
times per render, causing repeated lookup work and noisier call sites. In the
room.go rendering flow around roomHeader, cache the result once (for example in
the render method that builds the room output) and reuse that same skin value
for RoomHeader, Body, FrameBuffer, and the renderHUD call instead of invoking
activeSkin() repeatedly.

In `@internal/ui/skin.go`:
- Around line 126-131: classicSkin.FrameBuffer currently mutates the incoming
lines slice in place, which makes this shared interface method fragile compared
with phosphorSkin.FrameBuffer. Update FrameBuffer to build and return a new
slice with the prefixed lines instead of rewriting lines directly, so callers of
bufferLines() or any future reuse of the input slice won’t see unexpected
modifications.
- Around line 58-80: The roundedBox helper is letting overlong buffer content
push the right border out of alignment because it only clamps padding and never
limits the rendered line width. Update roundedBox to handle lines wider than
innerW-1 by truncating with an ellipsis or wrapping before computing pad, and
keep the numbering path in sync so border placement stays stable for
synthwave/charm rendering.
- Around line 105-120: The RoomHeader method in classicSkin is building strings
with WriteString(fmt.Sprintf(...)), which static analysis flags as unnecessary
formatting allocation. Update RoomHeader to write formatted content directly
with fmt.Fprintf on the strings.Builder for each interpolated line, keeping the
existing output unchanged for both the boss and non-boss branches.

In `@internal/ui/synthwave.go`:
- Around line 87-101: The HUD rendering in synthwaveSkin.HUD only distinguishes
segMode, so all other segment roles are drawn identically and lose their
semantic meaning. Update the switch on s.role in HUD to handle the additional
segRole values from internal/ui/skin.go—segRec, segPending, segHearts, segCombo,
and segKeys—by choosing distinct chip colors/styling for each, while keeping the
existing fallback for unknown roles.
🪄 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: 0155d665-e259-4717-97cd-929ef552814e

📥 Commits

Reviewing files that changed from the base of the PR and between a21cee6 and bdd509f.

📒 Files selected for processing (13)
  • README.md
  • docs/ARCHITECTURE.md
  • docs/superpowers/specs/2026-07-04-theme-skins-full-fidelity-design.md
  • internal/ui/charm.go
  • internal/ui/charm_test.go
  • internal/ui/phosphor.go
  • internal/ui/phosphor_test.go
  • internal/ui/room.go
  • internal/ui/skin.go
  • internal/ui/styles.go
  • internal/ui/synthwave.go
  • internal/ui/synthwave_test.go
  • internal/ui/title.go

Comment thread README.md
- roundedBox now grows to fit its widest line (capped at 76) so a long buffer
  line can never push the right border out of column (was clamped-pad only).
- Extract the shared roomInnerW=72 width const (was a magic literal in three
  spots across synthwave/charm).
- README: clarify only gameplay progress is untouched — the theme choice is
  saved.
- Add charm boss-room chrome coverage.

Skipped (with reason): WriteString→Fprintf style nits (matches the codebase's
existing pattern), per-render activeSkin() caching (a trivial map lookup), and
synthwave's single non-mode HUD role (intentional — one chip style for stats).
@StrangeNoob StrangeNoob merged commit e1e7132 into main Jul 3, 2026
2 checks passed
@StrangeNoob StrangeNoob deleted the feature/theme-skins-full-fidelity branch July 3, 2026 22:24
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