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>
This commit is contained in:
2026-08-18 16:53:33 +02:00
co-authored by Claude Opus 5
parent 5517133e25
commit 466aeac6ca
12 changed files with 137 additions and 26 deletions
+7
View File
@@ -203,6 +203,13 @@ export default {
subtitle: 'Our games on your own machine, kept up to date.',
lead: 'Two stores put our catalog on the two things that play it: a Batocera box, and RetroArch on anything else — desktop Linux, Windows, macOS, Android, a Steam Deck or a handheld. Both download the games straight from this site with box art, add them to the device\'s own library, and can be re-run any time to pick up new releases. Python 3 standard library only — nothing to install alongside them.',
gui: 'Graphical client',
clientTitle: 'A window, if you would rather not type',
clientDesc: 'WarpEngine Store is a small desktop application that does everything the command line does: the catalog as a grid of cards, one click to install a game into your own application menu, one to play it, one to remove it. It is also the way in on Windows, where `curl … | sh` does not exist.',
clientPointSetup: 'Sets the store up itself on first run — nothing to install beforehand.',
clientPointBrowse: 'Filter by platform, by what is installed, or by what has an update.',
clientPointSame: 'The same store underneath, so the command line keeps working on the same games.',
clientDownload: 'Download the app',
clientNote: 'Python 3 has to be on the machine, as it does for the command line — the store itself is a Python program and the window drives it. The macOS build is signed but not notarised, so macOS asks before opening it the first time; the download page says how.',
chooseTitle: 'Pick your device',
repo: 'Git repository',
docs: 'Documentation',
+7
View File
@@ -203,6 +203,13 @@ export default {
subtitle: 'A játékaink a saját gépeden, mindig frissen.',
lead: 'Két store teszi fel a katalógusunkat arra a kettőre, ami le is játssza: egy Batocera gépre, és RetroArch-ra minden máson — asztali Linuxon, Windowson, macOS-en, Androidon, Steam Decken vagy egy kézikonzolon. Mindkettő közvetlenül erről az oldalról tölti le a játékokat borítóképpel, felveszi őket a gép saját könyvtárába, és bármikor újrafuttatható az új kiadásokért. Csak a Python 3 alapkönyvtárát használják — nem kell mellé semmit telepíteni.',
gui: 'Grafikus kliens',
clientTitle: 'Ablak, ha nem szeretnél gépelni',
clientDesc: 'A WarpEngine Store egy kis asztali alkalmazás, ami mindent tud, amit a parancssor: a katalógus kártyákban, egy kattintás a telepítés a saját alkalmazásmenüdbe, egy az indítás, egy az eltávolítás. Windowson ez az egyetlen út is, mert ott a `curl … | sh` nem létezik.',
clientPointSetup: 'Első indításkor magát a store-t is beállítja — előre semmit nem kell telepíteni.',
clientPointBrowse: 'Szűrhetsz platformra, a telepítettekre vagy arra, amihez frissítés van.',
clientPointSame: 'Alatta ugyanaz a store, tehát a parancssor ugyanazokon a játékokon dolgozik tovább.',
clientDownload: 'Alkalmazás letöltése',
clientNote: 'Python 3 kell a gépre, ugyanúgy, mint a parancssorhoz — a store maga egy Python program, az ablak csak vezérli. A macOS-csomag aláírt, de nem notarizált, ezért a rendszer az első megnyitás előtt rákérdez; a letöltési oldal leírja, hogyan.',
chooseTitle: 'Válaszd ki a gépedet',
repo: 'Git tároló',
docs: 'Dokumentáció',
@@ -49,6 +49,33 @@
</div>
</section>
<!-- The window is a product of its own, not a footnote to the install command:
on Windows it is the only way in, because `curl … | sh` does not exist there. -->
<section v-if="client" class="st-section st-client">
<h2 class="st-section-title">
<i class="fa-solid fa-window-maximize st-engine-icon"></i> {{ t('stores.clientTitle') }}
</h2>
<p class="st-section-desc">{{ t('stores.clientDesc') }}</p>
<ul class="st-client-points">
<li><i class="fa-solid fa-check"></i> {{ t('stores.clientPointSetup') }}</li>
<li><i class="fa-solid fa-check"></i> {{ t('stores.clientPointBrowse') }}</li>
<li><i class="fa-solid fa-check"></i> {{ t('stores.clientPointSame') }}</li>
</ul>
<div class="st-links">
<a :href="client.releasesUrl" target="_blank" rel="noopener noreferrer"
class="st-link st-link-emerald">
<i class="fa-solid fa-download"></i> {{ t('stores.clientDownload') }}
</a>
<a :href="client.repoUrl" target="_blank" rel="noopener noreferrer" class="st-link st-link-dark">
<i class="fa-solid fa-code-branch"></i> {{ t('stores.repo') }}
</a>
<a :href="client.wikiUrl" target="_blank" rel="noopener noreferrer" class="st-link st-link-indigo">
<i class="fa-solid fa-book"></i> {{ t('stores.docs') }}
</a>
</div>
<p class="st-note">{{ t('stores.clientNote') }}</p>
</section>
<section class="st-section">
<h2 class="st-section-title">
<span class="st-step">1</span> {{ t('stores.installTitle') }}
@@ -128,6 +155,15 @@ const router = useRouter()
const FORGE = 'https://git.teletypegames.org/stores'
const ENGINES_FORGE = 'https://git.teletypegames.org/engines'
// The graphical client. Its own thing rather than a link on the desktop store: it
// drives any WarpEngine store the site's registry offers, and on Windows it is the
// only way in — there is no `curl … | sh` there.
const CLIENT = {
repoUrl: `${FORGE}/warp-engine-client`,
releasesUrl: `${FORGE}/warp-engine-client/releases`,
wikiUrl: `${CONFIG.wikiBase}/stores/warp-engine-client`,
}
type DeviceId = 'desktop' | 'batocera' | 'retroarch'
const BATOCERA_CLI = '/userdata/system/batocera-store/ttg-store'
@@ -160,7 +196,7 @@ const devices = [
wikiUrl: `${CONFIG.wikiBase}/stores/ttg-desktop-store`,
installCmd: `curl -fsSL ${FORGE}/ttg-desktop-store/raw/branch/master/install.sh | sh`,
afterInstallCmd: '',
guiUrl: `${FORGE}/warp-engine-desktop-gui/releases`,
guiUrl: CLIENT.releasesUrl,
uninstallCmd: `curl -fsSL ${FORGE}/ttg-desktop-store/raw/branch/master/uninstall.sh | sh`,
cliSnippet: [
`${DESKTOP_CLI} paths # where things go on this machine`,
@@ -194,6 +230,9 @@ const initial = devices.some((d) => d.id === route.query.device)
: 'desktop'
const device = ref<DeviceId>(initial)
const current = computed(() => devices.find((d) => d.id === device.value) ?? devices[0])
// Only the desktop store has a window; the other two run on devices with no desktop
// to put one on.
const client = computed(() => (device.value === 'desktop' ? CLIENT : null))
function select(id: DeviceId) {
device.value = id
@@ -271,6 +310,18 @@ const engines = [
.st-engine {
@apply bg-gray-50;
}
.st-client {
@apply bg-emerald-50/60 border-emerald-100;
}
.st-client-points {
@apply space-y-2 mb-5;
}
.st-client-points li {
@apply flex items-start gap-2 text-gray-700;
}
.st-client-points i {
@apply text-emerald-500 mt-1 text-sm;
}
.st-engine-icon {
@apply text-gray-400 text-lg;
}