Registry-only stores, a quieter window, and a CI that builds
ci/woodpecker/manual/woodpecker Pipeline was successful
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:
+66
-42
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user