Skip to content

[CI test only] chore(docker): slim image (upstream #1279) - #6

Closed
Fabio1988 wants to merge 24 commits into
mainfrom
chore/docker-slim-v2
Closed

Fabio1988 wants to merge 24 commits into
mainfrom
chore/docker-slim-v2

Conversation

@Fabio1988

Copy link
Copy Markdown
Owner

Draft PR in the fork, only to run the Docker job (upstream skips it for fork PRs). Do not merge, the real PR is WatWowMap#1279.

🤖 Generated with Claude Code

Mygod and others added 24 commits October 30, 2026 23:39
Golbat shipped the available quest field as temp_evolution_id and
advertises it on /api/status as filters.quest_temp_evolution_id
(UnownHash/Golbat#432). Read the new name and gate temp evo branch mega
energy keys on the status flag instead of the field's presence.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# [1.56.0](WatWowMap/ReactMap@v1.55.2...v1.56.0) (2026-10-31)

### Bug Fixes

* **pokestop:** key mega energy by temp evolution ([6827243](WatWowMap@6827243)), closes [UnownHash/Golbat#432](UnownHash/Golbat#432)
* **pokestop:** read Golbat quest temp evolution id ([8dfbcf6](WatWowMap@8dfbcf6)), closes [UnownHash/Golbat#432](UnownHash/Golbat#432)

### Features

* **tappable:** support candy rewards ([9b78da7](WatWowMap@9b78da7)), closes [UnownHash/Golbat#428](UnownHash/Golbat#428) [WatWowMap#1272](WatWowMap#1272)
VirtuosoGrid hides every tile while it restores a snapshot's scrollTop
and only finishes once the scroller has reported its position twice,
but the restore's own scroll request goes out before the scroller
listens for it. In production builds the second report never comes,
so a grid that had been scrolled reopened blank every other time;
StrictMode's double-run mount effect hid this in dev.

Pass the snapshot with scrollTop zeroed instead: the grid still gets
its measured sizes on the first render, which makes it tall enough for
useDrawerScrollMemory to restore the scroll offset.

Closes WatWowMap#1275

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## [1.56.1](WatWowMap/ReactMap@v1.56.0...v1.56.1) (2026-10-31)

### Bug Fixes

* **drawer:** stop scrolled selector grids from reopening blank ([3c493b6](WatWowMap@3c493b6)), closes [WatWowMap#1275](WatWowMap#1275)
Fixes WatWowMap#1270

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## [1.56.2](WatWowMap/ReactMap@v1.56.1...v1.56.2) (2026-10-31)

### Bug Fixes

* **map:** cancel stale idle timer on repeated blur ([959d74b](WatWowMap@959d74b)), closes [WatWowMap#1270](WatWowMap#1270)
…nce (WatWowMap#1278)

* fix(holiday): use standard Wikimedia thumbnail size and load images once

Wikimedia only serves thumbnails at its standard widths, so the 640px
pumpkin and ghost URLs returned 400. Switch them to 120px; the sprites
render at about 30px.

The animation also created a new Image for every sprite on every frame
(about 1000 requests/sec), so a failing URL was requested again on every
frame. Load each image once and skip ones that haven't loaded or failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AjBJekNgjZKdL2oSVY4PUe

* fix(holiday): rely on the naturalWidth check for failed images

Chrome marks a failed image complete before its onerror runs, and
drawImage throws on it, so the naturalWidth check is what keeps draw()
safe. Deleting the map entry in onerror was redundant; drop it and the
!image check it required.

Also correct the comment: same-URL loads are merged, so the old code
re-requested each failed URL once per frame, not once per sprite.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Mygod <contact-git@mygod.be>
## [1.56.3](WatWowMap/ReactMap@v1.56.2...v1.56.3) (2026-11-01)

### Bug Fixes

* **holiday:** use standard Wikimedia thumbnail size and load images once ([WatWowMap#1278](WatWowMap#1278)) ([5202ee2](WatWowMap@5202ee2))
engines.node allowed any release from 22.23.4 up, which still includes
the unpatched 23.x, 24.x before 24.22.1, 25.x and 26.x before 26.12.1.
CVE-2026-111497 is only fixed in 22.23.4, 24.22.1 and 26.12.1, and
request body inflation has relied on a patched runtime since 410570c.

Accept only the patched release of each line. Yarn refuses to install or
run any script on an exposed runtime, so `yarn start` stops there until
Node is upgraded. 23.x and 25.x are end of life with no fix and fall
outside the range on purpose.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
useManualPopupTracker writes manualParams whenever a popup opens, and
useForcePopup's cleanup writes it again when one closes. Every pokemon
marker subscribed to the whole manualParams object twice (PokemonTile's
isPopupOpen and useForcePopup, which every tile type uses), and so did
every QueryData and Clustering instance. Each popup click therefore
re-rendered every marker of every layer, and since the marker icons are
rebuilt on render, replaced every marker's DOM too.

Each subscriber now selects only what it compares:
- useForcePopup: whether this marker is the manual target (boolean)
- PokemonTile: whether its own popup is open (boolean)
- QueryData: this category's manual id, or undefined
- Clustering: this category's manual key, or null

Opening a popup still refetches its own layer, as before, because that
layer's manual id still changes. One behaviour difference: useForcePopup
used to rerun when manualParams was rewritten with the same id; it now
reruns when the marker becomes or stops being the target. Closing the
popup clears manualParams, so selecting the same pokemon again from
search still reopens it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
PokemonTile built a new Leaflet icon (an Icon, or a divIcon with the
glow, background, badge and weather markup) on every render.
react-leaflet's Marker calls setIcon whenever the icon prop is a new
object, and setIcon removes the marker's element and creates a new one,
so any re-render of a tile (a store change its selectors return a new
value for, the opacity step, a popup) rebuilt that marker's DOM even
when nothing it draws had changed.

The icon is now memoized on its inputs: whether it is the fancy variant,
the pkmn fields fancyPokemonMarker reads (id, background, pokemon_id,
seen_type, weather), the icon URL and size, glow, weather, badge,
opacity, time of day and background URL. fancyPokemonMarker also reads
the weather, grass and modifier assets straight from Icons, which
setSelection changes in place when the viewer picks another set, so the
stored icon selection (useStorage icons, a new object on every pick)
keys the icon too. The markup is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each pan or zoom ran a full pass over every pokemon marker, twice for a
zoom:

- Container listened to both zoomend and moveend. Leaflet only fires
  zoomend from _moveEnd, immediately followed by moveend, so a zoom
  saved location/zoom to the persisted store twice (re-serializing
  every filter into localStorage each time) and fired fetchdata twice.
  moveend alone now covers both.
- setLocationZoom wrote timeOfDay to useMemory on every pan, even when
  it hadn't changed, and zustand notifies every subscriber on any
  setState. It now writes only when the value changes.
- That mattered because PokemonTile's useMemory selector resolved five
  asset URLs (pokemon icon, size, badge, background and cry, each a
  nested suffix search in uicons) for every marker on every useMemory
  write. The selector now returns only cheap values (timer flag, the
  interaction-range zoom, time of day, and the Icons and Audio
  instances), and the lookups are memoized on their inputs.

The interaction-range check read the map's zoom inside that selector,
so it only followed a zoom because of the timeOfDay write. It now
compares the stored zoom in a useStorage selector, as GymTile and
TappableTile do, so a tile only re-renders when a zoom crosses
interactionRangeZoom.

Icons and Audio are mutated in place by setSelection when the viewer
picks another icon or audio set, so their identity alone can't key the
cache. The stored selection (useStorage icons/audio) is replaced on
every change, so the memos key on that as well.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
QueryData filtered its results against the hide list and created a
fresh element for every marker on every render, and it re-renders on
any write to its category's filters or settings, the hide list or the
active flag. Clustering's effect depends on its children, so above the
category's forcedLimit (3000 pokemon by default), each of those renders
reloaded Supercluster with every point, cleared and re-added the
cluster layer, and set a new marker set, forcing a second render that
reconciled every child again.

The filtered list and the marker elements are now memoized on the
query result and the hide list, so Clustering only re-clusters when
the data does. The empty-result fallback is a module constant, so it
doesn't defeat the memo while nothing has loaded or after an error.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A reverse lookup at coordinates without an address (open water, a field)
returns no results, and geocoder() formatted the missing first result
anyway. The TypeError was caught and logged as a failed lookup, so every
such gym logged a warning with a stack trace for a normal answer. It now
returns the empty address straight away.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Perm, trial, area restriction, webhook and scanner roles lists only
matched a member's Discord role ids, so a user id listed there was
silently ignored, and giving one person a perm took a role of their own.

getPerms now adds the user's own id to the roles it fetched for each
allowed guild. Snowflakes are unique across users and roles, so the id
can never match a real role. The user still has to be in an allowed
guild, same as with roles. Telegram is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A gym, pokestop or station search ran against every source of the model,
including a Golbat endpoint source with no database behind it. Only
Pokemon.search has an endpoint branch; the fort search methods call
this.query() on an unbound model, so the endpoint source rejected every
time and runScannerSources logged it. A deployment that lists a pure
Golbat endpoint next to its database for gyms and pokestops therefore
logged a "no database connection available" warning for every widening
step of every search, which buried real errors while the database source
still answered.

search now passes runScannerSources only the sources it can query: every
source for Pokemon, and the ones with a database connection for every
other model. runScannerSources takes the source list so its rejection log
still names the right source. A fort model with only endpoint sources
still gets no results, now without a warning, and a failing database
source is still logged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The testing guidelines still said there was no dedicated test suite, but
`yarn test` runs the masterfile tests and the server suite under node's
test runner. Say how to run it, and one file of it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# [1.57.0](WatWowMap/ReactMap@v1.56.3...v1.57.0) (2026-11-01)

### Bug Fixes

* **geocoder:** answer empty when a reverse lookup finds no address ([677ac81](WatWowMap@677ac81))
* **search:** skip sources a fort search cannot query ([f56b021](WatWowMap@f56b021))

### Features

* **discord:** accept user ids in roles lists ([b2fa07f](WatWowMap@b2fa07f))

### Performance Improvements

* **map:** make a pan cheap for every marker on the map ([5a9536f](WatWowMap@5a9536f))
* **map:** rebuild a layer's markers only when its data changes ([4e9c564](WatWowMap@4e9c564))
* **map:** stop re-rendering every marker when a popup opens ([d4fc29e](WatWowMap@d4fc29e))
* **pokemon:** build a marker's icon only when what it draws changes ([5dac9d3](WatWowMap@5dac9d3))
…#1261)

* fix(auth): link Discord and Telegram for users signed in with either

The profile offers "Link Discord" / "Link Telegram" to every signed-in
user, but the server only linked when the signed-in row was a local
account. Discord and Telegram users got a button that did nothing, and
worse:

- Telegram checked the row found by the Telegram id instead of the
  signed-in user, so linking Telegram never worked, not even for local
  accounts.
- Linking Discord while signed in with Telegram rewrote the Telegram
  row's strategy to "discord" without storing a Discord id.

Both clients now share linkAccount(): when a signed-in user authenticates
with a platform their row has no id for, the id is attached to their row.
A row that only existed for that account is merged in (gym badges keeping
the higher badge, backups, nest submissions, profile data) and deleted, in
one transaction, and its sessions are cleared. A local account, or one
already linked to a different account of the other platform, is never
merged away; the user lands on the blocked page with an explanation
instead. Signing in with an account the row already has stays a plain
login.

Webhooks keep following the sign-in method: a Discord sign-in alerts the
Discord account and a Telegram sign-in the Telegram one, so the webhook
strategy selector stays reserved for local accounts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(auth): address review on account linking

- Show the webhook strategy selector to local sessions again. They carry
  no strategy, so gating on strategy === 'local' hid it for everyone;
  gate on "not a Discord or Telegram sign-in" instead.
- Read and lock both rows (FOR UPDATE, one query) inside the transaction
  that merges them, so concurrent link callbacks for the same user cannot
  both link.
- Refuse to merge away a row that already has both ids. It is a linked
  account of its own, and carrying its second id over would carry it
  without that provider's permissions.
- Hand the row read for a plain login back to the Discord and Telegram
  clients instead of querying it again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(auth): expire linked trial perms and keep linked webhook

A session that linked a second platform kept only its own rmStrategy, so
the trial cleanup of the linked platform never matched it and its trial
perms outlived the trial. Sessions now list every strategy they hold perms
from in linkedStrategies, and clearNonDonor/clearTrial match either.

Linking also overwrote the computed webhook fallback with the session's
null selectedWebhook. linkAccount now stores the fallback on the row when
it has none and returns it with the linked fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(auth): clear linked trial perms even when the session is a donor

A linked session merges both platforms' perms, so a donor on one platform
kept the other platform's trial perms after that trial ended, since
clearNonDonor skips donor sessions. Linked sessions now record the
strategies that granted trial perms in `trialStrategies`, and
clearNonDonor also clears sessions listing the ending strategy there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(auth): only link accounts from a local sign-in

Linking from a Discord or Telegram session merged both platforms' perms
into one session, which trial expiry and donor checks keyed on a single
strategy cannot clear correctly. Only local sign-ins link now, and the
profile hides the link buttons for other sessions; a callback for them
signs in to that account like any other login. This drops the
linkedStrategies and trialStrategies session tracking.

Linking also has to be bound to the session that started it: the legacy
Telegram widget's signed callback is not, so only the Telegram OAuth flow
links, and the Discord strategy now checks the OAuth state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(profile): only offer account linking to local sign-ins

Linking Telegram to a Discord sign-in (or the reverse) fails, and making
it work means merging two platforms' perms into one session. Hide the
link buttons for Discord and Telegram sessions instead and leave the
server's linking for local accounts as it is on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## [1.57.1](WatWowMap/ReactMap@v1.57.0...v1.57.1) (2026-11-02)

### Bug Fixes

* **profile:** only offer account linking to local sign-ins ([WatWowMap#1261](WatWowMap#1261)) ([f01a1a2](WatWowMap@f01a1a2))
- Multi-stage Dockerfile (build -> prune -> runtime) with a BuildKit
  yarn cache mount, so the 2.2 GB yarn cache never lands in a layer
- Add .dockerignore (keeps .git, node_modules and local configs/secrets
  out of the build context; configs are mounted as documented)
- Pass GIT_SHA/GIT_REF as build args instead of .gitsha/.gitref files,
  and remove the dead placeholder files
- checkForUpdates: read sha/ref from env, fetch the remote sha via the
  GitHub API (no git binary needed), skip non-branch refs (PR images)
- Move vite + build plugins to dependencies since `yarn start` builds
  the client on boot; lazy-load vite-plugin-checker (dev only)
- Name the Sentry release from SENTRY_RELEASE/GIT_SHA (the plugin used
  `git rev-parse HEAD`, which only worked because the image had .git)
- Move openai/gpt-tokenizer to @rm/locales devDependencies and drop the
  unused `generate` re-export; keep loading .env in @rm/locales
- Remove unused monaco-editor devDependency (the playground loads
  Monaco from jsDelivr via @monaco-editor/loader)
- docker.yml: buildx, current action versions, GHA layer cache,
  provenance disabled to keep publishing a single manifest

Image: 3 GB -> 608 MB uncompressed (~1.4 GB -> ~207 MB compressed)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Dockerfile always defines GIT_REF/GIT_SHA (empty without
--build-arg), so treat their presence as Docker and never fall back to
git, which the image doesn't ship. Also skip the remote lookup whenever
the local sha is unknown instead of awaiting a fetch whose result can't
be compared.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- checkout v7, setup-buildx v4, login v4, metadata v6, build-push v7
  (none of their breaking changes affect this workflow)
- BUILDX_METADATA_PROVENANCE=disabled drops the ~600 line provenance
  trace from the printed build metadata
- DOCKER_BUILD_RECORD_UPLOAD=false stops uploading a .dockerbuild
  artifact on every run; the job summary is kept

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants