Images are now moderated by a local NudeNet service; explicit uploads
get 422 nsfw_image. Map that to a clear 'image rejected: explicit
content' notice instead of a raw error code.
Server now fails closed when ClamAV can't scan (refuses with 503
scanner_unavailable instead of storing an unverified file). Map that to
a friendly 'virus scanning temporarily unavailable' notice instead of a
raw error code.
The module sends the token line first, then 'No need to log in again' —
which the previous heuristic matched as not-identified. Match the real
guest line ('must be logged in to use file hosting') instead, and also
swallow it (it lacks the FILEHOST prefix).
The automatic /FILEHOST command behind image/voice uploads emitted help
notices that leaked into chat once the token line resolved. Suppress all
FILEHOST notices unconditionally (still extract the token when an upload
is pending), and tighten not-identified detection so it no longer matches
the 'Your logged in account: …' success line.
Record a voice message in the composer (MediaRecorder → opus/webm) and
share it through the same /FILEHOST upload flow as images; received audio
URLs render as an inline player. Works in channels and PMs.
Each game (chess / Connect 4 / tic-tac-toe) now has its own rank, points
and record. The panel shows a rank row per game played, the leaderboard
gains a per-game tab, and the profile chip shows a player's strongest
game. Reads the per-type GS$ / GS$TOP lines GameServ now sends.
GameServ now pushes board/stats state in a +tchatou.fr/gs tag on a
bodiless TAGMSG instead of a GS# NOTICE, so plain IRC clients never see
the machine lines and only get the human-readable NOTICE. The web client
reads the tag to drive the UI and hides GameServ's NOTICEs from chat.
chess, tic-tac-toe and Connect Four played against the GameServ services
referee (it validates moves + keeps the account-keyed Bronze→Master
ladder); the client only sends commands and renders the pushed state.
adds a generic plugin message-filter so a service's machine lines stay
out of chat.
filing a report echoed our REPORT command into a ReportServ PM buffer
and ReportServ's confirmation landed in the active channel. route both
(by sender/recipient = report.service) to Status instead, and put the
optimistic confirmation there too.
the report action opened a hardcoded PRIVMSG to #staff, which a +n
staff channel silently drops for non-members. now it opens a report
window (target + reason) and sends REPORT to the ReportServ service,
which queues it for staff and isn't blocked by +n. config gains
report.service (default ReportServ); empty falls back to the channel.
the Home list's Status row now shows where you're connected (the ircd
hostname) instead of the generic "System", falling back to System
until 004 arrives.
rename the server/console window to Status everywhere and render its
title as "Status: <nick> [+<umodes>] on <servername>". track the ircd
hostname from RPL_MYINFO (004) and query our user modes on register so
the line is populated.
The Tchatou entry form now sends visitors to /app/?nick=&channel= and parks
any SASL password in same-origin sessionStorage (kept out of the URL). On
boot we consume that one-shot marker and connect right away, so a visitor who
already chose a pseudo on the site never sees the join form a second time.
A new autoConnecting flag shows a small splash while the handoff connects;
any failure clears it and falls back to the join form with the nick and
channel still prefilled from the URL.
A render error anywhere previously unmounted the whole tree into a blank
page. AppErrorBoundary now wraps the app and shows a localized recovery
screen with a reload button, logging the error. Mirrors the proven
PluginBoundary pattern.
Off-screen member rows now skip layout & paint via content-visibility,
with contain-intrinsic-size for correct scrollbar sizing (yomirc gets a
smaller estimate). A 2k-member panel only renders the visible rows; small
channels are unchanged. Together with the earlier per-message re-render
fixes, this closes the large-channel lag (layout/paint + re-render cost).
Was subscribing to the whole active buffer (new object on every message)
and recounting members each time. Now selects name/isChannel/topic/modes/
members individually — re-renders only when those change, not on each line.
Audited the rest: MessageList must track messages (capped 500, virtualise
later); Sidebar is bounded; the typing indicator is trivial and benefits
from frequent renders; modal selectors only run while open.
Subscribe to buffer.members by reference (updated immutably) instead of
the whole active buffer, and memoise the role grouping + name sort. The
list now recomputes only when membership/mode/filter actually changes —
not on each incoming line, which was O(members) work per message in big
channels.
MessageInfo.text is now the human-readable text with mIRC formatting/
colour codes stripped, so orbit-copy copies clean text instead of raw
control codes; the original is available as MessageInfo.raw.
New addMessageAction() puts a plugin button in each message's hover
toolbar next to reply/react (inherits its styling), distinct from the
inline addMessageDecorator. orbit-copy now lives there with its green
check-on-copy feedback — tidier than a copy icon on every line.
Plugin entries may now be { url, integrity, crossorigin } so off-origin
plugins can be pinned with Subresource Integrity (crossorigin defaults to
anonymous when a hash is set). Documents a sample CSP header and SRI hash
generation in SECURITY.md; the policy is a header (deployment-specific
origins) rather than a baked meta tag.
Adds a clear :focus-visible keyboard ring for buttons/links/menu items,
a skip-to-messages link, landmark labels on the message log (role=log),
conversation sidebar and member list, and aria-labels on the emoji-only
message action buttons. New a11y i18n namespace across all locales.
Adds topbar_item and sidebar_item slots, plus addMessageDecorator() —
a per-message hook handed a stable read-only view {id,nick,text,kind,
ts,mine}, each rendered inside its own error boundary. Ships orbit-clock
(topbar) and orbit-copy (decorator) examples and documents both.
Replaces the binary mute with a three-way level stored per channel
(legacy muted channels migrate to Mute). The topbar bell opens an
accessible popover; the notify decision honours the level — All alerts
on every line, Mentions only on your nick/highlights, Mute never.
Global hotkeys (chat view): Ctrl/Cmd-K switcher, Alt+Up/Down to cycle
conversations, Shift+Esc to mark everything read, ? for the help sheet.
Composer arrow handler now ignores Alt so buffer-nav passes through.