Skip to content

Commit aec3710

Browse files
authored
Merge pull request #137 from atomantic/better/stack-specific
fix: EventEmitter warnings, React hook bugs, lazy-load ChiefOfStaff, a11y labels
2 parents 151320e + 2e78fea commit aec3710

11 files changed

Lines changed: 113 additions & 74 deletions

File tree

.env.example

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,19 @@ PGPASSWORD=portos
1919

2020
# Server host binding
2121
# HOST=0.0.0.0
22+
23+
# OpenClaw API credentials
24+
# OPENCLAW_API_URL=https://api.openclaw.example.com
25+
# OPENCLAW_API_KEY=your_openclaw_api_key_here
26+
27+
# Chief of Staff runner endpoint (used when CoS runs as a separate process)
28+
# COS_RUNNER_URL=http://localhost:5558
29+
30+
# LM Studio local inference server URL
31+
# LM_STUDIO_URL=http://localhost:1234
32+
33+
# Path to the Claude CLI binary (defaults to system PATH)
34+
# CLAUDE_PATH=/usr/local/bin/claude
35+
36+
# Duration (minutes) for Moltworld agent sessions
37+
# MOLTWORLD_DURATION_MINUTES=60

client/src/App.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import Apps from './pages/Apps';
88
import CreateApp from './pages/CreateApp';
99
import Templates from './pages/Templates';
1010
import PromptManager from './pages/PromptManager';
11-
import ChiefOfStaff from './pages/ChiefOfStaff';
1211
import Brain from './pages/Brain';
1312
import Security from './pages/Security';
1413
import DigitalTwin from './pages/DigitalTwin';
@@ -64,6 +63,7 @@ const Messages = lazyWithReload(() => import('./pages/Messages'));
6463
const Goals = lazyWithReload(() => import('./pages/Goals'));
6564
const OpenClawPage = lazyWithReload(() => import('./pages/OpenClaw'));
6665
const Submodules = lazyWithReload(() => import('./pages/Submodules'));
66+
const ChiefOfStaff = lazyWithReload(() => import('./pages/ChiefOfStaff'));
6767

6868
// Loading fallback for lazy-loaded pages
6969
const PageLoader = () => (

client/src/components/meatspace/tabs/BodyTab.jsx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -119,44 +119,44 @@ export default function BodyTab() {
119119
</h4>
120120
<div className="grid grid-cols-7 gap-2 mb-3">
121121
<div>
122-
<label className="text-xs text-gray-500">Date</label>
123-
<input type="date" value={eyeForm.date}
122+
<label htmlFor="eye-date" className="text-xs text-gray-500">Date</label>
123+
<input id="eye-date" type="date" value={eyeForm.date}
124124
onChange={e => setEyeForm(f => ({ ...f, date: e.target.value }))}
125125
className="w-full bg-port-bg border border-port-border rounded px-2 py-1 text-sm text-gray-200 font-mono" />
126126
</div>
127127
<div>
128-
<label className="text-xs text-gray-500">L SPH</label>
129-
<input type="number" step="0.25" value={eyeForm.leftSphere}
128+
<label htmlFor="eye-left-sphere" className="text-xs text-gray-500">L SPH</label>
129+
<input id="eye-left-sphere" type="number" step="0.25" value={eyeForm.leftSphere}
130130
onChange={e => setEyeForm(f => ({ ...f, leftSphere: e.target.value }))}
131131
className="w-full bg-port-bg border border-port-border rounded px-2 py-1 text-sm text-gray-200 font-mono" />
132132
</div>
133133
<div>
134-
<label className="text-xs text-gray-500">L CYL</label>
135-
<input type="number" step="0.25" value={eyeForm.leftCylinder}
134+
<label htmlFor="eye-left-cylinder" className="text-xs text-gray-500">L CYL</label>
135+
<input id="eye-left-cylinder" type="number" step="0.25" value={eyeForm.leftCylinder}
136136
onChange={e => setEyeForm(f => ({ ...f, leftCylinder: e.target.value }))}
137137
className="w-full bg-port-bg border border-port-border rounded px-2 py-1 text-sm text-gray-200 font-mono" />
138138
</div>
139139
<div>
140-
<label className="text-xs text-gray-500">L AXIS</label>
141-
<input type="number" step="1" min="0" max="180" value={eyeForm.leftAxis}
140+
<label htmlFor="eye-left-axis" className="text-xs text-gray-500">L AXIS</label>
141+
<input id="eye-left-axis" type="number" step="1" min="0" max="180" value={eyeForm.leftAxis}
142142
onChange={e => setEyeForm(f => ({ ...f, leftAxis: e.target.value }))}
143143
className="w-full bg-port-bg border border-port-border rounded px-2 py-1 text-sm text-gray-200 font-mono" />
144144
</div>
145145
<div>
146-
<label className="text-xs text-gray-500">R SPH</label>
147-
<input type="number" step="0.25" value={eyeForm.rightSphere}
146+
<label htmlFor="eye-right-sphere" className="text-xs text-gray-500">R SPH</label>
147+
<input id="eye-right-sphere" type="number" step="0.25" value={eyeForm.rightSphere}
148148
onChange={e => setEyeForm(f => ({ ...f, rightSphere: e.target.value }))}
149149
className="w-full bg-port-bg border border-port-border rounded px-2 py-1 text-sm text-gray-200 font-mono" />
150150
</div>
151151
<div>
152-
<label className="text-xs text-gray-500">R CYL</label>
153-
<input type="number" step="0.25" value={eyeForm.rightCylinder}
152+
<label htmlFor="eye-right-cylinder" className="text-xs text-gray-500">R CYL</label>
153+
<input id="eye-right-cylinder" type="number" step="0.25" value={eyeForm.rightCylinder}
154154
onChange={e => setEyeForm(f => ({ ...f, rightCylinder: e.target.value }))}
155155
className="w-full bg-port-bg border border-port-border rounded px-2 py-1 text-sm text-gray-200 font-mono" />
156156
</div>
157157
<div>
158-
<label className="text-xs text-gray-500">R AXIS</label>
159-
<input type="number" step="1" min="0" max="180" value={eyeForm.rightAxis}
158+
<label htmlFor="eye-right-axis" className="text-xs text-gray-500">R AXIS</label>
159+
<input id="eye-right-axis" type="number" step="1" min="0" max="180" value={eyeForm.rightAxis}
160160
onChange={e => setEyeForm(f => ({ ...f, rightAxis: e.target.value }))}
161161
className="w-full bg-port-bg border border-port-border rounded px-2 py-1 text-sm text-gray-200 font-mono" />
162162
</div>

client/src/hooks/useMoltworldWs.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,13 @@ export default function useMoltworldWs() {
7373
socket.on('moltworld:nearby', handleNearby);
7474

7575
// Fetch initial WS status
76-
api.moltworldWsStatus().then(data => {
76+
const abortCtrl = new AbortController();
77+
api.moltworldWsStatus({ signal: abortCtrl.signal, silent: true }).then(data => {
7778
if (data?.status) setConnectionStatus(data.status);
7879
}).catch(() => {});
7980

8081
return () => {
82+
abortCtrl.abort();
8183
socket.off('moltworld:status', handleStatus);
8284
socket.off('moltworld:event', handleEvent);
8385
socket.off('moltworld:presence', handlePresence);

client/src/hooks/useNotifications.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,18 @@ export function useNotifications() {
1212
useEffect(() => {
1313
const fetchNotifications = async () => {
1414
setLoading(true);
15-
const [notifs, countData] = await Promise.all([
16-
api.getNotifications({ limit: 50 }),
17-
api.getNotificationCount()
18-
]);
19-
setNotifications(notifs);
20-
setUnreadCount(countData.count);
21-
setLoading(false);
15+
try {
16+
const [notifs, countData] = await Promise.all([
17+
api.getNotifications({ limit: 50 }),
18+
api.getNotificationCount()
19+
]);
20+
setNotifications(notifs);
21+
setUnreadCount(countData.count);
22+
} catch (err) {
23+
console.error(`❌ Failed to load notifications: ${err.message}`);
24+
} finally {
25+
setLoading(false);
26+
}
2227
};
2328

2429
fetchNotifications();

client/src/hooks/useProviderModels.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect, useCallback, useMemo } from 'react';
1+
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
22
import * as api from '../services/api';
33

44
/**
@@ -12,19 +12,21 @@ export default function useProviderModels({ filter } = {}) {
1212
const [selectedProviderId, setSelectedProviderId] = useState('');
1313
const [selectedModel, setSelectedModel] = useState('');
1414
const [loading, setLoading] = useState(true);
15+
const hasSetInitialRef = useRef(false);
1516

1617
const load = useCallback(async () => {
1718
setLoading(true);
1819
const data = await api.getProviders().catch(() => ({ providers: [] }));
1920
const filterFn = filter || (p => p.enabled);
2021
const filtered = (data.providers || []).filter(filterFn);
2122
setProviders(filtered);
22-
if (filtered.length > 0 && !selectedProviderId) {
23+
if (filtered.length > 0 && !hasSetInitialRef.current) {
24+
hasSetInitialRef.current = true;
2325
setSelectedProviderId(filtered[0].id);
2426
setSelectedModel(filtered[0].defaultModel || '');
2527
}
2628
setLoading(false);
27-
}, [filter, selectedProviderId]);
29+
}, [filter]);
2830

2931
useEffect(() => { load(); }, [load]);
3032

client/src/hooks/useUpdateChecker.jsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect } from 'react';
1+
import { useEffect, useRef } from 'react';
22
import { useNavigate } from 'react-router-dom';
33
import toast from '../components/ui/Toast';
44
import socket from '../services/socket';
@@ -12,6 +12,8 @@ const TOAST_ID = 'portos-update-available';
1212
*/
1313
export function useUpdateChecker() {
1414
const navigate = useNavigate();
15+
const navigateRef = useRef(navigate);
16+
navigateRef.current = navigate;
1517

1618
useEffect(() => {
1719
const showUpdateToast = (data) => {
@@ -25,7 +27,7 @@ export function useUpdateChecker() {
2527
<button
2628
onClick={() => {
2729
toast.dismiss(t.id);
28-
navigate(`/apps/${api.PORTOS_APP_ID}/update`);
30+
navigateRef.current(`/apps/${api.PORTOS_APP_ID}/update`);
2931
}}
3032
className="px-2 py-1 bg-port-accent text-white text-xs rounded hover:bg-port-accent/80"
3133
>
@@ -67,5 +69,5 @@ export function useUpdateChecker() {
6769
return () => {
6870
socket.off('portos:update:available', showUpdateToast);
6971
};
70-
}, [navigate]);
72+
}, []);
7173
}

client/src/services/apiAccounts.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,8 @@ export const moltworldWsConnect = (accountId) =>
116116
});
117117
export const moltworldWsDisconnect = () =>
118118
request('/agents/tools/moltworld/ws/disconnect', { method: 'POST' });
119-
export const moltworldWsStatus = () =>
120-
request('/agents/tools/moltworld/ws/status');
119+
export const moltworldWsStatus = (options = {}) =>
120+
request('/agents/tools/moltworld/ws/status', options);
121121
export const moltworldWsMove = (x, y, thought) =>
122122
request('/agents/tools/moltworld/ws/move', {
123123
method: 'POST',

0 commit comments

Comments
 (0)