sendDefaultSanction passed `selectedTopic` (the index into the topics array) as
the CFH topic id to DefaultSanctionMessageComposer, so the emulator applied the
default sanction against whatever topic happened to sit at that array position.
The sibling sendSanction (and the topic label) correctly use
`topics[selectedTopic].id`. Pass `category.id`, and move the `category` lookup
after the `selectedTopic === -1` guard (+ a `!category` guard).
The ModTools template refresh introduced ~80 hardcoded English strings
(labels, placeholders, tooltips, empty-state copy, button text). Move
every one of them onto the modtools.* namespace and read via
LocalizeText so the panels translate alongside the rest of the client.
UITexts.example (versioned template) extended with the full set:
modtools.window.* Launcher box (toolbar item, tools,
selected-user state, ticket count)
modtools.userinfo.* User info card — already had the
legacy modtools.userinfo.{userName,
cfhCount, …} keys from before; added
refresh tooltip, presence pill labels
(in_room / online / offline with
matching .title tooltips), section
headings, action button labels, stat
card labels
modtools.roominfo.* Room info card — title, refresh, loading,
owner pill (here/away + tooltips), stat
labels, action buttons, moderate panel
heading + checkboxes + textarea
placeholder + caution/alert CTAs
modtools.user.message.* Send-message dialog (recipient label,
body label, placeholder, char counter,
empty state, send button)
modtools.user.modaction.* Mod Action form — header, sanctioning
label, 3-step section titles, select
placeholders, message label + optional
note, message placeholder, preview
heading, default/apply buttons, every
sendAlert error message
modtools.user.visits.* Room visits — title, header strip
heading, entry count (singular/plural),
empty state, column headers, visit
button + tooltip
modtools.user.chatlog.* User chatlog — title (with username
variant), loading state
modtools.room.chatlog.* Room chatlog title
modtools.chatlog.* Shared ChatlogView — column headers,
empty state, room-separator Visit/Tools
buttons
modtools.tickets.* Tickets window — title, tab labels
(open/mine/picked), column headers,
empty states, action buttons (pick/
handle/release), issue resolution
window (title, label, details heading,
field labels, chatlog toggle, resolve-as
heading, resolution buttons, release
back to queue), CFH chatlog title
The same 130 entries land in Nitro-Files/.../UITexts.json (runtime).
Both files validate as JSON. The runtime additions take effect on
next client reload; the template additions ship the strings to any
fresh deploy.
Notes:
- The MOD_ACTION_DEFINITIONS sanction names ("Alert", "Mute 1h",
"Ban 18h" …) stay hardcoded for now since they're keyed off
server-side action IDs that don't have an existing locale key
convention. Worth a follow-up if needed.
- help.cfh.topic.* keys (CFH topic display names) are already in
ExternalTexts.json and were already read via LocalizeText, so
they didn't need changes.
typecheck + vitest 214/214 + lint:hooks all clean.
Applies the visual language introduced in ModToolsUserView yesterday
to every other ModTools window. The design tokens used consistently:
emerald — present in current room / positive state
sky — online / informational / current selection
zinc — neutral / disabled
amber — warn-level (CFH, alerts, cautions)
rose — danger (bans, releases, abusive)
Files redesigned:
ModToolsRoomView
Identity header with FaDoorOpen, room name + ID, owner-present pill
(emerald/zinc), manual refresh button. Stat strip: user count (sky)
+ clickable owner name (zinc) opening user info. Quick actions
(Visit / Chatlog) in a 2-col grid. Moderate panel collapsed into an
amber-tinted card with the 3 toggles + textarea + two CTAs (Send
Caution=danger, Send Alert=warning). CTAs disabled until a message
is typed AND the room info has loaded.
ModToolsUserModActionView
Numbered 3-step form (CFH topic → sanction → optional message).
Live preview row showing the chosen topic + sanction as tone-coded
pills (amber/sky/rose/orange/fuchsia/zinc by action type). Primary
CTA = Default Sanction, success CTA = Apply Sanction, both
disabled until the required selections are made.
ModToolsUserSendMessageView
Recipient header with FaEnvelope and the username, autofocused
textarea, char counter, single full-width Send button gated on
non-empty message.
ModToolsUserRoomVisitsView
Header strip with entry count badge, three-column grid (time / room
name / visit button), monospace timestamps, hover row highlight,
empty state with FaDoorOpen icon.
ModToolsUserChatlogView / ModToolsChatlogView / CfhChatlogView
Loading state with spinner instead of returning null. Cards grow to
min-w-[460px] max-w-[520px] max-h-[500px] for usable chatlog area.
ChatlogView
Replace Bootstrap-ish striped table with a CSS grid (60px / 120px /
1fr). Room-info separator rendered as a sky card with Visit/Tools
pill buttons. Per-row hover + even-row tint; highlighted rows
(hasHighlighting) get an amber wash. Username is a button opening
user info via existing link event. Empty state with FaCommentDots.
ModToolsTicketsView
Tabs get icons (FaListUl / FaUserCheck / FaCheckSquare) and inline
count badges (amber/sky/zinc) so the moderator sees the queue size
at a glance. ticket bucket filtering memoized off the tickets array.
ModToolsOpenIssuesTabView / MyIssuesTabView / PickedIssuesTabView
Same CSS grid table style. Category renders as a tone-coded pill
(Open=amber, Mine=sky, All picked=zinc). Action buttons get icons
(FaHandPointer Pick, FaTools Handle, FaSignOutAlt Release). Empty
state with FaInbox.
ModToolsIssueInfoView
Card header with category + topic pills. Details rendered as a dl
grid instead of a striped table. Caller / Reported names as inline
link buttons with external-link icon. Chatlog toggle is full-width
secondary. Resolution buttons in a 3-col grid with intent colours
(success=Resolved, dark=Useless, danger=Abusive) + a separate
Release-to-queue button on its own row so it isn't confused with
the resolutions.
No behaviour changes — all composers, message events, parent state
hookups, and sanction validation paths are unchanged. This is purely
a presentation pass. typecheck + vitest 214/214 + lint:hooks all
clean.
Replace ~70 hardcoded English strings across 15 mod-tools files
with LocalizeText() calls using moderation.* keys matching the
existing ExternalTexts convention. Includes mod-tools-external-texts.json
with all required keys for ExternalTexts.json.