A store with paid titles had nothing to tell this client and no way for it to listen: the catalog carried no price, no entitlement and no sign-in, so a gated download could only come back 403 and leave the window guessing why. The knowledge belongs on the server, not here. This client serves whichever catalog a registry names, so anything it knew about a particular shop would be a rule that breaks every other one. WarpEngine 0.5 answers GET /api/service with what it offers and puts an `access` block on every entry; this reads both. There is no store name anywhere in the diff. - **0.5 is a dialect of its own**, the older shape with `access` added. The version list is exhaustive over the selector, so adding it was a compile error until somebody said what it reads like — which is what that switch is for. - **A card shows a price and a Buy button** when a title is not yours, opening the store's own page. Buying stays in a browser: a checkout rebuilt here would be a second place to get card handling wrong. - **Signing in is the device grant**: a short code, the person's own browser, and no password crossing this window. The token goes in the OS keychain through safeStorage — one per store — and where no keychain exists it is not stored at all rather than written out in the clear. - **Owned / To buy** join the categories, since owning something is not the same as having installed it. Three things worth stating about the shape: The bearer token stops at the origin that issued it. A gated download redirects to signed storage — often somebody else's host — and some object stores refuse a request outright when an Authorization header arrives alongside the signature. An absent access block is not "free". It is an engine too old to have an opinion, and only one of those two is a reason to offer somebody a sign-in, so the three states are kept apart all the way to the card. state.json does not carry entitlement. Whether somebody may download a title is the server's answer to a question asked now; a copy on disk would go stale on the next purchase or refund, and a stale yes is the dangerous direction. A store with no sign-in shows none, and every WarpEngine before 0.5 is such a store: no Account block, no prices, no new categories. The smoke test against the live catalog reports exactly that — `sign-in: not offered`, `access: open:13`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+28
-66
@@ -1,72 +1,34 @@
|
||||
# WarpEngine Client 2.3.0
|
||||
# WarpEngine Client 2.4.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 reads `0.1 → 0.3` instead 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.
|
||||
**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.
|
||||
|
||||
Which version is installed was already recorded — that is what `state.json` has always
|
||||
been for. What was missing was somewhere to act on 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.
|
||||
|
||||
**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:
|
||||
**None of this is knowledge about any particular store.** It all arrives from the
|
||||
catalog's own server: WarpEngine 0.5 answers `GET /api/service` with what it offers, and
|
||||
puts an `access` block 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.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **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 |
|
||||
**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.
|
||||
|
||||
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".
|
||||
**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.
|
||||
|
||||
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.dmg` rather 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 every `curl`, 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:
|
||||
|
||||
```sh
|
||||
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 check` is 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:on
|
||||
```
|
||||
|
||||
Pressing Upgrade runs the same call the window makes, and the record went from `0.1` to
|
||||
`0.2` with 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 `.exe` targets cannot
|
||||
resolve to one name and overwrite each other.
|
||||
**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 an `Authorization` header rides along with the
|
||||
signature. The credential stops at the origin it belongs to; a redirect back to the
|
||||
catalog keeps it.
|
||||
|
||||
Reference in New Issue
Block a user