Skip to content

0.18 - #222

Open
endel wants to merge 69 commits into
masterfrom
0.18
Open

0.18#222
endel wants to merge 69 commits into
masterfrom
0.18

Conversation

@endel

@endel endel commented Jul 24, 2026

Copy link
Copy Markdown
Member

No description provided.

endel added 30 commits May 12, 2026 16:16
Documents the RoomPlugin system (definePlugins, lifecycle ordering,
message handler rules, typed state access, isolated testing) and the
first-party IdleKickPlugin shipped at colyseus/plugins/idle-kick.

Assisted-by: Claude Opus 4.7
Documents the WebRTC signaling plugin shipped at colyseus/plugins/webrtc,
including the signaling message table, client-side WebRTCClient API from
@colyseus/webrtc/client, and the maxPayload caveat for SDP-heavy
exchanges.

Assisted-by: Claude Opus 4.7
…ions

Rewrite the Room Plugins page around the array form of
`definePlugins([...])`: each plugin owns its key via
`readonly pluginName`, so `this.plugins.<key>` autocomplete works
without the user picking a name. The keyed-record form is kept as
"Multiple instances of the same plugin" for when the same class is
mounted twice.

Document `static dependencies` (auto-pull-in of prerequisite plugins,
zero-arg constructors, transitive resolution, dedup by class) and add
a warning callout against `<this>` in the `plugins =` initializer —
it triggers a TS7022 cycle and collapses autocomplete to `any`. The
canonical pattern is a type alias declared outside the room class.

Two new plugin pages: UniqueSessionPlugin (one concurrent session per
user, reject/replace) and TrackUserSessionsPlugin (per-user reverse
index in Presence — explicit install when the admin Active rooms tab
should surface a user's current room).

Assisted-by: Claude Opus 4.7
- Unify section skeleton: Features → Installation → Mounting → Usage → Password protection
- Lead with Features right after the intro on both pages
- Sentence-case headings; consistent app.config.ts code samples
- playground: drop Screenshots/Rooms wrapper, promote API endpoints to top level

Assisted-by: Claude Opus 4.7
…layer

- Rewrite pages/database.mdx around @colyseus/database (GameDatabase, services,
  matchmaker driver, custom schema), with Drizzle/BYO as the secondary fallback
- Cover dialects (SQLite default, PostgreSQL; PGlite as lower-profile option),
  migration strategies, room plugins, and feature overviews
  (auth, cloud saves, leaderboards, live configs, analytics/moderation/notes)
- Move Database from the Tools section to API Reference in the nav

Assisted-by: Claude Opus 4.7
…o-end

database.mdx:
- Promote Authentication to a top-level ### section under @colyseus/database
  with what's-wired-automatically bullets, minimal setup, admin helpers,
  and customization pointer
- Update cloud saves to the current save(userId, data, slot?, expectedVersion?)
  signature; document delete()
- Reframe "Custom schema" as "Customizing built-in table schemas" with a
  full table of the 10 built-in tables and which service uses each
- Simplify the configs Zod example to .default({}) (inner field defaults
  fill the object)

auth/module.mdx:
- Top-of-page callout pointing to the database integration
- Frame the Backend API section as the manual path

auth.mdx (landing):
- Lead with the database-backed path as the fastest route
- Add a Database-backed card alongside the existing three

auth/room.mdx:
- Callout noting JWT revocation is automatic when @colyseus/database is wired
- New "With @colyseus/database" section showing db.auth.isBanned() in onAuth

auth/http.mdx:
- Add an HTTP Routes (createEndpoint) tab — auth.middleware() works in both
  Express and createMiddleware modes
- Callout on revocation-awareness when @colyseus/database is in use

server/http-routes.mdx:
- New ### JWT Authentication subsection documenting auth.middleware() as the
  canonical JWT gate for createEndpoint, with the same revocation callout

Assisted-by: Claude Opus 4.7
Replace the icon Cards with a 5-row table mapping common questions
("How do I authenticate a room join?", "How do I gate a new HTTP route?")
to the page that answers each one. Keep the recommended-path Callout
above the table as the lede.

The Cards previously duplicated the table's destinations; the table
gives readers a one-glance "where do I go" rather than a row of
icon+title-only links.

Assisted-by: Claude Opus 4.7
@colyseus/database AuthService.ban() now folds the tokenVersion bump
into the same UPDATE as the ban fields — the package-side fix removes
the foot-gun where ban() alone left existing JWTs valid until expiry.

Doc changes reflect the new behavior:

- pages/auth.mdx: scenario 3 (revoke/force-logout) no longer says
  "bans don't bump tokenVersion — pair with bumpTokenVersion()";
  bans now invalidate live tokens on their own. Refactored /auth
  landing as an intent-based FAQ with Cards-per-scenario along the
  way (Common scenarios, 7 entries).
- pages/auth/room.mdx: top callout + "With @colyseus/database"
  section drop the pair-with-bump caveat.
- pages/auth/http.mdx, pages/server/http-routes.mdx: revocation
  callouts now name ban() alongside bumpTokenVersion() as both
  invalidating live tokens.
- pages/database.mdx: JWT-session-revocation bullet rewritten to
  describe ban() as atomic ban+bump; bumpTokenVersion() positioned
  as the standalone lever for non-ban revocation.

Assisted-by: Claude Opus 4.7
New section:
- pages/admin.mdx — landing page covering install, mount (router + Express),
  bootstrap, hardening checklist, full AdminOptions reference. Recommends
  auth.settings.onResetPassword for reset emails (player-side flow already
  handles delivery) over the panel-specific onResetRequest.
- pages/admin/authentication.mdx — sessions, JWT cookies, 3-role hierarchy,
  scoped mods, admin.guard() for gating monitor/playground/custom routes,
  password reset, rate limits.
- pages/admin/resources.mdx — defineAdminResource(), list/form/show/create,
  per-row + bulk actions, policies, FK auto-linking, audit log.
- pages/admin/dashboard.mdx — 5 preset widgets, custom widgets, 4 render
  modes (kpi/table/list/json), polling, 12-column grid.
- pages/admin/rooms.mdx — built-in room inspector (kick/lock/state edit/
  dispose), multi-process visibility, RBAC defaults.
- pages/admin/_meta.tsx — sidebar order.
- pages/_meta.tsx — Admin Panel entry in API Reference (after Database).

Reusable component:
- components/scenario-card.tsx — <ScenarioGrid> + <ScenarioCard icon title
  href>{children}</ScenarioCard> for landing-page intent indexes.
- style.css — .scenario-grid / .scenario-card with hover lift, shadow,
  150ms transitions; theme-agnostic via rgba.

Refactored:
- pages/auth.mdx — collapsed 7 vertical scenario sections into a single
  ScenarioGrid with 7 ScenarioCards.

Assisted-by: Claude Opus 4.7
Apply the new CLAUDE.md writing guideline ("Plain, approachable prose.
Skip marketing copy and hedging adverbs; keep the why alongside the
what.") across the admin section. Smaller, more surgical pass than the
previous attempt: cut marketing language ("ships with", "bundles") and
hedging adverbs ("typically", "generally"), but preserve transitional
sentences, brief rationale, and use-case examples.

Net: -/+19 lines across 5 files; CLAUDE.md committed for the first time.

Assisted-by: Claude Opus 4.7
Break pages/database.mdx (395 lines, single page) into a focused landing
plus four sub-pages — matching the cadence of the admin section.

Landing (pages/database.mdx, ~130 lines):
- Intro + service overview + beta callout
- Installation
- Quick start
- Dialects & connection strings
- Migrations
- Matchmaking driver
- Next steps (links to sub-pages)

Sub-pages:
- pages/database/authentication.mdx — built-in @colyseus/auth integration,
  admin/moderation helpers, customizing callbacks.
- pages/database/services.mdx — cloud saves, leaderboards, live configs,
  analytics/moderation/notes, plus the matching room plugins folded in
  (CloudSavesPlugin, LeaderboardsPlugin, AnalyticsPlugin).
- pages/database/schemas.mdx — customizing built-in table schemas, with
  the 10-row built-in tables reference.
- pages/database/bring-your-own.mdx — ORMs, query builders, Firebase,
  and the onAuth/onLeave integration patterns for BYO setups.
- pages/database/_meta.tsx — sidebar order.

Cross-references updated across 7 pages: /database#authentication-built-in-
colyseusauth-integration → /database/authentication;
/database#customizing-callbacks → /database/authentication#customizing-callbacks.

Assisted-by: Claude Opus 4.7
Covers the three database delivery modes (bring-your-own MMDB, MaxMind
auto-download, bundled DB-IP Lite), licensing terms for each, and
worked examples for region-locked matchmaking, locale defaults,
GDPR-aware analytics routing, and manual lookup via
`this.plugins.geoip.lookup(ip)` for reconnect-time re-resolution.

Assisted-by: Claude Opus 4.7
Replace the Cards grid with an early table of all five plugins (now
including GeoIPPlugin) with one-line descriptions; drop unused Cards import.

Assisted-by: Claude Opus 4.7
- matchmaker: add Batch Lookup by Room Id section for
  matchMaker.findRoomsByIds — single wire op for K known roomIds.

- room/plugins/track-user-sessions: document the new static
  listUserSessions(userId, options?), the UserSessionInfo shape,
  and the reconcile / removeStale options. Replace the stale
  matchMaker.query reconcile caveat.

- room/plugins/unique-session: update conflictsWith for its new
  (existing, currentRoom) signature with full IRoomCache exposure.
  Drop the misleading "scope by game mode metadata" example (it
  couldn't actually work before) and add three realistic ones:
  mode-based scoping, per-process exemption, capacity-aware
  filtering. Add a Performance section noting the 2 wire-op cap.

Assisted-by: Claude Opus 4.7
Server (room.mdx): new "Responding to a message" section — a message
handler returns a value (awaited) to answer the client; throwing settles
the request as an error; the "*" fallback can't answer and unknown types
reject with no_handler.

Client (sdk.mdx): new "Request/Response" section — room.request() and
room.send(type, payload, callback), timeout/rejection behavior, flagged
as JS/TS-only for now.

roadmap.mdx: point the #331 entry at the new section. Also folds in the
previously-staged restructure into a "Shipped in v0.18" section.

Assisted-by: Claude Opus 4.7
Add a Server-section page for the Colyseus Vite plugin (colyseus/vite),
covering setup, options, dev HMR (tied to devMode), production build, and
middleware mode. Cross-link from the Development Mode page and the server
devMode option.

Assisted-by: Claude Opus 4.7
# Conflicts:
#	style.css
New pages/netcode/ section (client-prediction, lag-compensation,
determinism, recipes) synced to the 0.18 observer-model predict API, with
targeted fixes across auth, database, faq, migrating and the phaser
tutorial surfaced by the full-site audit.

Assisted-by: Claude Opus 4.8
- sim step examples flipped to (ctx, world, command)
- room.clock section rewritten reader-first: always safe to call (no
  optional chaining), custom-clock swap note moved below the timelines
- input.data: note that undeclared-field writes do nothing and warn once
  per key with the debug panel active
- concepts.mdx removed — superseded by the netcode section

Assisted-by: Claude Opus 4.8
…cessor bookkeeping as a list

Assisted-by: Claude Opus 4.8
Migration guide gains: preview/next-tag install callout, schema 4→5 and
SDK version bumps, request/response messaging section, client.id /
fossil-delta-serializer removals, playground production lockdown.
Banner + header badge flip from 0.17 to 0.18.

Assisted-by: Claude Opus 4.8
…cipe

The three matchmaking flags were listed without semantics, and the
password recipe recommended setPrivate() — which excludes the room from
joinOrCreate() matching, defeating its own filterBy(['password']) step
(verified against the 0.18 test harness).

- room#matchmaking-properties: flag × join-path table, per-flag guidance,
  no-built-in-password callout; restore the truncated lock()/unlock()
  sections and the individual-setters list; metadata is replaced, not
  merged
- password recipe: filterBy + unlisted (not private) + onAuth() check to
  cover the joinById bypass and the missing-password-field hole
- consistency: driver/matchmaker/lobby pages now agree on what each flag
  filters

Thanks @jeffreyhugh for the heads-up!

Assisted-by: Claude Opus 4.8
- Document `onAuth` as a plugin hook: ordering row, the returns-nothing
  contract, and how GeoIPPlugin uses it to populate `client.geoip`.
- Switch examples to the array form of `definePlugins()`, the recommended
  shape; explain when the record form is worth it.
- Note that `@colyseus/geoip` is the one plugin outside the core package.
- Replace the misleading `onKick` note: arrows bind `this` to the room.
- Repoint broken links (`/auth/room`, `/sdk#send-and-receive-messages`)
  and swap `this.log.warn` for `console.warn`.

Assisted-by: Claude Opus 4.8
Tier 1 of TODO/sidebar-reorg.md — sidebar labels and ordering only, no URL
or content changes.

Root sidebar:
- "API Reference" becomes "Building Your Game", reordered to the dev loop:
  Server, Rooms, State Sync, Client SDK, Matchmaking, Auth, Database.
- Netcode moves out to its own "Advanced" separator — as the flagship 0.18
  feature it was buried at 5th-of-9, and the split signals "after the basics".
- "Matchmaker API" retitled "Matchmaking"; it was the lone "...API" entry.
- Admin Panel moves under "Tools & Integrations" (ex-"Tools") — it's a
  product, not API reference.
- "Infrastructure" renamed "Deploy & Scale".
- The consecutive "More" and "Extra" junk drawers merge into "Resources".

Server: group children under Setup / Development / Production instead of
accretion order.

Rooms: title the three untitled children and order by learning curve —
messages, timing events, reconnection, exceptions, plugins, built-in.

Assisted-by: Claude Opus 4.8
These anchors pointed at headings that no longer exist (or never did):

- /room#patchrate            -> #configuration-properties (it's a table row)
- /room#set-simulation-interval -> #game-loop, and the link text had a typo
  ("setSimiulationInterval"); renamed to setTimestep(), the current name
- /room#client               -> #client-instance
- /sdk#consume-seat-reservation -> #other-join-methods
- /sdk#join-or-create-a-room -> #join-or-create-recommended
- /sdk#join-existing-room    -> #other-join-methods

Found by slugging every heading with github-slugger (what Nextra uses) and
resolving every `](/page#anchor)` link against it. The existing check-links.js
and find-broken-links.js both split on "#" and only validate the page path,
which is why BROKEN_LINKS.md came out empty.

Assisted-by: Claude Opus 4.8
The two existing checkers (check-links.js, find-broken-links.js) were dead:
both used `require` under `"type": "module"`, so they crashed on startup.
That's why BROKEN_LINKS.md was empty — it wasn't "no broken links found",
the script never ran. Both are replaced by scripts/check-links.js.

The new checker validates anchors, not just page paths. A link to a renamed
heading still loads the page, so it never 404s and rots silently — that class
of rot accounted for every one of the 33 fixes here. Headings are slugged with
github-slugger (added as a devDependency), the same slugger Nextra uses, so
results match what ships. It also:

- checks same-page `#anchor` links and trailing-slash routes (`/sdk/#x`)
- skips fenced code, JSX/HTML comments, and inline code, so commented-out
  <Cards.Card href> entries don't register as links
- unwraps `[label](url)` in headings, so `## Self-hosting on [Vultr](…)`
  slugs as self-hosting-on-vultr
- exits non-zero, so it can gate CI

Run with `npm run check-links`.

Notable fixes: 4 tutorials pointed at a Unity heading renamed to "Running the
test server locally"; 9 links used an SDK page structure that no longer exists;
migrating/0.15 had a case-mismatched anchor (#onLeave-… vs the lowercase id).

Assisted-by: Claude Opus 4.8
Tier 2 (part 1) of TODO/sidebar-reorg.md. room.mdx drops 1,241 -> 801 lines:

- pages/room/visibility.mdx — the locked/private/unlisted flags, setMatchmaking,
  lock/unlock, and the password callout. Titled "Room Visibility & Access:
  locked, private, unlisted" so the terms people actually search for are in the
  page title. This is the answer that was invisible at an anchor inside a
  1,241-line page.
- pages/room/lifecycle.mdx — the full onCreate/onAuth/onJoin/onDrop/onReconnect/
  onLeave/onDispose reference plus the devMode and shutdown hooks.

room.mdx keeps overview, state, messages, config, communication, reconnection,
and Client Instance, with a short pointer where each extracted section was.

Old anchors: the plan called for mapping these in pages/404.mdx, but that can't
work — /room still exists, so Next serves it 200 and the 404 page never runs.
Server-side redirects can't help either, since browsers don't send the fragment.
Added <MovedAnchors> (components/moved-anchors.tsx), a small inline script on
the source page that maps the 16 moved hashes to their new homes, so external
links and bookmarks keep working.

Headings were promoted a level (### -> ##), which leaves their slugs unchanged,
so every moved anchor resolves on the new page. check-links.js now also
validates bare "/path#anchor" string literals, so the redirect map itself is
covered — verified with a negative test.

Verified: 165/165 pages build, check-links clean, and a line-by-line diff of the
original against the three resulting files shows no content lost (the only
deltas are the 16 heading promotions and 8 deliberately rewritten links).

Assisted-by: Claude Opus 4.8
endel added 30 commits July 22, 2026 14:31
The Matchmaking unification made "matchmaking/matchmaker" the canon (166
occurrences), but 29 hyphenated stragglers remained — "Match-maker API",
"Standalone Match-maker", "match-making requests". Normalized everywhere in
prose; code identifiers (matchMaker, isStandaloneMatchMaker) untouched.

Renamed headings get new slugs: the /matchmaker shim now also maps
match-maker-api (the page's long-lived former H1 slug) alongside
match-making. The shim's own historical keys are deliberately left
hyphenated — they ARE the old slugs.

Also fixed a stale "Built-in Rooms → LobbyRoom" link label left from the
section dissolution.

Verified: check-links clean, 165/165 pages build.

Assisted-by: Claude Opus 4.8
Batch 1 of the SEO pass: every page without frontmatter gets a title and a
meta description written from what the page actually covers. Includes the
highest-traffic section roots (server.mdx, room.mdx, auth.mdx) and the whole
Server child set. Excluded: 404.mdx (script page), the _js_ts_header partial,
and the hidden postgresql stub.

Verified: check-links clean, 165/165 pages build.

Assisted-by: Claude Opus 4.8
Batch 2 of the SEO pass: every page that had a frontmatter title but no
description gets one, written from the page's actual content — cloud/*, the
transports, state pages, auth children, tutorials, example indexes, and the
moved matchmaking pages. Docs-wide coverage is now complete except the
hidden postgresql stub and the _js_ts_header partial, both intentional.

Verified: check-links clean, 165/165 pages build.

Assisted-by: Claude Opus 4.8
room, sdk, server, state, and database all end by pointing onward; the
Matchmaking root ended mid-list on exposedMethods values and auth on its
scenario grid. Both now close with the established Next Steps pattern,
following the build-a-game journey (matchmaking -> auth -> database).

netcode intentionally left alone — its Prediction Playground closer is
stronger than a generic link list.

Verified: check-links clean, 165/165 pages build.

Assisted-by: Claude Opus 4.8
The repo had no CI. Two checks:

- `pnpm run check-links` — validates every internal link and anchor against
  real heading slugs, plus the 404.mdx redirect map (dead targets,
  never-fires entries, shadowed entries). This is the guard that keeps the
  38 heading moves from this reorg — and future ones — from rotting.
- `pnpm run build` with one retry: next build intermittently fails
  page-data collection on a random unrelated page and passes on rerun
  (observed 4x locally on this Next 13 setup).

Assisted-by: Claude Opus 4.8
Sidebar shows human titles (Kick Idle, GeoIP, WebRTC, Unique Session,
Track User Sessions) instead of class names; prose and page bodies keep
referring to the classes (IdleKickPlugin, ...) by their real names.

Assisted-by: Claude Opus 4.8
- TODO/sidebar-reorg.md — the completed IA-reorg plan, kept as a record of
  what moved where and why (all tiers shipped 2026-07-21/22).
- TODO/talking-to-the-server.mdx — staged draft, parked until
  `predict.action` ships; see its header comment.
- *.psd ignored (images/hero.psd is a 1.8 MB source file that doesn't
  belong in git history).

Assisted-by: Claude Opus 4.8
First run passed but annotated: v4 actions target the deprecated Node 20
runner and are being forced onto Node 24.

Assisted-by: Claude Opus 4.8
Verified against colyseus-0.18 packages/sdk (v0.18.1):

- sendBytes examples passed plain number[] arrays. The SDK reads
  .byteLength from the payload (Room.ts sendBytes), so a plain array
  yields NaN and silently sends an EMPTY frame. Examples now wrap in
  Uint8Array, with a warning callout.
- Reconnection options table was missing `enabled` (default true) —
  the switch that disables automatic reconnection entirely.
- `enqueuedMessages` status property documented as a number; it is the
  array of buffered messages (Reconnection.ts).
- "Removing Listeners" section had a heading and no body. Documented
  room.removeAllListeners(), what it clears, and that it runs
  automatically on leave.

Assisted-by: Claude Opus 4.8
Verified against colyseus-0.18 packages/core (v0.18.1):

- All five seat-reservation examples showed the old nested shape
  ({ sessionId, room: {...} }). 0.18's buildSeatReservation returns a flat
  { name, sessionId, roomId, processId } (ISeatReservation) — no nested
  room object, no locked field.
- reserveSeatFor(room) missing its required options argument -> (room, {}).
- findOneRoomAvailable params: roomName is the room *type* (was described
  as "id of a specific room instance"), the second parameter is filter
  options (not onJoin/onAuth options), and the optional
  additionalSortOptions parameter was undocumented.
- The filterBy(['maxClients']) example couldn't work as written: the cache
  stores the room instance's real maxClients, not the client option. The
  example room now applies the option in onCreate.
- Lobby filters require `name` — the name check runs unconditionally, so a
  metadata-only filter matches nothing. Reworded feature bullet + section.
- Lobby JS tab used a default import (@colyseus/sdk has named exports only)
  and a TS annotation in a .js sample.
- QueueRoom: maxWaitingCyclesForPriority is declared in QueueOptions but
  onCreate never applies it in 0.18.1 — removed from the defineRoom example
  and marked subclass-only in the options table.
- Visibility: exact lock-rejection error string is `room "<id>" is locked`.
- Standalone: documented the two fallbacks that can still create a room on
  the matchmaker process (no healthy game servers; IPC timeout).

Assisted-by: Claude Opus 4.8
Verified against colyseus-0.18 packages/core (v0.18.1). Highlights:

- Failed message validation does NOT silently ignore the message — it
  disconnects the client with 4002 (WITH_ERROR), or settles a request as
  an error.
- Server-side onDrop semantics: it runs for EVERY non-consented close code
  (the docs' 1006/1001/1005/4010 list was the client-SDK trigger set);
  only CONSENTED or a denied in-progress reconnection bypasses it. onLeave
  additionally runs after a drop once reconnection fails or times out.
- Custom close codes are 4011-4999 (4000-4010 are framework-reserved; the
  page's own table already said so). 4003 is not client-side-only.
- onAuth sample used a (token, request) signature matching neither
  overload; metadata sample assigned a property on undefined; three
  samples were internally inconsistent (userData.reconnection vs
  reconnectionInterval, playerNumber vs team); clients.getById is
  deprecated for clients.get; client.sendBytes needs Uint8Array (plain
  arrays send an empty frame); 1000-range close-code names aligned with
  the exported CloseCode enum.
- Exception handling: methods list gains onDrop/onReconnect and
  setTimestep/setFixedTimestep; OnLeaveException's property is
  `consented`, not `code`; SimulationIntervalException is a deprecated
  alias of TimestepException; documented OnDropException /
  OnReconnectException with a callout that 0.18.1 doesn't export them yet.
- Reconnection page imported from "colyseus.js" (package is @colyseus/sdk).
- RelayRoom: allowReconnectionTime is clamped to 40s (example used 120);
  the option is allowReconnectionTime, not allowReconnection: true; client
  snippets rewritten from the removed schema-v2 state.players.onAdd(...)
  style to Callbacks.get(relay).

messages and timing-events pages verified fully clean.

Assisted-by: Claude Opus 4.8
Verified against colyseus-0.18 (v0.18.1), including compiling type-level
claims against the monorepo's TypeScript 6.0.3:

- The "typed client-side use" claim for plugin messages was empirically
  false for `protected messages` — TS structural checks skip protected
  members, so they never reach the SDK's message type extraction. Now
  documents the tension with the protected-visibility guidance and the
  public escape hatch.
- The <this> callout claimed a TS7022 cycle that no longer reproduces on
  TS 6.0.3 — softened to historical, keeping the type-alias recommendation.
- idle-kick: custom close codes are 4011-4999; 4000-4003/4010 are
  framework-reserved, and 4010 specifically triggers the SDK's
  auto-reconnect — the opposite of a kick.
- geoip: the EuropeRoom sample threw ServerError without importing it.
- unique-session: the per-process conflictsWith example exempted the most
  common duplicate (second tab in the same room — existing.room is
  undefined for same-room conflicts): now `!existing.room || ...`.

webrtc and track-user-sessions pages verified fully clean.

Assisted-by: Claude Opus 4.8
Verified against colyseus-0.18 packages/core, transport, drivers,
presence (v0.18.1). Highlights:

- uWS transport install pinned old majors (@^2.0.1/@^1.4.1) that
  peer-conflict with core 0.18 — the Express v4/v5 split belongs to the
  optional uwebsockets-express layer, which the express: callback also
  silently requires. Now installs @^0.18.0 + documents the extra package.
- H3Transport (WebTransport): the express: server option is silently
  ignored — the Express app must be passed as the required `app` transport
  option. Example rewritten; warning added.
- BunWebSockets options are Bun's WebSocketHandler options, not Bun.serve.
- Both selectProcessIdToCreateRoom samples used matchMaker without
  importing it; defaultOptions merge into onCreate only (not onAuth/onJoin);
  shutdown also triggers on SIGUSR2/uncaughtException; lifecycle-events
  list gains visibility-change, metadata-change, and leave's willDispose.
- Driver: the class is MongooseDriver (no MongoDriver exists) and takes a
  connection URI, not an options object.
- Presence: removed the hincrbyex section — source marks it "DO NOT USE,
  internal only".
- HTTP routes: get/setSignedCookie require a `secret` argument; "server"
  scope endpoints ARE routed — they're only hidden from the typed RPC
  client.
- Logging: no logger.log method exists (the Logger adaptor is
  trace/debug/info/warn/error — .log only worked via the console default);
  pino's level option takes a string, not 50.
- Debugging: three undocumented categories (driver, presence, devmode).

transport.mdx, ws, devmode, vite pages verified fully clean.

Assisted-by: Claude Opus 4.8
Verified against colyseus-0.18 packages/sdk + core (v0.18.1), including
the last 50 commits on the 0.18 branch. Highlights:

- Predict.get is NOT idempotent — each call constructs a fresh Predict
  (unlike Rewind.get, which caches per room). Reworded to "create one and
  share it".
- valueAt is offset-free only for reckon-tracked entities; for
  controller-bound instances it returns the smoothed pose. Dropped the
  wrong parenthetical.
- Lag-comp auto-record fires on each broadcast, not each tick (fixed in
  the overview diagram and the server-input summary; the lag-compensation
  page already had it right).
- MoveInput sample: `type as t` yields the decorator, not the fluent
  builder (t.int8 would be undefined at runtime) — import `t` directly;
  and Room<{ input: MoveInput }> needs `SchemaType<typeof MoveInput>`
  since schema() returns a value.
- Idle-callback comment implied returning true skips the seat — it
  synthesizes a defaults frame; clients.getById -> clients.get.
- determinism: `if (!ctx.isReplay)` IS the idiom for fire-and-forget
  presentation (source jsdoc is explicit; the page contradicted its own
  client-prediction sibling); snap-absorbed corrections are excluded from
  drift telemetry, not zeroed (only me.reset() zeroes).
- lag-compensation: per-attach maxRewindMs sizes that group's history
  ring only — the anti-spoof clamp always uses the room-level default.

recipes.mdx verified fully clean.

Assisted-by: Claude Opus 4.8
…audit

Verified against colyseus-0.18 packages/auth + database + core (v0.18.1).
The big three are architectural:

- tokenVersion revocation is enforced by the default static Room.onAuth
  ONLY — auth.middleware() never consults revocationCheck, so a revoked
  but unexpired JWT still passes every protected HTTP route. The http page
  claimed the opposite ("no per-route check needed"); now a warning with
  the actual boundary, and the "force-logout" phrasing is qualified to
  "next room join" everywhere it appears.
- Static onAuth short-circuits instance onAuth: with the colyseus bundle
  installed, the patched static hook decodes the token at matchmake time
  and its payload becomes client.auth — the instance hook never runs for
  token-bearing clients (and client.auth is undefined when it does run).
  Both example overrides (auth/room, database quick-start) were dead code
  as written; rewritten as a static onAuth with JWT.verify + isBanned, and
  the precedence is now documented.
- db.auth.settings does NOT wire email delivery: onForgotPassword /
  onSendEmailConfirmation / onEmailConfirmed remain the user's job (unset,
  forgot-password silently no-ops). Also, assigning onto db.auth.settings
  mutates a throwaway object (the getter builds a fresh one per access)
  and boot-time copying clobbers earlier singleton overrides — the
  "customizing callbacks" section now shows the working after-listen()
  wrap pattern.

Also: onHashPassword is declared but never invoked in 0.18 (use
Hash.algorithm; noted as non-functional); SESSION_SECRET is preferred,
not required (falls back to JWT_SECRET); three broken samples fixed
(htmlContents -> html, undefined `name` in User.insert, missing async on
an awaiting onJoin).

services and schemas pages verified fully clean.

Assisted-by: Claude Opus 4.8
Verified against colyseus-0.18 packages/monitor, loadtest, testing, admin,
database (v0.18.1). Highlights:

Tools:
- Monitor express-middleware mode: the docs claimed prefix is ignored; the
  monitor actually dispatches on originalUrl, so the mount path must match
  the prefix or everything silently 404s.
- Loadtest: default endpoint is ws:// not http://; onError's signature is
  (code, message), so err.message was always undefined.
- Unit-testing: samples used assert without importing it, and told readers
  to run Mocha/Jest tabs with vitest.

Admin (the serious ones):
- `roles: []` on a custom action was documented as "deny entirely" — it
  actually skips the role check and allows ANY authenticated identity.
  And a non-empty roles list does NOT implicitly include admins. Both now
  a warning; the security inversion is gone.
- The mod-role model was wrong in three places: mods have NO access
  outside assigned scopes (not "read everywhere"), never create/delete;
  user-role accounts can't use the panel UI at all. Policies REPLACE the
  default RBAC rule, not refine it.
- SessionConfig field names (ttlSeconds/cookieDomain/cookieSecure/
  cookieSameSite — docs showed maxAge/domain/sameSite/secure, and the
  cookie name is a fixed constant); only JWT_SECRET rotation invalidates
  admin sessions; reset emails ride onForgotPassword (not
  onResetPassword); reset links expire in 15 minutes, not 1 hour.
- Dashboard: health preset is a DB-latency ping (not process metrics),
  segments renders as KPI, spans are a 24-column antd grid, and half the
  suggested icon names don't exist in ADMIN_ICON_NAMES.
- Rooms: listing has no `unlisted` column; metadata lives on the detail
  page; state edits audit path+value (no before/after diff).

playground and tools index verified fully clean.

Assisted-by: Claude Opus 4.8
Verified against @colyseus/schema 5.0.8 (the version shipped in
colyseus-0.18), with the worst offenders runtime-confirmed. Highlights:

- Every JavaScript `schema()` tab used raw definitions ("string",
  [ "string" ], { map: Player }) — schema 5 accepts only t.* builders /
  Schema subclasses and THROWS on all documented forms, runtime-confirmed.
  All 10 JS tabs across state.mdx + schema.mdx rewritten to t.string(),
  t.array(...), t.map(...), t.set(...), t.collection(...); intro callout
  now states the builder requirement.
- defineTypes() is not "will be removed" — it IS removed; the legacy tab
  is now labeled as non-running historical reference.
- Type names are bigint64/biguint64 (the documented bigInt64 casing
  silently fails to encode); "number" limits are ±1.798e+308, not 5e±324.
- SetSchema sample deleted/checked string values on a set of numbers.
- Two TS samples used MapSchema without importing it; "cstring" is not a
  built-in string encoding.
- view.mdx: has() is a pure membership check (a copy-pasted callback
  sentence claimed otherwise); view.add(collection) brings existing
  children along — only later additions need individual adds.
- callbacks: codegen --help refreshed (adds --lua/--bundle/--decorator,
  fixes upstream "fhe" typo); Haxe unbind var-name mismatch; stray quote
  breaking the Unity codegen command.
- react: hooks ship today as @colyseus/react, not "being integrated".
- custom: refIds/refCounts don't exist in 5.0.8 — it's instance[$refId]
  and root.refCount, and getRawChangesCallback is now a built-in export
  instead of something to hand-roll.
- advanced-usage: $track sample was a syntax error; $encoder/$decoder
  samples read unbound variables and misnamed the class; imports fixed.

Assisted-by: Claude Opus 4.8
Twelve candidate framework fixes surfaced while verifying docs claims
against colyseus-0.18 v0.18.1 — behavior bugs (ignored QueueRoom option,
lobby metadata-only filters, non-revocation-aware HTTP middleware, the
admin roles:[] inversion), export/typing gaps, and cosmetics. Each is a
candidate upstream issue; the docs describe actual behavior meanwhile.

Assisted-by: Claude Opus 4.8
Accuracy edits left scar tissue; this pass restores read flow:

- auth/room: the instance-onAuth tab still verified a token — dead code by
  the page's own new precedence warning. Rewritten as what the hook is
  actually for (room-specific gates on token-less clients); also fixed a
  duplicated word in the static tab's callout.
- room.mdx / idle-kick: un-nested the double-parenthetical close-code
  sentences into plain prose.
- queue: shortened the in-cell option warning to one clause.
- unique-session: compressed the new guard comment to a single line, per
  inline-comment conventions.
- database/authentication: folded the trailing "(Note: ...)" parenthetical
  into the sentence.
- admin/resources: em-dashes in the empty Notes cells so the table doesn't
  read as half-finished.

Verified: check-links clean, 165/165 pages build.

Assisted-by: Claude Opus 4.8
…ment

Successor plan to sidebar-reorg.md. Tier 1: split the four collection
types (70% of state/schema.mdx, zero inbound anchors — cheapest split
yet) into state/schema/collections.mdx, de-nesting the Tabs-in-Tabs type
intros in the process. Tier 2: a reverse index from each
source-bugs-0.18.md item to the doc qualifiers that get DELETED when the
fix ships upstream. Tier 3 (parked): verify the non-JS SDK tabs against
the local Unity/Defold/Haxe checkouts.

Assisted-by: Claude Opus 4.8
The homepage feature list was four bullets of pre-0.16 messaging, two of
them generic infrastructure claims — while the differentiators the source
audit just verified went unmentioned. Now seven bullets:

- Prediction-Ready Netcode (the flagship 0.18 feature — built-in
  prediction, rollback, interpolation, lag compensation)
- Full-Stack Type Safety (Client<typeof server>: typed messages/state)
- Batteries Included (auth, database services, plugins, admin, dev tools)
- Any Engine (SDK breadth was only implied before)
- Rooms & Matchmaking now also points at the unified Matchmaking section;
  the two infra bullets merged into one Scale & Deploy entry.

Also: quickstart's onLeave showed `options` as the second parameter (it's
the close code — the audit fixed this everywhere but here, since the
homepage sat outside every audit cluster); room import aligned to the
`colyseus` package like the rest of the docs; "How does it look like?"
-> "What does it look like?"; meta description now mentions prediction.

Assisted-by: Claude Opus 4.8
Seven bold-link bullets rendered as a wall of underlined blue text. Now a
ScenarioGrid — the same card component the auth, matchmaking, and rooms
pages use — with icon + title per card and muted wrapping descriptions.

The inline links inside descriptions (database services, self-host, ...)
became plain text: ScenarioCard wraps the whole card in an anchor, so
nested links would be invalid HTML — and each card's own destination
already covers the section.

Assisted-by: Claude Opus 4.8
…onnection

The page said everything twice and owned content that belongs to the SDK
section. 432 -> ~320 lines:

- The entire client half (three event sections + Complete Client Example +
  Reconnection Options + Option Details + Message Buffering) duplicated
  sdk/connection.mdx — and had already drifted from it once (the options
  table needed the same audit fix in both places). Now one compact
  Client-Side snippet showing the three events, with a pointer to
  Connection Lifecycle & Reconnection for options, backoff, buffering,
  and manual reconnection. MovedAnchors shim covers the removed anchors.
- Server sections reordered to lifecycle order: onDrop ->
  allowReconnection (which onDrop calls) -> onReconnect -> onLeave.
  Previously allowReconnection was explained last, after onLeave.
- The blockquote Note became a Callout like the rest of the page; the
  close-codes table links to the full range table on /room; the buffering
  best-practice bullet links to where buffering now lives.

Zero inbound anchor links existed (verified), so the shim is external-link
insurance only. Content-preservation diff confirms every removal came from
the deduplicated client half. check-links clean, 165/165 build.

Assisted-by: Claude Opus 4.8
…uence diagram

The hand-drawn box diagram had drifted (emoji-width misalignment, two
boxes with different right-edge offsets) and was opaque to screen readers.
Nextra 3 bundles @theguild/remark-mermaid + mermaid 11, so a ```mermaid
fence renders natively — first use in these docs.

A sequence diagram is the correct form for this flow: two participants,
self-calls for the hooks, a loop for the backoff retries, and an alt
block for the success/failure fork — the same branch structure the
numbered overview above it describes. Renders theme-aware in dark mode
and scales instead of overflowing.

Verified the remark transform ran: the page chunk carries
chart:"sequenceDiagram..." into the Mermaid component (SSR emits a
client-rendered placeholder, so the static HTML intentionally shows no
trace). check-links clean, 165/165 build.

Assisted-by: Claude Opus 4.8
progression, not a message exchange

The sequence diagram was the wrong form: nearly every step is the
framework invoking a hook (a self-call), so the render degenerated into
six loopback arrows on two sparse lifelines with a single real
cross-arrow — tall, mostly whitespace, still confusing.

Now a five-node flowchart: connection lost -> onDrop on both sides (with
what each side does) -> the SDK retry loop as the one decision diamond ->
onReconnect or onLeave outcomes, stroke-colored green/red. Each node
pairs the client and server hooks that fire together, mirroring the
numbered overview's "on both sides" framing one-to-one.

Assisted-by: Claude Opus 4.8
…style

Decorators remain fully supported but move to their own child page
(/state/schema/decorators) with the tsconfig setup, @entity edge case,
and the defineTypes() legacy note. Schema snippets across the state
section, homepage and tutorials now show a single builder code block —
the TS/JS tab layer is gone, since builder code is identical in both.

Assisted-by: Claude Opus 4.8
Decorators are a big part of legacy apps, so instead of builder-only
snippets, every schema definition now has ["Schema Builder", "Decorators"]
tabs — builder first. A shared storageKey="schema-style" remembers the
selection across pages, same mechanism the old ts-or-js tabs used.
Builder-specific sections (SchemaType idiom, field modifiers) and the
versioning bodies stay single-snippet with decorator pointers.

Assisted-by: Claude Opus 4.8
Upstream revived it (schema-5.0 2a27125) with runtime and codegen
deprecation warnings — the removal only shipped in 5.0.0–5.0.8.

Assisted-by: Claude Opus 4.8
input() and clock are public members of the SDK Room class, but the
/sdk path never mentioned them — only the netcode pages did. Add a
pointer-style Sending Input section (matching the messages section
shape) and a clock row in the Room Reference table; the full reference
stays in /netcode/client-prediction. JS-SDK-only note included.

Assisted-by: Claude Opus 4.8
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.

1 participant