From 7c2aff9741f88f4a8e5d35eba5b44203f3c68e80 Mon Sep 17 00:00:00 2001 From: David Pierron Date: Mon, 2 Feb 2026 15:33:41 +0100 Subject: [PATCH 01/18] feat: make LOCAL_GAMES_PATHS editable in Settings UI - Add editable input field for local games paths in Settings page - Store LOCAL_GAMES_PATHS in database (environment variables still take precedence for Docker users) - Update UI instructions to reflect direct editing capability - Add database schema documentation - Create CHANGELOG.md to track project changes This allows users to configure local game folders directly from the web interface without needing to edit .env files or set environment variables, making the application more user-friendly for non-Docker deployments. --- .gitignore | 3 +++ CHANGELOG.md | 21 +++++++++++++++++++ web/routes/settings.py | 16 ++++++++++---- web/templates/settings.html | 42 +++++++++++++++++-------------------- 4 files changed, 55 insertions(+), 27 deletions(-) create mode 100644 CHANGELOG.md diff --git a/.gitignore b/.gitignore index 6063ce7..809a4b2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ game_library.db +# Copilot documentation +.copilot-docs/ + # Docker data/ .empty/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8831d5a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,21 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added +- **Editable local games paths in Settings UI**: Users can now configure local game folder paths directly from the Settings page without needing to edit environment variables or .env files + +### Changed +- `LOCAL_GAMES_PATHS` setting is now editable through the web interface and stored in the database +- Settings template updated to show an input field for local games paths instead of read-only display +- Docker users can still use `LOCAL_GAMES_DIR_1`, `LOCAL_GAMES_DIR_2`, etc. environment variables (these take precedence) + +### Technical Details +- Modified `web/routes/settings.py` to handle saving and loading `LOCAL_GAMES_PATHS` from database +- Updated `web/templates/settings.html` to provide an editable text input for local games paths +- Added `.copilot-docs/` to `.gitignore` for development documentation diff --git a/web/routes/settings.py b/web/routes/settings.py index 49f92f2..f4e6226 100644 --- a/web/routes/settings.py +++ b/web/routes/settings.py @@ -26,7 +26,7 @@ def settings_page( from ..services.settings import ( get_setting, STEAM_ID, STEAM_API_KEY, IGDB_CLIENT_ID, IGDB_CLIENT_SECRET, ITCH_API_KEY, HUMBLE_SESSION_COOKIE, BATTLENET_SESSION_COOKIE, GOG_DB_PATH, - EA_BEARER_TOKEN, IGDB_MATCH_THRESHOLD + EA_BEARER_TOKEN, IGDB_MATCH_THRESHOLD, LOCAL_GAMES_PATHS ) from ..sources.local import discover_local_game_paths @@ -41,6 +41,12 @@ def settings_page( if host_path and container_path in discovered_paths: host_paths.append(host_path) + # Get local_games_paths from database/env (supports both Docker and local usage) + local_games_paths_value = get_setting(LOCAL_GAMES_PATHS, "") + if not local_games_paths_value and host_paths: + # Fallback to Docker mount points for display + local_games_paths_value = ",".join(host_paths) + settings = { "steam_id": get_setting(STEAM_ID, ""), "steam_api_key": get_setting(STEAM_API_KEY, ""), @@ -52,7 +58,7 @@ def settings_page( "battlenet_session_cookie": get_setting(BATTLENET_SESSION_COOKIE, ""), "gog_db_path": get_setting(GOG_DB_PATH, ""), "ea_bearer_token": get_setting(EA_BEARER_TOKEN, ""), - "local_games_paths": ",".join(host_paths) if host_paths else "", + "local_games_paths": local_games_paths_value, } success_flag = success == "1" @@ -84,16 +90,17 @@ def save_settings( battlenet_session_cookie: str = Form(default=""), gog_db_path: str = Form(default=""), ea_bearer_token: str = Form(default=""), + local_games_paths: str = Form(default=""), ): """Save settings from the form.""" # Import here to avoid circular imports from ..services.settings import ( set_setting, STEAM_ID, STEAM_API_KEY, IGDB_CLIENT_ID, IGDB_CLIENT_SECRET, ITCH_API_KEY, HUMBLE_SESSION_COOKIE, BATTLENET_SESSION_COOKIE, GOG_DB_PATH, - EA_BEARER_TOKEN, IGDB_MATCH_THRESHOLD + EA_BEARER_TOKEN, IGDB_MATCH_THRESHOLD, LOCAL_GAMES_PATHS ) - # Save all form values (LOCAL_GAMES_PATHS is read-only from .env) + # Save all form values set_setting(STEAM_ID, steam_id.strip()) set_setting(STEAM_API_KEY, steam_api_key.strip()) set_setting(IGDB_CLIENT_ID, igdb_client_id.strip()) @@ -104,5 +111,6 @@ def save_settings( set_setting(BATTLENET_SESSION_COOKIE, battlenet_session_cookie.strip()) set_setting(GOG_DB_PATH, gog_db_path.strip()) set_setting(EA_BEARER_TOKEN, ea_bearer_token.strip()) + set_setting(LOCAL_GAMES_PATHS, local_games_paths.strip()) return RedirectResponse(url="/settings?success=1", status_code=303) diff --git a/web/templates/settings.html b/web/templates/settings.html index 581ad23..e588aa2 100644 --- a/web/templates/settings.html +++ b/web/templates/settings.html @@ -1556,35 +1556,28 @@

Option 2: B
- - {% if settings.local_games_paths %} -
- {% for path in settings.local_games_paths.split(',') %} - {% if path.strip() %} -
- {{ path.strip() }} -
- {% endif %} - {% endfor %} -
- {% else %} -
- No local game paths configured -
- {% endif %} -
- Configure paths in your .env file using LOCAL_GAMES_DIR_1, LOCAL_GAMES_DIR_2, etc. + + +
+ Enter one or more folder paths separated by commas. Each subfolder will be treated as a game. +
+ Example: D:\Games,E:\Steam Library
-

Setup (in .env file):

-
LOCAL_GAMES_DIR_1=/path/to/games
-LOCAL_GAMES_DIR_2=/another/path/to/games
-

How it works:

+

How it works:

    -
  1. Set paths in your .env file and restart the container
  2. +
  3. Enter your game folder paths above (comma-separated)
  4. Each subfolder in those paths is treated as a game
  5. Games are matched to IGDB by folder name for metadata
  6. +
  7. Click "Save Settings" then sync via the "Sync Local" button

game.json override (optional):

{
@@ -1594,6 +1587,9 @@ 

Option 2: B

Place game.json inside any game folder to override the detected name or specify an IGDB ID.

+

+ Note for Docker users: You can still use LOCAL_GAMES_DIR_1, LOCAL_GAMES_DIR_2, etc. in your .env file. Environment variables take precedence. +

From af7172360b62249d09ebd0f65fd65b1580807680 Mon Sep 17 00:00:00 2001 From: David Pierron Date: Tue, 3 Feb 2026 15:25:02 +0100 Subject: [PATCH 02/18] feat(filters): add 18 predefined query filters with global scope - Add PREDEFINED_QUERIES with 18 filters across 4 categories - Implement global filter persistence via localStorage - Convert /random to full page with configurable game grid - Add comprehensive test suite (69 tests, 100% passing) - Optimize performance with indexes and query caching - Document complete filter system architecture - Remove 'apply globally' checkbox for simpler UX --- .github/prompts/opsx-apply.prompt.md | 149 +++ .github/prompts/opsx-archive.prompt.md | 154 ++++ .github/prompts/opsx-bulk-archive.prompt.md | 239 +++++ .github/prompts/opsx-continue.prompt.md | 111 +++ .github/prompts/opsx-explore.prompt.md | 171 ++++ .github/prompts/opsx-ff.prompt.md | 91 ++ .github/prompts/opsx-new.prompt.md | 66 ++ .github/prompts/opsx-onboard.prompt.md | 522 +++++++++++ .github/prompts/opsx-sync.prompt.md | 131 +++ .github/prompts/opsx-verify.prompt.md | 161 ++++ .github/skills/openspec-apply-change/SKILL.md | 156 ++++ .../skills/openspec-archive-change/SKILL.md | 114 +++ .../openspec-bulk-archive-change/SKILL.md | 246 +++++ .../skills/openspec-continue-change/SKILL.md | 118 +++ .github/skills/openspec-explore/SKILL.md | 290 ++++++ .github/skills/openspec-ff-change/SKILL.md | 101 +++ .github/skills/openspec-new-change/SKILL.md | 74 ++ .github/skills/openspec-onboard/SKILL.md | 529 +++++++++++ .github/skills/openspec-sync-specs/SKILL.md | 138 +++ .../skills/openspec-verify-change/SKILL.md | 168 ++++ CHANGELOG.md | 54 +- README.md | 15 + .../add-predefined-queries/.openspec.yaml | 2 + .../add-predefined-queries/PROGRESS.md | 325 +++++++ .../changes/add-predefined-queries/design.md | 337 +++++++ .../add-predefined-queries/proposal.md | 89 ++ .../specs/predefined-query-filters/spec.md | 317 +++++++ .../changes/add-predefined-queries/tasks.md | 114 +++ openspec/config.yaml | 68 ++ requirements.txt | 2 + run.cmd | 2 + tests/__init__.py | 1 + tests/test_empty_library.py | 187 ++++ tests/test_large_library_performance.py | 266 ++++++ tests/test_predefined_filters.py | 307 +++++++ tests/test_predefined_filters_integration.py | 416 +++++++++ tests/test_recently_updated_edge_case.py | 134 +++ web/database.py | 60 ++ web/main.py | 4 +- web/routes/collections.py | 94 +- web/routes/discover.py | 428 ++++++--- web/routes/library.py | 141 ++- web/routes/settings.py | 18 +- web/static/css/filters.css | 433 +++++++++ web/static/js/filters.js | 386 ++++++++ web/templates/_filter_bar.html | 189 ++++ web/templates/collection_detail.html | 24 +- web/templates/collections.html | 5 +- web/templates/discover.html | 23 +- web/templates/game_detail.html | 5 +- web/templates/hidden_games.html | 2 +- web/templates/index.html | 386 +++----- web/templates/random.html | 845 ++++++++++++++++++ web/templates/settings.html | 44 +- web/utils/filters.py | 85 ++ web/utils/helpers.py | 62 ++ 56 files changed, 9159 insertions(+), 440 deletions(-) create mode 100644 .github/prompts/opsx-apply.prompt.md create mode 100644 .github/prompts/opsx-archive.prompt.md create mode 100644 .github/prompts/opsx-bulk-archive.prompt.md create mode 100644 .github/prompts/opsx-continue.prompt.md create mode 100644 .github/prompts/opsx-explore.prompt.md create mode 100644 .github/prompts/opsx-ff.prompt.md create mode 100644 .github/prompts/opsx-new.prompt.md create mode 100644 .github/prompts/opsx-onboard.prompt.md create mode 100644 .github/prompts/opsx-sync.prompt.md create mode 100644 .github/prompts/opsx-verify.prompt.md create mode 100644 .github/skills/openspec-apply-change/SKILL.md create mode 100644 .github/skills/openspec-archive-change/SKILL.md create mode 100644 .github/skills/openspec-bulk-archive-change/SKILL.md create mode 100644 .github/skills/openspec-continue-change/SKILL.md create mode 100644 .github/skills/openspec-explore/SKILL.md create mode 100644 .github/skills/openspec-ff-change/SKILL.md create mode 100644 .github/skills/openspec-new-change/SKILL.md create mode 100644 .github/skills/openspec-onboard/SKILL.md create mode 100644 .github/skills/openspec-sync-specs/SKILL.md create mode 100644 .github/skills/openspec-verify-change/SKILL.md create mode 100644 openspec/changes/add-predefined-queries/.openspec.yaml create mode 100644 openspec/changes/add-predefined-queries/PROGRESS.md create mode 100644 openspec/changes/add-predefined-queries/design.md create mode 100644 openspec/changes/add-predefined-queries/proposal.md create mode 100644 openspec/changes/add-predefined-queries/specs/predefined-query-filters/spec.md create mode 100644 openspec/changes/add-predefined-queries/tasks.md create mode 100644 openspec/config.yaml create mode 100644 run.cmd create mode 100644 tests/__init__.py create mode 100644 tests/test_empty_library.py create mode 100644 tests/test_large_library_performance.py create mode 100644 tests/test_predefined_filters.py create mode 100644 tests/test_predefined_filters_integration.py create mode 100644 tests/test_recently_updated_edge_case.py create mode 100644 web/static/css/filters.css create mode 100644 web/static/js/filters.js create mode 100644 web/templates/_filter_bar.html create mode 100644 web/templates/random.html diff --git a/.github/prompts/opsx-apply.prompt.md b/.github/prompts/opsx-apply.prompt.md new file mode 100644 index 0000000..494e10e --- /dev/null +++ b/.github/prompts/opsx-apply.prompt.md @@ -0,0 +1,149 @@ +--- +description: Implement tasks from an OpenSpec change (Experimental) +--- + +Implement tasks from an OpenSpec change. + +**Input**: Optionally specify a change name (e.g., `/opsx:apply add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. + +**Steps** + +1. **Select the change** + + If a name is provided, use it. Otherwise: + - Infer from conversation context if the user mentioned a change + - Auto-select if only one active change exists + - If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select + + Always announce: "Using change: " and how to override (e.g., `/opsx:apply `). + +2. **Check status to understand the schema** + ```bash + openspec status --change "" --json + ``` + Parse the JSON to understand: + - `schemaName`: The workflow being used (e.g., "spec-driven") + - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) + +3. **Get apply instructions** + + ```bash + openspec instructions apply --change "" --json + ``` + + This returns: + - Context file paths (varies by schema) + - Progress (total, complete, remaining) + - Task list with status + - Dynamic instruction based on current state + + **Handle states:** + - If `state: "blocked"` (missing artifacts): show message, suggest using `/opsx:continue` + - If `state: "all_done"`: congratulate, suggest archive + - Otherwise: proceed to implementation + +4. **Read context files** + + Read the files listed in `contextFiles` from the apply instructions output. + The files depend on the schema being used: + - **spec-driven**: proposal, specs, design, tasks + - Other schemas: follow the contextFiles from CLI output + +5. **Show current progress** + + Display: + - Schema being used + - Progress: "N/M tasks complete" + - Remaining tasks overview + - Dynamic instruction from CLI + +6. **Implement tasks (loop until done or blocked)** + + For each pending task: + - Show which task is being worked on + - Make the code changes required + - Keep changes minimal and focused + - Mark task complete in the tasks file: `- [ ]` → `- [x]` + - Continue to next task + + **Pause if:** + - Task is unclear → ask for clarification + - Implementation reveals a design issue → suggest updating artifacts + - Error or blocker encountered → report and wait for guidance + - User interrupts + +7. **On completion or pause, show status** + + Display: + - Tasks completed this session + - Overall progress: "N/M tasks complete" + - If all done: suggest archive + - If paused: explain why and wait for guidance + +**Output During Implementation** + +``` +## Implementing: (schema: ) + +Working on task 3/7: +[...implementation happening...] +✓ Task complete + +Working on task 4/7: +[...implementation happening...] +✓ Task complete +``` + +**Output On Completion** + +``` +## Implementation Complete + +**Change:** +**Schema:** +**Progress:** 7/7 tasks complete ✓ + +### Completed This Session +- [x] Task 1 +- [x] Task 2 +... + +All tasks complete! You can archive this change with `/opsx:archive`. +``` + +**Output On Pause (Issue Encountered)** + +``` +## Implementation Paused + +**Change:** +**Schema:** +**Progress:** 4/7 tasks complete + +### Issue Encountered + + +**Options:** +1.