rename: implement advertised backward-compat + prune dead community plugins

Address gaps where PR #537's "Backward compatibility" section was advertised
but not implemented, and clean up the community plugin list.

Env vars (FEEDBACK_* canonical, legacy SLOPSMITH_* honoured):
- New lib/env_compat.py (getenv_compat / env_flag_compat) + tests. server.py
  (_env_flag + all FEEDBACK_* reads), diagnostics_hardware, gp2midi and
  tailwind_rebuild now resolve the legacy alias, so existing SLOPSMITH_UI /
  SLOPSMITH_PLUGINS_DIR / etc. deployments keep working.
- Fix the rename collapsing plugins/__init__.py and minigames/routes.py from
  `FEEDBACK_PLUGINS_DIR or SLOPSMITH_PLUGINS_DIR` into a redundant
  `FEEDBACK_ or FEEDBACK_` (the fallback was silently lost).

Storage (app.js update-channel):
- Read feedBack-update-channel, fall back to legacy slopsmith-update-channel,
  and clear the legacy key on write — so a user's update-channel preference
  survives the rename instead of resetting to "stable".

Community plugin list (README): the rename rewrote third-party repo URLs we
don't own. Probed every one; their owners never renamed, so:
- Restore the 13 live community plugins to their real slopsmith-* names.
- Prune 6 that are 404 to the public (topkoa splitscreen/stems, OmikronApex
  tuner, Jafz2001 nam-rig-builder, DeathlySin song-preview, Erikcb91 shuffle).
- Fix a pre-existing Guitar Theory clone-command typo (nam-tone -> guitar-theory).

Verified: env_compat 7/7, JS 819/819, pytest 1690 collected / 0 import errors,
rename-sensitive + startup suites green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
byrongamatos
2026-06-23 10:43:48 +02:00
co-authored by Claude Opus 4.8
parent 1cf3dcba67
commit 932c0b28ac
10 changed files with 126 additions and 35 deletions
+13 -19
View File
@@ -15,34 +15,28 @@
| [MIDI Amp Control](https://github.com/got-feedback/feedBack-plugin-midi) | Auto-switch amp/modeler presets via MIDI on tone changes | `git clone ...feedBack-plugin-midi.git midi_amp` | | [MIDI Amp Control](https://github.com/got-feedback/feedBack-plugin-midi) | Auto-switch amp/modeler presets via MIDI on tone changes | `git clone ...feedBack-plugin-midi.git midi_amp` |
| [Section Map](https://github.com/got-feedback/feedBack-plugin-sectionmap) | Color-coded song structure minimap with clickable navigation | `git clone ...feedBack-plugin-sectionmap.git section_map` | | [Section Map](https://github.com/got-feedback/feedBack-plugin-sectionmap) | Color-coded song structure minimap with clickable navigation | `git clone ...feedBack-plugin-sectionmap.git section_map` |
| [Arrangement Editor](https://github.com/got-feedback/feedBack-plugin-editor) | DAW-like visual editor for creating and editing song note charts | `git clone ...feedBack-plugin-editor.git editor` | | [Arrangement Editor](https://github.com/got-feedback/feedBack-plugin-editor) | DAW-like visual editor for creating and editing song note charts | `git clone ...feedBack-plugin-editor.git editor` |
| [MIDI Capo](https://github.com/masc0t/feedBack-plugin-midi-capo) | MIDI capo control for real-time transposition | `git clone ...feedBack-plugin-midi-capo.git midi_capo` | | [MIDI Capo](https://github.com/masc0t/slopsmith-plugin-midi-capo) | MIDI capo control for real-time transposition | `git clone ...slopsmith-plugin-midi-capo.git midi_capo` |
| [Note Detection](https://github.com/got-feedback/feedBack-plugin-notedetect) | Real-time pitch detection and scoring against highway notes | `git clone ...feedBack-plugin-notedetect.git note_detect` | | [Note Detection](https://github.com/got-feedback/feedBack-plugin-notedetect) | Real-time pitch detection and scoring against highway notes | `git clone ...feedBack-plugin-notedetect.git note_detect` |
| [Find More](https://github.com/masc0t/feedBack-plugin-find-more) | Search for more songs by the same artist | `git clone ...feedBack-plugin-find-more.git find_more` | | [Find More](https://github.com/masc0t/slopsmith-plugin-find-more) | Search for more songs by the same artist | `git clone ...slopsmith-plugin-find-more.git find_more` |
| [Piano Highway](https://github.com/got-feedback/feedBack-plugin-piano) | Scrolling piano/keyboard view for Keys arrangements with MIDI input | `git clone ...feedBack-plugin-piano.git piano` | | [Piano Highway](https://github.com/got-feedback/feedBack-plugin-piano) | Scrolling piano/keyboard view for Keys arrangements with MIDI input | `git clone ...feedBack-plugin-piano.git piano` |
| [Studio](https://github.com/got-feedback/feedBack-plugin-studio) | Collaborative band recording and multi-track mixing | `git clone ...feedBack-plugin-studio.git studio` | | [Studio](https://github.com/got-feedback/feedBack-plugin-studio) | Collaborative band recording and multi-track mixing | `git clone ...feedBack-plugin-studio.git studio` |
| [Drum Highway](https://github.com/got-feedback/feedBack-plugin-drums) | Lane-based drum highway with MIDI drum pad input and built-in sounds | `git clone ...feedBack-plugin-drums.git drums` | | [Drum Highway](https://github.com/got-feedback/feedBack-plugin-drums) | Lane-based drum highway with MIDI drum pad input and built-in sounds | `git clone ...feedBack-plugin-drums.git drums` |
| [Split Screen](https://github.com/topkoa/feedBack-plugin-splitscreen) | 2-4 highway panels side-by-side for multi-arrangement practice | `git clone ...feedBack-plugin-splitscreen.git splitscreen` | | [Invert Highway](https://github.com/masc0t/slopsmith-plugin-invert-highway) | Flip the highway note direction | `git clone ...slopsmith-plugin-invert-highway.git invert_highway` |
| [Stems Mixer](https://github.com/topkoa/feedBack-plugin-stems) | Per-stem mute/volume controls for .sloppak songs | `git clone ...feedBack-plugin-stems.git stems` | | [Jumping Tab](https://github.com/renanboni/slopsmith-plugin-jumpingtab) | Yousician-style 2D horizontal tab with trajectory arcs and hopping ball | `git clone ...slopsmith-plugin-jumpingtab.git jumpingtab` |
| [Invert Highway](https://github.com/masc0t/feedBack-plugin-invert-highway) | Flip the highway note direction | `git clone ...feedBack-plugin-invert-highway.git invert_highway` |
| [Jumping Tab](https://github.com/renanboni/feedBack-plugin-jumpingtab) | Yousician-style 2D horizontal tab with trajectory arcs and hopping ball | `git clone ...feedBack-plugin-jumpingtab.git jumpingtab` |
| [Step Mode](https://github.com/got-feedback/feedBack-plugin-stepmode) | Step-by-step practice mode — highway freezes at each note until played (via Note Detection) or Space | `git clone ...feedBack-plugin-stepmode.git step_mode` | | [Step Mode](https://github.com/got-feedback/feedBack-plugin-stepmode) | Step-by-step practice mode — highway freezes at each note until played (via Note Detection) or Space | `git clone ...feedBack-plugin-stepmode.git step_mode` |
| [Lyrics Sync](https://github.com/got-feedback/feedBack-plugin-lyrics-sync) | Generate synced LRC lyrics from text + vocals stem via Whisper alignment | `git clone ...feedBack-plugin-lyrics-sync.git lyrics_sync` | | [Lyrics Sync](https://github.com/got-feedback/feedBack-plugin-lyrics-sync) | Generate synced LRC lyrics from text + vocals stem via Whisper alignment | `git clone ...feedBack-plugin-lyrics-sync.git lyrics_sync` |
| [Lyrics Karaoke](https://github.com/got-feedback/feedBack-plugin-lyrics-karaoke) | Per-syllable karaoke pitch ribbon for sloppak songs (Whisper alignment + librosa pYIN) | `git clone ...feedBack-plugin-lyrics-karaoke.git lyrics_karaoke` | | [Lyrics Karaoke](https://github.com/got-feedback/feedBack-plugin-lyrics-karaoke) | Per-syllable karaoke pitch ribbon for sloppak songs (Whisper alignment + librosa pYIN) | `git clone ...feedBack-plugin-lyrics-karaoke.git lyrics_karaoke` |
| [NAM Tone Engine](https://github.com/got-feedback/feedBack-plugin-nam-tone) | In-browser amp modeling with NAM WASM, cabinet IRs, tone auto-switching | `git clone ...feedBack-plugin-nam-tone.git nam_tone` | | [NAM Tone Engine](https://github.com/got-feedback/feedBack-plugin-nam-tone) | In-browser amp modeling with NAM WASM, cabinet IRs, tone auto-switching | `git clone ...feedBack-plugin-nam-tone.git nam_tone` |
| [Guitar Theory Lab](https://github.com/topkoa/feedBack-plugin-guitar-theory) | Explore scales, chords, intervals, tunings, and voicings on a fully interactive fretboard | `git clone ...feedBack-plugin-nam-tone.git guitar-theory-lab` | | [Guitar Theory Lab](https://github.com/topkoa/slopsmith-plugin-guitar-theory) | Explore scales, chords, intervals, tunings, and voicings on a fully interactive fretboard | `git clone ...slopsmith-plugin-guitar-theory.git guitar-theory-lab` |
| [Themes](https://github.com/masc0t/feedBack-plugin-themes) | Offers several basic recolorings of the interface | `git clone ...feedBack-plugin-themes.git themes` | | [Themes](https://github.com/masc0t/slopsmith-plugin-themes) | Offers several basic recolorings of the interface | `git clone ...slopsmith-plugin-themes.git themes` |
| [Update Manager](https://github.com/masc0t/feedBack-update-manager) | Installs, updates, and uninstalls other plugins and the feedBack core itself | `git clone ...feedBack-update-manager.git update_manager` | | [Update Manager](https://github.com/masc0t/slopsmith-update-manager) | Installs, updates, and uninstalls other plugins and the feedBack core itself | `git clone ...slopsmith-update-manager.git update_manager` |
| [Tuner](https://github.com/OmikronApex/feedBack-plugin-tuner) | Floating tuner with customizable tunings | `git clone ...feedBack-plugin-tuner.git tuner` | | [Simplify Chords](https://github.com/bkranendonk/slopsmith-plugin-simplify-chords) | Changes complex chords on the note highway to simpler ones. Inspired by Ultimate Guitar's Simplify button. | `git clone ...slopsmith-plugin-simplify-chords.git simplify-chords` |
| [Simplify Chords](https://github.com/bkranendonk/feedBack-plugin-simplify-chords) | Changes complex chords on the note highway to simpler ones. Inspired by Ultimate Guitar's Simplify button. | `git clone ...feedBack-plugin-simplify-chords.git simplify-chords` | | [Key Bindings](https://github.com/jackipicco/slopsmith-plugin-key-bindings) | Highway key bindings for keyboard and TV remote | `git clone ...slopsmith-plugin-key-bindings.git key_bindings` |
| [Key Bindings](https://github.com/jackipicco/feedBack-plugin-key-bindings) | Highway key bindings for keyboard and TV remote | `git clone ...feedBack-plugin-key-bindings.git key_bindings` | | [Folder Organizer](https://github.com/Elit3d/slopsmith-plugin-folder-organizer) | Organize your sloppak DLC songs into a folder tree view, grouped by subfolder name | `git clone ...slopsmith-plugin-folder-organizer.git folder-organizer` |
| [Folder Organizer](https://github.com/Elit3d/feedBack-plugin-folder-organizer) | Organize your sloppak DLC songs into a folder tree view, grouped by subfolder name | `git clone ...feedBack-plugin-folder-organizer.git folder-organizer` |
| [Virtuoso](https://github.com/got-feedback/feedback-plugin-virtuoso) | Practice studio for guitar & bass — scale, technique, and rhythm drills, timed workouts, and jam backing that teach skills you take off the screen. | `git clone ...feedback-plugin-virtuoso.git virtuoso` | | [Virtuoso](https://github.com/got-feedback/feedback-plugin-virtuoso) | Practice studio for guitar & bass — scale, technique, and rhythm drills, timed workouts, and jam backing that teach skills you take off the screen. | `git clone ...feedback-plugin-virtuoso.git virtuoso` |
| [NAM Rig Builder](https://github.com/Jafz2001/feedBack-plugin-nam-rig-builder) | Map tones to chained NAM neural-amp rigs (tone3000 captures + IRs) — full pedal→amp→cab playback, per-stage bypass, and a gear catalog | `git clone ...feedBack-plugin-nam-rig-builder.git nam_rig_builder` | | [Audio Preview](https://github.com/saleemk/slopsmith-plugin-audio-preview) | Quick audio previews from library cards with configurable start time, volume, and duration | `git clone ...slopsmith-plugin-audio-preview.git audio_preview` |
| [Audio Preview](https://github.com/saleemk/feedBack-plugin-audio-preview) | Quick audio previews from library cards with configurable start time, volume, and duration | `git clone ...feedBack-plugin-audio-preview.git audio_preview` | | [Song Mastery](https://github.com/jamesgaiser/slopsmith-plugin-song-mastery) | Auto-adjusts difficulty based on your rolling note accuracy and saves the slider position per song | `git clone ...slopsmith-plugin-song-mastery.git song_mastery` |
| [Song Mastery](https://github.com/jamesgaiser/feedBack-plugin-song-mastery) | Auto-adjusts difficulty based on your rolling note accuracy and saves the slider position per song | `git clone ...feedBack-plugin-song-mastery.git song_mastery` | | [Mobile Note Highway](https://github.com/saleemk/slopsmith-plugin-mobile-note-highway) | Touch-optimized player with collapsible controls, highway gestures, and device-adaptive layouts for phones and tablets | `git clone ...slopsmith-plugin-mobile-note-highway.git mobile_note_highway` |
| [Song Preview](https://github.com/DeathlySin/feedBack-plugin-song-preview) | Quickly hear previews of songs in your library with a clean visual indicator of what's playing. Supports .sloppak and loose folders song formats, with the visual indicator matching up to whatever theme you are using! | `git clone ...feedBack-plugin-song-preview.git song_preview` |
| [Mobile Note Highway](https://github.com/saleemk/feedBack-plugin-mobile-note-highway) | Touch-optimized player with collapsible controls, highway gestures, and device-adaptive layouts for phones and tablets | `git clone ...feedBack-plugin-mobile-note-highway.git mobile_note_highway` |
| [Shuffle](https://github.com/Erikcb91/FeedBack-Shuffle-Mode) | Random playback from your library — artist & tuning filters, auto-advance with countdown popup, note_detect compatible | `git clone https://github.com/Erikcb91/FeedBack-Shuffle-Mode.git shuffle` |
Install any plugin by cloning it into your `plugins/` directory and restarting: Install any plugin by cloning it into your `plugins/` directory and restarting:
+3 -1
View File
@@ -16,6 +16,8 @@ import platform
import subprocess import subprocess
from pathlib import Path from pathlib import Path
from env_compat import getenv_compat
SCHEMA = "system.hardware.v1" SCHEMA = "system.hardware.v1"
@@ -41,7 +43,7 @@ def detect_runtime() -> dict:
nvidia-smi / psutil CPU probes. nvidia-smi / psutil CPU probes.
""" """
out: dict = {"kind": "bare", "in_docker": False, "in_kubernetes": False} out: dict = {"kind": "bare", "in_docker": False, "in_kubernetes": False}
env_runtime = os.environ.get("FEEDBACK_RUNTIME", "").strip().lower() env_runtime = (getenv_compat("FEEDBACK_RUNTIME", "") or "").strip().lower()
if env_runtime in ("electron", "docker", "bare"): if env_runtime in ("electron", "docker", "bare"):
out["kind"] = env_runtime out["kind"] = env_runtime
if Path("/.dockerenv").exists(): if Path("/.dockerenv").exists():
+38
View File
@@ -0,0 +1,38 @@
"""Backward-compatible environment lookup for the slopsmith -> feedBack rename.
Canonical configuration variables are now ``FEEDBACK_*``. Deployments that
predate the rename may still set the old ``SLOPSMITH_*`` names (docker-compose
overrides, shell profiles, CI), so we honour those as a fallback. New code
should always read the canonical ``FEEDBACK_*`` name and let this shim resolve
the legacy alias.
Flat-importable, no import-time IO or global state (constitution P-V).
"""
import os
_CANON_PREFIX = "FEEDBACK_"
_LEGACY_PREFIX = "SLOPSMITH_"
_TRUE_VALUES = {"1", "true", "yes", "on"}
def getenv_compat(name, default=None):
"""``os.environ.get`` with a legacy ``SLOPSMITH_*`` fallback.
For a canonical ``FEEDBACK_<X>`` name, returns the value of ``FEEDBACK_<X>``
if set, else ``SLOPSMITH_<X>`` if set, else ``default``. Names that do not
start with ``FEEDBACK_`` behave exactly like ``os.environ.get``.
"""
value = os.environ.get(name)
if value is not None:
return value
if name.startswith(_CANON_PREFIX):
legacy = os.environ.get(_LEGACY_PREFIX + name[len(_CANON_PREFIX):])
if legacy is not None:
return legacy
return default
def env_flag_compat(name):
"""Parse a conventional boolean env flag, honouring the legacy alias."""
return (getenv_compat(name, "") or "").strip().lower() in _TRUE_VALUES
+3 -1
View File
@@ -8,6 +8,8 @@ import sys
import tempfile import tempfile
from pathlib import Path from pathlib import Path
from env_compat import getenv_compat
log = logging.getLogger("feedBack.lib.gp2midi") log = logging.getLogger("feedBack.lib.gp2midi")
import guitarpro import guitarpro
@@ -156,7 +158,7 @@ def _find_soundfont() -> str | None:
2. Bundled ``<RESOURCESPATH>/soundfonts/*.sf2`` (Electron desktop builds) 2. Bundled ``<RESOURCESPATH>/soundfonts/*.sf2`` (Electron desktop builds)
3. Common system locations per OS. 3. Common system locations per OS.
""" """
override = os.environ.get("FEEDBACK_SOUNDFONT") override = getenv_compat("FEEDBACK_SOUNDFONT")
if override: if override:
if os.path.isfile(override): if os.path.isfile(override):
return override return override
+3 -1
View File
@@ -24,6 +24,8 @@ import tempfile
import threading import threading
from pathlib import Path from pathlib import Path
from env_compat import getenv_compat
log = logging.getLogger("feedBack.tailwind") log = logging.getLogger("feedBack.tailwind")
# Pin matches scripts/build-tailwind.sh and the Dockerfile build stage so every # Pin matches scripts/build-tailwind.sh and the Dockerfile build stage so every
@@ -45,7 +47,7 @@ APP_DIR = Path(__file__).resolve().parent.parent
def _user_plugins_dir() -> Path | None: def _user_plugins_dir() -> Path | None:
raw = os.environ.get("FEEDBACK_PLUGINS_DIR", "").strip() raw = (getenv_compat("FEEDBACK_PLUGINS_DIR", "") or "").strip()
if not raw: if not raw:
return None return None
p = Path(raw) p = Path(raw)
+1 -1
View File
@@ -1118,7 +1118,7 @@ def load_plugins(app: FastAPI, context: dict, progress_cb=None, route_setup_fn=N
# Collect plugin directories — user plugins first so they override built-in # Collect plugin directories — user plugins first so they override built-in
plugin_dirs = [] plugin_dirs = []
user_plugins_dir = os.environ.get("FEEDBACK_PLUGINS_DIR") or os.environ.get("FEEDBACK_PLUGINS_DIR") user_plugins_dir = os.environ.get("FEEDBACK_PLUGINS_DIR") or os.environ.get("SLOPSMITH_PLUGINS_DIR")
if user_plugins_dir: if user_plugins_dir:
user_path = Path(user_plugins_dir) user_path = Path(user_plugins_dir)
if user_path.is_dir() and user_path != PLUGINS_DIR: if user_path.is_dir() and user_path != PLUGINS_DIR:
+1 -1
View File
@@ -336,7 +336,7 @@ def setup(app, context):
# Duplicates are removed via a seen-set keyed on resolved paths. # Duplicates are removed via a seen-set keyed on resolved paths.
def _resolve_plugin_dirs(): def _resolve_plugin_dirs():
roots = [] roots = []
env_dir = os.environ.get("FEEDBACK_PLUGINS_DIR") or os.environ.get("FEEDBACK_PLUGINS_DIR") env_dir = os.environ.get("FEEDBACK_PLUGINS_DIR") or os.environ.get("SLOPSMITH_PLUGINS_DIR")
if env_dir: if env_dir:
roots.append(Path(env_dir)) roots.append(Path(env_dir))
# Built-in plugins/ next to server.py (one level above this file's # Built-in plugins/ next to server.py (one level above this file's
+10 -9
View File
@@ -15,6 +15,7 @@ from pathlib import Path
from typing import Any, ClassVar from typing import Any, ClassVar
from logging_setup import configure_logging from logging_setup import configure_logging
from env_compat import getenv_compat
configure_logging() configure_logging()
log = logging.getLogger("feedBack.server") log = logging.getLogger("feedBack.server")
@@ -230,7 +231,7 @@ _DEMO_BLOCKED: list[tuple[str, re.Pattern]] = [
@app.middleware("http") @app.middleware("http")
async def _demo_mode_guard(request: Request, call_next): async def _demo_mode_guard(request: Request, call_next):
if os.environ.get("FEEDBACK_DEMO_MODE") or os.environ.get("FEEDBACK_DEMO_MODE") == "1": if getenv_compat("FEEDBACK_DEMO_MODE") or getenv_compat("FEEDBACK_DEMO_MODE") == "1":
path = request.url.path path = request.url.path
for method, pattern in _DEMO_BLOCKED: for method, pattern in _DEMO_BLOCKED:
if request.method == method and pattern.match(path): if request.method == method and pattern.match(path):
@@ -275,8 +276,8 @@ SLOPPAK_CACHE_DIR = CONFIG_DIR / "sloppak_cache"
def _env_flag(name: str) -> bool: def _env_flag(name: str) -> bool:
"""Parse a conventional boolean env flag.""" """Parse a conventional boolean env flag (honours legacy SLOPSMITH_* alias)."""
return os.environ.get(name, "").strip().lower() in {"1", "true", "yes", "on"} return (getenv_compat(name, "") or "").strip().lower() in {"1", "true", "yes", "on"}
# Canonical Tuning-filter grouping key (feedBack#867). tuning_name collapses # Canonical Tuning-filter grouping key (feedBack#867). tuning_name collapses
@@ -3040,7 +3041,7 @@ def _make_scan_executor():
# A malformed override falls back to the core count rather than crashing. # A malformed override falls back to the core count rather than crashing.
try: try:
max_workers = int( max_workers = int(
os.environ.get("FEEDBACK_MAX_SCAN_WORKERS") getenv_compat("FEEDBACK_MAX_SCAN_WORKERS")
or os.environ.get("SCAN_MAX_WORKERS") or os.environ.get("SCAN_MAX_WORKERS")
or (os.cpu_count() or 1) or (os.cpu_count() or 1)
) )
@@ -3090,7 +3091,7 @@ def _get_progression_content() -> dict:
with _progression_content_lock: with _progression_content_lock:
if _progression_content is None: if _progression_content is None:
import progression as progression_mod import progression as progression_mod
root = os.environ.get("FEEDBACK_PROGRESSION_DATA") or ( root = getenv_compat("FEEDBACK_PROGRESSION_DATA") or (
_feedBack_server_root() / "data" / "progression" _feedBack_server_root() / "data" / "progression"
) )
content, warnings = progression_mod.load_content(root) content, warnings = progression_mod.load_content(root)
@@ -3488,7 +3489,7 @@ async def startup_events():
# Load plugins asynchronously so HTTP routes and the desktop window can # Load plugins asynchronously so HTTP routes and the desktop window can
# come up immediately while heavy plugin imports/install steps continue. # come up immediately while heavy plugin imports/install steps continue.
_sync_mode = os.environ.get("FEEDBACK_SYNC_STARTUP", "").lower() in {"1", "true", "yes", "on"} _sync_mode = getenv_compat("FEEDBACK_SYNC_STARTUP", "").lower() in {"1", "true", "yes", "on"}
def _load_plugins_background(): def _load_plugins_background():
try: try:
@@ -3723,7 +3724,7 @@ async def startup_events():
threading.Thread(target=_load_plugins_background, daemon=True).start() threading.Thread(target=_load_plugins_background, daemon=True).start()
global _DEMO_JANITOR_STARTED, _DEMO_JANITOR_THREAD global _DEMO_JANITOR_STARTED, _DEMO_JANITOR_THREAD
if os.environ.get("FEEDBACK_DEMO_MODE") or os.environ.get("FEEDBACK_DEMO_MODE") == "1" and not _DEMO_JANITOR_STARTED: if getenv_compat("FEEDBACK_DEMO_MODE") or getenv_compat("FEEDBACK_DEMO_MODE") == "1" and not _DEMO_JANITOR_STARTED:
_DEMO_JANITOR_STARTED = True _DEMO_JANITOR_STARTED = True
_DEMO_JANITOR_STOP.clear() _DEMO_JANITOR_STOP.clear()
def _janitor(): def _janitor():
@@ -6183,7 +6184,7 @@ def _diag_plugins_roots() -> list[Path]:
orphans in the external dir are reflected in the bundle. orphans in the external dir are reflected in the bundle.
""" """
roots: list[Path] = [] roots: list[Path] = []
user_dir = os.environ.get("FEEDBACK_PLUGINS_DIR", "").strip() user_dir = getenv_compat("FEEDBACK_PLUGINS_DIR", "").strip()
if user_dir: if user_dir:
p = Path(user_dir) p = Path(user_dir)
if p.is_dir(): if p.is_dir():
@@ -7683,7 +7684,7 @@ def index():
# UI remains fully available as a fallback — opt back in with # UI remains fully available as a fallback — opt back in with
# FEEDBACK_UI=v2 (or =legacy), or hit the dedicated /v2 route below (which # FEEDBACK_UI=v2 (or =legacy), or hit the dedicated /v2 route below (which
# serves it regardless of the env var). # serves it regardless of the env var).
if os.environ.get("FEEDBACK_UI") or os.environ.get("FEEDBACK_UI") in ("v2", "legacy"): if getenv_compat("FEEDBACK_UI") or getenv_compat("FEEDBACK_UI") in ("v2", "legacy"):
return FileResponse(str(STATIC_DIR / "index.html")) return FileResponse(str(STATIC_DIR / "index.html"))
return FileResponse(str(STATIC_DIR / "v3" / "index.html")) return FileResponse(str(STATIC_DIR / "v3" / "index.html"))
+4 -2
View File
@@ -3310,7 +3310,9 @@ function setupAppUpdates() {
// iframes, privacy modes, etc.); fall back to the default channel so the // iframes, privacy modes, etc.); fall back to the default channel so the
// panel still renders rather than aborting wiring entirely. // panel still renders rather than aborting wiring entirely.
let storedRaw = null; let storedRaw = null;
try { storedRaw = localStorage.getItem('feedBack-update-channel'); } catch (_) { /* fall through */ } // Read the canonical key, falling back to the pre-rename
// 'slopsmith-update-channel' so an existing channel preference survives.
try { storedRaw = localStorage.getItem('feedBack-update-channel') || localStorage.getItem('slopsmith-update-channel'); } catch (_) { /* fall through */ }
const stored = APP_UPDATE_CHANNELS.includes(storedRaw) ? storedRaw : 'stable'; const stored = APP_UPDATE_CHANNELS.includes(storedRaw) ? storedRaw : 'stable';
channelSelect.value = stored; channelSelect.value = stored;
@@ -3395,7 +3397,7 @@ function setupAppUpdates() {
channelSelect.addEventListener('change', async () => { channelSelect.addEventListener('change', async () => {
const val = channelSelect.value; const val = channelSelect.value;
if (!APP_UPDATE_CHANNELS.includes(val)) return; if (!APP_UPDATE_CHANNELS.includes(val)) return;
try { localStorage.setItem('feedBack-update-channel', val); } catch (_) {} try { localStorage.setItem('feedBack-update-channel', val); localStorage.removeItem('slopsmith-update-channel'); } catch (_) {}
try { try {
// Await setChannel so the status line reflects what actually // Await setChannel so the status line reflects what actually
// happened — rendering "Channel set" unconditionally would // happened — rendering "Channel set" unconditionally would
+50
View File
@@ -0,0 +1,50 @@
"""Coverage for the slopsmith -> feedBack env backward-compat shim."""
from env_compat import getenv_compat, env_flag_compat
def test_canonical_wins_over_legacy(monkeypatch):
monkeypatch.setenv("FEEDBACK_UI", "v3")
monkeypatch.setenv("SLOPSMITH_UI", "v2")
assert getenv_compat("FEEDBACK_UI") == "v3"
def test_legacy_fallback_when_canonical_unset(monkeypatch):
monkeypatch.delenv("FEEDBACK_UI", raising=False)
monkeypatch.setenv("SLOPSMITH_UI", "v2")
assert getenv_compat("FEEDBACK_UI") == "v2"
def test_default_when_neither_set(monkeypatch):
monkeypatch.delenv("FEEDBACK_UI", raising=False)
monkeypatch.delenv("SLOPSMITH_UI", raising=False)
assert getenv_compat("FEEDBACK_UI", "default") == "default"
assert getenv_compat("FEEDBACK_UI") is None
def test_non_feedback_names_have_no_fallback(monkeypatch):
monkeypatch.delenv("CONFIG_DIR", raising=False)
monkeypatch.setenv("SLOPSMITH_CONFIG_DIR", "/tmp/x")
# A non-FEEDBACK_ name must behave like os.environ.get (no aliasing).
assert getenv_compat("CONFIG_DIR") is None
def test_empty_canonical_is_respected_not_overridden(monkeypatch):
# An explicitly-empty canonical value is still "set" and wins.
monkeypatch.setenv("FEEDBACK_UI", "")
monkeypatch.setenv("SLOPSMITH_UI", "v2")
assert getenv_compat("FEEDBACK_UI") == ""
def test_flag_parses_true_values(monkeypatch):
for raw in ("1", "true", "YES", " On "):
monkeypatch.setenv("FEEDBACK_SYNC_STARTUP", raw)
assert env_flag_compat("FEEDBACK_SYNC_STARTUP") is True
def test_flag_false_and_legacy(monkeypatch):
monkeypatch.delenv("FEEDBACK_SYNC_STARTUP", raising=False)
monkeypatch.setenv("SLOPSMITH_SYNC_STARTUP", "1")
assert env_flag_compat("FEEDBACK_SYNC_STARTUP") is True
monkeypatch.setenv("FEEDBACK_SYNC_STARTUP", "0")
assert env_flag_compat("FEEDBACK_SYNC_STARTUP") is False