-
WarpEngine Client 2.5.1
Stablereleased this
2026-08-19 16:14:28 +00:00 | 0 commits to master since this releaseWarpEngine Client 2.5.1
The app has an icon. Until now every build shipped the default Electron one —
electron-buildersaid so on every run, in a line easy to read past: "default Electron
icon is used, reason=application icon is not set". A store you install games with
should not look like a framework demo in the Dock.The mark is a W with three lines running into it: the product's initial, and what it
is doing. It uses the window's own palette, so the icon and the application it opens are
the same object. It was drawn for the smallest size first — at 32px the W still reads
and the lines survive as motion rather than as noise. A portal, a play triangle and a
send arrow were each tried and each discarded: they already mean a loading spinner, a
media player and a submit button.resources/icon.svgis the source and the only file to edit.make iconsrenders the
.png, the.ico, the.icnsand the Linux size directory from it — three committed
binaries with no way to regenerate them is how an icon becomes something nobody dares
touch.The window also picks it up when run from source, where there is otherwise no icon to
carry and a dev run looks like a different application from the one being built.Nothing else changed: same store handling, same catalog, same sign-in.
Downloads
-
WarpEngine Client 2.5.0
Stablereleased this
2026-08-19 12:54:34 +00:00 | 1 commits to master since this releaseWarpEngine Client 2.5.0
A store can be taken off the machine again. Hovering a store in the side menu shows
a bin. It asks first, and says how many titles will go with it — because removing a
store uninstalls what it installed. That is not a convenience: a store'sstate.json
is the only record of which payloads, icons and menu entries belong to it, so leaving
the games behind would leave orphans nothing could ever identify, least of all a later
install of the same store into the same folder. The catalog cache, the settings and any
sign-in token go too.Adding one moved to a + beside Refresh, and it now takes a catalog address of your
own as well as the ones the registry lists. A bare host is enough —httpsis assumed —
and the name is taken from the address. Both are actions on the whole store rather than
on one of them, which is why they sit together; the full-width "Add a store…" button
under the list read as a third store.The picker has a way out. Opening it with a store already installed used to be a
trap: the grid was replaced and nothing short of installing something brought it back.Two things found by the new test.
make storetestadds and removes a store in a
sandbox, because removal is the only code here that deletes a directory tree and the
path it deletes is named by the window. It immediately caught thathttp://was
accepted and became a store called http — the trailing slashes were being stripped
before the scheme was checked — and thatSTORE_ROOTonly prepended to the search
path, so a "sandboxed" run still listed the real stores. With a delete button in the
window, a sandbox that can reach a working installation is not a sandbox; it replaces
the search path now.btn-secondaryhad no styling at all. It was introduced in 2.4.0 on the card's
sign-in button and on the sign-in panel, and rendered as a plain button in both places.Downloads
-
WarpEngine Client 2.4.0
Stablereleased this
2026-08-19 09:58:21 +00:00 | 3 commits to master since this releaseWarpEngine Client 2.4.0
The catalog can now say a title is not yours, and the client can do something about
it. Where a store sells things, a card shows the price and a Buy button that
opens the store's own page; where you own it, an Install as before. Two new
categories go with it — Owned and To buy — because owning something is not the
same as having installed it.Signing in, without a password ever reaching this window. The side menu grows an
Account block, and signing in shows a short code: your browser opens on the store's
own page and you type it there. That detour is the point — a desktop application asking
for a password is a desktop application people should not be giving one to. The token
lives in the OS keychain (Keychain, libsecret, DPAPI), one per store, and Sign out
revokes it at the server as well as forgetting it here.None of this is knowledge about any particular store. It all arrives from the
catalog's own server: WarpEngine 0.5 answersGET /api/servicewith what it offers, and
puts anaccessblock on every entry. A client that carried those facts would work for
exactly one shop — this one asks, which is why the same build serves any of them.A store with no sign-in shows none. Every WarpEngine before 0.5 has no descriptor at
all, and a 0.5 store that sells nothing reports none either. Both read as "a plain
catalog", which is what this client assumed for its whole life until now, and the window
behaves accordingly: no Account block, no prices, no new categories.A title nobody has bought is not dimmed. The dimming and the dashed badge belong to
what this machine cannot do — an unsupported platform, no build for this architecture
— and there is nothing wrong with the machine when a title simply costs money.A bearer token is never sent to a host that did not issue it. A gated download
answers with a redirect to signed storage, often somebody else's server, and some object
stores refuse a request outright when anAuthorizationheader rides along with the
signature. The credential stops at the origin it belongs to; a redirect back to the
catalog keeps it.Downloads
-
WarpEngine Client 2.3.0
Stablereleased this
2026-08-19 06:17:43 +00:00 | 5 commits to master since this releaseWarpEngine Client 2.3.0
A card says which version you have, and offers the newer one. Where the catalog has
moved on, an installed title's version line reads0.1 → 0.3instead of just the number
on your disk, so the card answers both questions a person came to it with: what is
installed, and is there anything better.Which version is installed was already recorded — that is what
state.jsonhas always
been for. What was missing was somewhere to act on it.The actions moved into a three-dot menu. Installed cards now lead with Play (or
Open for a hosted title) and put the rest behind the ⋮ button beside it:Upgrade fetches whatever the catalog now has, replacing the old payload and menu entry. Greyed out when there is nothing newer Uninstall as before — the payload, the icon and the menu entry, and nothing else Upgrade stays visible while disabled rather than appearing and disappearing: a menu whose
items come and go makes a person hunt for the one they used last time, and greyed out
already says "not now".Playing stays the headline action even when an upgrade is waiting. The version on the disk
still runs, and wanting to play it is not the same as wanting to wait for a download.Package names have no spaces.
WarpEngineClient-2.3.0-arm64.dmgrather than
WarpEngine Client-2.3.0-arm64.dmg, and the same for the AppImage and both Windows
builds — a space in a release asset is a space in everycurl, script and shell command
that ever touches it. The app is still called WarpEngine Client, which is what appears
in the Dock and in/Applications; only the file names changed.Also
The catalog refresh control's tooltip now says Refresh the catalog, because in Hungarian
it and the new Upgrade both wanted the word "Frissítés" and only one of them reloads a
list.Opening it on macOS
Ad-hoc signed, not notarised, so macOS asks first:
xattr -dr com.apple.quarantine "/Applications/WarpEngine Client.app"What is attached
The macOS package, built and verified on a Mac, plus the Linux (AppImage, deb) and
Windows (installer, portable) packages the pipeline builds when the tag is pushed.Verified
make checkis clean: typecheck, lint, the headless smoke test and the window self-test.The upgrade path was tested on a sandbox store rather than reasoned about. Two titles were
installed, then one of their state records was rewritten to claim an older build — which is
exactly what the engine compares — and the window was asked what it would offer:BombExpert [newer] Upgrade:on Uninstall:on Rabbit Roller [current] Upgrade:off Uninstall:onPressing Upgrade runs the same call the window makes, and the record went from
0.1to
0.2with the old payload removed first. The self-test now asserts that pairing on every
installed card, because a closed menu photographs identically whether or not its items are
right.The new package names were read off a real build (
WarpEngineClient-2.3.0-arm64.dmg,
…-arm64-mac.zip) and checked against the release script's own file filter, which finds
both. The Windows names are-Setup-and-Portable-so the two.exetargets cannot
resolve to one name and overwrite each other.Downloads
-
WarpEngine Client 2.2.0
Stablereleased this
2026-08-19 05:27:52 +00:00 | 6 commits to master since this releaseWarpEngine Client 2.2.0
A registry record is a name and a catalog. Nothing else. The
configfield added in
2.1.0 is gone, and so isstoreRepositoryUrl— along with the two store repositories they
pointed at, which have been deleted.2.1.0 had the registry say how each store behaves. That was the wrong shape. How a store
behaves is fixed per installed client: this application carries its own store engine and
is the only thing that can see the machine it is running on. A copy of that on a server
was a second authority over decisions this side had already made correctly — including
which directories the store is allowed to delete from — and a second authority is a way
for the two to disagree.Keeping two stores on one machine out of each other's files needs none of it. It is a
subfolder, and the client derives it:store id a slug of the catalog host — teletypegames.orgbecomesteletypegamesstore home <id>-desktop, under the usual store rootgames folder <id>, inside the OS's usual place for programsdelete boundary that same folder, and nothing outside it Derived from the catalog on purpose: the catalog is what a store is, so two records
naming the same one are the same store and land in the same place. Installing the same
store twice is idempotent rather than a way to orphan what is already there.Existing installations are untouched. A store already on the machine is recognised by
theconfig.jsonin its own home and keeps the identity written there — a store installed
asttgstaysttg, inttg-desktop, with its games where they are. Only a new install
derives its id from the catalog.Also
The public
/storespage loses its desktop card, which advertised acurl … | shfor a
repository that no longer exists; an ordinary computer is served by the app. The/desktop
URL now lands on the app rather than on a device tab, so what someone typing it wants is
what they get.Opening it on macOS
Ad-hoc signed, not notarised, so macOS asks first:
xattr -dr com.apple.quarantine "/Applications/WarpEngine Client.app"What is attached
The macOS package, built and verified on a Mac, plus the Linux (AppImage, deb) and
Windows (installer, portable) packages the pipeline builds when the tag is pushed.Verified
make checkis clean: typecheck, lint, the headless smoke test and the window self-test.The install path was measured end to end against a local registry serving exactly the new
record shape. The slug came outteletypegames, the hometeletypegames-desktop, the
games subfolderteletypegames, and installing the same record twice landed in the same
home. A record carryingconfigandstoreRepositoryUrl— what a stale registry or a
tampering renderer might still send — changed nothing on disk, because neither field exists
in the model any more.On this machine the check also shows both halves of the compatibility claim at once: the
registry's new slug readsteletypegames, while the store that was installed before today
is still found asttginttg-desktop, listing its ten installed titles from
Application Support/teletypegames.The site side was migrated and its specs re-run, and the frontend was built — which first
required removing a deadengineslist that had been failingvue-tscon master, so that
page could not be built to check at all.Downloads
-
WarpEngine Client 2.1.0
Stablereleased this
2026-08-19 04:43:39 +00:00 | 7 commits to master since this releaseWarpEngine Client 2.1.0
A store's configuration now comes from the registry itself.
GET /api/stores
records carry aconfigfield — the same thing a store'sconfig.jsonheld, moved into
the record that already says what the store is. The client applies it directly, so
installing a store no longer depends on a second repository existing and staying
reachable, and a store can be configured entirely from the site's admin.The old path still works. Where a record has no
configbut names a repository, the
client reads…/raw/branch/master/config.jsonfrom it exactly as before, so a registry
whose stores have not moved over is unaffected. With neither, the engine's defaults
carry the store, as they always have. The order is: registry config, then repository
file, then defaults.The window cannot supply a store config. It is handed stores to show and hands one
back to install, but only as an identity — the config is dropped on the way in, and the
main process reads the record again from the registry before installing anything. This
is not tidiness: a config decides where files are written and, through
paths.subfolder, which subtree the store may later delete from. That is not a decision
the renderer gets to make, for the same reason aGameDtocarries no paths.Also
The headless check now reports which source configured a store — registry, repository
or defaults — and names the resulting prune boundary, because "it installed" and "it
installed where I meant" are different claims.Opening it on macOS
Ad-hoc signed, not notarised, so macOS asks first:
xattr -dr com.apple.quarantine "/Applications/WarpEngine Client.app"What is attached
The macOS package, built and verified on a Mac, plus the Linux (AppImage, deb) and
Windows (installer, portable) packages the pipeline builds when the tag is pushed.Verified
make checkis clean: typecheck, lint, the headless smoke test and the window
self-test.All three configuration sources were exercised end to end against the real serializer
output, with the client's own classes and a sandbox store root:Registry record What configured the store Prune boundary written configpresentthe registry, with no extra request teletypegamesconfig: null, repository giventhe repository's config.jsonteletypegamesneither the engine's defaults teletypegamesThe renderer barrier was tested adversarially rather than assumed: an install started
from a record carryingpaths.subfolder: "ATTACKER"andpaths.install_root: "/tmp/pwned"wrotesubfolder: teletypegamesandinstall_root: nullto disk, because
the service re-read the registry and the mapper had already dropped the config.Downloads
-
WarpEngine Client 2.0.0
Stablereleased this
2026-08-18 21:36:35 +00:00 | 8 commits to master since this releaseWarpEngine Client 2.0.0
The store engine is part of the app. Nothing has to be installed on the machine any
more. Reading the catalog, choosing which release fits your computer, downloading and
unpacking it, writing the menu entry and remembering what went where all happen inside
the application now. There is no Python to find, no child process, and no JSON protocol
between the two halves — which is why this is a major version rather than a feature.What that changes for a person: on Windows and on a fresh Mac the app simply works.
Before it looked forpython3,pythonandpy -3, and where none answered it drew a
screen with a link to python.org instead of a catalog. That screen is gone, along with
the one that offered to refresh a store engine too old to drive.Your existing library is kept.
config.jsonandstate.jsonon disk are unchanged —
the same field names, the same<scope>:<name>keys, the same file modes — so a machine
whose games were installed by the shell store keeps them. Opening this version against
such a store lists them as installed, offers no needless update, and a sync reports
already up to date. Aversion: 1state file is still migrated on first read.The two Python files an earlier install left in the store folder are removed the next
time that store is set up. Nothing reads them, and a folder that still looks like it
holds the engine invites someone to run it against a state file this app is also writing.The client knows which WarpEngine served a catalog. Every WarpEngine API response
carries aWarpEngine-Versionheader, and the client now reads it, names the version in
the log, and picks the catalog dialect for it.SUPPORTED_WARP_ENGINE_VERSIONSlists what
this build was written against — 0.2, 0.3 and 0.4 — and the four cases are all handled:The header says What the client does a supported version reads the catalog with that version's dialect nothing at all reads it as the oldest supported version — an engine before 0.4.0 sent no header something older the same, and says so in the log something newer tries the newest dialect anyway, warning that titles may be missed Adding a version to that list fails the build until somebody says what it reads like, in
the type checker and in the linter both. A new engine version cannot arrive unnoticed.Refresh and the language picker are icons. They sit together at the foot of the side
menu, and the Actions heading that used to head a section of one button is gone. Both
carry their name as a tooltip and to a screen reader, and the language picker is still a
real<select>underneath — the native dropdown, keyboard and all, with only the glyph
showing.Also
No runtime dependencies, still: the zip reader the installer needs is about 150 lines over
node:zlibrather than a package. It restores the executable bit from each entry's
external attributes, which is what makes an unpacked game able to start at all, and it
refuses a zip64 archive, an unknown compression method or an entry that would be written
outside its destination rather than guessing.The repository itself is free of Python too — the Makefile, the CI check and the release
script readpackage.jsonand the forge's JSON with Node now.Opening it on macOS
Ad-hoc signed, not notarised, so macOS asks first:
xattr -dr com.apple.quarantine "/Applications/WarpEngine Client.app"What is attached
The macOS package, built and verified on a Mac, plus the Linux (AppImage, deb) and
Windows (installer, portable) packages the pipeline builds when the tag is pushed.Verified
make checkis clean: typecheck, lint, the headless smoke test and the window self-test.The new engine was measured against the old one rather than trusted. On the same catalog
and the same config, the Python engine and this one produce the same 13-title listing
with zero field differences and the same resolved paths. Installing three titles — a
bare TIC-80 binary wrapped in a bundle, a Godot.appsymlinked, and a hosted web entry —
gives byte-identical payloads, identical file modes and an identicalInfo.plist; the
only difference in the two trees is the sandbox path inside the generated launcher script.
state.jsonmatches record for record.The upgrade path was tested directly: pointed at a store home installed by the Python
engine, this one reports all three titles installed with no update available, and a
re-sync writes nothing. Remove, prune, prune-suppression on a named sync, and the v1→v2
state migration were each exercised. The zip reader was checked against Python's
extractallon an archive holding stored, deflated, directory and symlink entries —
identical bytes and identical modes — and its zip-slip and not-a-zip guards both fire.Downloads
-
WarpEngine Client 1.5.0
Stablereleased this
2026-08-18 17:45:54 +00:00 | 9 commits to master since this releaseWarpEngine Client 1.5.0
The app is called WarpEngine Client. It was WarpEngine Store, which named the thing
it opens rather than the thing you run — and the store is a catalog on a site, not a
window on your machine. The window title, the packages, the bundle and the menu entry all
say Client now; the repository already did. The store the window is driving is named in
the side menu, so the bar no longer repeats it as a badge.Every title in the catalog is listed, including the ones this machine cannot install.
A C64 cartridge on a desktop, or a game with no build for your operating system, used to
be silently absent — leaving you to wonder whether the catalog is small or your machine
is unusual. Those cards are now there, dimmed, with an unsupported platform or no
build for this machine badge and the engine's own sentence underneath, and with nothing
to press. They have a category of their own, Not for this machine, and they stay out of
the native/hosted counts: a title with no build has no mode to be counted under.This needs store engine 1.2.0 (desktop) on warpstore 1.4.0, which report what
they had to leave out and why. An older engine still works — its listing is simply all
installable, as it was before.A build can be pointed at another site's registry.
make dist STORES_API=https://games.example.org/api/storesThe address is written into the packaged app's own
package.json, so a client built for
somebody else's catalog needs no source change and nothing set on the user's machine. A
runtimeSTORES_APIstill wins, which is for trying something out rather than shipping.Also
The scrollbars are the window's own now: the platform's light track down the side menu of
a dark window looked like a mistake.Opening it on macOS
Ad-hoc signed, not notarised, so macOS asks first:
xattr -dr com.apple.quarantine "/Applications/WarpEngine Client.app"What is attached
The macOS package, built and verified on a Mac, plus the Linux (AppImage, deb) and
Windows (installer, portable) packages the pipeline builds when the tag is pushed.Verified
make checkis clean. The store on this machine lists 13 titles — ten installable, three
C64 cartridges this store does not carry — and the window was photographed with all
thirteen on screen, the three dimmed and labelled. The engine change was measured through
the CLI as well, in both its human and its--jsonlisting, and the other two store
engines still get the two-value answer they ask for.Downloads
-
WarpEngine Store 1.4.0
Stablereleased this
2026-08-18 15:24:42 +00:00 | 10 commits to master since this releaseWarpEngine Store 1.4.0
A store no longer needs a repository of its own. Until now every store in the
registry pointed at a repository holding itsconfig.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.So
storeRepositoryUrlis 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 aconfig.jsonis treated as no repository at all.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 site's registry endpoint changed to match —
storeRepositoryUrlanswersnull
when there is none — and adding a store is now genuinely one database row with two
fields filled in.The "Install all" button is gone. 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, 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.The repository is now
warp-engine-client. The app has always been called
WarpEngine Store;warp-engine-desktop-guidescribed 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
displayin 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
A
vX.Y.Ztag now starts the pipeline, which builds the AppImage, the deb, the NSIS
installer and the portable exe — Windows through Wine — creates this release and
attaches all four. macOS stays a local build, because Apple's toolchain and its signing
exist only on a Mac, somake releasefrom a Mac pushes that package onto the same
release afterwards. Publishing uses agitea_tokenrepository secret in Woodpecker.The Windows installer is not signed: Windows will warn about an unknown publisher until
there is a certificate.Opening it on macOS
Ad-hoc signed, not notarised, so macOS asks first:
xattr -dr com.apple.quarantine "/Applications/WarpEngine Store.app"What is attached
The macOS arm64 package, built and verified here, plus whatever the pipeline attaches
for Linux (AppImage, deb) and Windows (installer, portable).Verified
A repository-less store was installed end to end against a local registry serving one
record withstoreRepositoryUrl: null: the id came out asteletypegames, 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 checkis clean, and
the setup gate was photographed on a machine with no store at all.Downloads
-
WarpEngine Store 1.3.0 Stable
released this
2026-08-18 13:30:23 +00:00 | 14 commits to master since this releaseWarpEngine Store 1.3.0
TypeScript, in layers. The client was one
main.js, onepreload.js, three files
inlib/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;applicationorchestrates 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 is
the map: every layer, every pattern in use, and the naming rules, written to be read
before adding anything.Nothing about the window changed. Same side menu, same categories, same switcher, same
two languages — this release is the inside of the app.Two properties came out of the move, and both are worth having:
- 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.
A strict linter, and types everywhere.
strictplus
noUncheckedIndexedAccess,exactOptionalPropertyTypes,noImplicitOverride,
noPropertyAccessFromIndexSignatureand friends; typescript-eslint's
strictTypeCheckedandstylisticTypeCheckedsets; explicit return types, parameter
types and property types required even where inference would manage; exhaustive
switches; noany, no!, no casts on foreign data — engine output and the registry
go through readers that turnunknowninto typed values. The naming patterns are
enforced bynaming-conventionrather than trusted.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.New make targets:
make build,make typecheck,make lint,make lint-fixand
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.Opening it on macOS
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).Verified
make checkis 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.Downloads
- The catalog can be driven with no window and no Electron at all.