feat(strings): the app's words for every game (M20) #58

Merged
whitlocktech merged 5 commits from feat/app-vocabulary into edge 2026-10-23 01:50:51 -05:00
2 changed files with 35 additions and 2 deletions
Showing only changes of commit bb9cd9008d - Show all commits

View File

@@ -196,8 +196,21 @@ val APP_MENU: List<MenuEntry> = listOf(
// Staff operations (§1, M10) — revealed for staff roles; the backend re-checks every call.
MenuEntry(Routes.ADMIN_DASHBOARD, R.string.menu_admin_dashboard, MenuAccess.STAFF),
MenuEntry(Routes.ADMIN_CONTENT, R.string.menu_admin_content, MenuAccess.STAFF),
MenuEntry(Routes.ADMIN_MODERATION, R.string.menu_admin_moderation, MenuAccess.MODERATOR),
MenuEntry(Routes.ADMIN_SUPPORT, R.string.menu_admin_support, MenuAccess.MODERATOR),
// Moderation and the support queue act on the shard (`/admin/shard/*`, module-uo's
// admin mount), so they need the module installed like the player rows do. On a
// Rust-only site they once showed a UO form whose every action failed (M20).
MenuEntry(
Routes.ADMIN_MODERATION,
R.string.menu_admin_moderation,
MenuAccess.MODERATOR,
capability = Capability.SHARD,
),
MenuEntry(
Routes.ADMIN_SUPPORT,
R.string.menu_admin_support,
MenuAccess.MODERATOR,
capability = Capability.SHARD,
),
)
/**

View File

@@ -168,6 +168,26 @@ class MenuCapabilityGatingTest {
}
}
@Test fun moderationAndSupportNeedTheShardModule() {
// Both call `/admin/shard/*`, module-uo's admin mount. Before M20 they
// carried no capability, so a moderator on a Rust-only site saw a UO form
// whose every action failed.
val staffRows = APP_MENU.filter { it.route == Routes.ADMIN_MODERATION || it.route == Routes.ADMIN_SUPPORT }
assertEquals(2, staffRows.size)
assertTrue(staffRows.all { it.capability == Capability.SHARD })
val moderator = signedIn(Role.MODERATOR)
val rustOnly = visibleEntries(APP_MENU, moderator, null, serving(Capability.RUST)).map { it.route }
assertFalse(Routes.ADMIN_MODERATION in rustOnly)
assertFalse(Routes.ADMIN_SUPPORT in rustOnly)
// …while the core staff rows stay.
assertTrue(Routes.ADMIN_DASHBOARD in rustOnly)
val uo = visibleEntries(APP_MENU, moderator, null, serving(Capability.SHARD)).map { it.route }
assertTrue(Routes.ADMIN_MODERATION in uo)
assertTrue(Routes.ADMIN_SUPPORT in uo)
}
@Test fun bothEventRowsDeclareCoresCapabilityAndNoFeature() {
// Events are core's. A `feature` on one of them would gate a core screen on
// a module's visibility config, which is the coupling this separation exists