The module's half of the org lead's correction: Teams is the contract, guilds are the presentation, and the presentation is this module's. Adds `/uo/guilds/:id` — the detail view the board never had — with the roster from this module's OWN board, which is the same data it answers core's Team provider from. Reading core's projection of our own answer back would be a round trip through a staler copy of it. The page declares `uo.guild.detail` and core fills it with the Team activity feed. That is the one part of this page core cannot hand over: only core can resolve whether the viewer is inside the Team, and the public/members split on that feed is a security boundary. The guild is named in OUR terms — core maps its own Team from the module id and the external id — so this module never holds core's row id or slug. `TeamOverviewStrip` is deleted with the core Team page it filled. `team.member.row` is not declared here either: the useful thing to put in a roster row is a link to the character behind it, and nothing core could supply identifies one. `GET /public/shard/guilds/:id` backs the page, gated and projected through the same `guilds` feature as the board — so an operator who raises that audience raises this too, and the locked acct/webId fields never survive below admin. A roster is where those appear in bulk, which makes this the endpoint where getting the projection wrong would matter most. Co-Authored-By: Claude <noreply@anthropic.com>
78 lines
3.9 KiB
JavaScript
78 lines
3.9 KiB
JavaScript
// ── What core hands this module, on the client side ────────────────────────
|
|
//
|
|
// The client twin of `server/core.js`, and deliberately much simpler than it.
|
|
// Every ported page imports its layout, its state components and its hooks from
|
|
// here, so the boundary is one file and `client/scripts/checkExternals.js` has
|
|
// one place to look. The normative contract is MODULE_API.md §3.2 and §3.4.
|
|
//
|
|
// **Why this is a plain read and the server's is a lazy accessor.** On the
|
|
// server, `ctx` arrives at `register(ctx)` — after every `require` has already
|
|
// run — so `server/core.js` has to defer resolution to call time or a router
|
|
// would capture `undefined` at file scope. There is no such gap here.
|
|
// `window.__rg` is published by core's own bundle (client/src/modules/shared.js),
|
|
// and every module chunk is a deferred script the server injects *after* that
|
|
// bundle's tag, so by the time the first line of this file executes the global
|
|
// is already there. Reading it once, at module scope, is safe — and it means a
|
|
// ported component keeps the ordinary `import { PageHeader } from '…'` shape
|
|
// rather than being wrapped in an accessor that would cost it its identity.
|
|
//
|
|
// The absent-global case is handled by `shim/rg.js`, which every shim beside it
|
|
// also goes through — the shims touch the global before this file does, so a
|
|
// check here would be unreachable.
|
|
|
|
import { createElement } from 'react'
|
|
import { createRoot } from 'react-dom/client'
|
|
import { Link } from 'react-router-dom'
|
|
import { rg as shared } from './shim/rg.js'
|
|
|
|
const rg = shared()
|
|
|
|
// ── The shared-dependency self-check ───────────────────────────────────────
|
|
//
|
|
// Slice 0 carried this in entry.jsx, back when nothing else imported React and
|
|
// an unexercised alias was an unproven one. The aliases are thoroughly exercised
|
|
// now — thirty-five files import React and ten import the router — so what is
|
|
// left for a runtime check to do is narrower, and worth keeping for exactly that
|
|
// reason: the two BUILD guards (`assertSharedNotBundled` at resolution time,
|
|
// `checkExternals.js` on the artifact) both reason about the chunk in isolation,
|
|
// and neither can see the one failure that only exists once the chunk meets a
|
|
// core: a `window.__rg` whose React is not the React that rendered the page.
|
|
//
|
|
// Identity is the only question worth asking. A second React satisfies every
|
|
// type check, renders its first element happily, and then throws about an invalid
|
|
// hook call somewhere unrelated.
|
|
if (createElement !== rg.react.createElement || createRoot !== rg.reactDom.createRoot || Link !== rg.router.Link) {
|
|
console.error(
|
|
'[module-uo] the bindings this chunk imported are not the ones core published — it has bundled ' +
|
|
'its own copy of a shared dependency. Check the aliases in vite.config.js (MODULE_API.md §3.6).',
|
|
)
|
|
}
|
|
|
|
// The curated kit (§3.4). Eight members, closed: anything else this module needs
|
|
// it bundles itself, which is why `components/` next door exists at all.
|
|
export const {
|
|
PublicLayout,
|
|
PageHeader,
|
|
Loading,
|
|
ErrorState,
|
|
EmptyState,
|
|
useAsync,
|
|
useAuth,
|
|
useSite,
|
|
// Eighth member (MODULE_API 1.6.0): the slot renderer, for the INVERTED
|
|
// direction — this module declares a place on its own page and CORE fills it.
|
|
// Shared rather than reimplemented so core's content failing inside our page is
|
|
// contained by core's own error boundary.
|
|
Slot,
|
|
} = rg.ui
|
|
|
|
// The registry, for entry.jsx. Everything else here is read by pages.
|
|
export const registry = rg.registry
|
|
|
|
// The core API version this module was loaded against. Logged by entry.jsx —
|
|
// `module.json`'s `coreApi` range is checked by the loader before this file is
|
|
// ever served, so there is nothing to re-check, only something to report.
|
|
export const coreApiVersion = rg.version
|
|
|
|
export default rg
|