Registry-only stores, a quieter window, and a CI that builds
ci/woodpecker/manual/woodpecker Pipeline was successful

A store no longer needs a repository of its own. The engine's built-in defaults
already cover the host-to-asset mapping, the install modes, the platforms and the
behaviour; what they cannot know is identity — a slug, a name and a catalog URL — and
that is exactly what a registry record carries. So `storeRepositoryUrl` is optional: a
record with a name and a catalog is a complete store, the id falls back from the
repository name to the catalog host (`teletypegames.org` becomes `teletypegames`) to the
display name, and the client writes a three-section config. Given a repository it still
reads it, and that file stays the authority on how the store behaves; a repository
without a config.json is treated as no repository at all.

Measured end to end against a local registry serving one record with a null repository:
the engine and the core downloaded, engine 1.1.0 accepted the written config, it listed
the same ten titles the configured store does, and a hosted title synced into a sandbox
with its menu entry written.

The "Install all" button is gone, and with it the string it used. Titles are installed
one at a time from their own cards.

No footer. The window carried a bar at the bottom at all times — a toggle and a line of
absolute paths — for something most sessions never need. The log is still there, folder
buttons included, behind a quiet switch at the bottom of the side menu; it takes no room
until it is opened, and an arriving line does not open it, because the store logs on
every refresh and a window that unfolds panels by itself is worse than one that keeps
quiet.

Three faults that every automated count had passed, found by photographing the setup
screen: the store badge rendered as an empty pill with no store open; the gate's picker
showed as an empty dropdown stub, because an explicit `display` beats the browser's own
`[hidden]` rule; and the gate went up while the empty-catalog line stayed on screen
underneath it. The last was a design fault — whether the gate is up was a call on a
view rather than state, so the two could disagree. The setup screen is now a field in
the state store, and that one field decides which of the gate and the grid is drawn.
The window test's gate assertion was wrong too: it demanded a store picker, which only
appears when the registry offers more than one store, so one store — the ordinary case —
failed it.

CI builds the packages this machine cannot. `.woodpecker.yaml` runs the checks on every
push and, on a tag or by hand, builds the Linux packages in
`electronuserland/builder:22` and the Windows ones in `:22-wine`, then attaches them to
the release with scripts/ci-upload.sh. The pipeline lives here rather than in the update
server's `/build/config` extension, which serves game-platform pipelines publishing into
the site's catalog — a different product with a different target. macOS stays a local
build: Apple's toolchain and its signing exist only on a Mac.

Both build steps verify what they produced, because a half-finished Wine build leaves a
162 KB stub named like the real installer and `ls` is happy with it. The Linux step was
rehearsed locally in the same image (AppImage 128 MB, deb 100 MB); the Wine step cannot
be rehearsed on Apple Silicon, where 16 KB host pages break Wine's 4 KB assumption, so
the runner is where it is proven. The size check was tested against both outcomes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-18 16:52:59 +02:00
co-authored by Claude Opus 5
parent 3d63c8a0b0
commit 526c67b069
29 changed files with 581 additions and 184 deletions
+66 -42
View File
@@ -1,42 +1,68 @@
# WarpEngine Store 1.3.0
# WarpEngine Store 1.4.0
**TypeScript, in layers.** The client was one `main.js`, one `preload.js`, three files
in `lib/` and one renderer script. It is now a typed application with the dependency
rule pointing inward: `domain` (models, ports, errors) knows nothing about Electron,
Node or Python; `application` orchestrates it through those ports; `infrastructure`
holds the adapters — the Python CLI, HTTP, the filesystem, Electron itself — and the
hosts (`main`, `preload`, `renderer`) sit on top. **[STRUCTURE.md](STRUCTURE.md)** is
the map: every layer, every pattern in use, and the naming rules, written to be read
before adding anything.
**A store no longer needs a repository of its own.** Until now every store in the
registry pointed at a repository holding its `config.json`, and the client read that
file to know what to install. It turns out almost nothing in there was necessary: the
store engine's built-in defaults already cover the host-to-asset mapping, the install
modes, the platforms and the behaviour. What defaults cannot know is *identity* — a
slug, a name and a catalog URL — and that is exactly what a registry record carries.
Nothing about the window changed. Same side menu, same categories, same switcher, same
two languages — this release is the inside of the app.
So `storeRepositoryUrl` is now optional. A record with a name and a catalog URL is a
complete store: the client derives the slug from the catalog host
(`teletypegames.org``teletypegames`), writes a small config and installs. Given a
repository it still reads it, and that file remains the authority on how the store
behaves — which platforms it offers, which statuses it shows, where things land. A
repository without a `config.json` is treated as no repository at all.
Two properties came out of the move, and both are worth having:
With the defaults, games land in a folder named after the store id and released,
archived **and demo** titles are listed: a catalog that publishes a demo means it to
be played.
- **The catalog can be driven with no window and no Electron at all.** `make smoke`
assembles the same services against the same ports in a plain Node process. It was a
script that reimplemented the bridge before; now it is a second composition root.
- **The window never receives a filesystem path.** A title crosses the bridge without
one, and launching is asked for *by name* — the main process resolves what that means
from the store's own state. Nothing in the renderer can be talked into opening a path.
The site's registry endpoint changed to match — `storeRepositoryUrl` answers `null`
when there is none — and adding a store is now genuinely one database row with two
fields filled in.
**A strict linter, and types everywhere.** `strict` plus
`noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, `noImplicitOverride`,
`noPropertyAccessFromIndexSignature` and friends; typescript-eslint's
`strictTypeChecked` and `stylisticTypeChecked` sets; explicit return types, parameter
types and property types required even where inference would manage; exhaustive
switches; no `any`, no `!`, no casts on foreign data — engine output and the registry
go through readers that turn `unknown` into typed values. The naming patterns are
enforced by `naming-convention` rather than trusted.
**The "Install all" button is gone.** Titles are installed one at a time from their
own cards.
Two things the types now catch that a person used to: a translation with a missing key
does not compile, and a channel the preload does not implement does not compile.
**No footer.** The window carried a bar at the bottom at all times — a toggle and a
line of absolute paths — for something most sessions never need. The log is still
there, with the two folder buttons in it, but it lives behind a quiet switch at the
bottom of the side menu and takes no room until it is opened. The grid gets the height
back.
**New make targets:** `make build`, `make typecheck`, `make lint`, `make lint-fix` and
`make check` — the gate, which runs the type-check, the linter and both test suites in
that order, cheapest failure first. Every script that runs the app builds first, so a
stale bundle cannot be tested.
**The repository is now `warp-engine-client`.** The app has always been called
WarpEngine Store; `warp-engine-desktop-gui` described the role rather than the
product, and the host-specific engines keep their own shape
(`warp-engine-desktop-store`, `-retroarch-store`, `-batocera-store`). Gitea keeps a
redirect from the old path, and the releases and tags moved with the repository, so
existing links and clones still resolve.
### Three things a screenshot found
Photographing the setup screen — which no automated count had ever looked at — turned
up three faults that every check had passed:
- the store badge in the bar rendered as an empty pill when no store was open;
- the gate's store picker showed as an empty dropdown stub, because an explicit
`display` in the stylesheet beats the browser's own `[hidden]` rule;
- the gate went up while the *"No installable titles in the catalog"* line stayed on
screen underneath it.
The last one was a design fault, not a typo: whether the gate is up was an imperative
call on a view rather than state, so the gate and the grid could disagree. The setup
screen is now a field in the state store, and that one field decides which of the two
is drawn. The window test's gate assertion was wrong too — it demanded a store picker,
which only appears when the registry offers more than one store, so a perfectly good
window failed it.
### Linux and Windows packages now come from CI
`.woodpecker.yaml` builds the AppImage, the deb, the NSIS installer and the portable
exe — Windows through Wine — and on a tag attaches them to this release. macOS stays a
local build, because Apple's toolchain and its signing exist only on a Mac, so a full
release is one local `make release` plus the pipeline. The Windows installer is not
signed: Windows will warn about an unknown publisher until there is a certificate.
### Opening it on macOS
@@ -46,18 +72,16 @@ Ad-hoc signed, **not notarised**, so macOS asks first:
xattr -dr com.apple.quarantine "/Applications/WarpEngine Store.app"
```
*Open Anyway* under **System Settings ▸ Privacy & Security** works as well.
### What is attached
**macOS arm64 only**, the machine this was built and verified on. Windows and Linux
packages need a build on those platforms (`make dist-win` / `make dist-linux`).
The macOS arm64 package, built and verified here, plus whatever the pipeline attaches
for Linux (AppImage, deb) and Windows (installer, portable).
### Verified
`make check` is clean: no type errors, no lint findings, the smoke test green against
the real store and against a sandbox one, and the window test green with one store and
with two — where it clicks the store that is not open and checks that the bar, the grid
and the categories follow. The window was photographed before and after the refactor
and the two are the same picture. The packaged app was run from the built bundle, not
from a dev launch.
A repository-less store was installed end to end against a local registry serving one
record with `storeRepositoryUrl: null`: the id came out as `teletypegames`, the engine
and the shared core downloaded, the written config had three sections, engine 1.1.0
accepted it, it listed the same ten titles the configured store does, and a hosted
title synced into a sandbox with its menu entry written. `make check` is clean, and
the setup gate was photographed on a machine with no store at all.