spectator nicknames, overseer common issue filtering

This commit is contained in:
legop3
2026-06-07 15:21:42 -04:00
parent 097d23dc66
commit 124cdc0c64
7 changed files with 34 additions and 25 deletions
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -11,7 +11,7 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Roomba Rover" /> <meta name="apple-mobile-web-app-title" content="Roomba Rover" />
<title>Roomba Rover</title> <title>Roomba Rover</title>
<script type="module" crossorigin src="/assets/index-CXr-XVVB.js"></script> <script type="module" crossorigin src="/assets/index-zQNOJD7V.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DaajoH22.css"> <link rel="stylesheet" crossorigin href="/assets/index-DaajoH22.css">
</head> </head>
<body> <body>
@@ -100,10 +100,21 @@ function buildModelMessages({
}); });
(conversationMessages || []).forEach((message) => { (conversationMessages || []).forEach((message) => {
if (!message || !message.role || !message.content) return; if (!message || !message.role || !message.content) return;
message.content.replace("sourceMapping", ""); const content = String(message.content || '')
message.content.replace("SourceMapping", ""); // The overseer can sometimes write plain-text lines that look like tool
message.content.replace("sourcemapping", ""); // calls instead of returning real structured tool_calls to Ollama. If
messages.push(message); // those fake calls are replayed in later context, the model sees its own
// malformed pattern as chat history and tends to repeat it. This removes
// any whole line shaped like "(tool_name {"json": "payload"})" regardless
// of the invented tool name, while leaving normal prose and legitimate
// structured tool metadata untouched.
.replace(/^\s*\([a-zA-Z_][a-zA-Z0-9_]*\s+\{.*\}\)\s*$/gm, '')
.replace(/sourceMapping/g, '')
.replace(/SourceMapping/g, '')
.replace(/sourcemapping/g, '')
.trim();
if (!content) return;
messages.push({ ...message, content });
}); });
return messages; return messages;
} }
+5 -7
View File
@@ -2,29 +2,28 @@
// Purpose: Defines the Nickname Form module and the local helpers/components used in this file. // Purpose: Defines the Nickname Form module and the local helpers/components used in this file.
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit. // Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useSessionActions, useSessionSelector } from '../../context/SessionContext.jsx'; import { useSessionActions } from '../../context/SessionContext.jsx';
import { useSettingsNamespace } from '../../settings/index.js'; import { useSettingsNamespace } from '../../settings/index.js';
export default function NicknameForm({ compact = false }) { export default function NicknameForm({ compact = false }) {
const role = useSessionSelector((state) => state.session?.role || null);
const { setNickname } = useSessionActions(); const { setNickname } = useSessionActions();
const { value, save } = useSettingsNamespace('profile', { nickname: '' }); const { value, save } = useSettingsNamespace('profile', { nickname: '' });
const [nicknameInput, setNicknameInput] = useState(value.nickname || ''); const [nicknameInput, setNicknameInput] = useState(value.nickname || '');
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const canSetNickname = role !== 'spectator';
useEffect(() => { useEffect(() => {
setNicknameInput(value.nickname || ''); setNicknameInput(value.nickname || '');
}, [value.nickname]); }, [value.nickname]);
async function handleSave(event) { async function handleSave(event) {
event.preventDefault(); event.preventDefault();
if (!canSetNickname) return;
const trimmed = (nicknameInput || '').trim().slice(0, 32); const trimmed = (nicknameInput || '').trim().slice(0, 32);
if (!trimmed) return; if (!trimmed) return;
setSaving(true); setSaving(true);
try { try {
// Nicknames are identity metadata rather than rover-control permission.
// Spectator pages use the same socket action and local profile storage as
// the driver UI so chat/user-list labels stay consistent across routes.
await setNickname(trimmed); await setNickname(trimmed);
save({ nickname: trimmed }); save({ nickname: trimmed });
} catch (err) { } catch (err) {
@@ -47,12 +46,11 @@ export default function NicknameForm({ compact = false }) {
}} }}
maxLength={32} maxLength={32}
placeholder="Enter a nickname" placeholder="Enter a nickname"
disabled={!canSetNickname}
/> />
<button <button
type="button" type="button"
onClick={handleSave} onClick={handleSave}
disabled={!canSetNickname || saving} disabled={saving}
className="button-dark h-full shrink-0 whitespace-nowrap px-0.5 py-0 disabled:opacity-50" className="button-dark h-full shrink-0 whitespace-nowrap px-0.5 py-0 disabled:opacity-50"
> >
{saving ? 'Saving…' : compact ? 'Set' : 'Save'} {saving ? 'Saving…' : compact ? 'Set' : 'Save'}
@@ -36,10 +36,8 @@ export default function RawUserPilePanel({
fillHeight = false, fillHeight = false,
compact = false, compact = false,
}) { }) {
const role = useSessionSelector((state) => state.session?.role || null);
const users = useSessionSelector((state) => state.session?.users ?? []); const users = useSessionSelector((state) => state.session?.users ?? []);
const selfId = useSessionSelector((state) => state.session?.socketId || null); const selfId = useSessionSelector((state) => state.session?.socketId || null);
const canSetNickname = role !== 'spectator';
const sorted = useMemo( const sorted = useMemo(
() => () =>
@@ -76,7 +74,6 @@ export default function RawUserPilePanel({
</div> </div>
<SocialButtonsGrid /> <SocialButtonsGrid />
</div> </div>
{!canSetNickname && <p className="text-xs text-slate-500">Spectators cannot set nicknames.</p>}
</div> </div>
)} )}
@@ -56,13 +56,11 @@ export default function UserListPanel({
compact = false, compact = false,
showBothTurnsAndUsers = false, showBothTurnsAndUsers = false,
}) { }) {
const role = useSessionSelector((state) => state.session?.role || null);
const users = useSessionSelector((state) => state.session?.users ?? []); const users = useSessionSelector((state) => state.session?.users ?? []);
const selfId = useSessionSelector((state) => state.session?.socketId || null); const selfId = useSessionSelector((state) => state.session?.socketId || null);
const mode = useSessionSelector((state) => state.session?.mode || null); const mode = useSessionSelector((state) => state.session?.mode || null);
const turnQueues = useSessionSelector((state) => state.session?.turnQueues || {}); const turnQueues = useSessionSelector((state) => state.session?.turnQueues || {});
const roster = useSessionSelector((state) => state.session?.roster || []); const roster = useSessionSelector((state) => state.session?.roster || []);
const canSetNickname = role !== 'spectator';
const isTurnsMode = mode === 'turns'; const isTurnsMode = mode === 'turns';
const [turnView, setTurnView] = useState('queues'); const [turnView, setTurnView] = useState('queues');
@@ -168,7 +166,6 @@ export default function UserListPanel({
</div> </div>
<SocialButtonsGrid /> <SocialButtonsGrid />
</div> </div>
{!canSetNickname && <p className="text-xs text-slate-500">Spectators cannot set nicknames.</p>}
</div> </div>
)} )}
@@ -4,6 +4,7 @@
import { useSession } from '../../context/SessionContext.jsx'; import { useSession } from '../../context/SessionContext.jsx';
import { useSpectatorMode } from '../../hooks/useSpectatorMode.js'; import { useSpectatorMode } from '../../hooks/useSpectatorMode.js';
import useDefaultNickname from '../../hooks/useDefaultNickname.js'; import useDefaultNickname from '../../hooks/useDefaultNickname.js';
import useUserIdentitySync from '../../hooks/useUserIdentitySync.js';
import ChatPanel from '../../components/ChatPanel/index.jsx'; import ChatPanel from '../../components/ChatPanel/index.jsx';
import AlertFeed from '../../components/AlertFeed/index.jsx'; import AlertFeed from '../../components/AlertFeed/index.jsx';
import GlobalObjectiveBanner from '../../components/GlobalObjectiveBanner/index.jsx'; import GlobalObjectiveBanner from '../../components/GlobalObjectiveBanner/index.jsx';
@@ -20,6 +21,11 @@ export default function SpectatorContent() {
const { session } = useSession(); const { session } = useSession();
const inLockdown = session?.mode === 'lockdown'; const inLockdown = session?.mode === 'lockdown';
useDefaultNickname(); useDefaultNickname();
// The spectator route is not rendered through App.jsx, so it must opt into
// the same persisted identity heartbeat here. That keeps the existing
// cookie-backed identity and saved nickname behavior active without adding a
// separate spectator identity path.
useUserIdentitySync();
useSpectatorMode(); useSpectatorMode();
const isPortraitLayout = usePortraitLayout(); const isPortraitLayout = usePortraitLayout();
const roster = session?.roster ?? []; const roster = session?.roster ?? [];