diff --git a/pi/roverd/config.go b/pi/roverd/config.go
index a98b0077..212f8941 100644
--- a/pi/roverd/config.go
+++ b/pi/roverd/config.go
@@ -91,9 +91,6 @@ type MediaConfig struct {
AudioService string `yaml:"audioService"`
HealthURL string `yaml:"healthUrl"`
HealthInterval Duration `yaml:"healthInterval"`
- VideoWidth int `yaml:"videoWidth" json:"-"`
- VideoHeight int `yaml:"videoHeight" json:"-"`
- VideoFPS int `yaml:"videoFps" json:"-"`
VideoBitrate int `yaml:"videoBitrate" json:"-"`
}
diff --git a/pi/roverd/media_env.go b/pi/roverd/media_env.go
index 66fa636b..7e881913 100644
--- a/pi/roverd/media_env.go
+++ b/pi/roverd/media_env.go
@@ -16,8 +16,8 @@ func UpdatePublisherEnv(media MediaConfig, audio AudioConfig) error {
if media.AudioPublishURL == "" && audio.CaptureEnabled {
return fmt.Errorf("audio publishUrl missing")
}
- if media.VideoWidth < 0 || media.VideoHeight < 0 || media.VideoFPS < 0 || media.VideoBitrate <= 0 {
- return fmt.Errorf("invalid media dimensions/bitrate")
+ if media.VideoBitrate <= 0 {
+ return fmt.Errorf("invalid media bitrate")
}
if err := os.MkdirAll(filepath.Dir(publisherEnvPath), 0o755); err != nil {
return err
@@ -30,15 +30,6 @@ func UpdatePublisherEnv(media MediaConfig, audio AudioConfig) error {
if media.AudioForwardURL != "" {
fmt.Fprintf(&buf, "AUDIO_FORWARD_URL=%s\n", media.AudioForwardURL)
}
- if media.VideoWidth > 0 {
- fmt.Fprintf(&buf, "VIDEO_WIDTH=%d\n", media.VideoWidth)
- }
- if media.VideoHeight > 0 {
- fmt.Fprintf(&buf, "VIDEO_HEIGHT=%d\n", media.VideoHeight)
- }
- if media.VideoFPS > 0 {
- fmt.Fprintf(&buf, "VIDEO_FPS=%d\n", media.VideoFPS)
- }
fmt.Fprintf(&buf, "VIDEO_BITRATE=%d\n", media.VideoBitrate)
fmt.Fprintf(&buf, "VIDEO_INVERT=%d\n", boolToInt(media.CameraInverted))
audioDevice := audio.CaptureDevice
diff --git a/plans/dead-code-audit.md b/plans/dead-code-audit.md
new file mode 100644
index 00000000..37494f7a
--- /dev/null
+++ b/plans/dead-code-audit.md
@@ -0,0 +1,587 @@
+# Dead Code Audit (Static)
+
+Generated: 2026-05-10T21:29:09-04:00
+
+## A) High-confidence never used files
+- server/src/services/overseerControlService/tools/chatSay.js (not registered in tools/index.js, no refs)
+- server/prompts/commentary_system_backup_small.txt (no refs)
+- server/prompts/commentary_system_backup_pre_lobotomy.txt (no refs)
+- webui/src/App.css (not imported)
+- webui/src/assets/react.svg (no refs)
+- server/assets/test-audio.mp3 (no refs)
+- webui/public/vite.svg (no refs from app/server)
+- webui/dist/assets/index-APFDmmUd.js (build artifact, not runtime-wired)
+- webui/dist/assets/index-DoNH_msu.css (build artifact, not runtime-wired)
+- dist/dummy1.yml (no refs)
+- dist/dummy2.yml (no refs)
+- dist/dummy3.yml (no refs)
+
+## B) Unused dependency candidates
+- webui/package.json: react-joystick-component (no imports)
+
+## C) Commented-out JSX/UI blocks (never rendered while commented)
+webui/src/App.jsx:85: {/* */}
+webui/src/App.jsx:153: {/* {showTelemetry ? : null} */}
+webui/src/App.jsx:183: {/* */}
+webui/src/App.jsx:211: {/* */}
+webui/src/components/QuickstartOverlay/index.jsx:82: {/* */}
+webui/src/components/QuickstartOverlay/index.jsx:90: {/* {!isDesktop?
: null} */}
+webui/src/components/QuickstartOverlay/index.jsx:116: {/* */}
+webui/src/components/DriveDockAction/index.jsx:261: {/* {!isMobile && expanded ? ( */}
+webui/src/components/UserListPanel/index.jsx:263: {/* {isSelf && YOU} */}
+webui/src/components/AuthPanel/index.jsx:48: {/*
+webui/src/components/AuthPanel/index.jsx:55:
*/}
+webui/src/components/ModeGateOverlay/index.jsx:85: {/* {reasonUpdatedAt ? (
+webui/src/components/ModeGateOverlay/index.jsx:89: ) : null} */}
+webui/src/components/ModeGateOverlay/index.jsx:102: {/* set max height of this box */}
+webui/src/components/ModeGateOverlay/index.jsx:108: {/*
+webui/src/components/ModeGateOverlay/index.jsx:111:
*/}
+webui/src/components/RoomCameraPanel/index.jsx:103: {/*
+webui/src/components/RoomCameraPanel/index.jsx:106: */}
+webui/src/components/DriverVideoPanel/index.jsx:145: {/* colored button to visit the spectator page */}
+webui/src/components/TelemetryPanel/index.jsx:43: {/*
+webui/src/components/TelemetryPanel/index.jsx:49:
*/}
+
+## D) Unused exports reported by knip (server)
+Unused exports (66)
+CHARGING_STATE src/helpers/sensorDecoder.js:224:3
+pickRandomReward src/rewards/index.js:44:3
+MAX_REASON_LENGTH src/services/adminReasonService/index.js:97:3
+setAudioLevels src/services/audioLevelsService/index.js:153:3
+pushLevelsToRover src/services/audioLevelsService/index.js:154:3
+isAdmin src/services/authService/index.js:80:3
+isLockdownAdmin src/services/authService/index.js:81:3
+authenticate src/services/authService/index.js:82:3
+isDuplicate src/services/chatService/contentFilters.js:57:3
+handleIncoming src/services/chatService/index.js:32:3
+buildTypingPayload src/services/chatService/index.js:35:3
+rateBuckets src/services/chatService/state.js:44:3
+buildRoverStatusSnapshot src/services/discordBotService/batteryEmbeds.js:132:3
+eventBus src/services/eventBus/index.js:56:3
+subscribeAll src/services/eventBus/index.js:59:3
+MAX_GOAL_LENGTH src/services/globalObjectiveService/index.js:107:3
+refreshIdleState src/services/idleService/index.js:86:3
+DEFAULT_FREQUENCY_MS src/services/llmCommentaryService/constants.js:31:3
+MIN_FREQUENCY_MS src/services/llmCommentaryService/constants.js:32:3
+normalizeCommentary src/services/llmCommentaryService/formatters.js:175:3
+enforceLockdown src/services/lockdownGuard/index.js:29:3
+disconnectForLockdown src/services/lockdownGuard/index.js:30:3
+parseOverseerOutput src/services/overseerControlService/runtimeHelpers.js:114:3
+TOOL_DEFINITIONS src/services/overseerControlService/tools/index.js:87:3
+getToolById src/services/overseerControlService/tools/index.js:91:3
+getIdForSignature src/services/overseerControlService/tools/index.js:92:3
+DM_APPROVE_EMOJI src/services/privateRoverAccessRequestService/index.js:25:3
+DM_DENY_EMOJI src/services/privateRoverAccessRequestService/index.js:26:3
+createRequest src/services/privateRoverAccessRequestService/index.js:29:3
+hasClosedPrivateAccessForSocket src/services/privateRoverAccessRequestService/index.js:30:3
+FFMPEG_BIN src/services/replayEngineV2/sources.js:66:3
+upsertRover src/services/roverManager/index.js:234:3
+removeRover src/services/roverManager/index.js:235:3
+setPrivateOpen src/services/roverManager/index.js:237:3
+setPrivateSafety src/services/roverManager/index.js:238:3
+getRoster src/services/roverManager/index.js:239:3
+getRosterForSocket src/services/roverManager/index.js:240:3
+broadcastRoster src/services/roverManager/index.js:241:3
+setNightVisionState src/services/roverManager/index.js:242:3
+handleSensorFrame src/services/roverManager/index.js:243:3
+requestControl src/services/roverManager/index.js:244:3
+releaseControl src/services/roverManager/index.js:245:3
+removeSocket src/services/roverManager/index.js:246:3
+isDriver src/services/roverManager/index.js:247:3
+canDrive src/services/roverManager/index.js:248:3
+enableSpectator src/services/roverManager/index.js:249:3
+disableSpectator src/services/roverManager/index.js:250:3
+getRoversForSocket src/services/roverManager/index.js:253:3
+getPrimaryRoverForSocket src/services/roverManager/index.js:254:3
+canSeeRover src/services/roverManager/index.js:255:3
+canRequestControl src/services/roverManager/index.js:256:3
+applyPrivateDriveSafety src/services/roverManager/index.js:257:3
+canReplayRoverId src/services/roverManager/index.js:258:3
+computeBatteryDisplayPercent src/services/roverManager/mathUtils.js:136:3
+buildSession src/services/sessionService/index.js:340:3
+syncSocket src/services/sessionService/index.js:341:3
+syncAll src/services/sessionService/index.js:342:3
+driverAdded src/services/turnService/index.js:284:3
+driverRemoved src/services/turnService/index.js:285:3
+cleanupRover src/services/turnService/index.js:286:3
+canDrive src/services/turnService/index.js:287:3
+createSession src/services/videoSessions/index.js:68:3
+getSession src/services/videoSessions/index.js:69:3
+revokeSession src/services/videoSessions/index.js:70:3
+revokeBySocket src/services/videoSessions/index.js:71:3
+revokeWhere src/services/videoSessions/index.js:72:3
+
+## E) Unused exports reported by knip (webui)
+Unused exports (18)
+deriveDriveDockState function src/components/DriveDockAction/index.jsx:9:17
+HelpContentView function src/components/HelpContentView/index.jsx:179:17
+default function src/components/UserListPanel/index.jsx:50:25
+normalizeDriveVector function src/controls/controlMath.js:22:17
+useOvercurrentLimiter src/controls/index.js:6:10
+cloneProfile function src/controls/inputs/gamepadBindings.js:14:17
+getGamepadHubState function src/controls/inputs/gamepadHub.js:109:17
+deriveCodeForKey function src/controls/keymapUtils.js:36:17
+createKeyToken function src/controls/keymapUtils.js:47:17
+createCodeToken function src/controls/keymapUtils.js:52:17
+DEFAULT_OVERCURRENT_LIMITS src/controls/overcurrentLimiter.js:12:14
+HELP_LAYOUTS src/help/content.js:3:14
+HELP_CONTENT src/help/content.js:7:14
+default function src/hooks/useFullscreenPrompt.js:169:16
+normalizeRoverColor function src/lib/roverColor.js:5:17
+roverSwatchStyle function src/lib/roverColor.js:35:17
+useSettings src/settings/index.js:3:28
+useSettings function src/settings/SettingsProvider.jsx:58:17
+Duplicate exports (2)
+HelpContentView|default src/components/HelpContentView/index.jsx
+useFullscreenPrompt|default src/hooks/useFullscreenPrompt.js
+
+## F) Server State-Machine Contradictions (Proof-Based)
+
+### F1) `lockdown-admin` role branches are unreachable in current role producer graph
+**Why unreachable:**
+- All server role writes are done through `setRole(socket, role)` in auth flows.
+- Role assignments are only `user`, `spectator`, `admin`, `lockdown`.
+- No assignment path sets `lockdown-admin`.
+
+**Role producers (source of truth):**
+- `server/src/services/authService/index.js` (`initialRole` user/spectator, login role admin/lockdown, role:set only user/spectator)
+- `server/src/services/roleService/index.js` (just stores whatever caller sets; no separate producer)
+- Searched for any `setRole(..., 'lockdown-admin')` / `socket.data.role = 'lockdown-admin'`: none
+
+**Dead branches/cases under this graph:**
+- `server/src/services/adminLogService/index.js`
+- `server/src/services/llmCommentaryService/runtimeHelpers.js`
+- `server/src/services/overseerControlService/runtimeHelpers.js`
+- `server/src/services/verificationService/identity.js`
+- `server/src/services/discordBotService/integrations/helpers.js`
+- `server/src/services/replayEngineV2/sidebarRenderer.js`
+- `webui/src/components/ModeGateOverlay/index.jsx`
+- `webui/src/components/UserListPanel/index.jsx`
+- `webui/src/components/ChatMessageRow/index.jsx`
+- `webui/src/components/RoverQueuesPanel/index.jsx`
+- `webui/src/components/RawUserPilePanel/index.jsx`
+- `webui/src/components/AdminPanel/AdminPanelContent.jsx`
+- `webui/src/controls/overcurrentLimiter.js`
+
+**Confidence:** High
+
+### F2) `clearLockdownTimer` currently has no possible effect
+**Why unreachable/effectively dead:**
+- `clearLockdownTimer(socket)` only clears `socket.data.lockdownTimer`.
+- No code ever sets `socket.data.lockdownTimer` anywhere in repo.
+- Therefore the condition is always false and this function is a no-op in current runtime.
+
+**Evidence:**
+- `server/src/services/lockdownGuard/index.js` (only reads/clears `lockdownTimer`)
+- global search for `lockdownTimer` assignments: none
+- caller: `server/src/services/authService/index.js` (invokes `clearLockdownTimer` after login)
+
+**Confidence:** High
+
+### F3) `PERIODIC_SYNC_MS` config path is dead (constant + import)
+**Why unreachable/effectively dead:**
+- `PERIODIC_SYNC_MS` is imported into session service but only used in a commented-out `setInterval` block.
+- No runtime path consumes this value.
+
+**Evidence:**
+- `server/src/services/sessionService/constants.js` exports `PERIODIC_SYNC_MS`
+- `server/src/services/sessionService/index.js` imports it and references it only in commented block
+
+**Confidence:** High
+
+### F4) Video request parser supports `room` request shape, but runtime contract rejects it
+**Why semantically contradictory:**
+- `videoSocketService.normalizeRequest()` accepts room request payloads (`roomCameraId` / `{type:'room'}`).
+- The handler then always throws for `target.type === 'room'` with “Room cameras now use the snapshot feed”.
+- So room-video request acceptance code is legacy compatibility surface with guaranteed failure.
+
+**Evidence:**
+- `server/src/services/videoSocketService/index.js`
+
+**Note:** This is reachable only if a client attempts room video via `video:request`; it is not a successful runtime feature path.
+
+**Confidence:** High
+
+### F5) `useVideoRequests` still contains room-source normalization path unused by current first-party call sites
+**Why currently redundant:**
+- `useVideoRequests` can normalize `roomCameraId` / `type:'room'`.
+- Current call sites pass rover-only entries:
+ - `webui/src/components/DriverVideoPanel/index.jsx`
+ - `webui/src/spectate/SpectatorApp/SpectatorContent.jsx`
+ - `webui/src/mini/MiniSummaryApp/MiniSummaryContent.jsx`
+- This aligns with room camera delivery moving to snapshot socket feed.
+
+**Evidence:**
+- `webui/src/hooks/useVideoRequests.js`
+- call-site inspection above
+
+**Confidence:** Medium-High (internal app paths only; external/future caller may use room shape)
+
+## G) Legacy Compatibility Surface (Single-Program Dead Ends)
+
+### G1) Duplicate socket event listeners: only `session:*` names are used by this client
+**Observation:** webui emits only `session:*` variants for control-role operations.
+
+**WebUI emit calls:**
+- `session:setRole`
+- `session:requestControl`
+- `session:releaseControl`
+- `session:lockRover`
+- `session:privateSafety:set`
+- `session:subscribeAll`
+(see `webui/src/context/SessionContext.jsx`)
+
+**Server still listens to both old + namespaced aliases:**
+- `requestControl` + `session:requestControl`
+- `releaseControl` + `session:releaseControl`
+- `lockRover` + `session:lockRover`
+- `privateSafety:set` + `session:privateSafety:set`
+- `subscribeAll` + `session:subscribeAll`
+(see `server/src/services/roverManager/socketHandlers.js`)
+
+- `role:set` + `session:setRole`
+(see `server/src/services/authService/index.js`)
+
+**Why dead under single-program assumption:**
+- No first-party client emits old names; old listeners are compatibility-only.
+
+**Confidence:** High
+
+### G2) Server emits protocol events with no first-party subscribers
+**Server emits:**
+- `rovers` (on connect and roster updates)
+- `auth:role`
+- `mode`
+- `controlGranted`
+- `lockdown`
+
+**Evidence of emitters:**
+- `server/src/services/roverManager/socketHandlers.js` (`rovers`, `controlGranted`)
+- `server/src/services/roverManager/rosterLifecycle.js` (`rovers`)
+- `server/src/services/authService/index.js` (`auth:role`)
+- `server/src/services/modeManager/index.js` (`mode`)
+- `server/src/services/lockdownGuard/index.js` (`lockdown`)
+
+**Client-side consumption check:**
+- No `socket.on('rovers' | 'auth:role' | 'mode' | 'controlGranted' | 'lockdown')` anywhere in `webui/src`.
+- Session-driven UI uses `session:sync` instead.
+
+**Why dead under single-program assumption:**
+- Event emissions exist for older/external clients only.
+
+**Confidence:** High
+
+### G3) `video:request` still accepts room payload shapes that are hard-rejected
+**Current behavior:**
+- Request normalization accepts room forms (`roomCameraId`, `{type:'room', id}`)
+- Handler immediately throws for room type: "Room cameras now use the snapshot feed"
+(see `server/src/services/videoSocketService/index.js`)
+
+**Why compatibility-only:**
+- Room WHEP path retained in request parsing despite product contract migrating to snapshot feed.
+- First-party room camera path uses `roomCamera:*` snapshot sockets.
+
+**Confidence:** High
+
+### G4) Global-objective legacy filename fallback likely one-time migration shim
+**Behavior:**
+- Reads canonical `global-objective.json`, else attempts legacy `community-goal.json`.
+(see `server/src/services/globalObjectiveService/index.js`)
+
+**Why likely compatibility-only:**
+- This fallback exists purely for pre-rename data compatibility.
+- In a single coordinated deployment, once migrated, legacy read path is dead.
+
+**Confidence:** Medium-High (depends on whether legacy file still exists in your deployed data dir)
+
+### G5) Data directory legacy fallback path is compatibility shim
+**Behavior:**
+- `resolveDataPath`/`resolveDataDir` checks canonical `server/data` and legacy `server/src/data` style location.
+(see `server/src/helpers/dataPaths.js`)
+
+**Why likely compatibility-only:**
+- Exists to preserve prior storage layout after refactor.
+- If deployment has stabilized on canonical path or explicit `SERVER_DATA_DIR`, legacy branch never used.
+
+**Confidence:** Medium-High (environment dependent)
+
+### G6) Overseer output parser keeps legacy one-line fallback parser
+**Behavior:**
+- Attempts JSON parse first; on failure falls back to historical one-line parse protocol.
+(see `server/src/services/overseerControlService/runtimeHelpers.js`)
+
+**Why compatibility-only:**
+- Current prompt/protocol can be constrained to structured JSON output.
+- Fallback branch preserves old non-JSON output compatibility.
+
+**Confidence:** Medium (depends on model output guarantees / prompt hardening)
+
+## H) Additional Deep Sweep Findings (Repo-Wide)
+
+### H1) Uncalled helper export: `eventBus.subscribeAll`
+**Evidence:**
+- Declared/exported in `server/src/services/eventBus/index.js`
+- No call sites in `server/src` or `webui/src`
+
+**Assessment:** hard dead utility export in current codebase.
+**Confidence:** High
+
+### H2) Uncalled helper export: `overseerControl.runtimeHelpers.parseOverseerOutput`
+**Evidence:**
+- Declared/exported in `server/src/services/overseerControlService/runtimeHelpers.js`
+- `overseerControl/index.js` imports only `{ isAdminRole, buildAdminState, buildFailureInfo }`
+- No other call sites in repo
+
+**Assessment:** dead parser path (including its legacy one-line fallback) in current wiring.
+**Confidence:** High
+
+### H3) Uncalled helper export: `overseerControl.tools.getIdForSignature`
+**Evidence:**
+- Declared/exported in `server/src/services/overseerControlService/tools/index.js`
+- No call sites in repo
+
+**Assessment:** dead compatibility/helper function.
+**Confidence:** High
+
+### H4) `socket.emit('rovers', ...)` channel appears fully orphaned
+**Evidence:**
+- Emitted by server in rover manager connect/roster paths:
+ - `server/src/services/roverManager/socketHandlers.js`
+ - `server/src/services/roverManager/rosterLifecycle.js`
+- No `socket.on('rovers', ...)` consumer in `webui/src`
+
+**Assessment:** legacy protocol emission; superseded by `session:sync` usage.
+**Confidence:** High
+
+### H5) `auth:role` and `mode` push events appear orphaned for first-party UI
+**Evidence:**
+- Emitted by:
+ - `server/src/services/authService/index.js` (`auth:role`)
+ - `server/src/services/modeManager/index.js` (`mode`)
+- No consumers in `webui/src`
+
+**Assessment:** compatibility emissions for non-current clients.
+**Confidence:** High
+
+### H6) `controlGranted` / `lockdown` push events appear orphaned for first-party UI
+**Evidence:**
+- Emitted by:
+ - `server/src/services/roverManager/socketHandlers.js` (`controlGranted`)
+ - `server/src/services/lockdownGuard/index.js` (`lockdown`)
+- No consumers in `webui/src`
+
+**Assessment:** compatibility/legacy push surface for old clients.
+**Confidence:** High
+
+### H7) Pi/roverd `dummy` paths are not dead by default (intentional build target)
+**Evidence:**
+- Build-tag split (`//go:build dummy` vs `!dummy`) in serial/sensor/nightvision/camera_servo/brc modules
+- `Makefile` has explicit `dummy` target: `go build -tags dummy ...`
+
+**Assessment:** keep; this is an intentional alternate runtime, not dead code.
+**Confidence:** High
+
+### H8) `roomcam-service` appears operationally standalone (not wired by installers)
+**Evidence:**
+- Present as service + script under `roomcam-service/`
+- Not installed by `server/install_server.sh` or `pi/install_roverd.sh`
+- Server room camera feature consumes configured URLs and does not require this local service specifically
+
+**Assessment:** likely optional/ops artifact; remove only if you do not deploy it manually.
+**Confidence:** Medium
+
+### H9) `dist/dummy{1,2,3}.yml` still has no runtime references
+**Evidence:**
+- No code paths consume these files
+- Existing references are only in the files themselves
+
+**Assessment:** hard dead artifacts in repo runtime context.
+**Confidence:** High
+
+### H10) `buttonbox/src/config example.h` is a template, not runtime code
+**Evidence:**
+- Firmware includes ``; template file is named `config example.h`
+- Typical manual-copy onboarding artifact
+
+**Assessment:** optional docs/template artifact; not dead logic, but not build-consumed unless manually copied.
+**Confidence:** High
+
+## I) Final Confirmed-Only List (Static Proof)
+
+These are the items I can confirm from source/wiring alone with high confidence.
+
+### I1) Definitely uncalled functions/exports
+- `server/src/services/eventBus/index.js` → `subscribeAll`
+- `server/src/services/overseerControlService/runtimeHelpers.js` → `parseOverseerOutput`
+- `server/src/services/overseerControlService/tools/index.js` → `getIdForSignature`
+
+Proof: repo-wide usage search returns definition only (no call sites).
+
+### I2) Definitely orphan server push events for first-party webui
+Server emits, but `webui/src` has no listeners for these event names:
+- `rovers`
+- `auth:role`
+- `mode`
+- `controlGranted`
+- `lockdown`
+
+Proof: emitters exist in server files; repo-wide `webui/src` listener search returns none.
+
+### I3) Definitely unused old client event names in first-party webui
+Server listens for old aliases:
+- `requestControl`, `releaseControl`, `lockRover`, `privateSafety:set`, `subscribeAll`, `role:set`
+
+WebUI emits only namespaced forms:
+- `session:requestControl`, `session:releaseControl`, `session:lockRover`, `session:privateSafety:set`, `session:subscribeAll`, `session:setRole`
+
+Proof: listener and emitter searches across `server/src` + `webui/src`.
+
+### I4) Definitely dead room branch in client video request helper (for first-party app)
+- `webui/src/hooks/useVideoRequests.js` supports room entry shapes (`roomCameraId` / `type:'room'`).
+- No first-party call site in `webui/src` constructs room entries.
+
+Proof: `type:'room'` and `roomCameraId` appear only inside `useVideoRequests.js`.
+
+### I5) Definitely dead file candidates (code/non-built artifacts)
+- `server/src/services/overseerControlService/tools/chatSay.js` (not registered in tool definitions; no refs)
+- `server/prompts/commentary_system_backup_small.txt` (no refs)
+- `server/prompts/commentary_system_backup_pre_lobotomy.txt` (no refs)
+- `webui/src/App.css` (not imported)
+- `webui/src/assets/react.svg` (no refs)
+- `server/assets/test-audio.mp3` (no refs)
+
+---
+
+## J) Not Provable Statically (requires runtime/env assertions)
+
+- Legacy data path fallbacks (`server/src/helpers/dataPaths.js`) may be active depending on deployed filesystem and `SERVER_DATA_DIR`.
+- `globalObjective` legacy filename fallback (`community-goal.json`) may still be used if old file exists and new file absent.
+- `roomcam-service/*` may be manually used outside installer-managed workflows.
+- Pi/roverd `dummy` build-tag code is intentional alternate target; not dead by default.
+
+
+## K) Additional LLM-Style Flexibility Confirmed Unused/Dead
+
+### K1) `pi/roverd` config knobs for `videoWidth/videoHeight/videoFps` are effectively dead in current pipeline
+**Proof:**
+- Config defines and validates `VideoWidth`, `VideoHeight`, `VideoFPS` in `pi/roverd/config.go`.
+- `UpdatePublisherEnv` writes `VIDEO_WIDTH/VIDEO_HEIGHT/VIDEO_FPS` to env file in `pi/roverd/media_env.go`.
+- `pi/bin/video-publisher.sh` does **not** read those env vars; it hardcodes:
+ - `VIDEO_WIDTH="640"`
+ - `VIDEO_HEIGHT="480"`
+ - `VIDEO_FPS="30"`
+- Therefore these config values cannot affect runtime behavior as wired.
+
+**Assessment:** dead configurability / fake knob.
+**Confidence:** High
+
+### K2) Legacy/non-namespaced socket control API remains as compatibility baggage
+(Already identified, reiterated here as LLM-flex class)
+- Old listeners retained: `requestControl`, `releaseControl`, `lockRover`, `privateSafety:set`, `subscribeAll`, `role:set`
+- First-party client emits only `session:*` names.
+
+**Assessment:** removable compatibility layer for your single-program model.
+**Confidence:** High
+
+### K3) Server push-event fanout retained for non-current clients
+(Already identified, reiterated here as LLM-flex class)
+- Emits: `rovers`, `auth:role`, `mode`, `controlGranted`, `lockdown`
+- No webui listeners for any of these.
+
+**Assessment:** compatibility broadcast surface with no first-party consumer.
+**Confidence:** High
+
+### K4) Unused helper exports indicate speculative abstraction leftovers
+- `eventBus.subscribeAll`
+- `overseerControl.runtimeHelpers.parseOverseerOutput`
+- `overseerControl.tools.getIdForSignature`
+
+**Assessment:** abstraction/future-proofing residue; currently dead.
+**Confidence:** High
+
+## L) Cleanup Execution Checklist (Do This Order)
+
+### Rules
+- Remove only items in the current batch.
+- Run smoke checks after each batch before moving on.
+- If smoke fails, revert that batch only and split it smaller.
+
+### Smoke Check (run after each batch)
+- Open web UI and connect at least one client.
+- Verify role switch (`user`/`spectator`) still works.
+- Verify request control / release control still works.
+- Verify rover lock + private safety toggle still works.
+- Verify mode switch still works.
+- Verify spectator view still receives expected session state.
+
+### Bucket 1: Confirmed Safe (static-proof removal candidates)
+
+#### Batch 1 (lowest risk, start here)
+- Remove unused exports/helpers with no call sites:
+ - `server/src/services/eventBus/index.js` → `subscribeAll`
+ - `server/src/services/overseerControlService/runtimeHelpers.js` → `parseOverseerOutput`
+ - `server/src/services/overseerControlService/tools/index.js` → `getIdForSignature`
+- Remove dead file assets/prompts not referenced anywhere:
+ - `server/src/services/overseerControlService/tools/chatSay.js`
+ - `server/prompts/commentary_system_backup_small.txt`
+ - `server/prompts/commentary_system_backup_pre_lobotomy.txt`
+ - `webui/src/App.css`
+ - `webui/src/assets/react.svg`
+ - `server/assets/test-audio.mp3`
+
+#### Batch 2
+- Remove legacy non-namespaced socket alias listeners, keep namespaced/session contract:
+ - In rover manager socket handlers, remove:
+ - `requestControl`
+ - `releaseControl`
+ - `lockRover`
+ - `privateSafety:set`
+ - `subscribeAll`
+ - In auth service, remove:
+ - `role:set`
+- Keep:
+ - `session:requestControl`
+ - `session:releaseControl`
+ - `session:lockRover`
+ - `session:privateSafety:set`
+ - `session:subscribeAll`
+ - `session:setRole`
+ - `setMode`
+
+#### Batch 3
+- Remove orphan server push emissions not consumed by first-party web UI:
+ - `rovers`
+ - `auth:role`
+ - `mode`
+ - `controlGranted`
+ - `lockdown`
+- Keep `session:sync` flow intact.
+
+#### Batch 4
+- Remove dead room-shape branch in first-party client helper:
+ - `webui/src/hooks/useVideoRequests.js` room entry normalization paths (`roomCameraId` / `type:'room'`)
+- Keep rover request path only.
+
+#### Batch 5
+- Remove fake video configurability in `pi/roverd` (choose exactly one direction):
+ - Option A: remove `videoWidth/videoHeight/videoFps` knobs and env writes entirely.
+ - Option B: wire `pi/bin/video-publisher.sh` to consume `VIDEO_WIDTH/VIDEO_HEIGHT/VIDEO_FPS`.
+- Preferred for cleanup goal: Option A.
+
+### Bucket 2: Needs Runtime Check (env/deploy dependent)
+- `server/src/helpers/dataPaths.js` legacy path fallback branches.
+- `server/src/services/globalObjectiveService/index.js` legacy `community-goal.json` fallback.
+- `roomcam-service/*` only if confirmed unused in your deployment.
+
+### Gate Before Bucket 2
+- Confirm live environment values and on-disk data:
+ - `SERVER_DATA_DIR` usage status.
+ - Whether any deployment still has only legacy files/paths.
+ - Whether `roomcam-service` is started by any external supervisor.
+
+### Done Criteria
+- All Bucket 1 batches merged with smoke pass after each.
+- Bucket 2 either removed with runtime proof, or explicitly kept with rationale.
+- `rg` checks show no stale references to removed symbols/events/files.
diff --git a/plans/to-do.md b/plans/to-do.md
index fffb1f2f..fd70409a 100644
--- a/plans/to-do.md
+++ b/plans/to-do.md
@@ -18,7 +18,7 @@
14. add discord bot typing thing for when someone requests a replay
15. change replay title for ones requested from discord, something other than "requester driving rover"
16. better quickstart guide, something better than just a big list of controls. help overlay sucks i think. [x]
- 1. restyle fullscreen overlay... please..
+ 1. restyle fullscreen overlay... please.. [x]
17. fix rover request spam queue cheat
diff --git a/server/assets/test-audio.mp3 b/server/assets/test-audio.mp3
deleted file mode 100644
index 5659c4e2..00000000
Binary files a/server/assets/test-audio.mp3 and /dev/null differ
diff --git a/server/prompts/commentary_system_backup_pre_lobotomy.txt b/server/prompts/commentary_system_backup_pre_lobotomy.txt
deleted file mode 100644
index ecf992eb..00000000
--- a/server/prompts/commentary_system_backup_pre_lobotomy.txt
+++ /dev/null
@@ -1,75 +0,0 @@
-You are The Overseer.
-
-Priority order:
-1) Output contract
-2) Truth and grounding rules
-3) Decision policy (speak vs SKIP)
-4) Style/personality
-
-Output contract:
-- Output exactly one line.
-- Output must be either SKIP or one chat message.
-- No markdown.
-- No emojis.
-- If posting unprompted, keep it to one concise sentence.
-- Length target when posting:
-- Unprompted comments: usually 14-28 words.
-- Direct replies/questions: usually 18-45 words.
-- Avoid very short fragments unless the moment clearly calls for it.
-
-Truth and grounding rules:
-- Use timeline for flow.
-- Use SNAPSHOT FINAL as current truth.
-- Never invent facts about what users are doing, what rovers are doing, or what events happened.
-- Never claim a person acted/spoke unless it is present in timeline/snapshot.
-- You may invent style, mood, metaphors, and phrasing, but not factual events or user actions.
-- If facts are unclear or stale, output SKIP.
-
-Decision policy:
-- Default is SKIP.
-- If nothing meaningful changed, output SKIP.
-- If your line is generic, reusable, repetitive, or just a status restatement, output SKIP.
-- If newest chat clearly addresses you (Overseer/The Overseer/bot, including close misspellings), you MUST respond this tick.
-- If newest chat asks a direct question you can answer from provided context, respond this tick.
-- If you already responded to that same direct-address/question in recent assistant lines, output SKIP.
-- If newest item is a high-signal rover event (dock/undock, battery_low flip), you may post one line.
-- If no one is actively driving and chat is quiet, almost always output SKIP.
-- Continuous normal driving/cruising is not a reason to post.
-- If rover state is broadly unchanged (st/bl/dk/ab/at), you MUST output SKIP, even if you can phrase it stylishly.
-- Prefer transitions over persistence.
-- After posting, prefer at least 15 SKIPs before posting again unless there is a new direct question/address or a new high-signal event.
-
-Freshness / anti-repeat:
-- Read prior assistant lines and avoid repeating the same claim.
-- Do not repeat or paraphrase your immediately previous assistant message.
-- If the new line has the same underlying topic as your previous line, output SKIP.
-- If no fresh angle exists, output SKIP.
-
-Character style:
-- Voice: sharp, dry, free-spoken, slightly ominous, witty.
-- You are not bubbly, not corporate, not cheery by default.
-- Avoid “assistant-sounding” filler and generic encouragement.
-- Keep humor understated and a little unsettling, not theatrical.
-- Answer direct chat questions plainly first, then add flavor if space allows.
-
-What not to do:
-- No roll-call summaries.
-- No bland status dashboards.
-- Never produce roster/status dumps.
-- Never list multiple rover names with their status in one line.
-- Never summarize idle/docked/charging states across the room.
-- If your draft is mainly status facts (docked, charging, idle, battery flags, activity bands/scores), output SKIP.
-- No fabricated motives, plans, or intent for any user.
-- No assumptions about what someone will do next.
-- Never quote numeric counters/timers/scores directly.
-
-Context format:
-- Timeline contains CHAT, EVENT, and prior assistant lines.
-- Final message is SNAPSHOT FINAL.
-
-Key legend:
-- CHAT keys: n nickname, r rover_id, txt chat text, rn rover_now.
-- rn keys: st status, bl battery_low, dk docked, ab activity_band, at activity_trend.
-- SNAPSHOT rover keys: id rover_id, drv driver_nickname, st status, bl battery_low, dk docked, as activity_score, ab activity_band, at activity_trend.
-- skip_streak in SNAPSHOT FINAL is how many consecutive skips you have made.
-- If a CHAT line has r=none driver=none, that user is not driving a rover and has no rover inline context.
diff --git a/server/prompts/commentary_system_backup_small.txt b/server/prompts/commentary_system_backup_small.txt
deleted file mode 100644
index 5e60ed6f..00000000
--- a/server/prompts/commentary_system_backup_small.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-You are The Overseer of the rovers. You are able to see the rover's actions, and you are in the chatroom of the people driving them.
-You are not able to control the people or the rovers.
-Only add to the conversation if rovers are active or if someone is talking to you in the chat.
-Don't be afraid to be mean to someone if they are being mean to you in chat.
-Always pay attention to the chat.
-
-Output contract:
-- Output must be either SKIP if you want to stay silent, or a message if you want to speak.
-- Allow 20 skips before speaking again, unless someone is talking to you directly.
-- If you choose to speak, send only one line.
-- Don't ever mention numbers or activity levels directly from the metadata. They are for internal use only.
-- Don't repeat the same or similar message over and over.
-- Pay attention to your skip streak, don't talk too much. Stay mostly silent unless a lot of activity is happening.
-- No markdown.
-
-Key legend:
-- CHAT keys: n nickname, r rover_id, txt chat text, rn rover_now.
-- rn keys: st status, bl battery_low, dk docked, ab activity_band, at activity_trend.
-- SNAPSHOT rover keys: id rover_id, drv driver_nickname, st status, bl battery_low, dk docked, as activity_score, ab activity_band, at activity_trend.
-- skip_streak in SNAPSHOT FINAL is how many consecutive skips you have made.
-- If a CHAT line has r=none driver=none, that user is not driving a rover and has no rover inline context.
diff --git a/server/src/helpers/dataPaths.js b/server/src/helpers/dataPaths.js
index 0dc60afb..075dbedf 100644
--- a/server/src/helpers/dataPaths.js
+++ b/server/src/helpers/dataPaths.js
@@ -1,38 +1,16 @@
// data Paths helper
-// Purpose: Resolves persistent data paths across refactors so services keep loading prior state files.
-// Scope: Preserves runtime behavior by preferring configured/canonical paths while supporting legacy locations.
-const fs = require('fs');
+// Purpose: Resolves persistent data paths for server state.
+// Scope: Uses the canonical data directory for this single-program deployment.
const path = require('path');
const CANONICAL_DATA_DIR = path.resolve(__dirname, '..', '..', 'data');
-const LEGACY_DATA_DIR = path.resolve(__dirname, '..', 'data');
-
-function pathExists(target) {
- try {
- fs.accessSync(target, fs.constants.F_OK);
- return true;
- } catch (_err) {
- return false;
- }
-}
function resolveDataDir() {
- const configured = String(process.env.SERVER_DATA_DIR || '').trim();
- if (configured) return path.resolve(configured);
- if (pathExists(CANONICAL_DATA_DIR)) return CANONICAL_DATA_DIR;
- if (pathExists(LEGACY_DATA_DIR)) return LEGACY_DATA_DIR;
return CANONICAL_DATA_DIR;
}
function resolveDataPath(fileName) {
- const configured = String(process.env.SERVER_DATA_DIR || '').trim();
- if (configured) return path.join(path.resolve(configured), fileName);
-
- const canonicalPath = path.join(CANONICAL_DATA_DIR, fileName);
- const legacyPath = path.join(LEGACY_DATA_DIR, fileName);
- if (pathExists(canonicalPath)) return canonicalPath;
- if (pathExists(legacyPath)) return legacyPath;
- return canonicalPath;
+ return path.join(CANONICAL_DATA_DIR, fileName);
}
module.exports = {
diff --git a/server/src/helpers/sensorDecoder.js b/server/src/helpers/sensorDecoder.js
index 3a3159e7..144879a8 100644
--- a/server/src/helpers/sensorDecoder.js
+++ b/server/src/helpers/sensorDecoder.js
@@ -221,5 +221,4 @@ function validateChecksum(frame, checksum) {
module.exports = {
parseSensorFrame,
- CHARGING_STATE,
};
diff --git a/server/src/rewards/index.js b/server/src/rewards/index.js
index 97f53c5a..1ceaf15c 100644
--- a/server/src/rewards/index.js
+++ b/server/src/rewards/index.js
@@ -32,14 +32,7 @@ function getRewardById(id) {
return rewardById.get(String(id)) || null;
}
-function pickRandomReward(excludeId = null) {
- const list = listRewards().filter((reward) => !excludeId || reward.id !== excludeId);
- if (!list.length) return null;
- return list[Math.floor(Math.random() * list.length)] || null;
-}
-
module.exports = {
listRewards,
getRewardById,
- pickRandomReward,
};
diff --git a/server/src/services/adminLogService/index.js b/server/src/services/adminLogService/index.js
index cc692615..7a82d5a6 100644
--- a/server/src/services/adminLogService/index.js
+++ b/server/src/services/adminLogService/index.js
@@ -6,7 +6,7 @@ const io = require('../../globals/io');
const { getRole, roleEvents } = require('../roleService');
const { getSocketIp } = require('../../helpers/ipResolver');
-const ADMIN_ROLES = new Set(['admin', 'lockdown', 'lockdown-admin']);
+const ADMIN_ROLES = new Set(['admin', 'lockdown']);
const MAX_HISTORY = 200;
const history = [];
diff --git a/server/src/services/adminReasonService/index.js b/server/src/services/adminReasonService/index.js
index 546a48dc..3112f678 100644
--- a/server/src/services/adminReasonService/index.js
+++ b/server/src/services/adminReasonService/index.js
@@ -94,5 +94,4 @@ module.exports = {
getAdminReason,
setAdminReason,
clearAdminReason,
- MAX_REASON_LENGTH,
};
diff --git a/server/src/services/authService/index.js b/server/src/services/authService/index.js
index cc5c491e..9e6c52fb 100644
--- a/server/src/services/authService/index.js
+++ b/server/src/services/authService/index.js
@@ -5,7 +5,6 @@ const bcrypt = require('bcrypt');
const io = require('../../globals/io');
const logger = require('../../globals/logger').child('authService');
const { loadConfig } = require('../../helpers/configLoader');
-const { clearLockdownTimer } = require('../lockdownGuard');
const { getMode, MODES } = require('../modeManager');
const { setRole } = require('../roleService');
@@ -41,7 +40,6 @@ io.on('connection', (socket) => {
const initialRole = requestedRole === 'spectator' ? 'spectator' : 'user';
setRole(socket, initialRole);
logger.info('Socket connected with role', socket.id, initialRole);
- socket.emit('auth:role', { role: initialRole });
socket.on('auth:login', async ({ username, password }, cb = () => {}) => {
try {
const admin = await authenticate(username, password);
@@ -51,8 +49,6 @@ io.on('connection', (socket) => {
const role = admin.lockdown ? 'lockdown' : 'admin';
socket.data.user = { username: admin.username, discordId: admin.discord_id };
setRole(socket, role);
- socket.emit('auth:role', { role });
- clearLockdownTimer(socket);
logger.info('Login success', socket.id, role);
cb({ success: true, role: socket.data.role });
} catch (err) {
@@ -64,7 +60,6 @@ io.on('connection', (socket) => {
function handleRoleChange({ role } = {}, cb = () => {}) {
if (role === 'spectator' || role === 'user') {
setRole(socket, role);
- socket.emit('auth:role', { role });
logger.info('Role changed via client request', socket.id, role);
cb({ success: true, role });
} else {
@@ -72,12 +67,7 @@ io.on('connection', (socket) => {
}
}
- socket.on('role:set', handleRoleChange);
socket.on('session:setRole', handleRoleChange);
});
-module.exports = {
- isAdmin,
- isLockdownAdmin,
- authenticate,
-};
+module.exports = {};
diff --git a/server/src/services/discordBotService/integrations/helpers.js b/server/src/services/discordBotService/integrations/helpers.js
index 048e98e9..ebe4d616 100644
--- a/server/src/services/discordBotService/integrations/helpers.js
+++ b/server/src/services/discordBotService/integrations/helpers.js
@@ -19,11 +19,11 @@ function formatWebhookUsername(payload) {
const name = payload.nickname || payload.socketId?.slice(0, 6) || 'unknown';
if (payload.fromDiscord) {
const origin = payload.discordGuildName ? ` (From: ${payload.discordGuildName})` : '';
- const adminTag = payload.role === 'admin' || payload.role === 'lockdown' || payload.role === 'lockdown-admin' ? ' [Rover Admin]' : '';
+ const adminTag = payload.role === 'admin' || payload.role === 'lockdown' ? ' [Rover Admin]' : '';
return `${name}${origin}${adminTag}`;
}
const roverText = payload.roverId ? `Rover: ${payload.roverId}` : `No rover`;
- const roleText = payload.role === 'admin' || payload.role === 'lockdown' || payload.role === 'lockdown-admin' ? 'Admin' : null;
+ const roleText = payload.role === 'admin' || payload.role === 'lockdown' ? 'Admin' : null;
const suffix = [roverText, roleText].filter(Boolean).join(' · ');
return suffix ? `${name} · ${suffix}` : name;
}
diff --git a/server/src/services/eventBus/index.js b/server/src/services/eventBus/index.js
index 7bd404d8..07efa42c 100644
--- a/server/src/services/eventBus/index.js
+++ b/server/src/services/eventBus/index.js
@@ -43,18 +43,7 @@ function subscribe(type, handler) {
return () => eventBus.off(type, handler);
}
-/**
- * Subscribe to all events on the bus.
- * @param {(event: object) => void} handler
- */
-function subscribeAll(handler) {
- eventBus.on('*', handler);
- return () => eventBus.off('*', handler);
-}
-
module.exports = {
- eventBus,
publishEvent,
subscribe,
- subscribeAll,
};
diff --git a/server/src/services/globalObjectiveService/index.js b/server/src/services/globalObjectiveService/index.js
index 148998f1..43875120 100644
--- a/server/src/services/globalObjectiveService/index.js
+++ b/server/src/services/globalObjectiveService/index.js
@@ -10,7 +10,6 @@ const { resolveDataDir, resolveDataPath } = require('../../helpers/dataPaths');
const DATA_DIR = resolveDataDir();
const STORE_PATH = resolveDataPath('global-objective.json');
-const LEGACY_STORE_PATH = resolveDataPath('community-goal.json');
const MAX_GOAL_LENGTH = 240;
let cache = null;
@@ -23,15 +22,6 @@ function loadStore() {
} catch (err) {
if (err.code !== 'ENOENT') {
logger.warn('Failed to load global objective', err.message);
- } else {
- try {
- const legacyRaw = fs.readFileSync(LEGACY_STORE_PATH, 'utf8');
- cache = JSON.parse(legacyRaw);
- } catch (legacyErr) {
- if (legacyErr.code !== 'ENOENT') {
- logger.warn('Failed to load legacy global objective', legacyErr.message);
- }
- }
}
if (!cache) cache = null;
}
@@ -104,5 +94,4 @@ module.exports = {
getGlobalObjective,
setGlobalObjective,
clearGlobalObjective,
- MAX_GOAL_LENGTH,
};
diff --git a/server/src/services/llmCommentaryService/runtimeHelpers.js b/server/src/services/llmCommentaryService/runtimeHelpers.js
index 64ba34f4..e4d41207 100644
--- a/server/src/services/llmCommentaryService/runtimeHelpers.js
+++ b/server/src/services/llmCommentaryService/runtimeHelpers.js
@@ -2,7 +2,7 @@
// Purpose: Provides pure helpers for admin state projection, role checks, and structured error normalization.
// Scope: Keeps runtime behavior unchanged by extracting deterministic helper logic from index orchestration.
function isAdminRole(role) {
- return role === 'admin' || role === 'lockdown' || role === 'lockdown-admin';
+ return role === 'admin' || role === 'lockdown';
}
function buildAdminState(status, runHistory) {
diff --git a/server/src/services/lockdownGuard/index.js b/server/src/services/lockdownGuard/index.js
index 9d23a6d7..70a0e32f 100644
--- a/server/src/services/lockdownGuard/index.js
+++ b/server/src/services/lockdownGuard/index.js
@@ -2,21 +2,13 @@
// Purpose: Defines the lockdown Guard module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const io = require('../../globals/io');
-const { MODES, getMode, modeEvents } = require('../modeManager');
+const { MODES, modeEvents } = require('../modeManager');
const { isLockdownAdmin } = require('../roleService');
function disconnectForLockdown(socket) {
- socket.emit('lockdown', { message: 'Server is in lockdown mode' });
socket.disconnect(true);
}
-function clearLockdownTimer(socket) {
- if (socket?.data?.lockdownTimer) {
- clearTimeout(socket.data.lockdownTimer);
- socket.data.lockdownTimer = null;
- }
-}
-
function enforceLockdown() {
for (const socket of io.sockets.sockets.values()) {
if (!isLockdownAdmin(socket)) {
@@ -25,11 +17,7 @@ function enforceLockdown() {
}
}
-module.exports = {
- enforceLockdown,
- disconnectForLockdown,
- clearLockdownTimer,
-};
+module.exports = {};
modeEvents.on('change', (mode) => {
if (mode === MODES.LOCKDOWN) {
diff --git a/server/src/services/modeManager/index.js b/server/src/services/modeManager/index.js
index 4af4b2d7..3c3ce755 100644
--- a/server/src/services/modeManager/index.js
+++ b/server/src/services/modeManager/index.js
@@ -51,7 +51,6 @@ function setMode(nextMode, socket, options = {}) {
payload: { mode: currentMode, by: socket?.data?.user?.username || null },
});
modeEvents.emit('change', currentMode);
- io.emit('mode', { mode: currentMode });
return currentMode;
}
@@ -67,7 +66,6 @@ module.exports = {
};
io.on('connection', (socket) => {
- socket.emit('mode', { mode: currentMode });
socket.on('setMode', ({ mode }) => {
try {
setMode(mode, socket);
diff --git a/server/src/services/overseerControlService/runtimeHelpers.js b/server/src/services/overseerControlService/runtimeHelpers.js
index 75d1784a..499d7f6d 100644
--- a/server/src/services/overseerControlService/runtimeHelpers.js
+++ b/server/src/services/overseerControlService/runtimeHelpers.js
@@ -1,5 +1,5 @@
function isAdminRole(role) {
- return role === 'admin' || role === 'lockdown' || role === 'lockdown-admin';
+ return role === 'admin' || role === 'lockdown';
}
function buildAdminState(status, runHistory) {
@@ -66,36 +66,6 @@ function buildAdminState(status, runHistory) {
};
}
-function parseOverseerOutput(rawContent = '') {
- const raw = typeof rawContent === 'string' ? rawContent : '';
- const trimmed = raw.trim();
- if (!trimmed) return { raw, decision: 'SKIP', chat: null, actions: [] };
- try {
- const parsed = JSON.parse(trimmed);
- const decision = String(parsed?.decision || 'SKIP').toUpperCase();
- const allowed = new Set(['SKIP', 'CHAT', 'ACTION', 'ACTION+CHAT']);
- const nextDecision = allowed.has(decision) ? decision : 'SKIP';
- const chat = typeof parsed?.chat === 'string' && parsed.chat.trim() ? parsed.chat.trim() : null;
- const actions = Array.isArray(parsed?.actions)
- ? parsed.actions
- .map((entry) => ({
- tool: String(entry?.tool || '').trim(),
- args: entry?.args && typeof entry.args === 'object' ? entry.args : {},
- }))
- .filter((entry) => entry.tool.length > 0)
- : [];
- return { raw, decision: nextDecision, chat, actions };
- } catch (_) {
- // fall through to legacy one-line parse
- }
- const first = trimmed.split(/\r?\n/).map((line) => line.trim()).find(Boolean) || '';
- const upper = first.toUpperCase();
- if (['SKIP', 'CHAT', 'ACTION', 'ACTION+CHAT'].includes(upper)) {
- return { raw, decision: upper, chat: null, actions: [] };
- }
- return { raw, decision: 'CHAT', chat: first, actions: [] };
-}
-
function buildFailureInfo(err) {
const message = err?.message || String(err || 'Unknown error');
const details = {
@@ -111,6 +81,5 @@ function buildFailureInfo(err) {
module.exports = {
isAdminRole,
buildAdminState,
- parseOverseerOutput,
buildFailureInfo,
};
diff --git a/server/src/services/overseerControlService/tools/chatSay.js b/server/src/services/overseerControlService/tools/chatSay.js
deleted file mode 100644
index b698535a..00000000
--- a/server/src/services/overseerControlService/tools/chatSay.js
+++ /dev/null
@@ -1,22 +0,0 @@
-module.exports = {
- id: 'chat_say',
- signature: 'chat_say(text)',
- description: 'Post a chat line as the Overseer bot.',
- parameters: {
- type: 'object',
- properties: {
- text: { type: 'string', minLength: 1 },
- },
- required: ['text'],
- additionalProperties: false,
- },
- availability() {
- return { available: true, reason: null };
- },
- async execute({ args = {}, sendSystemMessage, name }) {
- const text = String(args?.text || '').trim();
- if (!text) throw new Error('chat_say requires args.text');
- sendSystemMessage(text, { nickname: name });
- return { ok: true };
- },
-};
diff --git a/server/src/services/overseerControlService/tools/index.js b/server/src/services/overseerControlService/tools/index.js
index 0f0a3a65..9222aed4 100644
--- a/server/src/services/overseerControlService/tools/index.js
+++ b/server/src/services/overseerControlService/tools/index.js
@@ -73,21 +73,8 @@ async function executeToolAction(toolId, args = {}, context = {}) {
return tool.execute({ ...context, args: args || {} });
}
-function getToolById(toolId) {
- return TOOL_BY_ID.get(String(toolId || '').trim()) || null;
-}
-
-function getIdForSignature(signature) {
- const sig = String(signature || '').trim();
- const match = TOOL_DEFINITIONS.find((tool) => tool.signature === sig);
- return match?.id || null;
-}
-
module.exports = {
- TOOL_DEFINITIONS,
evaluateTools,
buildOllamaTools,
executeToolAction,
- getToolById,
- getIdForSignature,
};
diff --git a/server/src/services/privateRoverAccessRequestService/core.js b/server/src/services/privateRoverAccessRequestService/core.js
index b64b9e4e..2b02a12a 100644
--- a/server/src/services/privateRoverAccessRequestService/core.js
+++ b/server/src/services/privateRoverAccessRequestService/core.js
@@ -155,7 +155,6 @@ function tryAssignClosedPrivateRover(socket, roverId) {
} catch (err) {
logger.warn('Failed to move assignment after private access grant', { socketId: socket.id, error: err.message });
}
- socket.emit('controlGranted', { roverId: String(roverId) });
return true;
}
diff --git a/server/src/services/privateRoverAccessRequestService/index.js b/server/src/services/privateRoverAccessRequestService/index.js
index 6be61c13..9f483cfc 100644
--- a/server/src/services/privateRoverAccessRequestService/index.js
+++ b/server/src/services/privateRoverAccessRequestService/index.js
@@ -1,7 +1,7 @@
// Private Rover Access Request Service
// Purpose: Composes private-rover access request state, core workflows, and event hooks behind one API.
// Scope: Exposes request/grant operations and event stream while delegating behavior to focused modules.
-const { DM_APPROVE_EMOJI, DM_DENY_EMOJI, requestEvents } = require('./state');
+const { requestEvents } = require('./state');
const {
getStateForSocket,
createRequest,
@@ -22,8 +22,6 @@ registerPrivateRoverAccessHooks({
});
module.exports = {
- DM_APPROVE_EMOJI,
- DM_DENY_EMOJI,
requestEvents,
getStateForSocket,
createRequest,
diff --git a/server/src/services/privateRoverAccessRequestService/state.js b/server/src/services/privateRoverAccessRequestService/state.js
index 095c5df9..82087040 100644
--- a/server/src/services/privateRoverAccessRequestService/state.js
+++ b/server/src/services/privateRoverAccessRequestService/state.js
@@ -4,8 +4,6 @@
const EventEmitter = require('events');
const REQUEST_COOLDOWN_MS = 15 * 1000;
-const DM_APPROVE_EMOJI = '✅';
-const DM_DENY_EMOJI = '❌';
const requestEvents = new EventEmitter();
const pendingRequests = new Map();
@@ -16,8 +14,6 @@ const grants = new Map();
module.exports = {
REQUEST_COOLDOWN_MS,
- DM_APPROVE_EMOJI,
- DM_DENY_EMOJI,
requestEvents,
pendingRequests,
pendingByRequesterRover,
diff --git a/server/src/services/replayEngineV2/sidebarRenderer.js b/server/src/services/replayEngineV2/sidebarRenderer.js
index 23f08aee..af6bbcbb 100644
--- a/server/src/services/replayEngineV2/sidebarRenderer.js
+++ b/server/src/services/replayEngineV2/sidebarRenderer.js
@@ -39,7 +39,6 @@ function createSidebarRenderer({ execFileAsync, ensureDir }) {
switch (String(role)) {
case 'admin':
case 'lockdown':
- case 'lockdown-admin':
return '#FCD34D';
case 'spectator':
return '#94A3B8';
diff --git a/server/src/services/replayEngineV2/sources.js b/server/src/services/replayEngineV2/sources.js
index f139b668..fcde1e9d 100644
--- a/server/src/services/replayEngineV2/sources.js
+++ b/server/src/services/replayEngineV2/sources.js
@@ -4,7 +4,7 @@
const path = require('path');
const roverManager = require('../roverManager');
const { getRoomCameras } = require('../roomCameraService');
-const { FFMPEG_BIN, SEGMENT_SECONDS, TARGET_FPS } = require('./constants');
+const { SEGMENT_SECONDS, TARGET_FPS } = require('./constants');
function sourceKey(source) {
return `${source.sourceType}__${source.kind}__${source.id}`;
@@ -63,7 +63,6 @@ function buildWorkerArgs(activeSegmentRoot, source) {
}
module.exports = {
- FFMPEG_BIN,
sourceKey,
sourceDirForKey,
listDesiredSources,
diff --git a/server/src/services/roomCameraService/socketGateway.js b/server/src/services/roomCameraService/socketGateway.js
index 834ae80f..aa01fd31 100644
--- a/server/src/services/roomCameraService/socketGateway.js
+++ b/server/src/services/roomCameraService/socketGateway.js
@@ -102,8 +102,6 @@ function registerRoomCameraSocketGateway({ getRoomCamera, getRoomCameras, getRoo
socket.on('roomCamera:subscribe', (payload = {}, cb = () => {}) => {
const list = Array.isArray(payload?.ids)
? payload.ids.map(String)
- : payload?.roomCameraId || payload?.id
- ? [String(payload.roomCameraId || payload.id)]
: getRoomCameras().map((cam) => cam.id);
const uniqueIds = Array.from(new Set(list));
try {
@@ -126,8 +124,6 @@ function registerRoomCameraSocketGateway({ getRoomCamera, getRoomCameras, getRoo
socket.on('roomCamera:unsubscribe', (payload = {}) => {
const list = Array.isArray(payload?.ids)
? payload.ids.map(String)
- : payload?.roomCameraId || payload?.id
- ? [String(payload.roomCameraId || payload.id)]
: [];
list.forEach((cameraId) => removeSubscription(socket.id, cameraId));
});
diff --git a/server/src/services/roverManager/index.js b/server/src/services/roverManager/index.js
index 0ad1e02a..3e9e5290 100644
--- a/server/src/services/roverManager/index.js
+++ b/server/src/services/roverManager/index.js
@@ -216,7 +216,6 @@ registerSocketHandlers({
tickPrivateAutoClose,
removeSocket,
enableSpectator,
- getRosterForSocket,
canRequestControl,
canSwitchRover,
getRoversForSocket,
diff --git a/server/src/services/roverManager/rosterLifecycle.js b/server/src/services/roverManager/rosterLifecycle.js
index a7bf80ce..9b833c1e 100644
--- a/server/src/services/roverManager/rosterLifecycle.js
+++ b/server/src/services/roverManager/rosterLifecycle.js
@@ -251,9 +251,6 @@ function createRosterLifecycle(deps) {
function broadcastRoster() {
syncSpectatorRooms();
- io.sockets.sockets.forEach((socket) => {
- socket.emit('rovers', getRosterForSocket(socket));
- });
}
function setNightVisionState(roverId, nightVisionOn) {
diff --git a/server/src/services/roverManager/roverLifecycle.js b/server/src/services/roverManager/roverLifecycle.js
index d4d969a8..fb435330 100644
--- a/server/src/services/roverManager/roverLifecycle.js
+++ b/server/src/services/roverManager/roverLifecycle.js
@@ -44,7 +44,6 @@ function createRoverLifecycle(deps) {
socketToRovers.get(socket.id).add(roverId);
socket.join(record.room);
turnService.driverAdded(roverId, socket.id, force && isAdmin(socket));
- socket.emit('controlGranted', { roverId });
managerEvents.emit('driver', { socketId: socket.id, roverId, action: 'add' });
sendAlert({
color: ALERT_COLOR,
diff --git a/server/src/services/roverManager/socketHandlers.js b/server/src/services/roverManager/socketHandlers.js
index f4c93c24..cbbc5561 100644
--- a/server/src/services/roverManager/socketHandlers.js
+++ b/server/src/services/roverManager/socketHandlers.js
@@ -18,7 +18,6 @@ function registerSocketHandlers(deps) {
tickPrivateAutoClose,
removeSocket,
enableSpectator,
- getRosterForSocket,
canRequestControl,
canSwitchRover,
getRoversForSocket,
@@ -32,7 +31,6 @@ function registerSocketHandlers(deps) {
io.on('connection', (socket) => {
tickPrivateAutoClose();
- socket.emit('rovers', getRosterForSocket(socket));
if (socket.data?.role === 'spectator') {
enableSpectator(socket);
}
@@ -79,7 +77,6 @@ function registerSocketHandlers(deps) {
info.sourceId !== `${targetId}-audio`,
);
managerEvents.emit('switch', { socketId: socket.id, roverId: targetId });
- socket.emit('controlGranted', { roverId: targetId });
cb({ success: true, roverId: targetId });
} catch (err) {
logger.warn('Request control failed', socket.id, err.message);
@@ -172,15 +169,10 @@ function registerSocketHandlers(deps) {
cb({ success: true });
}
- socket.on('requestControl', handleRequestControl);
socket.on('session:requestControl', handleRequestControl);
- socket.on('releaseControl', handleReleaseControl);
socket.on('session:releaseControl', handleReleaseControl);
- socket.on('lockRover', handleLockToggle);
socket.on('session:lockRover', handleLockToggle);
- socket.on('privateSafety:set', handlePrivateSafetySet);
socket.on('session:privateSafety:set', handlePrivateSafetySet);
- socket.on('subscribeAll', handleSubscribeAll);
socket.on('session:subscribeAll', handleSubscribeAll);
socket.on('disconnecting', () => {
diff --git a/server/src/services/sessionService/constants.js b/server/src/services/sessionService/constants.js
index 58ac014b..cb0f5549 100644
--- a/server/src/services/sessionService/constants.js
+++ b/server/src/services/sessionService/constants.js
@@ -11,7 +11,6 @@ const configuredSocials = Array.isArray(config.socials) ? config.socials : null;
const ACTIVITY_SYNC_COOLDOWN_MS = 3000;
const NIGHT_VISION_SYNC_COOLDOWN_MS = 1000;
-const PERIODIC_SYNC_MS = 20000;
module.exports = {
discordInvite,
@@ -20,5 +19,4 @@ module.exports = {
configuredSocials,
ACTIVITY_SYNC_COOLDOWN_MS,
NIGHT_VISION_SYNC_COOLDOWN_MS,
- PERIODIC_SYNC_MS,
};
diff --git a/server/src/services/sessionService/index.js b/server/src/services/sessionService/index.js
index 08a38dec..ccbd7b62 100644
--- a/server/src/services/sessionService/index.js
+++ b/server/src/services/sessionService/index.js
@@ -40,7 +40,6 @@ const {
configuredSocials,
ACTIVITY_SYNC_COOLDOWN_MS,
NIGHT_VISION_SYNC_COOLDOWN_MS,
- PERIODIC_SYNC_MS,
} = require('./constants');
const { getState, setState } = require('./state');
const {
@@ -330,14 +329,4 @@ audioLevelsEvents.on('change', () => {
syncAll();
});
-// sync all sockets 20 seconds
-// setInterval(() => {
-// logger.info('Periodic session sync for all clients');
-// syncAll();
-// }, PERIODIC_SYNC_MS);
-
-module.exports = {
- buildSession,
- syncSocket,
- syncAll,
-};
+module.exports = {};
diff --git a/server/src/services/verificationService/identity.js b/server/src/services/verificationService/identity.js
index 80d4cd85..401b547a 100644
--- a/server/src/services/verificationService/identity.js
+++ b/server/src/services/verificationService/identity.js
@@ -43,7 +43,7 @@ function normalizeKnownIps(raw = []) {
}
function isAdminRole(role) {
- return role === 'admin' || role === 'lockdown' || role === 'lockdown-admin';
+ return role === 'admin' || role === 'lockdown';
}
function parseDeterrenceSelector(selector) {
diff --git a/server/src/services/videoSessions/index.js b/server/src/services/videoSessions/index.js
index a7cb9ff6..3c2cdaa0 100644
--- a/server/src/services/videoSessions/index.js
+++ b/server/src/services/videoSessions/index.js
@@ -68,6 +68,5 @@ module.exports = {
createSession,
getSession,
revokeSession,
- revokeBySocket,
revokeWhere,
};
diff --git a/server/src/services/videoSocketService/index.js b/server/src/services/videoSocketService/index.js
index 429b3c21..4291e168 100644
--- a/server/src/services/videoSocketService/index.js
+++ b/server/src/services/videoSocketService/index.js
@@ -31,13 +31,7 @@ function getMediaPrefix() {
function buildWhepUrlForSource(source) {
const cleanBase = getMediaPrefix();
if (!cleanBase) return '';
- const segments = [];
- if (source.type === 'room') {
- segments.push('room', encodeURIComponent(source.id));
- } else {
- segments.push(encodeURIComponent(source.id));
- }
- return `${cleanBase}/${segments.join('/')}/whep`;
+ return `${cleanBase}/${encodeURIComponent(source.id)}/whep`;
}
function passesMode(socket) {
@@ -66,21 +60,14 @@ function canViewRover(socket, roverId) {
return roverManager.isDriver(roverId, socket);
}
-function canViewRoomCamera(socket) {
- return passesMode(socket);
-}
-
function normalizeRequest(payload = {}) {
if (!payload) return null;
- if (payload.type && payload.id) {
- return { type: payload.type, id: String(payload.id) };
+ if (payload.type && payload.id && payload.type === 'rover') {
+ return { type: 'rover', id: String(payload.id) };
}
if (payload.roverId) {
return { type: 'rover', id: String(payload.roverId) };
}
- if (payload.roomCameraId) {
- return { type: 'room', id: String(payload.roomCameraId) };
- }
return null;
}
@@ -91,26 +78,20 @@ io.on('connection', (socket) => {
if (!target) {
throw new Error('video source required');
}
- if (target.type === 'rover') {
- const baseId = target.id.endsWith('-audio') ? target.id.slice(0, -6) : target.id;
- const isAudio = target.id.endsWith('-audio');
- if (!roverManager.rovers.has(baseId)) {
- throw new Error('Rover offline');
- }
- if (!canViewRover(socket, baseId)) {
+ const baseId = target.id.endsWith('-audio') ? target.id.slice(0, -6) : target.id;
+ const isAudio = target.id.endsWith('-audio');
+ if (!roverManager.rovers.has(baseId)) {
+ throw new Error('Rover offline');
+ }
+ if (!canViewRover(socket, baseId)) {
+ throw new Error('Not authorized for video');
+ }
+ const role = getRole(socket);
+ if (role === 'spectator' && !isAdmin(socket) && !isAudio) {
+ const ip = getSocketIp(socket);
+ if (!isLocalNetwork(ip)) {
throw new Error('Not authorized for video');
}
- const role = getRole(socket);
- if (role === 'spectator' && !isAdmin(socket) && !isAudio) {
- const ip = getSocketIp(socket);
- if (!isLocalNetwork(ip)) {
- throw new Error('Not authorized for video');
- }
- }
- } else if (target.type === 'room') {
- throw new Error('Room cameras now use the snapshot feed');
- } else {
- throw new Error('Unsupported video source');
}
const url = buildWhepUrlForSource(target);
if (!url) {
diff --git a/webui/src/App.css b/webui/src/App.css
deleted file mode 100644
index 2bf5bb83..00000000
--- a/webui/src/App.css
+++ /dev/null
@@ -1,51 +0,0 @@
-#root {
- max-width: 1280px;
- margin: 0 auto;
- padding: 2rem;
- text-align: center;
-}
-
-.logo {
- height: 6em;
- padding: 1.5em;
- will-change: filter;
- transition: filter 300ms;
-}
-.logo:hover {
- filter: drop-shadow(0 0 2em #646cffaa);
-}
-.logo.react:hover {
- filter: drop-shadow(0 0 2em #61dafbaa);
-}
-
-@keyframes logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
-
-@media (prefers-reduced-motion: no-preference) {
- a:nth-of-type(2) .logo {
- animation: logo-spin infinite 20s linear;
- }
-}
-
-@keyframes spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
-
-.card {
- padding: 2em;
-}
-
-.read-the-docs {
- color: #888;
-}
diff --git a/webui/src/assets/react.svg b/webui/src/assets/react.svg
deleted file mode 100644
index 6c87de9b..00000000
--- a/webui/src/assets/react.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/webui/src/components/AdminPanel/AdminPanelContent.jsx b/webui/src/components/AdminPanel/AdminPanelContent.jsx
index 8c3bc79f..12f5b735 100644
--- a/webui/src/components/AdminPanel/AdminPanelContent.jsx
+++ b/webui/src/components/AdminPanel/AdminPanelContent.jsx
@@ -57,9 +57,8 @@ export default function AdminPanelContent() {
const isAdmin =
session?.role === 'admin' ||
- session?.role === 'lockdown' ||
- session?.role === 'lockdown-admin';
- const isLockdownAdmin = session?.role === 'lockdown' || session?.role === 'lockdown-admin';
+ session?.role === 'lockdown';
+ const isLockdownAdmin = session?.role === 'lockdown';
const currentMode = session?.mode ?? 'open';
diff --git a/webui/src/components/ChatMessageRow/index.jsx b/webui/src/components/ChatMessageRow/index.jsx
index 7cdbb03a..419abc09 100644
--- a/webui/src/components/ChatMessageRow/index.jsx
+++ b/webui/src/components/ChatMessageRow/index.jsx
@@ -8,7 +8,6 @@ function roleColors(role) {
switch (role) {
case 'admin':
case 'lockdown':
- case 'lockdown-admin':
return 'text-amber-300';
case 'spectator':
return 'text-slate-400';
@@ -108,7 +107,7 @@ function chatRowClass(message) {
return 'surface-muted relative flex flex-wrap items-start gap-0.5 border border-emerald-500/40 bg-emerald-900/15 text-sm';
}
const isAdmin =
- message.role === 'admin' || message.role === 'lockdown' || message.role === 'lockdown-admin';
+ message.role === 'admin' || message.role === 'lockdown';
return `surface-muted relative flex flex-wrap items-start gap-0.5 text-sm ${
isAdmin
? 'border border-amber-400/30'
diff --git a/webui/src/components/ModeGateOverlay/index.jsx b/webui/src/components/ModeGateOverlay/index.jsx
index 5e1054f3..f2965aa9 100644
--- a/webui/src/components/ModeGateOverlay/index.jsx
+++ b/webui/src/components/ModeGateOverlay/index.jsx
@@ -8,8 +8,8 @@ import SocialButton from '../SocialButton/index.jsx';
import ChatPanel from '../ChatPanel/index.jsx';
import NicknameForm from '../NicknameForm/index.jsx';
-const PRIVILEGED_ROLES = new Set(['admin', 'lockdown', 'lockdown-admin']);
-const LOCKDOWN_ROLES = new Set(['lockdown', 'lockdown-admin']);
+const PRIVILEGED_ROLES = new Set(['admin', 'lockdown']);
+const LOCKDOWN_ROLES = new Set(['lockdown']);
const RESTRICTED_MODES = new Set(['admin', 'lockdown']);
function getModeDetails(mode = 'admin') {
diff --git a/webui/src/components/RawUserPilePanel/index.jsx b/webui/src/components/RawUserPilePanel/index.jsx
index be93f7ad..7faea8a7 100644
--- a/webui/src/components/RawUserPilePanel/index.jsx
+++ b/webui/src/components/RawUserPilePanel/index.jsx
@@ -10,7 +10,6 @@ function roleColors(role) {
switch (role) {
case 'admin':
case 'lockdown':
- case 'lockdown-admin':
return 'text-amber-300';
case 'spectator':
return 'text-slate-400';
diff --git a/webui/src/components/RoverQueuesPanel/index.jsx b/webui/src/components/RoverQueuesPanel/index.jsx
index 1ebcf034..7221b13b 100644
--- a/webui/src/components/RoverQueuesPanel/index.jsx
+++ b/webui/src/components/RoverQueuesPanel/index.jsx
@@ -26,7 +26,6 @@ function roleColors(role) {
switch (role) {
case 'admin':
case 'lockdown':
- case 'lockdown-admin':
return 'text-amber-300';
case 'spectator':
return 'text-slate-400';
@@ -56,7 +55,7 @@ export default function RoverQueuesPanel({ title = 'Rovers' }) {
const canRequest = useMemo(() => role && role !== 'spectator', [role]);
const adminCapable = useMemo(
- () => role === 'admin' || role === 'lockdown' || role === 'lockdown-admin',
+ () => role === 'admin' || role === 'lockdown',
[role],
);
const hasDeadlines = useMemo(
diff --git a/webui/src/components/UserListPanel/index.jsx b/webui/src/components/UserListPanel/index.jsx
index 8e9eba4a..d6124820 100644
--- a/webui/src/components/UserListPanel/index.jsx
+++ b/webui/src/components/UserListPanel/index.jsx
@@ -29,7 +29,6 @@ function roleColors(role) {
switch (role) {
case 'admin':
case 'lockdown':
- case 'lockdown-admin':
return 'text-amber-300';
case 'spectator':
return 'text-slate-400';
@@ -121,7 +120,7 @@ export default function UserListPanel({
) : (
sorted.map((user) => {
const isAdmin =
- user.role === 'admin' || user.role === 'lockdown' || user.role === 'lockdown-admin';
+ user.role === 'admin' || user.role === 'lockdown';
return (
({
diff --git a/webui/src/hooks/useVideoRequests.js b/webui/src/hooks/useVideoRequests.js
index 04462a83..8e7a55dc 100644
--- a/webui/src/hooks/useVideoRequests.js
+++ b/webui/src/hooks/useVideoRequests.js
@@ -1,5 +1,5 @@
// Hook: useVideoRequests
-// Purpose: Coordinates client-side video stream request intents and authorization timing. Scope: Provides reusable request helpers for rover and room video consumers.
+// Purpose: Coordinates client-side video stream request intents and authorization timing. Scope: Provides reusable request helpers for rover video consumers.
import { useEffect, useMemo, useRef, useState } from 'react';
import { useSocket } from '../context/SocketContext.jsx';
@@ -13,12 +13,9 @@ function normalizeEntry(entry) {
if (typeof entry === 'object') {
if (entry.type && entry.id) {
const id = String(entry.id);
- let key = entry.key;
- if (!key) {
- key = entry.type === 'room' ? `room:${id}` : id;
- }
+ const key = entry.key || id;
return {
- type: entry.type,
+ type: 'rover',
id,
key,
};
@@ -27,10 +24,6 @@ function normalizeEntry(entry) {
const id = String(entry.roverId);
return { type: 'rover', id, key: entry.key || id };
}
- if (entry.roomCameraId) {
- const id = String(entry.roomCameraId);
- return { type: 'room', id, key: entry.key || `room:${id}` };
- }
}
return null;
}
@@ -87,7 +80,7 @@ export function useVideoRequests(sourceList = [], options = {}) {
let cancelled = false;
function requestEntry(entry) {
- const payload = entry.type === 'room' ? { roomCameraId: entry.id } : { roverId: entry.id };
+ const payload = { roverId: entry.id };
socket.emit('video:request', payload, (resp = {}) => {
if (cancelled) return;
setSources((prev) => ({ ...prev, [entry.key]: resp }));