ModToolsChatlogView and CfhChatlogView were on the useNitroQuery
pattern. Symptom: the card opens, the spinner spins, but the data
never arrives — even when the server is correctly answering with
ModToolRoomChatlogComposer (header 3434) and GetCfhChatlogComposer
(607). Both header IDs match the renderer's Incoming map, both server
handlers gate only on ACC_SUPPORTTOOL and reply unconditionally when
the room/issue lookup succeeds. So the request DOES go out and the
response DOES come back — but useNitroQuery's listener (registered
via `new (ParserCtor)(callback)` + `registerMessageEvent`) isn't
delivering the event to the React side here.
ModToolsUserChatlogView already uses the plain `useMessageEvent` +
`useEffect(sendComposer)` pattern and works on this same setup, so
align the two broken views with it. Keep the loading-spinner empty
state introduced yesterday so the user still gets visible feedback
while the response is in flight.
This sidesteps useNitroQuery for these two cases rather than fixing
it in place — the underlying createNitroQuery + listener registration
plumbing still works for OfferView, useUserGroups, useClubOffers,
useSellablePetPalette, useMarketplaceConfiguration, useClubGifts,
CatalogLayoutRoomAdsView, so the regression is specific to these two
parsers and worth investigating separately. Filed as a follow-up.
ModToolsChatlogView returned null whenever roomChatlog was undefined
— including the entire window between click and server response (up
to a 15-second NitroQuery timeout). Result: clicking the Chatlog
button in the launcher or in Room Info appeared to do nothing at all
on any session where the server reply was slow or the accept-filter
correlation didn't match.
The other two chatlog wrappers (ModToolsUserChatlogView,
CfhChatlogView) already render a spinner while data is loading after
yesterday's redesign — this view was the one I missed.
Apply the same fix: always render the NitroCardView, and show the
FaSpinner loading state inside until useNitroQuery resolves.
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.
Many composer/parser pairs on the Nitro wire are correlation-key based:
the request carries a key (roomId, issueId, etc.) and the response shows
up on the globally-shared event bus, where other components may be
listening for the same parser type with a different key. The previous
useNitroQuery resolved on the FIRST matching parser event regardless of
key — useless for that pattern, which is why two obvious migration
targets (ModToolsChatlogView, CfhChatlogView) were skipped earlier.
Adapter change
- New optional `accept?: (event) => boolean` on NitroQueryConfig.
- In awaitNitroResponse, events for which accept returns false are
IGNORED rather than resolving the promise. The listener stays
registered, the timeout still applies. This lets callers do:
accept: e => e.getParser()?.data.roomId === roomId
Migrations
- src/components/mod-tools/views/room/ModToolsChatlogView.tsx
- Was: useState<ChatRecordData>(null) + useMessageEvent with
`if (parser.data.roomId !== roomId) return; setRoomChatlog(...)` +
a mount-only useEffect dispatching the composer.
- Now: a single useNitroQuery call keyed on roomId; accept filters
by roomId; the query is enabled only when roomId is set.
The composer is no longer re-dispatched on remount within
staleTime; switching to a different room still triggers a fresh
fetch because the queryKey changes.
- src/components/mod-tools/views/tickets/CfhChatlogView.tsx
- Same pattern, keyed on issueId.
Both migrations drop ~15 lines per file (no more local state + manual
listener + manual send) while gaining cache/dedup/loading/error
handling from TanStack Query.
Verification
- yarn eslint on the four files: 1 pre-existing error (the
IMessageEvent "redundant union" false positive in createNitroQuery
that we already documented — local sandbox doesn't have the
renderer SDK installed, so its types resolve as `any`).
- yarn test: 49/49 passing.
- yarn tsc on the four files: clean.
Run eslint --fix across src/ to clear ~1900 mechanical lint errors
surfaced by the @typescript-eslint v8 + react-hooks v7 + react-compiler
upgrade in the React 19 modernization PR.
Issues fixed automatically:
- brace-style (Allman): try/catch one-liners reformatted to multi-line
- indent: tab-vs-space and depth corrections
- semi: missing trailing semicolons
- no-trailing-spaces
No semantic changes. Remaining 701 errors are real-code issues
(set-state-in-effect, rules-of-hooks, no-unsafe-* type checks) that
need manual per-file review.
https://claude.ai/code/session_01GrR87LAqnAEyKG2ZbmQt5Q
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.
- Fix icon alignment using flexbox instead of absolute positioning
- Add active state indicators on buttons when sub-panels are open
- Add min-width constraints to prevent cramped layouts
- Improve user button with placeholder text and truncated username
- Improve room info panel with better spacing, clickable owner, colored owner status
- Improve chatlog with scrollable container, alternating row colors, compact headers
- Clean up room info header and room name display