Files
Module-uo/client/src/lib/format.js
wtclaude 28f4b9afe2 feat(client): the whole client half (phase 3, slice 3)
The 35 files behind twelve public pages, seven admin views, two player views
and three core-page extensions, ported onto `window.__rg`. Every one of them
imports exactly the seven kit members plus `lib/format.js`, which is the
finding §2.7.1 predicted and this confirms.

`client/src/core.js` is the port mechanism, and unlike the server's it is a
plain read: `window.__rg` is published before any module chunk evaluates, so
there is no gap to defer around and a ported component keeps its ordinary
import shape. `client/src/api.js` rebuilds the UO namespaces over the request
primitive — same URLs, because §1.2 freezes the API surface.

SPA paths changed and API paths did not. `/site/shard` is `/uo/shard`, and the
admin paths lost their now-redundant `shard-` prefixes (`/admin/uo/ops`), a
clean break being the only moment that is free.

`shim/rg.js` becomes the single reader of the global, so the "core did not
publish its dependencies" message is reachable from whichever module the
bundler happens to touch first rather than from whichever one is imported
first — a guarantee that used to last until someone sorted the imports.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-11 18:00:25 -05:00

37 lines
1.5 KiB
JavaScript

// A vendored copy of the one helper this module uses from core's
// `client/src/lib/format.js`.
//
// **Vendored rather than added to the kit, and trimmed rather than copied
// whole.** The kit is curated and closed (MODULE_API.md §3.4): every member
// added to it is a minor version bump core can never take back, and a date
// formatter is not the kind of thing a module should be unable to write. Copying
// all six of core's helpers to get one would leave five with no consumer here
// and a standing question about which copy is authoritative.
//
// The vendoring line, from the server half of the extraction (slice 1): **pure
// leaf helpers may be copied, security controls may not.** This is a pure leaf.
// Core's HTML sanitiser sits two files away and stays exactly where it is.
//
// The two copies will drift, and that is correct — core's is core's to change.
// Nothing here reads a shared format.
function parse(value) {
if (!value) return null
const d = new Date(value)
return isNaN(d.getTime()) ? null : d
}
/** "3m ago". Coarse on purpose: the live feed's timestamps are approximate. */
export function ago(value) {
const d = parse(value)
if (!d) return ''
const secs = Math.max(1, Math.floor((Date.now() - d.getTime()) / 1000))
if (secs < 60) return `${secs}s ago`
const mins = Math.floor(secs / 60)
if (mins < 60) return `${mins}m ago`
const hrs = Math.floor(mins / 60)
if (hrs < 24) return `${hrs}h ago`
const days = Math.floor(hrs / 24)
return `${days}d ago`
}