Tiles and screenshots were abstract shapes. Each is now a small scene for
its genre, with several layouts per genre steered by the game's tags, so
no two games look alike. The screenshot script pins the browser's date so
the Game of the Day doesn't change between runs.
The README's screenshots showed real games whose authors never agreed to
be featured. scripts/demo/build-demo-data.mjs builds a Flashpoint install
of invented games with generated art, plus Pixel Penguin Panic, a small
HTML5 game written for it. scripts/demo/take-screenshots.mjs retakes the
screenshots from an app running on it.
mirror/github-mirror.sh publishes main and its tags to a read-only
GitHub mirror, adding a notice under the README title, with a systemd
timer to run it hourly. A GitHub Actions workflow builds the images on
the mirror and pushes them to ghcr.io. The docs point prebuilt images
there, and CONTRIBUTING no longer invites issues and pull requests.
upstream-status.sh now fetches without tags, so upstream's version tags
stay out of local clones.
Adds scripts/upstream-status.sh to report upstream flashpoint-web changes
since the last merge, docs/08-development/upstream-sync.md describing the
merge process, and .dejima.md for agents working in dejima.
The portal's look is inspired by Texgriddy, an ES-DE theme by
Weestuarty, recreated in CSS without any of its files. The README and
the portal doc now say so, and credit the Fredoka font. The color
scheme was named after Texgriddy's "Chilled" scheme; it's now Slate.
A SWF a wrapper loads from memory saves under [[DYNAMIC]]/<n>, and
MochiAds does that in Ruffle but not in the standalone Flash Player, so
Bloons kept its progress at different paths in each. Saves differing
only by that step are now one save: the stream starts with Ruffle's
copy (the dynamic one when both exist) at every path it has for it, and
only saves the stream changed are copied back, to every Ruffle key for
them.
The stream starts with Ruffle's copy of each save, but Ruffle starts
first, and a game with progress only in the stream would write a fresh
save there that then replaced it. Before Ruffle starts a game it now
gets the stream's saves it doesn't have yet. This replaces the
first-switch rule, which kept a stale stream save over Ruffle progress.
Moving the Flash content type above GamePlayer's early return also
fixes a conditional hook.
Ruffle keeps SharedObjects in localStorage and the streamed Flash Player
as .sol files, in the same format under different names. Starting the
stream now adds the game's Ruffle saves, translated to Flash Player's
paths, and stream save updates are copied back into Ruffle's storage.
Root-path saves carry over only when this game made them. A game's
first switch keeps the stream's copy of a save both sides already had.
The session manager logs each session's creation, readiness time and
removal with its reason. A streamer that exits during startup now fails
the start at once instead of after 60 seconds, and the log carries its
exit code and last output; containers are no longer auto-removed, so
that output survives until teardown. A failed start reaches the app as
a 502.
The upgrade guide still said the /stream/ route was unchanged, but the
app now proxies streams itself and the session manager publishes no
port; an old route times out with a 504.
The privileged runner's job image has no Docker client, which the login
and build actions need. The registry host can be set with a REGISTRY_HOST
repository variable when the runner reaches Gitea at an internal address.
Drops the /flash and /html5 sections (old URLs redirect to /browse),
the GitHub star count endpoint, the unused HOME_RECENT_HOURS setting,
upstream's design notes and code review docs, and the issue template's
link to upstream discussions.
The README opens with storage, CPU and HTTPS requirements, including the
per-player cost of streaming, and explains Flashpoint's legal standing.
Setup now uses the torrent and Infinity archive from Flashpoint's site,
and the streaming docs reflect the app proxying /stream/.
A new README for the combined project with setup from the Flashpoint
Ultimate torrent. New feature docs for streaming, proxy sign-in and
game saves; deployment, upgrade and streaming READMEs updated for the
single compose file; the project renamed throughout.
Game detail and play are one portal page: a stage with the screenshot
and a Play button (or Enter) that loads the game in place, controls
under it, details, plays, rating, tags, favorite and add to playlist,
and related games.
A proxy that sets the username header twice, as when Authentik's nginx
snippet and the documented lines are both present, made Node join the values
into "brad, brad" and create an account of that name instead of signing in
brad. Read each occurrence separately: identical repeats count once, and
conflicting ones are refused. The docs now note Authentik's snippet already
sets the headers.
Ruffle publishes tagged releases and daily nightlies into a single
GitHub feed. The updater took whichever release came first carrying a
web-selfhosted.zip, so a stable release landing at the top of the feed
was installed on hosts tracking nightlies -- and because version
comparison normalised everything to a build date, the resulting semver
never matched, leaving the host reporting an update forever.
Selection now filters on GitHub's prerelease flag, and comparison
splits by channel: build date for nightlies, numeric parts for stable.
The channel lives in the ruffle.channel system setting (default
stable) and governs both manual installs and the scheduled job.
Switching channels records the choice and reports the target build;
nothing is downloaded until the install is requested.
Stable releases are months apart and can fall outside the recent page
the changelog is built from, so that channel falls back to the
latest-release endpoint.
Ruffle updates land in the container writable layer and are lost on
recreate. Specifies installing into the persisted /app/data volume,
seeding from the bundled copy, and serving from there.
The index was scraped from the Flashpoint Datahub wiki on every request.
That page now sits behind a Cloudflare managed challenge that no plain
HTTP client can pass, so the endpoint answered 500 for every caller.
Ship the list instead, extracted from a saved copy of the page into
data/community-playlists.json: 104 playlists across 9 categories. Only
entries whose download URL is on the allowlist are kept, so everything
the modal offers can also be imported. That drops the Old Default
Playlists section, hosted off the allowlist and still in the
pre-Flashpoint 10 format.
The modal now fetches only while open rather than on every render of the
playlists page, and shows when the snapshot was captured. Drops cheerio,
whose only consumer was the scrape.
A Fable review checked the spec against the code and found three things
that would have shipped wrong: the response envelope was copied from the
wrong router, the forced-refresh path could exhaust the shared GitHub
quota, and a 502 on total failure would double up with the axios
interceptor's global 5xx toast.
Answer 200 always with a lastCheckFailed flag, hold in-flight fetches so
a cold cache costs one call, floor forced refreshes at 60s, strip a
leading v from both sides, and reconcile the new server-side version with
the build-time one the General tab already shows.
Notify-only card on the Update page: compare APP_VERSION against the
latest GitHub release, show the changelog and the upgrade command, and
stay quiet on development builds.
The card always displayed 1.0.0. VITE_APP_VERSION was never set: CI
passes the release tag as a VERSION build arg, but the Dockerfile never
declared it or forwarded it to the frontend build, so the hardcoded
fallback always won.
Declare the build arg and export it as VITE_APP_VERSION, and resolve the
version in vite.config.ts for every other build: the env var, then the
release tag via git describe, then package.json. The image version label
was pinned at 1.0.0 for the same reason and now follows the arg too.
The Version Information card could not tell one Flashpoint install from
another: Ultimate names only a major version in version.txt, and nothing
showed when the metadata was last synced.
Serve two more values with the public settings. The package date is the
mtime of version.txt, falling back to .preferences.defaults.json. Both
are written when the package is built and never rewritten afterwards,
unlike preferences.json and flashpoint.sqlite, which the Launcher and
this app write to.
The last update comes from preferences.json. A missing source or the
1970-01-01 placeholder is normalized to null, which the UI renders as
"Never" - what Ultimate always shows, since it ships no metadata source.
Deployments on 1.0.31 or earlier cannot take an image update without
changing their compose file and .env, and nothing in the repo said so.
Document the migration from the two-container setup to the single
flashpoint-web image, the environment variables added, removed, and
changed since 1.0.31, and per-version release notes for 1.0.32-1.0.36.
Link it from the README and both documentation indexes.
The shipped compose mounted the Flashpoint directory read-only, so every write
the app makes to it failed: recording downloaded state in flashpoint.sqlite,
saving downloaded game data under Data/Games, and metadata sync. The Downloaded
page marked nothing and the reconciler warned on every boot.
Drop :ro from both compose files and the README's snippet, and correct the docs
that described the mount as read-only.
This Vitest version's jsdom environment installed localStorage as a bare object
with no methods, so the auth store's persist middleware threw and took 69 tests
across 6 files down with it. A suite that red hides real regressions.
Install an in-memory Storage for localStorage and sessionStorage, cleared
between tests. That surfaced two failures it had been masking: PlaylistCard
asserted a stale string id in its href, and the MSW mocks lacked a handler for
POST /api/games, which is how search has worked since filter combinations
outgrew the URL length limit.
Frontend suite: 115/115.
better-sqlite3 is synchronous, so a slow read froze the entire server — a
filter-options request over a slow mount was measured blocking it for 167
seconds, during which even /health timed out.
Add DbQueryPool: read-only SQLite connections in worker threads, with
DatabaseService.allAsync routing the full-table scans through them and falling
back in process when workers are unavailable. Filter-options queries now run
concurrently rather than serially. Workers reopen on database reload and stop
on shutdown.
Also set UV_THREADPOOL_SIZE=16: libuv's default of 4 throttled every image and
ZIP read in the process, which dominates on a network mount.
The Ultimate edition ships preferences.json without gameMetadataSources, so
metadata sync was permanently unavailable there with no way to opt in.
Add an admin-only override (metadata.custom_source_enabled / _url) resolved
ahead of preferences, restricted to the two trusted FPFSS hosts and validated
on save. While it is off, the Metadata Update card explains that Ultimate does
not support metadata updates and that updating means a newer full package,
pointing advanced admins at the override. Enabling it requires confirming a
dialog that spells out what a sync rewrites.