mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
community goal? never jnew her
This commit is contained in:
+1
-1
@@ -14,6 +14,6 @@ server/config.yaml
|
|||||||
server/package-lock.json
|
server/package-lock.json
|
||||||
package-lock.json
|
package-lock.json
|
||||||
server/data/discord-guilds.json
|
server/data/discord-guilds.json
|
||||||
server/data/community-goal.json
|
server/data/global-objective.json
|
||||||
server/data/admin-reason.json
|
server/data/admin-reason.json
|
||||||
server/data
|
server/data
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
- Continued `roverManager` split by extracting sensor processing + private safety + dock guard logic into `roverManager/sensorPipeline.js`.
|
- Continued `roverManager` split by extracting sensor processing + private safety + dock guard logic into `roverManager/sensorPipeline.js`.
|
||||||
- Finished `roverManager` decomposition by extracting private access policy, roster lifecycle, and spectator/auto-close orchestration into `roverManager/privateAccess.js`, `roverManager/rosterLifecycle.js`, and `roverManager/spectatorAccess.js`; `roverManager/index.js` is now a thin composition layer.
|
- Finished `roverManager` decomposition by extracting private access policy, roster lifecycle, and spectator/auto-close orchestration into `roverManager/privateAccess.js`, `roverManager/rosterLifecycle.js`, and `roverManager/spectatorAccess.js`; `roverManager/index.js` is now a thin composition layer.
|
||||||
- Hotfix: corrected `llmCommentaryService` prompt file path to `server/prompts/commentary_system.txt` after service folder move.
|
- Hotfix: corrected `llmCommentaryService` prompt file path to `server/prompts/commentary_system.txt` after service folder move.
|
||||||
- Hotfix: added `server/src/helpers/dataPaths.js` and rewired data-backed services to resolve canonical + legacy data-file locations safely after folderization (`adminReason`, `audioLevels`, `buttonBox`, `communityGoal`, `discordGuildStore`, `verification`, `replayEngineV2`).
|
- Hotfix: added `server/src/helpers/dataPaths.js` and rewired data-backed services to resolve canonical + legacy data-file locations safely after folderization (`adminReason`, `audioLevels`, `buttonBox`, `globalObjective`, `discordGuildStore`, `verification`, `replayEngineV2`).
|
||||||
- Began `llmCommentaryService` decomposition by extracting immutable runtime limits/path/frequency normalization to `llmCommentaryService/constants.js` and pure prompt/text output helpers to `llmCommentaryService/formatters.js`.
|
- Began `llmCommentaryService` decomposition by extracting immutable runtime limits/path/frequency normalization to `llmCommentaryService/constants.js` and pure prompt/text output helpers to `llmCommentaryService/formatters.js`.
|
||||||
- Continued `llmCommentaryService` decomposition by extracting admin/runtime projection + failure-normalization helpers to `llmCommentaryService/runtimeHelpers.js`.
|
- Continued `llmCommentaryService` decomposition by extracting admin/runtime projection + failure-normalization helpers to `llmCommentaryService/runtimeHelpers.js`.
|
||||||
- Continued `llmCommentaryService` decomposition by extracting sensor activity aggregation and snapshot assembly to `llmCommentaryService/snapshotEngine.js`; rewired commentary tick/event flow to use the new engine.
|
- Continued `llmCommentaryService` decomposition by extracting sensor activity aggregation and snapshot assembly to `llmCommentaryService/snapshotEngine.js`; rewired commentary tick/event flow to use the new engine.
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ require('./src/services/verificationService');
|
|||||||
require('./src/services/privateRoverAccessRequestService');
|
require('./src/services/privateRoverAccessRequestService');
|
||||||
require('./src/services/chatService');
|
require('./src/services/chatService');
|
||||||
require('./src/services/llmCommentaryService');
|
require('./src/services/llmCommentaryService');
|
||||||
require('./src/services/communityGoalService');
|
require('./src/services/globalObjectiveService');
|
||||||
require('./src/services/serverControlService');
|
require('./src/services/serverControlService');
|
||||||
require('./src/services/videoSessions');
|
require('./src/services/videoSessions');
|
||||||
require('./src/services/videoAuthService');
|
require('./src/services/videoAuthService');
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -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="Multi Roomba Rover" />
|
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
|
||||||
<title>Multi Roomba Rover</title>
|
<title>Multi Roomba Rover</title>
|
||||||
<script type="module" crossorigin src="/assets/index-DBuBh8sB.js"></script>
|
<script type="module" crossorigin src="/assets/index-DIzOCfJB.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-CQrwYoqn.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-CQrwYoqn.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
// Discord Goal Command
|
// Discord Goal Command
|
||||||
// Purpose: Handles community goal view/update/clear operations.
|
// Purpose: Handles global objective view/update/clear operations.
|
||||||
// Scope: Allows read by all and write by admins.
|
// Scope: Allows read by all and write by admins.
|
||||||
function createGoalCommand({ getCommunityGoal, setCommunityGoal, clearCommunityGoal, isAdminUser, sanitizeMentions }) {
|
function createGoalCommand({ getGlobalObjective, setGlobalObjective, clearGlobalObjective, isAdminUser, sanitizeMentions }) {
|
||||||
return async function handleGoalCommand(message, tokens) {
|
return async function handleGoalCommand(message, tokens) {
|
||||||
const query = tokens.join(' ').trim();
|
const query = tokens.join(' ').trim();
|
||||||
const lower = query.toLowerCase();
|
const lower = query.toLowerCase();
|
||||||
if (!query) {
|
if (!query) {
|
||||||
const goal = getCommunityGoal();
|
const goal = getGlobalObjective();
|
||||||
await message.reply({ content: goal?.text ? `Community goal: ${sanitizeMentions(goal.text)}` : 'No community goal set.', allowedMentions: { parse: [], repliedUser: false } });
|
await message.reply({ content: goal?.text ? `Global objective: ${sanitizeMentions(goal.text)}` : 'No global objective set.', allowedMentions: { parse: [], repliedUser: false } });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!isAdminUser(message.author.id)) {
|
if (!isAdminUser(message.author.id)) {
|
||||||
await message.reply({ content: 'Only admins can update the community goal.', allowedMentions: { parse: [], repliedUser: false } });
|
await message.reply({ content: 'Only admins can update the global objective.', allowedMentions: { parse: [], repliedUser: false } });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (lower === 'clear') {
|
if (lower === 'clear') {
|
||||||
clearCommunityGoal({ by: message.author?.id || null });
|
clearGlobalObjective({ by: message.author?.id || null });
|
||||||
await message.reply({ content: 'Community goal cleared.', allowedMentions: { parse: [], repliedUser: false } });
|
await message.reply({ content: 'Global objective cleared.', allowedMentions: { parse: [], repliedUser: false } });
|
||||||
} else {
|
} else {
|
||||||
setCommunityGoal(query, { by: message.author?.id || null });
|
setGlobalObjective(query, { by: message.author?.id || null });
|
||||||
await message.reply({ content: sanitizeMentions(`Community goal set: ${query}`), allowedMentions: { parse: [], repliedUser: false } });
|
await message.reply({ content: sanitizeMentions(`Global objective set: ${query}`), allowedMentions: { parse: [], repliedUser: false } });
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await message.reply({ content: sanitizeMentions(`Failed to update goal: ${err.message}`), allowedMentions: { parse: [], repliedUser: false } });
|
await message.reply({ content: sanitizeMentions(`Failed to update goal: ${err.message}`), allowedMentions: { parse: [], repliedUser: false } });
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ function formatHelp() {
|
|||||||
'`rs unlock <id>` — unlock a rover',
|
'`rs unlock <id>` — unlock a rover',
|
||||||
'`rs mode <open|turns|admin|lockdown>` — change server mode',
|
'`rs mode <open|turns|admin|lockdown>` — change server mode',
|
||||||
'`rs reason [text|clear]` — show or set admin mode reason',
|
'`rs reason [text|clear]` — show or set admin mode reason',
|
||||||
'`rs goal [text|clear]` — show or set community goal',
|
'`rs goal [text|clear]` — show or set global objective',
|
||||||
'`rs verify list|remove ...` — manage verified users',
|
'`rs verify list|remove ...` — manage verified users',
|
||||||
'`rs deter list|ban|unban ...` — manage deterred users',
|
'`rs deter list|ban|unban ...` — manage deterred users',
|
||||||
'`ts` — show time status',
|
'`ts` — show time status',
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const { sendExternalMessage, sendExternalTyping } = require('../chatService');
|
|||||||
const { buildReplayVideo, getReplaySources, getDefaultDiscordSources, validateSources, tryTriggerReplay } = require('../replayEngineV2');
|
const { buildReplayVideo, getReplaySources, getDefaultDiscordSources, validateSources, tryTriggerReplay } = require('../replayEngineV2');
|
||||||
const { getActiveDrivers } = require('../turnService');
|
const { getActiveDrivers } = require('../turnService');
|
||||||
const { getNickname } = require('../nicknameService');
|
const { getNickname } = require('../nicknameService');
|
||||||
const { getCommunityGoal, setCommunityGoal, clearCommunityGoal } = require('../communityGoalService');
|
const { getGlobalObjective, setGlobalObjective, clearGlobalObjective } = require('../globalObjectiveService');
|
||||||
const { getAdminReason, setAdminReason, clearAdminReason } = require('../adminReasonService');
|
const { getAdminReason, setAdminReason, clearAdminReason } = require('../adminReasonService');
|
||||||
const {
|
const {
|
||||||
getGuildConfig,
|
getGuildConfig,
|
||||||
@@ -108,7 +108,7 @@ const presence = createPresenceManager({
|
|||||||
client,
|
client,
|
||||||
logger,
|
logger,
|
||||||
getMode,
|
getMode,
|
||||||
getCommunityGoal,
|
getGlobalObjective,
|
||||||
countReady,
|
countReady,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -129,9 +129,9 @@ const commands = createCommandHandlers({
|
|||||||
getDefaultDiscordSources,
|
getDefaultDiscordSources,
|
||||||
validateSources,
|
validateSources,
|
||||||
tryTriggerReplay,
|
tryTriggerReplay,
|
||||||
getCommunityGoal,
|
getGlobalObjective,
|
||||||
setCommunityGoal,
|
setGlobalObjective,
|
||||||
clearCommunityGoal,
|
clearGlobalObjective,
|
||||||
getAdminReason,
|
getAdminReason,
|
||||||
setAdminReason,
|
setAdminReason,
|
||||||
clearAdminReason,
|
clearAdminReason,
|
||||||
|
|||||||
@@ -98,8 +98,8 @@ function createBusEventHandler(deps) {
|
|||||||
}
|
}
|
||||||
schedulePresenceRotation();
|
schedulePresenceRotation();
|
||||||
break;
|
break;
|
||||||
case 'communityGoal.updated':
|
case 'globalObjective.updated':
|
||||||
announce({ channelId: channels.announcements, content: payload?.text ? `Community goal: ${payload.text}` : 'Community goal cleared.', color: 0x8bc34a, title: 'Community Goal', description: payload?.text || 'Community goal cleared.' });
|
announce({ channelId: channels.announcements, content: payload?.text ? `Global objective: ${payload.text}` : 'Global objective cleared.', color: 0x8bc34a, title: 'Global Objective', description: payload?.text || 'Global objective cleared.' });
|
||||||
schedulePresenceRotation();
|
schedulePresenceRotation();
|
||||||
break;
|
break;
|
||||||
case 'rover.online':
|
case 'rover.online':
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
// Discord Presence Module
|
// Discord Presence Module
|
||||||
// Purpose: Owns rotating Discord presence text derived from rover readiness, mode, and community goals.
|
// Purpose: Owns rotating Discord presence text derived from rover readiness, mode, and global objectives.
|
||||||
// Scope: Handles presence update scheduling/state and exposes start/recompute controls for orchestration.
|
// Scope: Handles presence update scheduling/state and exposes start/recompute controls for orchestration.
|
||||||
const { ActivityType } = require('discord.js');
|
const { ActivityType } = require('discord.js');
|
||||||
|
|
||||||
function createPresenceManager({ client, logger, getMode, getCommunityGoal, countReady }) {
|
function createPresenceManager({ client, logger, getMode, getGlobalObjective, countReady }) {
|
||||||
const PRESENCE_ROTATE_MS = 20000;
|
const PRESENCE_ROTATE_MS = 20000;
|
||||||
let presenceInterval = null;
|
let presenceInterval = null;
|
||||||
let presenceShowGoal = false;
|
let presenceShowObjective = false;
|
||||||
|
|
||||||
function truncatePresenceText(text, maxLength) {
|
function truncatePresenceText(text, maxLength) {
|
||||||
if (!text) return '';
|
if (!text) return '';
|
||||||
@@ -18,11 +18,11 @@ function createPresenceManager({ client, logger, getMode, getCommunityGoal, coun
|
|||||||
function buildPresenceName() {
|
function buildPresenceName() {
|
||||||
const { ready, total } = countReady();
|
const { ready, total } = countReady();
|
||||||
const mode = getMode();
|
const mode = getMode();
|
||||||
const goal = getCommunityGoal();
|
const objective = getGlobalObjective();
|
||||||
const goalText = goal?.text ? String(goal.text).trim() : '';
|
const objectiveText = objective?.text ? String(objective.text).trim() : '';
|
||||||
if (presenceShowGoal && goalText) {
|
if (presenceShowObjective && objectiveText) {
|
||||||
const trimmed = truncatePresenceText(goalText, 110);
|
const trimmed = truncatePresenceText(objectiveText, 110);
|
||||||
return `Goal: ${trimmed}`;
|
return `Objective: ${trimmed}`;
|
||||||
}
|
}
|
||||||
return `${mode} · ${ready}/${total} Rovers Ready`;
|
return `${mode} · ${ready}/${total} Rovers Ready`;
|
||||||
}
|
}
|
||||||
@@ -44,16 +44,16 @@ function createPresenceManager({ client, logger, getMode, getCommunityGoal, coun
|
|||||||
clearInterval(presenceInterval);
|
clearInterval(presenceInterval);
|
||||||
presenceInterval = null;
|
presenceInterval = null;
|
||||||
}
|
}
|
||||||
const goal = getCommunityGoal();
|
const objective = getGlobalObjective();
|
||||||
if (!goal?.text) {
|
if (!objective?.text) {
|
||||||
presenceShowGoal = false;
|
presenceShowObjective = false;
|
||||||
updatePresence();
|
updatePresence();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
presenceShowGoal = false;
|
presenceShowObjective = false;
|
||||||
updatePresence();
|
updatePresence();
|
||||||
presenceInterval = setInterval(() => {
|
presenceInterval = setInterval(() => {
|
||||||
presenceShowGoal = !presenceShowGoal;
|
presenceShowObjective = !presenceShowObjective;
|
||||||
updatePresence();
|
updatePresence();
|
||||||
}, PRESENCE_ROTATE_MS);
|
}, PRESENCE_ROTATE_MS);
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-17
@@ -1,15 +1,16 @@
|
|||||||
// community Goal Service
|
// Global Objective Service
|
||||||
// Purpose: Defines the community Goal Service module and the helpers/state used by this service unit.
|
// Purpose: Defines the global objective service module and the helpers/state used by this service unit.
|
||||||
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
|
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const io = require('../../globals/io');
|
const io = require('../../globals/io');
|
||||||
const logger = require('../../globals/logger').child('communityGoalService');
|
const logger = require('../../globals/logger').child('globalObjectiveService');
|
||||||
const { isAdmin } = require('../roleService');
|
const { isAdmin } = require('../roleService');
|
||||||
const { publishEvent } = require('../eventBus');
|
const { publishEvent } = require('../eventBus');
|
||||||
const { resolveDataDir, resolveDataPath } = require('../../helpers/dataPaths');
|
const { resolveDataDir, resolveDataPath } = require('../../helpers/dataPaths');
|
||||||
|
|
||||||
const DATA_DIR = resolveDataDir();
|
const DATA_DIR = resolveDataDir();
|
||||||
const STORE_PATH = resolveDataPath('community-goal.json');
|
const STORE_PATH = resolveDataPath('global-objective.json');
|
||||||
|
const LEGACY_STORE_PATH = resolveDataPath('community-goal.json');
|
||||||
const MAX_GOAL_LENGTH = 240;
|
const MAX_GOAL_LENGTH = 240;
|
||||||
|
|
||||||
let cache = null;
|
let cache = null;
|
||||||
@@ -21,9 +22,18 @@ function loadStore() {
|
|||||||
cache = JSON.parse(raw);
|
cache = JSON.parse(raw);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code !== 'ENOENT') {
|
if (err.code !== 'ENOENT') {
|
||||||
logger.warn('Failed to load community goal', err.message);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cache = null;
|
if (!cache) cache = null;
|
||||||
}
|
}
|
||||||
return cache;
|
return cache;
|
||||||
}
|
}
|
||||||
@@ -39,11 +49,11 @@ function normalizeText(input) {
|
|||||||
return input.replace(/\s+/g, ' ').trim();
|
return input.replace(/\s+/g, ' ').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCommunityGoal() {
|
function getGlobalObjective() {
|
||||||
return loadStore();
|
return loadStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setCommunityGoal(text, meta = {}) {
|
function setGlobalObjective(text, meta = {}) {
|
||||||
const clean = normalizeText(text);
|
const clean = normalizeText(text);
|
||||||
if (!clean) {
|
if (!clean) {
|
||||||
throw new Error('Goal text required');
|
throw new Error('Goal text required');
|
||||||
@@ -57,23 +67,23 @@ function setCommunityGoal(text, meta = {}) {
|
|||||||
updatedBy: meta.by || null,
|
updatedBy: meta.by || null,
|
||||||
};
|
};
|
||||||
saveStore(payload);
|
saveStore(payload);
|
||||||
publishEvent({ source: 'communityGoal', type: 'communityGoal.updated', payload });
|
publishEvent({ source: 'globalObjective', type: 'globalObjective.updated', payload });
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearCommunityGoal(meta = {}) {
|
function clearGlobalObjective(meta = {}) {
|
||||||
const payload = {
|
const payload = {
|
||||||
text: null,
|
text: null,
|
||||||
updatedAt: Date.now(),
|
updatedAt: Date.now(),
|
||||||
updatedBy: meta.by || null,
|
updatedBy: meta.by || null,
|
||||||
};
|
};
|
||||||
saveStore(payload);
|
saveStore(payload);
|
||||||
publishEvent({ source: 'communityGoal', type: 'communityGoal.updated', payload });
|
publishEvent({ source: 'globalObjective', type: 'globalObjective.updated', payload });
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
io.on('connection', (socket) => {
|
io.on('connection', (socket) => {
|
||||||
socket.on('communityGoal:set', ({ text } = {}, cb = () => {}) => {
|
socket.on('globalObjective:set', ({ text } = {}, cb = () => {}) => {
|
||||||
if (!isAdmin(socket)) {
|
if (!isAdmin(socket)) {
|
||||||
cb({ error: 'Not authorized' });
|
cb({ error: 'Not authorized' });
|
||||||
return;
|
return;
|
||||||
@@ -81,8 +91,8 @@ io.on('connection', (socket) => {
|
|||||||
try {
|
try {
|
||||||
const result =
|
const result =
|
||||||
text == null || String(text).trim() === ''
|
text == null || String(text).trim() === ''
|
||||||
? clearCommunityGoal({ by: socket?.data?.user?.username || socket?.id })
|
? clearGlobalObjective({ by: socket?.data?.user?.username || socket?.id })
|
||||||
: setCommunityGoal(text, { by: socket?.data?.user?.username || socket?.id });
|
: setGlobalObjective(text, { by: socket?.data?.user?.username || socket?.id });
|
||||||
cb({ success: true, goal: result });
|
cb({ success: true, goal: result });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
cb({ error: err.message });
|
cb({ error: err.message });
|
||||||
@@ -91,8 +101,8 @@ io.on('connection', (socket) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getCommunityGoal,
|
getGlobalObjective,
|
||||||
setCommunityGoal,
|
setGlobalObjective,
|
||||||
clearCommunityGoal,
|
clearGlobalObjective,
|
||||||
MAX_GOAL_LENGTH,
|
MAX_GOAL_LENGTH,
|
||||||
};
|
};
|
||||||
@@ -26,7 +26,7 @@ const {
|
|||||||
} = require('../privateRoverAccessRequestService');
|
} = require('../privateRoverAccessRequestService');
|
||||||
const { getReplayState, replayEvents, getReplaySources } = require('../replayEngineV2');
|
const { getReplayState, replayEvents, getReplaySources } = require('../replayEngineV2');
|
||||||
const { getHealthSnapshot } = require('../healthService');
|
const { getHealthSnapshot } = require('../healthService');
|
||||||
const { getCommunityGoal } = require('../communityGoalService');
|
const { getGlobalObjective } = require('../globalObjectiveService');
|
||||||
const { getAdminReason } = require('../adminReasonService');
|
const { getAdminReason } = require('../adminReasonService');
|
||||||
const { subscribe } = require('../eventBus');
|
const { subscribe } = require('../eventBus');
|
||||||
const { getSocketIp, isLocalNetwork } = require('../../helpers/ipResolver');
|
const { getSocketIp, isLocalNetwork } = require('../../helpers/ipResolver');
|
||||||
@@ -105,7 +105,7 @@ function buildSession(socket) {
|
|||||||
replay: getReplayState(),
|
replay: getReplayState(),
|
||||||
replaySources: getReplaySources(socket),
|
replaySources: getReplaySources(socket),
|
||||||
health: getHealthSnapshot(),
|
health: getHealthSnapshot(),
|
||||||
communityGoal: getCommunityGoal(),
|
globalObjective: getGlobalObjective(),
|
||||||
adminReason: getAdminReason(),
|
adminReason: getAdminReason(),
|
||||||
users,
|
users,
|
||||||
socials,
|
socials,
|
||||||
@@ -308,8 +308,8 @@ verificationEvents.on('change', ({ socketId } = {}) => {
|
|||||||
syncAll();
|
syncAll();
|
||||||
});
|
});
|
||||||
|
|
||||||
subscribe('communityGoal.updated', () => {
|
subscribe('globalObjective.updated', () => {
|
||||||
logger.info('Community goal updated; syncing all clients');
|
logger.info('Global objective updated; syncing all clients');
|
||||||
syncAll();
|
syncAll();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -33,7 +33,7 @@ import SettingsPanel from './components/SettingsPanel/index.jsx';
|
|||||||
import Tabs, { Tab, TabList, TabPanel, TabPanels } from './components/Tabs/index.jsx';
|
import Tabs, { Tab, TabList, TabPanel, TabPanels } from './components/Tabs/index.jsx';
|
||||||
import useDefaultNickname from './hooks/useDefaultNickname.js';
|
import useDefaultNickname from './hooks/useDefaultNickname.js';
|
||||||
import useUserIdentitySync from './hooks/useUserIdentitySync.js';
|
import useUserIdentitySync from './hooks/useUserIdentitySync.js';
|
||||||
import CommunityGoalBanner from './components/CommunityGoalBanner/index.jsx';
|
import GlobalObjectiveBanner from './components/GlobalObjectiveBanner/index.jsx';
|
||||||
import RoverQueuesPanel from './components/RoverQueuesPanel/index.jsx';
|
import RoverQueuesPanel from './components/RoverQueuesPanel/index.jsx';
|
||||||
import VipPanel from './components/VipPanel/index.jsx';
|
import VipPanel from './components/VipPanel/index.jsx';
|
||||||
import { useSessionSelector } from './context/SessionContext.jsx';
|
import { useSessionSelector } from './context/SessionContext.jsx';
|
||||||
@@ -79,7 +79,7 @@ function DesktopLayout({ layout, onOpenHelpOverlay }) {
|
|||||||
<LogPanel />
|
<LogPanel />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex min-w-0 flex-1 flex-col gap-0.5 overflow-y-auto">
|
<div className="flex min-w-0 flex-1 flex-col gap-0.5 overflow-y-auto">
|
||||||
<CommunityGoalBanner layout={layout} />
|
<GlobalObjectiveBanner layout={layout} />
|
||||||
<RightPaneTabs layout={layout} onOpenHelpOverlay={onOpenHelpOverlay} />
|
<RightPaneTabs layout={layout} onOpenHelpOverlay={onOpenHelpOverlay} />
|
||||||
{/* <SessionSnapshot /> */}
|
{/* <SessionSnapshot /> */}
|
||||||
</div>
|
</div>
|
||||||
@@ -301,7 +301,7 @@ function AppWithProviders({ layout, isDesktop, fullscreen }) {
|
|||||||
<KeyboardInputManager />
|
<KeyboardInputManager />
|
||||||
<GamepadInputManager />
|
<GamepadInputManager />
|
||||||
<main className={`flex w-full flex-col gap-0.5 text-base ${isDesktop ? 'h-full overflow-hidden' : ''}`}>
|
<main className={`flex w-full flex-col gap-0.5 text-base ${isDesktop ? 'h-full overflow-hidden' : ''}`}>
|
||||||
{!isDesktop ? <CommunityGoalBanner layout={layout} /> : null}
|
{!isDesktop ? <GlobalObjectiveBanner layout={layout} /> : null}
|
||||||
{renderedLayout}
|
{renderedLayout}
|
||||||
</main>
|
</main>
|
||||||
<AlertFeed />
|
<AlertFeed />
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default function AdminPanelContent() {
|
|||||||
lockRover,
|
lockRover,
|
||||||
setMode,
|
setMode,
|
||||||
requestControl,
|
requestControl,
|
||||||
setCommunityGoal,
|
setGlobalObjective,
|
||||||
setAdminReason,
|
setAdminReason,
|
||||||
rebootRover,
|
rebootRover,
|
||||||
rebootServer,
|
rebootServer,
|
||||||
@@ -37,8 +37,8 @@ export default function AdminPanelContent() {
|
|||||||
const [serverRebooting, setServerRebooting] = useState(false);
|
const [serverRebooting, setServerRebooting] = useState(false);
|
||||||
const [clearingLlmHistory, setClearingLlmHistory] = useState(false);
|
const [clearingLlmHistory, setClearingLlmHistory] = useState(false);
|
||||||
const health = session?.health || null;
|
const health = session?.health || null;
|
||||||
const currentGoal = session?.communityGoal?.text || '';
|
const currentGoal = session?.globalObjective?.text || '';
|
||||||
const goalUpdatedAt = session?.communityGoal?.updatedAt || null;
|
const goalUpdatedAt = session?.globalObjective?.updatedAt || null;
|
||||||
const [goalDraft, setGoalDraft] = useState(currentGoal);
|
const [goalDraft, setGoalDraft] = useState(currentGoal);
|
||||||
const currentReason = session?.adminReason?.text || '';
|
const currentReason = session?.adminReason?.text || '';
|
||||||
const reasonUpdatedAt = session?.adminReason?.updatedAt || null;
|
const reasonUpdatedAt = session?.adminReason?.updatedAt || null;
|
||||||
@@ -128,7 +128,7 @@ export default function AdminPanelContent() {
|
|||||||
|
|
||||||
const handleGoalSave = async () => {
|
const handleGoalSave = async () => {
|
||||||
try {
|
try {
|
||||||
await setCommunityGoal(goalDraft);
|
await setGlobalObjective(goalDraft);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert(err.message);
|
alert(err.message);
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@ export default function AdminPanelContent() {
|
|||||||
|
|
||||||
const handleGoalClear = async () => {
|
const handleGoalClear = async () => {
|
||||||
try {
|
try {
|
||||||
await setCommunityGoal(null);
|
await setGlobalObjective(null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert(err.message);
|
alert(err.message);
|
||||||
}
|
}
|
||||||
@@ -351,7 +351,7 @@ export default function AdminPanelContent() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<div className="flex items-center justify-between text-xs text-slate-400">
|
<div className="flex items-center justify-between text-xs text-slate-400">
|
||||||
<span>Community goal</span>
|
<span>Global objective</span>
|
||||||
{goalUpdatedAt ? (
|
{goalUpdatedAt ? (
|
||||||
<span>Updated {new Date(goalUpdatedAt).toLocaleString()}</span>
|
<span>Updated {new Date(goalUpdatedAt).toLocaleString()}</span>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -360,12 +360,12 @@ export default function AdminPanelContent() {
|
|||||||
type="text"
|
type="text"
|
||||||
value={goalDraft}
|
value={goalDraft}
|
||||||
onChange={(event) => setGoalDraft(event.target.value)}
|
onChange={(event) => setGoalDraft(event.target.value)}
|
||||||
placeholder="Set a community goal"
|
placeholder="Set a global objective"
|
||||||
className="field-input text-sm"
|
className="field-input text-sm"
|
||||||
/>
|
/>
|
||||||
<div className="flex gap-0.5 text-xs">
|
<div className="flex gap-0.5 text-xs">
|
||||||
<button type="button" onClick={handleGoalSave} className="button-dark">
|
<button type="button" onClick={handleGoalSave} className="button-dark">
|
||||||
Set goal
|
Set objective
|
||||||
</button>
|
</button>
|
||||||
<button type="button" onClick={handleGoalClear} className="button-danger">
|
<button type="button" onClick={handleGoalClear} className="button-danger">
|
||||||
Clear
|
Clear
|
||||||
@@ -495,4 +495,3 @@ export default function AdminPanelContent() {
|
|||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -1,5 +1,5 @@
|
|||||||
// Community Goal Banner
|
// Global Objective Banner
|
||||||
// Purpose: Defines the Community Goal Banner module and the local helpers/components used in this file.
|
// Purpose: Defines the Global Objective Banner 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, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useSession } from '../../context/SessionContext.jsx';
|
import { useSession } from '../../context/SessionContext.jsx';
|
||||||
@@ -8,9 +8,9 @@ const MOBILE_DISMISS_MS = 10000;
|
|||||||
const MAX_FONT_PX = 28;
|
const MAX_FONT_PX = 28;
|
||||||
const MIN_FONT_PX = 14;
|
const MIN_FONT_PX = 14;
|
||||||
|
|
||||||
export default function CommunityGoalBanner({ layout = 'desktop', className = '', dismissable = true }) {
|
export default function GlobalObjectiveBanner({ layout = 'desktop', className = '', dismissable = true }) {
|
||||||
const { session } = useSession();
|
const { session } = useSession();
|
||||||
const goalText = session?.communityGoal?.text ? String(session.communityGoal.text).trim() : '';
|
const goalText = session?.globalObjective?.text ? String(session.globalObjective.text).trim() : '';
|
||||||
const isMobile = layout === 'mobile-portrait' || layout === 'mobile-landscape' || layout === 'mobile';
|
const isMobile = layout === 'mobile-portrait' || layout === 'mobile-landscape' || layout === 'mobile';
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [fontSize, setFontSize] = useState(MAX_FONT_PX);
|
const [fontSize, setFontSize] = useState(MAX_FONT_PX);
|
||||||
@@ -97,8 +97,8 @@ export default function CommunityGoalBanner({ layout = 'desktop', className = ''
|
|||||||
>
|
>
|
||||||
<span className="flex w-full items-stretch gap-0.5 whitespace-nowrap rounded-md">
|
<span className="flex w-full items-stretch gap-0.5 whitespace-nowrap rounded-md">
|
||||||
<span className="flex flex-col justify-center border-r border-slate-700/60 px-0.5 text-[0.55em] font-semibold leading-tight text-slate-400">
|
<span className="flex flex-col justify-center border-r border-slate-700/60 px-0.5 text-[0.55em] font-semibold leading-tight text-slate-400">
|
||||||
<span>Community</span>
|
<span>Global</span>
|
||||||
<span>Goal</span>
|
<span>Objective</span>
|
||||||
</span>
|
</span>
|
||||||
<span ref={textContainerRef} className="flex-1 overflow-hidden text-slate-100">
|
<span ref={textContainerRef} className="flex-1 overflow-hidden text-slate-100">
|
||||||
<span ref={textRef} className="block">
|
<span ref={textRef} className="block">
|
||||||
@@ -181,7 +181,7 @@ export function SessionProvider({ children }) {
|
|||||||
}
|
}
|
||||||
return emitWithAck('replay:trigger', { sources: sourcesOrPayload, title });
|
return emitWithAck('replay:trigger', { sources: sourcesOrPayload, title });
|
||||||
},
|
},
|
||||||
setCommunityGoal: (text) => emitWithAck('communityGoal:set', { text }),
|
setGlobalObjective: (text) => emitWithAck('globalObjective:set', { text }),
|
||||||
setAdminReason: (text) => emitWithAck('adminReason:set', { text }),
|
setAdminReason: (text) => emitWithAck('adminReason:set', { text }),
|
||||||
rebootRover: (roverId) =>
|
rebootRover: (roverId) =>
|
||||||
emitWithAck('command', { roverId, type: 'reboot', data: { reboot: {} } }),
|
emitWithAck('command', { roverId, type: 'reboot', data: { reboot: {} } }),
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { useSpectatorMode } from '../../hooks/useSpectatorMode.js';
|
|||||||
import useDefaultNickname from '../../hooks/useDefaultNickname.js';
|
import useDefaultNickname from '../../hooks/useDefaultNickname.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 CommunityGoalBanner from '../../components/CommunityGoalBanner/index.jsx';
|
import GlobalObjectiveBanner from '../../components/GlobalObjectiveBanner/index.jsx';
|
||||||
import RoverQueuesPanel from '../../components/RoverQueuesPanel/index.jsx';
|
import RoverQueuesPanel from '../../components/RoverQueuesPanel/index.jsx';
|
||||||
import RawUserPilePanel from '../../components/RawUserPilePanel/index.jsx';
|
import RawUserPilePanel from '../../components/RawUserPilePanel/index.jsx';
|
||||||
import ButtonBoxPanel from '../../components/ButtonBoxPanel/index.jsx';
|
import ButtonBoxPanel from '../../components/ButtonBoxPanel/index.jsx';
|
||||||
@@ -75,7 +75,7 @@ export default function SpectatorContent() {
|
|||||||
<section className={sidebarClass}>
|
<section className={sidebarClass}>
|
||||||
{isPortraitLayout ? (
|
{isPortraitLayout ? (
|
||||||
<div className={`${topBarItemClass} ${portraitItemHeight} flex flex-col gap-0.5`}>
|
<div className={`${topBarItemClass} ${portraitItemHeight} flex flex-col gap-0.5`}>
|
||||||
<CommunityGoalBanner layout="desktop" dismissable={false} className="text-sm" />
|
<GlobalObjectiveBanner layout="desktop" dismissable={false} className="text-sm" />
|
||||||
<ButtonBoxPanel />
|
<ButtonBoxPanel />
|
||||||
<div className="min-h-0 flex-1">
|
<div className="min-h-0 flex-1">
|
||||||
<RawUserPilePanel hideNicknameForm hideHeader compact fillHeight className="h-full" />
|
<RawUserPilePanel hideNicknameForm hideHeader compact fillHeight className="h-full" />
|
||||||
@@ -83,7 +83,7 @@ export default function SpectatorContent() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className={topBarItemClass}>
|
<div className={topBarItemClass}>
|
||||||
<CommunityGoalBanner layout="desktop" dismissable={false} className="text-sm" />
|
<GlobalObjectiveBanner layout="desktop" dismissable={false} className="text-sm" />
|
||||||
<ButtonBoxPanel />
|
<ButtonBoxPanel />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user