You had to reload the tab by hand to pick up a new build, and when the service
worker did reload it was an abrupt white flash. Now the build stamps version.json
with the commit and the running app (which already knows its own __GIT_COMMIT__)
polls it whenever the tab regains focus. When a newer build is live it cross-fades
a themed curtain (the theme's own --bg + accent spinner) and reloads, then holds
that curtain over the fresh load until the app mounts and fades it out - so it reads
as an in-place refresh, no flash, no manual reload.
If you're mid-message it waits until the composer is empty (or the tab is hidden)
before reloading, and a loop guard stops it re-reloading for a commit it already
tried. sw.js serves version.json network-only so the probe always sees the live id;
index.html restores the themed bg pre-paint so dark themes don't flash light.
Existing open tabs need one manual reload to pick up this machinery; after that it's
automatic.
The dice feature's buttons styled themselves with var(--panel) and var(--green-soft),
neither of which exists: --panel was never a token and --green-soft got renamed to
--accent-soft. So the buttons had no fill and the bare <button> picked up the OS dark
UA chrome (the sandbox iframe declared color-scheme: light dark, following the visitor's
OS, not the app). On a dark desktop the flip/roll buttons came out dark in a light theme.
Use the real, mirrored tokens (--accent-soft fill, --accent hover) so the buttons always
carry the theme's own accent, and pin the sandbox iframe's color-scheme to the app theme
(from its --bg) so no plugin's native controls track the OS instead of the skin. Drop the
dead --panel from the mirror list.
New addUi('footer_item', …) slot renders in the footer's actions area (next to
the away / settings buttons). Registry type + TabBar render site; demo plugin
gains a footer-button example using the native appbar__act class.
config.json now lists all 24 options (added defaults.lang: "" = auto-detect).
CONFIG.md gains the four undocumented ones (server.guestIdent, branding.projectUrl,
report.service, defaults.lang), corrects the startup default (#accueil) and the
turnstile.enabled/features.register descriptions, and the example is now complete.
branding.icon is documented as the favicon URL but was only used for the
in-app logo. Now it also drives the browser tab favicon (main.tsx), the
desktop-notification icon (notify.ts), and the iOS home-screen title uses
branding.name. Drop the dead hardcoded 'tchatou' ident fallback in client.ts.
Plugins had no i18n path, so copy/clock/invite/games showed hardcoded
English and French in every language. Add orbit.i18n (language/t/pick) +
orbit.config() to the plugin API (v4), move every plugin string into the
app locales under plugins.*, and rewrite the four bundled plugins to use it.
Also re-serve the PWA manifest (blob) with the brand name + a localized
description instead of the static French one. All 10 locales: 654 keys, in
sync.
- MessageList showed 'Nouveaux messages' and 'Chargement de l'historique…'
hardcoded (all languages); route through i18n (messages.newMessages,
messages.loadingHistory).
- 8 locales (de/es/it/ne/pt-BR/pt-PT/ru/tr) were each missing 25 keys, which
fell back to French (fallbackLng=fr). Translated all 27 keys natively; every
locale now has identical key coverage (590 keys, 0 missing).
- config.json: list report.service for completeness.
Regenerate the install/notification icons from tchatou.fr's own favicon
(France-blue tile + speech bubbles). Bump the SW cache version so clients
fetch the new icons; push handler unchanged.
iOS only delivers Web Push to a Home-Screen PWA and ignores SVG
apple-touch-icons, and Android can't use SVG notification icons — so the
SVG-only icons broke push on mobile while desktop worked. Add 180/192/512
PNG icons (rendered from the orbit mark) and point the manifest +
apple-touch-icon at them. Push delivery code untouched.
Backgrounded mentions fired the notification (hence the sound) but it was
easy to miss — auto-dismissed / silent in the shade. Add requireInteraction
+ a short vibrate so it stays visible, and bump the SW cache so clients pick
up the change.
GameServ already refereees guests (identity falls back to nick; ranked
stats only when both are registered), so route the duel link through it:
orbit-games auto-challenges via GameServ on a /app/?duel=chess&vs= link,
and the invite plugin offers that link again. No client-side game logic;
casual for guests, ranked for accounts.
A client-run game is a game the client can cheat. Drop the peer-to-peer
chess plugin and its duel links; GameServ stays the only game engine.
Invite plugin reverts to plain room links.
orbit-invite adds a topbar button that builds a room link (/i/<salon>) or
a chess-duel link to share. orbit-chess is restored as the engine behind
the duel: it plays peer-to-peer over the +tchatou.fr/chess client tag and
auto-starts when the invited friend connects — no account, no referee.
Replace the plain ⚠ sysline with a prominent amber alert card (SVG
warning icon, left-accent border, stands out from normal system lines)
when an upload is blocked by content policy. The message now reads as
a clear consequence — 'blocked' rather than 'refused' — and tells the
user that operators have been notified. Malware rejections also get the
alert treatment.