Commit Graph
357 Commits
Author SHA1 Message Date
mr.zeroandClaude Opus 5 92b4ae22b7 Két tengelyen kap vissza életet a felület: piktogram és szín
A facelift után a site helyenként túl csendes lett. Élet kerül vissza, de
nem dekorációként: két szabály mentén, amiket egymástól függetlenül lehet
olvasni.

A piktogram identitást jelöl — miféle dolog ez. Minden motor és platform
saját glifát kap (TIC-80, Ebitengine, LÖVE, Godot, Bevy, Phaser, C64), és
ugyanazt a jelet viszi a katalógusban, a részletlapon és a build-mátrix
motor-oszlopában, egyetlen PlatformIcon komponensből. A Contact IRC- és
BBS-csatornája is jelet kapott, mert a másik három brandnek eddig is volt,
és a hiány látszott.

A szín állapotot jelöl — mi van vele. A négy státuszból három kap hangot:
a megjelent az akcens zöldet, a demó egy kéket (új --c-info token, 6,7:1
fehéren és 7,8:1 feketén), a fejlesztés alatti a borostyánt. Az archivált
marad semleges, mert az a "már nem történik vele semmi" állapot.

A navigáció, a gombok és a szekciócímek továbbra sem kapnak ikont: ott a
szöveg megmondja, mi az.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 16:02:47 +02:00
mr.zeroandClaude Opus 5 02ffedbca2 Középre kerül, ami belépő pont, és sötéten indul a site
A facelift mindent balra igazított, pedig nem minden olvasnivaló. A szabály
mostantól: középre az kerül, ami tájékozódási pont és nem szöveg — az
oldalak hero-ja, a magányos állapotblokkok, és a lista végén álló egyetlen
vissza-link. Balra marad minden, amit olvasnak vagy pásztáznak: törzsszöveg,
listák, táblázatok, kártyák, vezérlők, lábléc.

A részletoldalak kivételek: a blogbejegyzés és a katalógus-részlet hero-ja
balra marad, mert ott a cím már a tartalom első sora, nem belépő pont.

Az alapértelmezett téma a sötét lett; süti nélkül az indul, és a világosat
az választja, aki kéri. Retro módban pedig eltűnik a téma-kapcsoló: ott a
retro palettája írja felül a tokeneket, a gomb nem csinált volna semmit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 13:02:28 +02:00
mr.zeroandClaude Opus 5 ebe3684d44 A kódbázis kommentek nélkül marad
ci/woodpecker/push/woodpecker Pipeline was successful
Kérésre: minden magyarázó komment kikerült a forrásfájlokból — 89 Ruby, 16
TypeScript, 14 Vue, plusz a CSS/JS/CJS. Nem soralapú kereséssel: a Ruby-t a
Ripper tokenizálta, a JS/TS/CSS-t állapotgép járta végig, hogy az URL-ekben,
reguláris kifejezésekben és heredocokban álló // és # jelek helyükön
maradjanak.

Három komment maradt, mert nélkülük nem indul a kód: az entrypoint.sh
shebangja, a vite-env.d.ts hármas perjeles referenciája, és a sanitize
teszt @vitest-environment direktívája (ez utóbbi a magyarázó része nélkül).

Egy helyen kódot is kellett írni: a CommandBlock másolás-hibaágán a komment
volt a catch egyetlen tartalma, és üres blokkot az eslint nem enged — a
copied jelző visszaállítása került a helyére.

A yaml, Dockerfile, Makefile, erb és markdown fájlokat nem érintettem.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 12:52:47 +02:00
mr.zeroandClaude Opus 5 e431e2b85b A site megtanul témát váltani, és leszokik az ikonokról
Nyolc akcentszín volt, oldalanként másik hero-gradienssel, tizenegy
blur-blobbal és 146 ikonhasználattal — ettől nézett ki réginek. Most egy
vizuális nyelv van: semleges zinc-skála, egyetlen akcens (a saját
foszforzöldünk), 1px keretek árnyék helyett, és ikon csak ott, ahol nincs
mellette szöveg.

A választható világos/sötét téma miatt minden szín tokenen keresztül megy.
A szerepeket a tokens.css írja le mindkét témára, a Tailwind szemantikus
színei (bg-surface, text-fg-muted, border-line, bg-accent) ezekre mutatnak,
és a <html data-theme> választ. A sütit az index.html inline scriptje
olvassa ki a bundle előtt, különben minden sötét témás betöltés villanna
egy világosat. A világos téma zöldje green-700: a green-600 fehéren csak
3,3:1, linkszínnek kevés.

A retro mód ebből lett a legtöbbet nyerő: mostantól ő is csak a tokeneket
írja felül, és azt szabályozza külön, ami formában más — betűtípus, kettős
keret, tui-sáv. 1153 sor és 535 !important helyett 300 sor. Menet közben
kiderült, hogy a fejléc- és menüstílusai régóta nem is fogtak: a
@apply óta nincsenek a DOM-ban azok a Tailwind-osztályok, amikre épültek.

A FontAwesome kikerült (258 KB webfont ~50 glifért), helyette Lucide, meg
tíz saját inline márkajel. A csapatképek WebP-ben: 1,6 MB helyett 30 KB
darabja. A TuiCSS és a retro.css már csak akkor töltődik be, ha valaki
bekapcsolja. A dist 8,2 MB-ról 784 KB-ra, a fő CSS 237 KB-ról 41 KB-ra.

Nem a faceliftből: a sanitize.test.ts jsdom alatt fut, és a jsdom
függőségfája ESM-only lett — Node 20-on a fájl el sem indult, a tesztjei
csendben kimaradtak, az npm run test meg hibakóddal lépett ki. jsdom 26
az utolsó, ami ezen a Node-on megy; a négy teszt így újra fut.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 12:24:42 +02:00
mr.zeroandClaude Opus 5 e040763454 The engines page leads with the three engines we lead with
Every engine-tagged wiki page landed on the page — the two store engines and
the shared warpstore core alongside the frameworks. The wiki now marks the
flagship three with a `highlighted` tag next to `engine`; the API already
carried tags through, so the list simply asks for that mark.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 11:13:01 +02:00
mr.zeroandClaude Opus 5 bcb95424c3 A store can be listed or not
Soft deletion already said "this store is gone". What was missing is "not yet" —
a catalog still being set up, or one pulled from the picker for a while without
losing the row and its history. `GET /api/stores` now answers with the active
ones only.

The client is deliberately told nothing about the flag. It has no state for
"there but switched off", and giving it one would mean every client release
having an opinion about it; an inactive store is simply absent, which is a case
the client already handles because it is the same as never having existed. The
payload stays two fields, and a spec holds it there.

Default true, so the migration lists every store that exists today. One that
silently emptied the registry would be a client with nothing to install from.
No index: a handful of rows, read once per client on first run.

In the admin the flag is what the page is *for*, so it is not just a checkbox on
the form: Active is the default scope, the index shows listed/hidden as a status
tag with a one-click toggle beside Edit, and the two batch actions do it in bulk.
A request spec covers all of it, because none of it is reachable from a model
spec — the pipelines resource shipped without `permit_params` and every edit
raised, which is the same layer and the same lesson. The last example toggles in
the admin and then reads /api/stores, since a change here that the registry does
not reflect is the only failure that actually matters.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 14:23:20 +02:00
mr.zeroandClaude Opus 5 23f4c43da8 Put the store where people are, not at the bottom of the catalog
The client was reachable from one grey link under the whole catalog listing and
from nowhere on the home page — which is a strange place for the thing that
actually gets our games onto somebody's machine.

**Catalog**: the Stores link moves into the hero, as a button in the shape the
Code and HowTo heroes already use (subtitle, breathing room, one white button).
Somebody who has come to look at games is one step from wanting to run one, and
that step should not be below the fold. The build matrix stays at the bottom,
which is where somebody who has already read the catalog goes looking for it.

**Home**: a section for the client under the featured game — the same dark band
as the YouTube and Spotify blocks below it, so the page keeps one rhythm rather
than growing a fifth nav card and breaking the four-column grid. Screenshot,
three things it does, download and a link to the full page. The screenshot leads
on a wide screen and follows on a narrow one: it is what makes the section
legible at a glance, but it is not what somebody reads first on a phone.

`btn-hero-purple` joins teal and indigo, and retro mode covers it — that block
lists the button classes by name, so a new one is invisible there until added.

Two things found on the way:

- the stores page said the client "needs Python 3". It has not since the engine
  moved into the app; the line now says nothing has to be installed first, which
  is the actual selling point.
- there was no test holding the two locale bundles together. A missing
  translation is not an error at runtime — vue-i18n renders the key itself, so
  `home.store.title` appears on screen looking like unfinished work, and only
  after switching language. The new spec names the missing key; I checked it
  fails by deleting one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 11:57:28 +02:00
mr.zeroandClaude Opus 5 c75c5acfe2 warp_engine 0.5.2 — 0.5.1's tag never produced a gem
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
The 0.5.1 tag build failed on the stale dummy schema, so nothing was published
under that number; the fix landed a commit later. Moving a tag would have been
the tidier answer — the gem contents are byte-identical, since spec/ is not in
`spec.files` — but deleting the remote tag is not something I can do here, and
burning a patch number is the conventional response to a burned tag anyway.

0.5.1 therefore exists in the history and in one lockfile and nowhere else.
0.5.2 is what ships.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
warp_engine-v0.5.2
2026-08-19 11:42:28 +02:00
mr.zeroandClaude Opus 5 9dadb276a5 The dummy schema still stamped the old migration version
ci/woodpecker/push/woodpecker Pipeline was canceled
CI builds the engine's test database from spec/dummy/db/schema.rb, and that file
still said `version: 2026_08_19_000001`. Loading it marks every migration up to
that number as applied — which no longer includes device_grants at
20260819093412 — so rspec aborted with a pending migration before running
anything, and both the push and the tag pipeline failed.

It passed on my machine because I had renumbered the row in the live test
database by hand and never re-dumped the schema. Verified this time the way CI
does it: dropped the database, `rake app:db:test:prepare` from schema.rb, 227
examples green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 11:39:45 +02:00
Zsolt Tasnadi 24d6add527 gemfile.lock upgrade 2026-08-19 11:36:43 +02:00
mr.zeroandClaude Opus 5 066451f31c warp_engine 0.5.1: the device_grants migration collided with the host's
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/tag/woodpecker Pipeline failed
`db:migrate` on the catalog API stopped before running anything:

    Multiple migrations have the version number 20260819000001.

The engine appends its `db/migrate` to the host's migration paths instead of
copying migrations in, so engine and host share one version namespace. Both had
picked 20260819000001 on the same day by the same habit — the engine for
device_grants, apps/api for carry_the_store_config_in_the_registry — and neither
repository could see the other's number.

Worse than a clash of our own making: it takes the *host's* migrations down with
it, for the whole application, before anything runs.

device_grants is renumbered to 20260819093412 — a real second-resolution
timestamp, which is the actual defence. A round hand-written number is precisely
what another repository lands on. Nothing had run it in production, so this is a
rename rather than a data migration; a host that already applied the old version
renumbers its schema_migrations row.

The older engine migrations keep their round numbers: renumbering one that has
been deployed everywhere is worse than the risk it carries. They are named in the
new spec's grandfather list rather than excused by a rule that would also let the
next one through.

That spec found a second thing, older than this change: the install template
creates `application_tokens`, and so does one of our own migrations — so a fresh
host runs CREATE TABLE twice and has to delete the block from its generated copy
by hand, which is exactly what teletype-orbit's migration header describes. I had
just made it worse by putting device_grants in the template too; that is out
again, and the template says why. `application_tokens` is grandfathered and left
for a change that is not a hotfix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
warp_engine-v0.5.1
2026-08-19 11:29:53 +02:00
mr.zeroandClaude Opus 5 65f337bcf4 README: the registry example named the wrong org
ci/woodpecker/push/woodpecker Pipeline was successful
`api/packages/tools/rubygems` was right until the org reshuffle moved the engine
to `engines`, and it has been copy-pasteable-but-broken since. The gemspec's
allowed_push_host and the CI's `gem push --host` both say `engines`; only the
README did not.

While there: pin the example to `~> 0.5` and say why the source is block-scoped.
A second *global* source leaves Bundler unable to say which gem came from where.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 11:20:53 +02:00
mr.zeroandClaude Opus 5 c1741f64f3 WarpEngine: let the host say who a browser is
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
The access policy asks who the caller is, and until now only a bearer token
could answer. That is what a desktop client carries — but a person clicking a
download link on the site carries a session instead, and the engine has no idea
what a session is. So a host that gated its catalog found its own signed-in
visitors refused at /api/download, which is a regression the shadowed route used
to hide.

c.subject_resolver is a callable taking the Rack request and returning the
host's subject: `->(request) { request.env["warden"]&.user }` for a Devise app.
Unset — every deployment today — a non-bearer request stays anonymous, exactly
as before. A resolver that raises is logged and treated as anonymous, because a
broken one turning every read into a 500 is worse than an anonymous request.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
warp_engine-v0.5.0
2026-08-19 10:36:30 +02:00
mr.zeroandClaude Opus 5 9f31c85256 WarpEngine 0.5.0: a catalog that can say a title is not yours
ci/woodpecker/push/woodpecker Pipeline was successful
A desktop client reading /api/software had no way to learn that a title costs
money. There was nothing in the response to say so, no way to sign in, and no
way to be told "you do not own this" — so a store with paid titles could only
hand the client a 403 at download time and let it guess why.

The fix belongs here rather than in the client. A client serves more than one
store, so anything it knows about a particular one has to arrive from that
store's own API; a rule compiled into the client is a rule that breaks every
other catalog it reads. Three seams, each following the storage adapter's
shape — documented contract, default that is byte for byte the old behaviour,
one config key to replace it:

- **access policy** — visible_software_scope / access_for / authorize_download.
  Every catalog entry now carries an `access` block (gated, entitled, price,
  purchaseUrl, webUrl) and both /api/download and /file/* ask before serving.
  The vocabulary is deliberately generic: a word from one host's domain would
  make every client that reads it specific to that host.
- **client sign-in** — the device authorization grant (RFC 8628), over the
  host's own user model. The approval page stays the host's, because approving
  needs a session and HTML. Tokens are ApplicationTokens with a `catalog`
  scope, so publishing and reading stay separable.
- **service descriptor** — GET /api/service says what this deployment is and
  whether it has a sign-in at all, which is how a client stops guessing.

With no policy and no subject class configured — every deployment today — the
API is unchanged: /api/auth/* answers 404, /api/service reports auth: null, and
the 187 pre-existing examples pass untouched.

A policy that raises is treated as a refusal, not permission. An artifact
served because the gatekeeper crashed is the one failure mode this must not
have, so a broken policy empties the catalog and denies the download.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 10:26:28 +02:00
mr.zero f8dee01c81 warpengine-client.png upgrade 2026-08-19 08:45:25 +02:00
mr.zeroandClaude Opus 5 021f2b9b07 A store record is a name and a catalog
Both extras go. `config` was added this morning on the idea that the registry should
say how each store behaves; that was wrong. The configuration is fixed per installed
client — the client carries its own store engine and knows its own machine — so a copy
here was a second authority over decisions the client had already made correctly,
including which directories it may delete from. Keeping two stores on one machine apart
is a subfolder, and the client derives that itself.

`store_repository_url` goes with it. The store engines it pointed at no longer exist, and
a URL nobody follows is a URL that goes stale.

The public stores page loses its desktop card for the same reason: it advertised a
`curl … | sh` for a repository that is gone, and an ordinary computer is served by the
app in the section above it. `/desktop` now lands on that app rather than on a device
tab, so the old URL still means what someone typing it wants.

Unrelated but in the way: the dead `engines` list in that page has been failing
`vue-tsc` on master, so the frontend could not be built to check any of this. Removed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 07:19:52 +02:00
mr.zeroandClaude Opus 5 9255a11254 Carry a store's configuration in the registry record
A store's `config.json` lived in a repository the desktop client fetched over HTTP,
which made a store's behaviour depend on a second thing existing and staying
reachable. The registry already answers what a store *is*; it now answers how it
behaves too, in the same shape that file had, so this record is the one source of
truth and a store can be configured from the admin alone.

`storeRepositoryUrl` stays, demoted to a pointer for a person — where the store's
own repository is, when it has one. Clients released before this field still fetch a
`config.json` from it, so nothing has to move at once.

The column is nullable because a store that configures nothing is still a store: the
client falls back to the engine's built-in defaults, which need only a name and a
catalog. The admin edits it as JSON text through a pair of accessors, so the column
holds real JSON and invalid input comes back with the text kept and a message rather
than a 500.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 06:43:09 +02:00
mr.zero 581d3c4371 new StoresIndexPage fix 2 2026-08-18 21:34:27 +02:00
mr.zero 7c0f1479b4 new StoresIndexPage fix 2026-08-18 21:33:08 +02:00
mr.zero a99834abf0 new StoresIndexPage 2026-08-18 21:25:40 +02:00
mr.zeroandClaude Opus 5 7ea8303b1a The pipeline admin form could never save
ci/woodpecker/push/woodpecker Pipeline was successful
`ActiveAdmin.register WarpEngine::Pipeline` declared no `permit_params`, so every edit
handed unpermitted attributes to the model and Rails raised ForbiddenAttributesError. That
is not new: the form has been unable to save for as long as it has existed. My flash
message on `update` sat at the top of the traceback and made it look like the cause, which
it was not — and it is gone anyway, because overriding an ActiveAdmin action to say
something is a poor trade for what it can break. The move is written to the log instead.

Adding a spec that would have caught it, in the host app, because that is where the
ActiveAdmin instance lives: it signs in, PUTs the form, and checks both that the record
saves and that the software link moves off the pipeline that had it. Driven the same way
by hand against the development database first — 302, the link moved, the previous holder
left without one.

The engine's other admin resources were checked for the same omission: downloads and
releases are read-only and the file manager posts to its own routes, so pipelines was the
only one affected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 20:41:16 +02:00
mr.zeroandClaude Opus 5 a0a7ff63e1 Do not let a response header take the API down
The version header was set from WarpEngine::VERSION_HEADER, a constant introduced in the
same commit. The deploy that followed ran these controllers with an older `lib/`, so the
before_action raised NameError on every request and every engine endpoint answered 500 —
the catalog, the images, the file server and the config extension Woodpecker calls, which
is how it surfaced: a pipeline could no longer fetch its own configuration.

The controller now spells the header name out. A response header is not worth a dependency
that can take the API down when one half of a deploy is older than the other, and the
constant remains the documented name with a spec holding the two in step.

Confirmed in production mode against the same code that failed: 200 with the header.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 20:20:39 +02:00
mr.zeroandClaude Opus 5 3d5ba3f31d A version header on every WarpEngine response, and a movable pipeline link
ci/woodpecker/push/woodpecker Pipeline was successful
Every WarpEngine API response now carries `WarpEngine-Version`, so a client can branch on
the engine's age without a round trip to ask. Set in a before_action rather than after:
`rescue_from` never reaches an after_action, and a client needs the version most when
something came back wrong. The name lives in `WarpEngine::VERSION_HEADER`. The host's own
endpoints — the store registry — do not carry it, because they are not the engine.

A software has one pipeline, and the newest assignment now wins. Two pipelines pointing at
the same software was not an error the database caught; it was a link that silently did
nothing, with the software still showing whichever row came first. Assigning a software
another pipeline holds therefore moves it, the admin says which pipeline it was taken
from, and `Pipeline#software_taken_from` carries that for anything else that cares.
Deliberately a callback and not a unique index: rows here are soft-deleted, and a unique
index counts deleted rows, so a pipeline removed last year would block its software from
ever being linked again.

The engine is 0.4.0. The site's /stores page and its screenshot follow the client's new
name, and the shot is a fresh one showing the greyed-out titles the client now lists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 19:46:29 +02:00
mr.zero bf10aa87e8 warpengine-client.png 2026-08-18 18:34:11 +02:00
mr.zero f8c9cfe996 new stores page 2026-08-18 18:12:46 +02:00
mr.zeroandClaude Opus 5 466aeac6ca A store registry record needs no repository
The client only ever took identity from a store repository — a slug, a name, a catalog —
and the store engine's own defaults cover everything else: the host-to-asset mapping, the
install modes, the platforms, the behaviour. So `store_repository_url` is now optional:
nullable in the schema, no presence validation, the format check only when a value is
given, and the serializer answers null rather than an empty string, because the client
branches on its absence.

Adding a store is therefore a row with two fields filled in. Given a repository the
client still reads its config.json, and that file remains the authority on how the store
behaves — the admin form and the endpoint's documentation say so.

The frontend's /stores page gains a section of its own for the graphical client on the
desktop tab: what it does, that it sets the store up itself, that it is the way in on
Windows where `curl … | sh` does not exist, and links to the releases, the repository and
the documentation — now under stores/warp-engine-client, which is where that repository
lives after the rename.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 16:53:33 +02:00
mr.zeroandClaude Opus 5 5517133e25 A store registry, so a client is not told which store to install
The desktop graphical client had our store's config URL compiled into it. That is
backwards: which stores exist for a catalog is something the site knows, and a
client should be able to ask. `GET /api/stores` answers, and the client picks from
what comes back.

A `Store` row is three fields — name, catalog_url, store_repository_url — with an
ActiveAdmin panel, a Blueprinter serializer in the catalog's camelCase, and a
service+controller pair following the events and members shape. `db/seeds.rb`
creates our own record, so a fresh database serves a working registry.

The endpoint is **public**, which is the point: the client runs on someone's
laptop before any store exists and has nobody to log in as. It exposes three URLs
that are public anyway.

This deliberately does not live in WarpEngine. The engine serves one catalog and
has no business knowing who ships stores for it; a registry of stores is a
property of this site, not of the catalog software. Anyone mounting WarpEngine can
keep their own list, or none.

16 model and controller examples pass. Worth noting for the next person who runs
them: the specs need RAILS_ENV=test, as the README says — without it rspec runs in
the development environment, host authorization rejects Rack::Test's hostname, and
every request spec fails with a 403 and an HTML body that looks nothing like a
routing problem.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 13:12:01 +02:00
mr.zero 4dccb9a815 Store resource 2026-08-18 13:04:21 +02:00
mr.zeroandClaude Opus 5 c85148d238 Offer the graphical client on the desktop tab
The desktop store now has a window, and it is the only one of the three that
does — so the download link and the sentence explaining it appear on that tab
alone. On Windows this is the way in, since the shell installer needs a shell
the platform does not have.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 11:21:56 +02:00
mr.zeroandClaude Opus 5 201e422be6 Put this computer first on the stores page
The page had two device tabs, both for hardware most visitors do not own. The
desktop store makes the third one the likeliest answer, so "This computer" is
added and selected by default, and /desktop redirects to it the way /batocera and
/retroarch already do.

Device names now come from i18n rather than the component: "Batocera" and
"RetroArch" are product names either way, but "This computer" has to be
translatable. Every key the page uses was checked to exist in both locales.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 10:00:39 +02:00
mr.zeroandClaude Opus 5 3eedabce43 One /stores page for both stores, with a device chooser
The Batocera store had /batocera to itself. A RetroArch store now serves the same
cartridges on every other machine, and copying the page would have duplicated
everything the two have in common — the framing, the platform table, the engine
links — so they share one page and a Batocera/RetroArch chooser. Only the install
command, the CLI and the uninstall line change with the tab.

/batocera and /retroarch both redirect here with the matching tab preselected,
so old links keep working and the name someone guesses after reading "RetroArch
store" lands somewhere useful. `?device=` makes a link to either half shareable.

The page also documents uninstalling, which it never did, and the five command
blocks share one CommandBlock component instead of repeating the copy button.
The i18n `batocera` block becomes `stores` in both locales; every key the page
uses was checked to exist in both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 08:33:19 +02:00
mr.zero f1536d8b39 woodpecker upgrade 2026-08-17 08:08:17 +02:00
mr.zero ce904489d4 remove ▶ chars 2026-08-17 08:00:46 +02:00
mr.zeroandClaude Opus 5 22ee724c09 Run the engine specs before mirroring and publishing
The engine's 180 specs were never run by CI: the mirror repo is
generated by a subtree split, so a pipeline committed there would be
overwritten on the next sync, and the monorepo's own pipeline only
mirrored and published. A broken engine could reach the gem registry.

The test now gates both — it runs on the same trigger the mirror does
(a change under libs/ruby/warp_engine), and the steps after it only run
if it passes. The specs need MySQL, hence the service, and a prepared
test database: on a fresh database maintain_test_schema! reports the
engine's own migrations as pending instead of loading the schema.

Verified by running the step as written in a clean ruby:3.2 container
against a fresh mysql:8 — 180 examples, 0 failures.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 07:07:56 +02:00
mr.zeroandClaude Opus 5 6b24707c8d Build bundled Phaser projects with their own toolchain
ci/woodpecker/push/woodpecker Pipeline was successful
The step assumed one project shape: plain sources concatenated with cat
and Phaser pulled from a CDN. trickster-tiles is the other shape — Vite
plus TypeScript, built with `tsc && vite build` — so `cat src/*.js`
found nothing to bundle and the build failed. The earlier guard on the
syntax check fixed only the first symptom of that mismatch.

A project with a build script now runs npm ci && npm run build and is
packaged from its own dist/, which already contains index.html and the
hashed assets. Plain projects keep the existing path untouched.

The bundler's base has to be relative, since games are served out of
/file/<name>-<version>/; the step fails loudly if the build leaves no
dist/index.html rather than shipping an empty zip.

Verified in the phaser-builder image against trickster-tiles: a 344 kB
package with index.html at the root and ./assets/ references.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 23:12:12 +02:00
mr.zeroandClaude Opus 5 71a5c15b4c Serve the builder images from the build org
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
The org reorganization moved the toolchain repos to build/ but left
their container images in internal/: a package namespace does not
travel with the repo and gets no redirect, which is the only reason
the internal org was still alive.

All eight images now live under build/ — the six unchanged ones copied
layer-for-layer, the Ebitengine and Bevy ones rebuilt for the ARM
target. The internal org can be emptied.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
warp_engine-v0.3.0
2026-08-16 20:57:37 +02:00
mr.zeroandClaude Opus 5 e8c3e2f792 Add linux_arm64 builds for Ebitengine and Bevy
Batocera and the ES-family distributions run on ARM as much as on
x86_64 — Raspberry Pi, Odroid, the retro handhelds — and a linux_x64
binary installs there but will not start. There was no Linux ARM asset
kind at all: KINDS had linux_x86 and linux_x64 and mac_arm64, but
nothing for 64-bit ARM Linux.

Registering the kind is deliberately separate from producing it: a
platform service only includes BuildLinuxArm64 once its pipeline builds
the artifact, otherwise /api/builds would report it missing for every
release. Hence Ebitengine and Bevy only. Godot needs a Linux arm64
export preset in each game repo first; LÖVE fuses an upstream AppImage
that ships x86_64 only; TIC-80's export command has no ARM target.

Ebitengine needs cgo on Linux, so binary_build gained a cross-compiler
argument — and unsets CC for native targets, otherwise a build after
the ARM one silently picks up the cross gcc.

Verified by cross-compiling both demos in the rebuilt images: each
produced a genuine AArch64 ELF (e_machine 183), not a silent fallback.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 20:57:23 +02:00
mr.zero 87e23545d1 phaser pipeline fix
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-16 16:45:16 +02:00
mr.zeroandClaude Opus 5 2b62070557 Give gem push a named credentials key
ci/woodpecker/tag/woodpecker Pipeline was successful
The push prompted for credentials and then died with "404 page not
found". Both are the same fault: RubyGems normalizes the keys in
~/.gem/credentials — dots become __ and a trailing slash is appended —
so a key written as the host URL can never match the --host value.
Finding no key, gem push falls back to signing in against the RubyGems
sign_in endpoint, which Gitea does not implement; that is the 404.

A named key is stored verbatim and is matched by --key, so the lookup
succeeds. Verified against Gem::ConfigFile with both formats.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
warp_engine-v0.2.0
2026-08-16 11:11:17 +02:00
mr.zeroandClaude Opus 5 d11610b6c7 Point the release pipeline at the engines org
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline failed
The publishing pipeline was already complete — subtree split to the
mirror, gem build and push on a warp_engine-v* tag — but it had never
been triggered, and after the org reorganization three of its targets
were stale: the mirror push URL and the rubygems registry namespace
(twice).

The gemspec's allowed_push_host has to match the --host that `gem push`
receives, so it now carries the full registry URL rather than the bare
forge host, plus source and documentation links.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 10:39:40 +02:00
mr.zeroandClaude Opus 5 9cbb909f08 Point the batocera page at ttg-batocera-store
The client was split into a reusable engine and a store definition, so
the page is now about our store: new repo and wiki links, the CLI path
the installer actually writes, and a closing section pointing at
warp-engine-batocera-store for anyone who wants a store of their own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 21:33:27 +02:00
mr.zero 2d750a4ce7 remove matrix link from batocera page 2026-08-11 00:01:14 +02:00
mr.zero 0a7e2d14e2 batocera store 2026-08-10 23:58:27 +02:00
mr.zero 763a092640 warp_engine: load ActiveJob itself so production eager load works
ci/woodpecker/push/woodpecker Pipeline was successful
The engine ships an ActiveJob based job (PipelineSyncJob), but a host's
application.rb does not necessarily require active_job/railtie - apps/api does
not. With eager loading off (development, test) nothing noticed; in production
WarpEngine::ApplicationJob blew up with "uninitialized constant
WarpEngine::ActiveJob", which is exactly what `rails zeitwerk:check` in
RAILS_ENV=production reported. An engine that ships jobs has to pull in the
framework it needs, so lib/warp_engine.rb requires the railtie.

Pre-existing on 0.1.0 as well; found while verifying that the 0.2.0 changes do
not break the portal. All three api-test steps are green now.

apps/api Gemfile.lock follows the 0.1.0 -> 0.2.0 path gem bump.
2026-08-10 11:30:06 +02:00
mr.zero ec43d2ae46 warp_engine 0.2.0: pluggable storage adapter and publish notifications
ci/woodpecker/push/woodpecker Pipeline was successful
Two seams the hosts needed, both backward compatible.

Storage: artifacts are served through WarpEngine::Storage.adapter instead of
raw filesystem calls. The default :local adapter keeps the previous behaviour
byte for byte, including the path traversal guard. A host can now set
config.storage_adapter to any object answering file?/directory?/locate and
serve builds from an object store - FileService and /api/download both honour
a Location.redirect, so a signing adapter turns them into redirects.
DownloadService#create still returns an absolute path (nil when missing) for
existing callers; #locate is the new entry point that can also return a
redirect. Ingestion (upload, extraction, file manager) stays local for now.

Publish: PublishService emits ActiveSupport::Notifications
("warp_engine.publish") with platform/name/version/software/release, so hosts
can react to a new build without hanging callbacks on the models.
WarpEngine.instruments_publish? lets a host feature-detect and keep its
fallback for older engine versions.
2026-08-10 11:16:17 +02:00
mr.zero afd1fe50c4 package upgrades 2026-08-09 17:57:08 +02:00
mr.zero 254d339656 warp_engine: CiRepository -> Pipeline rename everywhere, ci_ service prefixes dropped, unknown platform allowed
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-06 19:46:06 +02:00
mr.zero 9b89550766 warp_engine admin: CI Dashboard removed, Pipelines page with details sidebar, badge and Created fixes
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-06 19:32:50 +02:00
mr.zero 712fdbc97b fixes
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-06 19:23:04 +02:00
mr.zero 23bbccd2b1 engines digest: require What You Get as first heading 2026-08-06 19:03:16 +02:00