Commit Graph
100 Commits
Author SHA1 Message Date
mr.zero 9d8a546efa delete REFACT.md 2026-08-23 01:02:31 +02:00
mr.zeroandClaude Opus 5 82b87d019f WarpEngine 0.7.0: a képtár és a CI is a hoszté, adapteren keresztül
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
Két dolog volt beépítve az engine-be, ami nem az övé.

A **képtár** eddig `WarpEngine::Image` volt, pedig a modell teljesen általános:
a teletypegames-ben a tagok arcképét is ez hordozza, nem csak a katalógus
borítóit. Az `Image` modell, a feltöltött fájlok, az `/api/image/:id` végpont
és az admin oldal ezért átkerült a hosztba, az engine pedig adapteren szól
hozzá (`WarpEngine::Images`): `url_for` adja a katalógus JSON `imageUrl`-jét,
`select_options` a software-form képválasztóját, `build_from_upload` a
"tölts fel új képet" ágat. Az alapértelmezés az `Image` osztály, tehát a
default útvonal bitre a régi. A `SoftwareImage` (a katalógus-kapcsolat)
maradt az engine-ben, és **az `images` tábla nem mozdult**: az engine csak
abbahagyta a létrehozását, a generátor írja meg hoszt-kódként.

A **CI** eddig végig Woodpecker volt: kliens, aláírás-ellenőrzés,
pipeline-receptek, repo-szinkron, secret-kiosztás. Mindez egy adapter mögé
került (`WarpEngine.ci`), a Woodpecker-implementáció pedig az engine-ben
maradt `WarpEngine::CI::Woodpecker` néven — kliens, adapter, httpsig-ellenőrző
és a platformonkénti pipeline-receptek, mert a YAML-dialektus a szolgáltatóé.
Az engine saját kódja már nem nevez szolgáltatót: `CI::Repo` és `CI::Run`
értékeket kap, `CI::ConnectionError`/`ApiError`/`NotConfigured` hibákat dob, a
`Pipeline` pedig `remote_repo_id`-t ad a történelmi `woodpecker_repo_id`
kolumna fölött (a tábla itt sem mozdult). `c.ci_adapter = :none` azt jelenti,
hogy ez a hoszt nem buildel: az `/api/ci/*` 503, a `/build/config` elutasít,
az admin akciók elbújnak.

Mindkét seam a hoszt initializerében van kimondva, nem alapértelmezésre
hagyva — a hoszt megnevezi, mi a képtára és mi a CI-ja.

Törés a 0.6-hoz képest: `image_container_path`, `image_owners`,
`ci_platforms`, `ci_update_server`, `ci_extension_public_key(_url)`,
`woodpecker_url`, `woodpecker_api_token`, `woodpecker_repo_owner` és a
`WarpEngine.woodpecker_configured?` megszűnt; a helyük `c.image_class_name` /
`c.image_adapter` és `c.ci_adapter`. Az `/api/ci/*` `latest_run`/`trigger`
válasza a normalizált `CI::Run` alakot adja (number, status, branch, message,
createdAt, url), a `pipelines` lista pedig `repo_id`-t is közöl a megtartott
`woodpecker_repo_id` mellett.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 00:56:01 +02:00
mr.zero f2d0b72afd password reset fix 2026-08-21 21:28:10 +02:00
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>
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
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>
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>
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>
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>
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
mr.zero d82bb6f468 EnginesIndexPage digests 2026-08-06 18:19:40 +02:00
mr.zero a0ced02345 readme: woodpecker ci management
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-06 16:31:28 +02:00
mr.zero 546201c886 WarpEngine dropdown emoji
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-06 16:23:52 +02:00
mr.zero c853cfadbc WarpEngine dropdown 2026-08-06 16:22:17 +02:00
mr.zero bae6fc06a6 pipeline fix 2026-08-06 16:04:50 +02:00
mr.zero 731b267aa1 wp config fix 2 2026-08-06 15:05:55 +02:00
mr.zero 508e869080 wp config fix 2026-08-06 14:08:18 +02:00
mr.zero 435d22b71d wp config 2026-08-06 14:04:42 +02:00
mr.zero 267a13b600 woodpecker integration 2026-08-06 13:58:58 +02:00
mr.zeroandClaude Opus 4.6 b530dcd50d Update README template path to match platform directory structure
ci/woodpecker/push/woodpecker Pipeline was successful
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-08-06 10:34:06 +02:00
mr.zero 068c4db3f8 platform files refact
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-06 10:00:34 +02:00
mr.zero 565c086a0d Pin the pipeline update server URL: base_url is http behind the host nginx 2026-08-06 08:58:18 +02:00
mr.zero dd69dd79ab Register the config extension endpoint globally on the Woodpecker server 2026-08-06 08:26:10 +02:00
mr.zero fd0b64850f Serve the tic80 pipeline on the existing tic80pro image
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-06 08:16:47 +02:00
mr.zero 61ad1a87f2 godot builder image version update 2026-08-06 07:59:07 +02:00
mr.zero 7c9c8ff510 Verify RFC 9421 signatures on the Woodpecker config endpoint
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-06 07:24:47 +02:00
mr.zero 4b32252d2b Translate code comments and admin strings to English
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-06 01:25:53 +02:00
mr.zero dc45f2eb35 Serve Woodpecker pipeline configs from the engine (/build/config) 2026-08-06 01:14:22 +02:00
mr.zero c067d303bb Remove the droparea: artifacts arrive over /build/upload
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-05 22:16:34 +02:00
mr.zero 0c981b4590 build endpoints
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-05 20:12:35 +02:00
mr.zero f8ff7c394c Drop the unused UPDATE_SECRET_SOURCE env plumbing 2026-08-05 19:01:44 +02:00
mr.zero 3ade17ce9a enable db secret mode 2026-08-05 18:55:32 +02:00
mr.zero d0ce26c0a3 Rename the update auth switch to application_token_source and drop its ENV default
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-05 18:53:04 +02:00
mr.zero b2c780b697 db mode for update secrets
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-05 18:46:49 +02:00
mr.zero a217bcc14e Add DB-backed application tokens for the update endpoint
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-05 18:30:20 +02:00
mr.zero b69eff7866 Drop the date and new badge from the engine cards
Engines are evergreen products, not dated posts — the meta row, its
styles, the unused isNew wiring and the new-badge translations go.
2026-08-05 07:47:28 +02:00
mr.zero 7737a59850 Compact the top navigation and translate Engines as Motorok
The nav has too many items for the old spacing: smaller type, tighter
gaps, icons only on xl+ screens, and the hamburger now takes over below
lg (the desktop row did not fit between md and lg anymore).

The Hungarian engine strings drop the hyphenated loanword forms:
Engine-ek -> Motorok, Saját Engine-jeink -> Saját motorjaink.
2026-08-05 07:44:06 +02:00
mr.zero 1501f7f0b6 Drop the engines RSS feed
The engines listing is a handful of curated pages, not a stream — no feed
needed. Removes the route, controller action, RssService#engines_feed and
the footer link. The WikiService#pages alias stays (blog/howtos feeds use
it).
2026-08-05 07:40:25 +02:00
mr.zero a15f0ce24b Point the engine Explore action at the git repository
The engine pages' repo metadata (new in the wiki pages API) drives the
Explore button and card title links, with the wiki page as fallback. The
never-deployed /engines/:slug detail page and its store/api plumbing are
gone, and the engines RSS feed links to the repos too.
2026-08-05 07:36:58 +02:00
mr.zero f2c07c83c5 Serve engine-tagged wiki pages on a new /engines section
Frontend: /engines index + /engines/:slug detail routes, nav menu item and
en/hu translations. Engine pages are few, so the index uses an emphasized
poster-style design (dark slate, emerald accents, numbered full-width cards
with content preview) instead of the blog/howtos layouts. Slugs are the last
wiki path segment, since engine pages live scattered in the wiki tree.

API: /api/rss/engines feed linking to the site's engine pages, and a
WikiService#pages alias for #index — RssService called the alias-less name,
so the blog and howtos feeds were raising NoMethodError.
2026-08-05 07:29:07 +02:00
mr.zero 2358fe22ab Fix Release date field casing in CatalogShowPage
release.UpdatedAt does not exist on the Release interface (or in the API
response) — the release dates rendered as invalid values and vue-tsc failed
the build.
2026-08-05 07:28:47 +02:00
mr.zero 6833ab2070 Add a runnable example compose stack for WarpEngine
ci/woodpecker/push/woodpecker Pipeline was successful
examples/compose boots everything the engine's workflow assumes: mysql, a
minimal Rails host consuming the engine as a path gem, an SSH drop area
sharing the softwares volume with the app, and — behind the ci profile —
gitea plus woodpecker (agent attached to the stack network so pipeline
steps reach droparea/app by service name).

host_app doubles as a reference for a brand-new host: Gemfile, the two
initializers, apipie + engine mounts in routes.rb; on first boot the
entrypoint runs the install generator and db:prepare.

The README gains a detailed bring-up walkthrough: quickstart, publishing
a release by hand over scp + /update (verified end to end from a clean
slate), and the full gitea/woodpecker OAuth wiring.
2026-08-05 06:56:12 +02:00
mr.zero b09610bc33 Rewrite WarpEngine README for standalone consumers
ci/woodpecker/push/woodpecker Pipeline was successful
The README is what the tools/warp_engine mirror shows: present the engine as
a standalone product installed from git or the gem registry, with the
monorepo workflow reduced to a short Development note.
2026-08-04 20:16:46 +02:00
mr.zeroandClaude Fable 5 211dcccbf3 Trim whitespace from the forge token before use
ci/woodpecker/manual/woodpecker Pipeline was successful
A trailing newline pasted into the Woodpecker secret broke the push URL
("credential url cannot be parsed"); strip all whitespace from the token in
both the mirror and the gem-publish steps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 20:07:13 +02:00
mr.zeroandClaude Fable 5 b1b619befa Allow manual pipeline runs for the split mirror
ci/woodpecker/manual/woodpecker Pipeline failed
Path-filtered push events hide the workflow for unrelated pushes and manual
restarts have no changed-files list; add event: manual so the mirror can be
triggered from the Woodpecker UI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 20:03:35 +02:00
mr.zeroandClaude Fable 5 af779b0988 Fix YAML parse error in the gem-publish step
The credentials printf line contains ': ' which YAML reads as a mapping;
use a literal block scalar for that command.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 19:57:15 +02:00
mr.zeroandClaude Fable 5 b4d0198d2a Add CI split-mirror pipeline for WarpEngine
- .woodpecker.yaml: on master pushes touching libs/ruby/warp_engine, split the
  subtree and force-push it to the read-only tools/warp_engine mirror; on
  warp_engine-v* tags, build and push the gem to the Forgejo rubygems registry
- engine README documents the monorepo-first workflow and the mirror

Requires a `forge_token` Woodpecker secret (repository:write + package:write).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 19:47:15 +02:00
mr.zeroandClaude Fable 5 516f724f01 Rewrite READMEs in English for the WarpEngine split
- root README: monorepo layout with libs/, the WarpEngine/host layering,
  rebuild note for the root build context, make api-test and snapshot docs
- warp_engine README translated to English

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 19:36:40 +02:00
mr.zeroandClaude Fable 5 4d1e04afdf Phase 6: polish — apipie matcher, install generator, engine migrations, docs
- host apipie matcher now globs the engine controllers, so /api/docs and
  /api/swagger keep documenting the catalog endpoints
- rails g warp_engine:install: initializer template + a clean
  create_warp_engine_tables migration (signed bigint PKs) for new hosts;
  TTG never runs it
- engine append_migrations initializer: future catalog migrations in the
  engine's db/migrate run via the host's rails db:migrate
- README documents the updater contract, config surface, host expectations
  (admin JS picker, apipie matcher) and the soft-delete/resurrection behavior
- make api-test runs both suites plus a production zeitwerk:check

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 19:21:40 +02:00
mr.zeroandClaude Fable 5 d2b4b67e44 Phase 5: ship the catalog ActiveAdmin resources from the engine
- the 7 catalog admin files (softwares, releases, external_links,
  platform_links, images, files page, downloads) move to the engine's
  app/admin; the host ActiveAdmin instance loads them via
  ActiveAdmin.application.load_paths (single admin, URLs unchanged)
- engine initializers: Zeitwerk ignore for app/admin (production eager load)
  and load_paths + watchable_dirs registration, guarded by defined?(ActiveAdmin)
  so the admin-less dummy app boots
- images admin reads image owners from WarpEngine.config.image_owners; the
  host registers the Member owner in config/initializers/warp_engine.rb;
  the interim ImageUsage registry is gone
- fix: SoftwareImage.distinct.pluck clashed with its order(:position)
  default scope on MySQL (unscope(:order)) — introduced in phase 0, caught
  by the first authenticated /admin/images smoke test
- files page: download links use the public /file/ URL instead of the raw
  container path; the picker's stored path comes from config

Verified: both suites green, zeitwerk:check (production) clean, JSON
baselines intact, authenticated admin smoke test on every page incl.
the 3-level nested software form and Files picker mode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 19:18:28 +02:00
mr.zeroandClaude Fable 5 b79a73d62d Phase 4: move catalog controllers and routes into WarpEngine
- update, files and the 6 /api catalog controllers now live in the engine on
  a new WarpEngine::ApiController base (same rescue/mime behavior as host)
- engine routes serve /update, /file/*path, /api/software*, /api/builds*,
  /api/image/:id, /api/download at unchanged public paths via the root mount;
  host routes keep only TTG endpoints (events, members, wiki, rss, swagger)
- /update secret comes from WarpEngine.config.update_secret and an
  unconfigured secret now rejects every request (previously an empty
  UPDATE_SECRET env accepted empty secrets)
- apipie-rails is an engine dependency (DSL in engine controllers); dummy app
  configures apipie with validation off, mirroring the host
- engine request specs: catalog controller specs moved from host plus new
  /update auth contract spec

Verified: engine suite 53 green, host suite 6 green, /api/software and
/api/builds byte-identical to baselines, /update 401/400 behavior intact,
admin and TTG endpoints OK.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 19:09:11 +02:00
mr.zeroandClaude Fable 5 0dc3cebc14 Phase 3: move service layer, serializers and DTOs into WarpEngine + dummy-app test suite
- all catalog services (update/software/highlighted/builds/file/file-manager/
  download/image + SoftwareResponseBuilder), the SoftwareUpdater platform
  services and their concerns, Blueprinter serializers (incl. TimestampFields)
  and the 4 DTOs now live in the engine under WarpEngine::
- constantize dispatch strings use absolute names
  (WarpEngine::SoftwareUpdater::<Platform>Service)
- container paths read from WarpEngine.config everywhere (FileService,
  DownloadService, FileManagerService, ArchiveExtraction, ReleaseSerializer
  path rewriting); FileManagerService base path is now lazy
- engine requires blueprinter itself; gemspec declares blueprinter + rubyzip
- engine test suite: spec/dummy app (mysql warp_engine_test, catalog-only
  schema), rails_helper with engine-local factories; catalog model/service
  specs and factories moved from the host
- host suite keeps TTG specs and loads catalog factories from the engine

Verified: engine suite 40 green, host suite 14 green, /api/software and
/api/builds byte-identical to baselines, admin OK.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 19:03:58 +02:00
mr.zeroandClaude Fable 5 92f10197c1 Phase 2: move the 8 catalog models into the WarpEngine engine
- Software, Release, ReleaseAsset, ExternalLink, PlatformLink, Image,
  SoftwareImage, Download now live in the engine under WarpEngine::, on top
  of WarpEngine::ApplicationRecord; table names unchanged (empty prefix)
- each model runs an ActiveSupport load hook (:warp_engine_<model>) as a
  host extension point
- WarpEngine::Image reads its upload path from WarpEngine.config
- host references fully qualified (services, serializers, admin, specs);
  admin registrations renamed with as: so /admin URLs and route helpers are
  byte-identical; factories pinned to the namespaced classes
- Member#image now class_name: "WarpEngine::Image"

Verified: full suite green, /api/software and /api/builds byte-identical to
the phase-0 baselines, admin routes unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 18:51:12 +02:00
mr.zeroandClaude Fable 5 2f6cc4cdc2 Phase 1: warp_engine gem skeleton (path gem, mounted engine)
- libs/ruby/warp_engine: gemspec, WarpEngine::Engine (isolate_namespace with
  empty table_name_prefix), WarpEngine.configure surface (container paths,
  update_secret, image_owners), empty engine routes
- host Gemfile: path gem; host routes: mount WarpEngine::Engine => "/" as
  the last entry so host routes always win
- docker: api build context moved to repo root so libs/ is visible at
  bundle-install time; ./libs:/libs runtime mount; root .dockerignore to keep
  data/ and node_modules out of the build context

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 18:44:32 +02:00
mr.zeroandClaude Fable 5 bf921c589d Phase 0: in-place decoupling before WarpEngine extraction
- FileService/DownloadService: lazy container-path resolution instead of
  class-load-time realpath (boot no longer requires /softwares to exist)
- downloadCount/totalDownloads: single grouped query passed as Blueprinter
  option instead of per-release association counts (fixes N+1)
- admin Images: Member coupling replaced with ImageUsage owner registry
  (precursor of the WarpEngine.config.image_owners hook)
- Image: UPLOAD_PATH constant replaced with call-time upload_path accessor
- proper test environment (config/environments/test.rb, softwares_test DB,
  hosts.clear) — suite previously ran against the development DB
- spec fixes: case-insensitive uniqueness matchers (MySQL ai_ci collation),
  DB-cascade has_many expectations, PlatformLink::SUPPORTED_PLATFORMS
- JSON baselines of /api/software and /api/builds for post-extraction diffing

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 18:39:20 +02:00
mr.zero 724e5bc748 refact round 2026-08-04 15:45:13 +02:00
mr.zero 24ba77a0f2 Back to Catalog label fix 2026-08-04 12:11:37 +02:00
mr.zero 46c51bb317 platform links 2026-08-04 10:49:02 +02:00