diff --git a/client/src/entry.jsx b/client/src/entry.jsx
index 3a8d55f..3b78cf0 100644
--- a/client/src/entry.jsx
+++ b/client/src/entry.jsx
@@ -18,7 +18,7 @@
// size and it is the one thing this seam cannot have.
import { registry, coreApiVersion } from './core.js'
-import { IconShard } from './icons.jsx'
+import { IconShard, IconUser } from './icons.jsx'
import { useShardFlags } from './lib/useShardFeatures.js'
// Public pages — the twelve that used to live at /site/*.
@@ -159,7 +159,7 @@ registry.registerNav(ID, {
area: 'player',
// Order 0: Characters is the portal's first row today, and with the module
// installed it is also what core's `/player` index resolves to.
- items: [{ label: 'Characters', to: '/player/uo/characters', order: 0 }],
+ items: [{ label: 'Characters', to: '/player/uo/characters', icon: IconUser, order: 0 }],
})
// ── Feature provider ───────────────────────────────────────────────────────
diff --git a/client/src/icons.jsx b/client/src/icons.jsx
index 76242eb..f833945 100644
--- a/client/src/icons.jsx
+++ b/client/src/icons.jsx
@@ -35,4 +35,32 @@ export const IconShard = () => (
)
+/**
+ * A figure — the glyph core used for the portal's "Characters" row.
+ *
+ * A second icon rather than reusing IconShard, because these two rows sit in
+ * different navs and each matched its neighbours before the extraction: the
+ * admin sidebar's UO rows were all gems, and the portal's Characters row was a
+ * person beside Appeals' shield and Account's gear. Copied from core's
+ * PlayerPortalLayout, which uses a 16px frame and a heavier stroke than the
+ * admin one — matching the nav a row lands in is the whole reason `icon` exists.
+ */
+export const IconUser = () => (
+
+)
+
export default IconShard
diff --git a/client/test/registration.test.js b/client/test/registration.test.js
index 0a640ad..24e536f 100644
--- a/client/test/registration.test.js
+++ b/client/test/registration.test.js
@@ -139,9 +139,21 @@ it('every nav row points at a route this module actually registered', () => {
}
})
-it('admin nav rows carry an icon; core rows all have one and a text-only row reads as breakage', () => {
- for (const row of registered.nav.admin) {
- assert.equal(typeof row.icon, 'function', `admin nav row "${row.label}" has no icon`)
+it('every admin and player nav row carries an icon', () => {
+ // Both of those navs render a glyph on every core row, so a row without one
+ // reads as breakage rather than as a design. The PUBLIC header is text
+ // buttons and is deliberately excluded.
+ //
+ // The player half of this assertion is not symmetry for its own sake. Core's
+ // PlayerPortalLayout rendered `` UNGUARDED — fine for as long as
+ // every row in it was core's own and had one, and React error #130 with a
+ // blank portal the moment a module registered one without. Core is guarded
+ // now, but a missing icon there is still a visible defect and this is the
+ // cheap place to catch it.
+ for (const area of ['admin', 'player']) {
+ for (const row of registered.nav[area]) {
+ assert.equal(typeof row.icon, 'function', `${area} nav row "${row.label}" has no icon`)
+ }
}
})