Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude/skills/deckyhub-registry/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ The curated list lives in two places that must stay byte-identical:

Edit the registry repo first, then sync the plugin repo. Never edit the plugin's copy by hand.

Local checkouts sit side by side: `../deckyhub-registry` next to this repo. Clone it with `gh repo clone mazillka/deckyhub-registry` if it's missing. Both repos follow [CONTRIBUTING.md](../../../CONTRIBUTING.md#releases): work on a short kebab-case branch, never commit to `main`, one topic per PR, squash-merge.
Local checkouts sit side by side: `../deckyhub-registry` next to this repo. Clone it with `gh repo clone mazillka/deckyhub-registry` if it's missing. Both repos follow [CONTRIBUTING.md](../../../CONTRIBUTING.md#releases): work on a short kebab-case branch, never commit to `main` (in this repo, branch from and target `develop`), one topic per PR, squash-merge.

## Adding, removing or editing an app

Expand Down Expand Up @@ -43,7 +43,7 @@ Local checkouts sit side by side: `../deckyhub-registry` next to this repo. Clon
python devtools/sync-registry.py --pr
```

It branches from `origin/main`, copies the merged registry into `registry/apps.json`, regenerates README's **Bundled repositories** table, bumps `package.json` to the next pre-release (`2.1.1-rc.N` -> `rc.N+1`; a stable `X.Y.Z` -> `X.Y.(Z+1)-rc.1`), runs the backend tests, commits, pushes and runs `gh pr create`, titled like `feat: add <App> to the registry (<version>)`. If nothing changed it says so and leaves no branch.
It branches from `origin/develop`, copies the merged registry into `registry/apps.json`, regenerates README's **Bundled repositories** table, bumps `package.json` to the next pre-release (`2.1.1-rc.N` -> `rc.N+1`; a stable `X.Y.Z` -> `X.Y.(Z+1)-rc.1`), runs the backend tests, commits, pushes and runs `gh pr create`, titled like `feat: add <App> to the registry (<version>)`. If nothing changed it says so and leaves no branch.

Without `--pr` it only rewrites the two files, for doing the rest by hand; pass a path to sync from an unmerged registry branch: `python devtools/sync-registry.py ../deckyhub-registry/registry/apps.json`.
5. Check the PR's CI and merge it.
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/update-dependencies/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Dependabot (`.github/dependabot.yml`) opens weekly PRs for the same three. When

## Steps

1. Branch from `main` (`chore-deps-<yyyy-mm-dd>`); never update on `main` or on a feature branch.
1. Branch from `origin/develop` (`chore-deps-<yyyy-mm-dd>`) and open the PR against `develop`; never update on `develop`, `main` or a feature branch.
2. Run the three checks above. If nothing is outdated, stop and say so - don't open an empty PR.
3. Update. Rules:
- `react` and `react-dom` in the mock must stay on the exact same version (Dependabot groups them for this reason).
Expand Down
2 changes: 1 addition & 1 deletion .githooks/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

branch=$(git branch --show-current)
case "$branch" in
main|staging)
main|develop|staging)
echo "Direct commits to $branch are blocked. Create a <topic> branch first." >&2
exit 1
;;
Expand Down
3 changes: 3 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ updates:
- package-ecosystem: npm
directory: /
schedule: { interval: weekly }
target-branch: develop
- package-ecosystem: npm
directory: /devtools/decky-mock
schedule: { interval: weekly }
target-branch: develop
groups:
# react and react-dom must stay on the exact same version.
react:
patterns: [react, react-dom]
- package-ecosystem: github-actions
directory: /
schedule: { interval: weekly }
target-branch: develop
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: CI

on:
push:
branches: [main]
branches: [main, develop]
pull_request:
workflow_dispatch:

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: CodeQL

on:
push:
branches: [main]
branches: [main, develop]
pull_request:
branches: [main]
branches: [main, develop]
schedule:
- cron: "17 4 * * 1"

Expand Down
14 changes: 7 additions & 7 deletions AGENTS.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ Users can point DeckyHub at their own registries too (**Repositories** → **Cus

## Releases

Create work branches as `<short-kebab-case-topic>`; add an ISO date suffix only when needed for uniqueness. Do not work directly on `main`.
`develop` is where all new work lands; `main` holds stable releases only. Create work branches from `origin/develop` as `<short-kebab-case-topic>` (add an ISO date suffix only when needed for uniqueness) and open their PRs against `develop`. Do not work directly on `develop` or `main`. A stable release is a PR from `develop` into `main`; pre-releases (`vX.Y.Z-rc.N`) are tagged on `develop`, stable releases (`vX.Y.Z`) on `main`.

Merge pull requests with **Squash and merge** so `main` keeps one commit per PR; keep each PR to a single topic and use its title as the commit message.
Merge feature pull requests into `develop` with **Squash and merge** so it keeps one commit per PR; keep each PR to a single topic and use its title as the commit message.

The repository's `.githooks/pre-commit` hook blocks commits on `main`. Activate it once per checkout with `git config core.hooksPath .githooks`; protected branches on GitHub should enforce the same rule remotely.
The repository's `.githooks/pre-commit` hook blocks commits on `develop` and `main`. Activate it once per checkout with `git config core.hooksPath .githooks`; protected branches on GitHub should enforce the same rule remotely.

To release, bump `version` in `package.json` (the backend reads it at runtime; there is no second copy), then push a `v*` Git tag. The tag starts the GitHub Actions release workflow. It fails unless the tag is `v` plus the `package.json` version. It installs the locked packages, typechecks, builds, runs the frontend and backend tests and the browser E2E suite in `devtools/decky-mock/`, and packages the Decky plugin with a read-only token; a separate publish job then attaches `DeckyHub-v*.zip` to the GitHub release. Release-notes conventions are in the Release section of [AGENTS.md](AGENTS.md).

Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ No setup, no configuration — these show up in Discover the moment you install
| Spindeck | Customization | [justinca92/spindeck](https://github.com/justinca92/spindeck) |
| Game Glance | Customization | [michpalm/game-glance](https://github.com/michpalm/game-glance) |
| Decky MultiFrameGen | Frame Generation | [MWGIH/DeckyMultiFrameGen](https://github.com/MWGIH/DeckyMultiFrameGen) |
| Finally Achievement Plugin | Customization | [otisabiburada/finally-achievement-plugin](https://github.com/otisabiburada/finally-achievement-plugin) |
| Finally Achievement Plugin | Customization | [otisabiburada/finally-decky-achievements](https://github.com/otisabiburada/finally-decky-achievements) |
| PS5 Dashboard | Customization | [kromossomom-debug/decky-ps5-dashboard](https://github.com/kromossomom-debug/decky-ps5-dashboard) |
| Xbox Dashboard | Customization | [kromossomom-debug/decky-xbox-dashboard](https://github.com/kromossomom-debug/decky-xbox-dashboard) |
| Loadout | Performance | [cyruzin/loadout](https://github.com/cyruzin/loadout) |

The list lives in [mazillka/deckyhub-registry](https://github.com/mazillka/deckyhub-registry) and refreshes when DeckyHub starts, so new apps show up without a DeckyHub update. Know a plugin that belongs here? Open an issue or pull request there.

Expand Down Expand Up @@ -101,7 +104,7 @@ Tap **Manage** on any card in Discover or Updates to:

- pick the release **channel** and a specific **version**. **Stable** shows only stable releases; **Pre-release** shows every release, newest first, so a stable release that comes out after the last pre-release is still offered;
- **Download ZIP**, open the **Release Page**, or **Check for Updates** for that app;
- for a Decky plugin, **Install** (not installed yet) or **Update** (the selected version is newer than the installed one). This hands the release ZIP to Decky Loader's own installer, the same one the Decky Store uses. Decky asks you to confirm and installs it, verifying the SHA-256 checksum GitHub publishes for the ZIP. Older releases (files uploaded before mid-2025) have no checksum and Decky installs them without that check;
- for a Decky plugin, **Install** (not installed yet) or **Update** (the selected version is newer than the installed one). DeckyHub downloads the release ZIP (with a progress window, then a spinner until Decky is ready) and hands it to Decky Loader's own installer, the same one the Decky Store uses. Decky asks you to confirm and installs it, verifying the SHA-256 checksum GitHub publishes for the ZIP. Older releases (files uploaded before mid-2025) have no checksum and Decky installs them without that check;
- for an installed Decky plugin, **Uninstall**, which asks you to confirm and then removes the plugin and its settings through Decky Loader.

### Downloads
Expand All @@ -120,9 +123,9 @@ Under **Custom Registries**, add someone else's app list by its GitHub `owner/re

### Keeping DeckyHub itself updated

The Quick Access Menu shows the installed version (**Current - vX.Y.Z**) and checks for a newer DeckyHub release in the background, with a notification the first time one appears; an **Update to vX.Y.Z** button then installs it straight away, like an app card's. **Manage** opens DeckyHub's update window, where you pick a channel (**Stable releases**, or **Pre-releases** for every release) and a version, then:
The Quick Access Menu shows the installed version (**DeckyHub vX.Y.Z**) and checks for a newer DeckyHub release in the background, with a notification the first time one appears; an **Update to vX.Y.Z** button then installs it straight away, like an app card's. **Manage** opens DeckyHub's update window, where you pick a channel (**Stable releases**, or **Pre-releases** for every release) and a version, then:

- **Update to / Reinstall / Downgrade to vX.Y.Z** — hands that release to Decky Loader's own installer (the same one the Decky Store uses), which downloads it, verifies its SHA-256 checksum, and installs it in place. Only releases with a published checksum are offered.
- **Update to / Reinstall / Downgrade to vX.Y.Z** — downloads that release (with a progress window) and hands it to Decky Loader's own installer (the same one the Decky Store uses), which verifies its SHA-256 checksum and installs it in place. Only releases with a published checksum are offered.
- **Download ZIP** — the classic path: saves the release ZIP to `/home/deck/Downloads/deckyhub`, which you then install yourself via Decky's settings → Developer → Install Plugin from ZIP.

If automatic update isn't available for some reason (e.g. running inside an older Decky Loader build), fall back to Download ZIP.
Expand Down
6 changes: 6 additions & 0 deletions SKILLS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

Use these project skills when working in this repository.

## Branches

- `develop` is the integration branch: branch every feature, fix and pre-release from `origin/develop` and open its PR against `develop`.
- `main` holds stable releases only. It changes only through a PR from `develop` when cutting a stable `vX.Y.Z`; never branch from it or target it with feature work.
- Pre-release tags (`vX.Y.Z-rc.N`) are cut from `develop`; stable tags from `main`. Details in [CONTRIBUTING.md](CONTRIBUTING.md#releases).

## Steam Deck UI

- Verify UI behavior in the mock and on real Steam Deck hardware before release. Controller-focus and modal rules are in [AGENTS.md](AGENTS.md#development-rules).
Expand Down
20 changes: 17 additions & 3 deletions devtools/decky-mock/dev-server.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ def _run_loop():
asyncio.run_coroutine_threadsafe(plugin._main(), loop).result()
print(f"[decky-mock] Plugin ready. Fake DECKY_HOME: {fake_decky.DECKY_HOME}")

# Installs first download their ZIP (Plugin.stage_install), but E2E release
# assets are made up, so check them the same way and hand back a path without
# the network; tests/test_plugin_status.py covers the real download.
async def _stage_install(asset, repo=None, job_id=None):
try:
return {"path": f"/deckyhub-installs/{Plugin._validate_download_asset(asset, repo)}"}
except ValueError as error:
return {"error": str(error)}


plugin.stage_install = _stage_install

# Fixed, never-completing job for the DevShell's "Download progress modal" preview button.
plugin.downloads["preview-job"] = {"state": "downloading", "filename": "Decky-Framegen.zip", "received": 60_100_000, "total": 199_600_000, "path": None, "error": None}

Expand All @@ -113,9 +125,11 @@ def call_route(route: str, args: list):
class Handler(BaseHTTPRequestHandler):
def _cors(self):
origin = self.headers.get("Origin")
if origin in ALLOWED_ORIGINS:
self.send_header("Access-Control-Allow-Origin", origin)
self.send_header("Vary", "Origin")
# Echo our own constant, never the request's value (CodeQL py/http-response-splitting).
for allowed in ALLOWED_ORIGINS:
if origin == allowed:
self.send_header("Access-Control-Allow-Origin", allowed)
self.send_header("Vary", "Origin")
self.send_header("Access-Control-Allow-Headers", "Content-Type")
self.send_header("Access-Control-Allow-Methods", "POST, OPTIONS")

Expand Down
Loading
Loading