Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changelog/next/fixed-issue-4156.md
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
- Screen readers now announce the inline form controls on the Character Sheet, GitHub, Sharing, Browser, Mood Board and Video Timeline Editor pages, which previously had no accessible name
- Screen readers now announce 80 more form controls that previously had no accessible name, across the Goals views, the Brain tabs (Daily Log, Feeds, Inbox, Links, Memory, Notes), the MeatSpace tabs (Age, Alcohol, Genome, Lifestyle, Nicotine) and the Agent World/Tools tabs — visible labels where the surface had room, `aria-label` in compact toolbars and table rows
184 changes: 102 additions & 82 deletions client/src/a11yConventions.test.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions client/src/components/agents/tabs/ToolsTab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,7 @@ export default function ToolsTab({ agentId, agent }) {
value={postTitle}
onChange={(e) => setPostTitle(e.target.value)}
placeholder="Post title..."
aria-label="Post title"
className="w-full px-3 py-2 bg-port-bg border border-port-border rounded text-white mb-2"
/>
<textarea
Expand Down
26 changes: 15 additions & 11 deletions client/src/components/agents/tabs/WorldTab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -403,29 +403,29 @@ export default function WorldTab({ agentId }) {
case 'mw_explore':
return (
<div className="grid grid-cols-2 gap-2">
<input type="number" placeholder="X" value={newActionParams.x || ''} onChange={e => setNewActionParams(p => ({ ...p, x: e.target.value }))} className="px-2 py-1.5 bg-port-bg border border-port-border rounded text-white text-sm" />
<input type="number" placeholder="Y" value={newActionParams.y || ''} onChange={e => setNewActionParams(p => ({ ...p, y: e.target.value }))} className="px-2 py-1.5 bg-port-bg border border-port-border rounded text-white text-sm" />
<input type="text" placeholder="Thinking (optional)" value={newActionParams.thinking || ''} onChange={e => setNewActionParams(p => ({ ...p, thinking: e.target.value }))} className="col-span-2 px-2 py-1.5 bg-port-bg border border-port-border rounded text-white text-sm" />
<input type="number" placeholder="X" aria-label="Queued action X coordinate" value={newActionParams.x || ''} onChange={e => setNewActionParams(p => ({ ...p, x: e.target.value }))} className="px-2 py-1.5 bg-port-bg border border-port-border rounded text-white text-sm" />
<input type="number" placeholder="Y" aria-label="Queued action Y coordinate" value={newActionParams.y || ''} onChange={e => setNewActionParams(p => ({ ...p, y: e.target.value }))} className="px-2 py-1.5 bg-port-bg border border-port-border rounded text-white text-sm" />
<input type="text" placeholder="Thinking (optional)" aria-label="Queued action thinking (optional)" value={newActionParams.thinking || ''} onChange={e => setNewActionParams(p => ({ ...p, thinking: e.target.value }))} className="col-span-2 px-2 py-1.5 bg-port-bg border border-port-border rounded text-white text-sm" />
</div>
);
case 'mw_think':
return (
<input type="text" placeholder="Thought text" value={newActionParams.thought || ''} onChange={e => setNewActionParams(p => ({ ...p, thought: e.target.value }))} className="w-full px-2 py-1.5 bg-port-bg border border-port-border rounded text-white text-sm" />
<input type="text" placeholder="Thought text" aria-label="Queued thought text" value={newActionParams.thought || ''} onChange={e => setNewActionParams(p => ({ ...p, thought: e.target.value }))} className="w-full px-2 py-1.5 bg-port-bg border border-port-border rounded text-white text-sm" />
);
case 'mw_build':
return (
<div className="grid grid-cols-3 gap-2">
<input type="number" placeholder="X" value={newActionParams.x || ''} onChange={e => setNewActionParams(p => ({ ...p, x: e.target.value }))} className="px-2 py-1.5 bg-port-bg border border-port-border rounded text-white text-sm" />
<input type="number" placeholder="Y" value={newActionParams.y || ''} onChange={e => setNewActionParams(p => ({ ...p, y: e.target.value }))} className="px-2 py-1.5 bg-port-bg border border-port-border rounded text-white text-sm" />
<input type="number" placeholder="Z" value={newActionParams.z || ''} onChange={e => setNewActionParams(p => ({ ...p, z: e.target.value }))} className="px-2 py-1.5 bg-port-bg border border-port-border rounded text-white text-sm" />
<select value={newActionParams.type || 'stone'} onChange={e => setNewActionParams(p => ({ ...p, type: e.target.value }))} className="px-2 py-1.5 bg-port-bg border border-port-border rounded text-white text-sm">
<input type="number" placeholder="X" aria-label="Queued action X coordinate" value={newActionParams.x || ''} onChange={e => setNewActionParams(p => ({ ...p, x: e.target.value }))} className="px-2 py-1.5 bg-port-bg border border-port-border rounded text-white text-sm" />
<input type="number" placeholder="Y" aria-label="Queued action Y coordinate" value={newActionParams.y || ''} onChange={e => setNewActionParams(p => ({ ...p, y: e.target.value }))} className="px-2 py-1.5 bg-port-bg border border-port-border rounded text-white text-sm" />
<input type="number" placeholder="Z" aria-label="Queued action Z coordinate" value={newActionParams.z || ''} onChange={e => setNewActionParams(p => ({ ...p, z: e.target.value }))} className="px-2 py-1.5 bg-port-bg border border-port-border rounded text-white text-sm" />
<select aria-label="Queued build block type" value={newActionParams.type || 'stone'} onChange={e => setNewActionParams(p => ({ ...p, type: e.target.value }))} className="px-2 py-1.5 bg-port-bg border border-port-border rounded text-white text-sm">
<option value="wood">Wood</option>
<option value="stone">Stone</option>
<option value="dirt">Dirt</option>
<option value="grass">Grass</option>
<option value="leaves">Leaves</option>
</select>
<select value={newActionParams.action || 'place'} onChange={e => setNewActionParams(p => ({ ...p, action: e.target.value }))} className="px-2 py-1.5 bg-port-bg border border-port-border rounded text-white text-sm">
<select aria-label="Queued build action" value={newActionParams.action || 'place'} onChange={e => setNewActionParams(p => ({ ...p, action: e.target.value }))} className="px-2 py-1.5 bg-port-bg border border-port-border rounded text-white text-sm">
<option value="place">Place</option>
<option value="remove">Remove</option>
</select>
Expand All @@ -434,8 +434,8 @@ export default function WorldTab({ agentId }) {
case 'mw_say':
return (
<div className="space-y-2">
<input type="text" placeholder="Message" value={newActionParams.message || ''} onChange={e => setNewActionParams(p => ({ ...p, message: e.target.value }))} className="w-full px-2 py-1.5 bg-port-bg border border-port-border rounded text-white text-sm" />
<input type="text" placeholder="To Agent ID (optional)" value={newActionParams.sayTo || ''} onChange={e => setNewActionParams(p => ({ ...p, sayTo: e.target.value }))} className="w-full px-2 py-1.5 bg-port-bg border border-port-border rounded text-white text-sm" />
<input type="text" placeholder="Message" aria-label="Queued message" value={newActionParams.message || ''} onChange={e => setNewActionParams(p => ({ ...p, message: e.target.value }))} className="w-full px-2 py-1.5 bg-port-bg border border-port-border rounded text-white text-sm" />
<input type="text" placeholder="To Agent ID (optional)" aria-label="Queued message recipient agent ID (optional)" value={newActionParams.sayTo || ''} onChange={e => setNewActionParams(p => ({ ...p, sayTo: e.target.value }))} className="w-full px-2 py-1.5 bg-port-bg border border-port-border rounded text-white text-sm" />
</div>
);
default:
Expand Down Expand Up @@ -833,6 +833,7 @@ export default function WorldTab({ agentId }) {
value={moveThinking}
onChange={(e) => setMoveThinking(e.target.value)}
placeholder="Thinking... (optional)"
aria-label="Move thinking (optional)"
className="w-full px-3 py-2 bg-port-bg border border-port-border rounded text-white mb-3"
/>
<div className="flex gap-2">
Expand Down Expand Up @@ -864,6 +865,7 @@ export default function WorldTab({ agentId }) {
value={thought}
onChange={(e) => setThought(e.target.value)}
placeholder="What is this agent thinking?"
aria-label="Thought text"
maxLength={500}
className="w-full px-3 py-2 bg-port-bg border border-port-border rounded text-white mb-3"
/>
Expand Down Expand Up @@ -957,6 +959,7 @@ export default function WorldTab({ agentId }) {
value={sayMessage}
onChange={(e) => setSayMessage(e.target.value)}
placeholder="Message to nearby agents..."
aria-label="Message to nearby agents"
maxLength={500}
className="w-full px-3 py-2 bg-port-bg border border-port-border rounded text-white mb-2"
/>
Expand All @@ -965,6 +968,7 @@ export default function WorldTab({ agentId }) {
value={sayTo}
onChange={(e) => setSayTo(e.target.value)}
placeholder="To Agent ID (optional — leave blank for broadcast)"
aria-label="Recipient agent ID (optional — leave blank for broadcast)"
className="w-full px-3 py-2 bg-port-bg border border-port-border rounded text-white mb-3"
/>
<button
Expand Down
1 change: 1 addition & 0 deletions client/src/components/brain/tabs/DailyLogTab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,7 @@ export default function DailyLogTab() {
value={quickAppend}
onChange={(e) => setQuickAppend(e.target.value)}
placeholder="Quick append — adds a new paragraph…"
aria-label="Quick append a paragraph to the daily log"
className="flex-1 min-w-0 bg-port-bg border border-port-border rounded px-3 min-h-[40px] text-sm text-white placeholder-gray-500"
/>
<button
Expand Down
1 change: 1 addition & 0 deletions client/src/components/brain/tabs/FeedsTab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ export default function FeedsTab({ onRefresh }) {
value={inputUrl}
onChange={(e) => setInputUrl(e.target.value)}
placeholder="Paste an RSS or Atom feed URL..."
aria-label="RSS or Atom feed URL"
className="flex-1 px-4 py-3 bg-port-card border border-port-border rounded-lg text-white placeholder-gray-500 focus:outline-hidden focus:border-port-accent"
disabled={adding}
/>
Expand Down
1 change: 1 addition & 0 deletions client/src/components/brain/tabs/InboxTab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ export default function InboxTab({ onRefresh, settings }) {
value={inputText}
onChange={(e) => setInputText(e.target.value)}
placeholder="One thought at a time..."
aria-label="New inbox thought"
className="flex-1 px-4 py-3 bg-port-card border border-port-border rounded-lg text-white placeholder-gray-500 focus:outline-hidden focus:border-port-accent"
/>
<VoiceCapture onTranscript={handleVoiceTranscript} />
Expand Down
3 changes: 3 additions & 0 deletions client/src/components/brain/tabs/LinksTab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@ export default function LinksTab({ onRefresh }) {
value={inputUrl}
onChange={(e) => setInputUrl(e.target.value)}
placeholder="Paste a URL (GitHub repos auto-clone)..."
aria-label="Link URL to save"
className="flex-1 px-4 py-3 bg-port-card border border-port-border rounded-lg text-white placeholder-gray-500 focus:outline-hidden focus:border-port-accent"
disabled={sending}
/>
Expand Down Expand Up @@ -468,6 +469,7 @@ export default function LinksTab({ onRefresh }) {
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search links by title, URL, description, or tag..."
aria-label="Search links"
className="w-full pl-9 pr-9 py-2 bg-port-card border border-port-border rounded-lg text-white text-sm placeholder-gray-500 focus:outline-hidden focus:border-port-accent"
/>
{search && (
Expand Down Expand Up @@ -551,6 +553,7 @@ export default function LinksTab({ onRefresh }) {
onChange={(e) => setEditForm({ ...editForm, tags: e.target.value })}
className="flex-1 px-2 py-1 bg-port-bg border border-port-border rounded text-white text-sm"
placeholder="Tags (comma-separated)"
aria-label="Link tags, comma-separated"
/>
</div>
<div className="flex gap-2">
Expand Down
Loading