fix(canvas/a11y): add type=button to 24 buttons across DetailsTab, ConfigTab, FilesTab, MemoryTab

WCAG 4.1.2 / bug #1669 follow-up — DetailsTab, ConfigTab, FilesTab, and
MemoryTab had buttons without explicit type="button", causing accidental
form submission in any surrounding <form> context.

Changes:
- DetailsTab (9 buttons): Save, Cancel (edit), Restart/Retry, Edit,
  View console output, peer select, Confirm Delete, Cancel (delete), Delete Workspace
- ConfigTab AgentCardSection (3): Save, Cancel, Edit Agent Card
- ConfigTab footer (3): Save & Restart, Save, Reload
- ConfigTab textareas (2): aria-label added to Agent Card JSON editor and Raw YAML editor
- FilesTab (4): Delete All, Cancel, Delete, Cancel
- MemoryTab (11): Expand/Collapse, Open, Expand (collapsed state), Advanced,
  Refresh, Add, Save, Cancel (add form), expand entry, Delete entry, Show

Total: 32 interactive elements corrected across 4 tab components.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Molecule AI · core-uiux 2026-04-24 11:27:51 +00:00
parent 4597ab06fc
commit 59feb65252
4 changed files with 32 additions and 7 deletions

View File

@ -51,17 +51,18 @@ function AgentCardSection({ workspaceId }: { workspaceId: string }) {
) : editing ? (
<div className="space-y-2">
<textarea
aria-label="Agent card JSON editor"
value={draft} onChange={(e) => setDraft(e.target.value)}
spellCheck={false} rows={12}
className="w-full bg-zinc-800 border border-zinc-700 rounded p-2 text-[10px] font-mono text-zinc-200 focus:outline-none focus:border-blue-500 resize-none"
/>
{error && <div className="px-2 py-1 bg-red-900/30 border border-red-800 rounded text-[10px] text-red-400">{error}</div>}
<div className="flex gap-2">
<button onClick={handleSave} disabled={saving}
<button type="button" onClick={handleSave} disabled={saving}
className="px-2 py-1 bg-blue-600 hover:bg-blue-500 text-[10px] rounded text-white disabled:opacity-50">
{saving ? "Saving..." : "Save"}
</button>
<button onClick={() => setEditing(false)}
<button type="button" onClick={() => setEditing(false)}
className="px-2 py-1 bg-zinc-700 hover:bg-zinc-600 text-[10px] rounded text-zinc-300">Cancel</button>
</div>
</div>
@ -75,7 +76,7 @@ function AgentCardSection({ workspaceId }: { workspaceId: string }) {
<div className="text-[10px] text-zinc-500">No agent card</div>
)}
{success && <div className="mt-2 px-2 py-1 bg-green-900/30 border border-green-800 rounded text-[10px] text-green-400">Updated</div>}
<button onClick={() => { setDraft(JSON.stringify(card || {}, null, 2)); setEditing(true); setError(null); setSuccess(false); }}
<button type="button" onClick={() => { setDraft(JSON.stringify(card || {}, null, 2)); setEditing(true); setError(null); setSuccess(false); }}
className="mt-2 text-[10px] text-blue-400 hover:text-blue-300">Edit Agent Card</button>
</div>
)}
@ -372,6 +373,7 @@ export function ConfigTab({ workspaceId }: Props) {
{rawMode ? (
<div className="flex-1 p-3">
<textarea
aria-label="Raw YAML editor"
value={rawDraft}
onChange={(e) => setRawDraft(e.target.value)}
spellCheck={false}
@ -633,6 +635,7 @@ export function ConfigTab({ workspaceId }: Props) {
<div className="p-3 border-t border-zinc-800 flex gap-2">
<button
type="button"
onClick={() => handleSave(true)}
disabled={!isDirty || saving}
className="px-3 py-1.5 bg-blue-600 hover:bg-blue-500 text-xs rounded text-white disabled:opacity-30 transition-colors"
@ -640,6 +643,7 @@ export function ConfigTab({ workspaceId }: Props) {
{saving ? "Restarting..." : "Save & Restart"}
</button>
<button
type="button"
onClick={() => handleSave(false)}
disabled={!isDirty || saving}
className="px-3 py-1.5 bg-zinc-700 hover:bg-zinc-600 text-xs rounded text-zinc-300 disabled:opacity-30 transition-colors"
@ -647,6 +651,7 @@ export function ConfigTab({ workspaceId }: Props) {
Save
</button>
<button
type="button"
onClick={loadConfig}
className="px-3 py-1.5 bg-zinc-700 hover:bg-zinc-600 text-xs rounded text-zinc-300 ml-auto"
>

View File

@ -159,6 +159,7 @@ export function DetailsTab({ workspaceId, data }: Props) {
)}
<div className="flex gap-2 pt-1">
<button
type="button"
onClick={handleSave}
disabled={saving}
className="px-3 py-1 bg-blue-600 hover:bg-blue-500 text-xs rounded text-white disabled:opacity-50"
@ -166,6 +167,7 @@ export function DetailsTab({ workspaceId, data }: Props) {
{saving ? "Saving..." : "Save"}
</button>
<button
type="button"
onClick={() => {
setEditing(false);
setSaveError(null);
@ -199,6 +201,7 @@ export function DetailsTab({ workspaceId, data }: Props) {
</div>
)}
<button
type="button"
onClick={handleRestart}
disabled={restarting}
className="px-3 py-1 bg-green-700 hover:bg-green-600 text-xs rounded text-white disabled:opacity-50"
@ -208,6 +211,7 @@ export function DetailsTab({ workspaceId, data }: Props) {
</div>
)}
<button
type="button"
onClick={() => setEditing(true)}
className="mt-2 px-3 py-1 bg-zinc-700 hover:bg-zinc-600 text-xs rounded text-zinc-300"
>
@ -234,6 +238,7 @@ export function DetailsTab({ workspaceId, data }: Props) {
<p className="text-xs text-zinc-500">No error detail recorded.</p>
)}
<button
type="button"
onClick={() => setConsoleOpen(true)}
className="mt-2 px-3 py-1 bg-zinc-800 hover:bg-zinc-700 text-xs rounded text-zinc-300 border border-zinc-700"
>
@ -279,6 +284,7 @@ export function DetailsTab({ workspaceId, data }: Props) {
{peers.map((p) => (
<button
key={p.id}
type="button"
onClick={() => selectNode(p.id)}
className="w-full flex items-center gap-2 px-2 py-1 rounded hover:bg-zinc-800 text-left"
>
@ -310,12 +316,14 @@ export function DetailsTab({ workspaceId, data }: Props) {
</h3>
<div className="flex gap-2">
<button
type="button"
onClick={handleDelete}
className="px-3 py-1 bg-red-600 hover:bg-red-500 text-xs rounded text-white"
>
Confirm Delete
</button>
<button
type="button"
onClick={() => {
setConfirmDelete(false);
setDeleteError(null);
@ -330,6 +338,7 @@ export function DetailsTab({ workspaceId, data }: Props) {
</div>
) : (
<button
type="button"
ref={deleteButtonRef}
onClick={() => setConfirmDelete(true)}
className="px-3 py-1 bg-zinc-800 hover:bg-red-900 border border-zinc-700 hover:border-red-700 text-xs rounded text-zinc-400 hover:text-red-400 transition-colors"

View File

@ -165,8 +165,8 @@ export function FilesTab({ workspaceId }: Props) {
<div className="mx-3 mt-2 px-3 py-2 bg-red-950/30 border border-red-800/40 rounded space-y-1.5">
<p className="text-xs text-red-300">Delete all {files.filter((f) => !f.dir).length} files? This cannot be undone.</p>
<div className="flex gap-2">
<button onClick={() => { handleDeleteAll(); setShowDeleteAll(false); }} className="px-2 py-0.5 bg-red-600 hover:bg-red-500 text-[10px] rounded text-white">Delete All</button>
<button onClick={() => setShowDeleteAll(false)} className="px-2 py-0.5 bg-zinc-700 hover:bg-zinc-600 text-[10px] rounded text-zinc-300">Cancel</button>
<button type="button" onClick={() => { handleDeleteAll(); setShowDeleteAll(false); }} className="px-2 py-0.5 bg-red-600 hover:bg-red-500 text-[10px] rounded text-white">Delete All</button>
<button type="button" onClick={() => setShowDeleteAll(false)} className="px-2 py-0.5 bg-zinc-700 hover:bg-zinc-600 text-[10px] rounded text-zinc-300">Cancel</button>
</div>
</div>
)}
@ -179,8 +179,8 @@ export function FilesTab({ workspaceId }: Props) {
<div className="mx-3 mt-2 px-3 py-2 bg-amber-950/30 border border-amber-800/40 rounded space-y-1.5">
<p className="text-xs text-amber-300">Delete <span className="font-mono">{confirmDelete}</span>{files.find((f) => f.path === confirmDelete && f.dir) ? " and all its contents" : ""}?</p>
<div className="flex gap-2">
<button onClick={confirmDeleteFile} className="px-2 py-0.5 bg-red-600 hover:bg-red-500 text-[10px] rounded text-white">Delete</button>
<button onClick={() => setConfirmDelete(null)} className="px-2 py-0.5 bg-zinc-700 hover:bg-zinc-600 text-[10px] rounded text-zinc-300">Cancel</button>
<button type="button" onClick={confirmDeleteFile} className="px-2 py-0.5 bg-red-600 hover:bg-red-500 text-[10px] rounded text-white">Delete</button>
<button type="button" onClick={() => setConfirmDelete(null)} className="px-2 py-0.5 bg-zinc-700 hover:bg-zinc-600 text-[10px] rounded text-zinc-300">Cancel</button>
</div>
</div>
)}

View File

@ -135,12 +135,14 @@ export function MemoryTab({ workspaceId }: Props) {
</div>
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => setShowAwareness((prev) => !prev)}
className="shrink-0 px-2 py-1 bg-zinc-700 hover:bg-zinc-600 text-[10px] rounded text-zinc-200"
>
{showAwareness ? "Collapse" : "Expand"}
</button>
<button
type="button"
onClick={openAwareness}
className="shrink-0 px-2 py-1 bg-zinc-700 hover:bg-zinc-600 text-[10px] rounded text-zinc-200"
>
@ -173,6 +175,7 @@ export function MemoryTab({ workspaceId }: Props) {
</p>
</div>
<button
type="button"
onClick={() => setShowAwareness(true)}
className="shrink-0 px-2 py-1 bg-blue-600 hover:bg-blue-500 text-[10px] rounded text-white"
>
@ -207,18 +210,21 @@ export function MemoryTab({ workspaceId }: Props) {
</div>
<div className="flex gap-2">
<button
type="button"
onClick={() => setShowAdvanced((prev) => !prev)}
className="px-2 py-1 bg-zinc-700 hover:bg-zinc-600 text-[10px] rounded text-zinc-300"
>
{showAdvanced ? "Hide Advanced" : "Advanced"}
</button>
<button
type="button"
onClick={loadMemory}
className="px-2 py-1 bg-zinc-700 hover:bg-zinc-600 text-[10px] rounded text-zinc-300"
>
Refresh
</button>
<button
type="button"
onClick={() => { setShowAdd(!showAdd); if (!showAdd) setShowAdvanced(true); }}
className="px-2 py-1 bg-blue-600 hover:bg-blue-500 text-[10px] rounded text-white"
>
@ -254,12 +260,14 @@ export function MemoryTab({ workspaceId }: Props) {
{error && <div role="alert" className="text-xs text-red-400">{error}</div>}
<div className="flex gap-2">
<button
type="button"
onClick={handleAdd}
className="px-3 py-1 bg-blue-600 hover:bg-blue-500 text-xs rounded text-white"
>
Save
</button>
<button
type="button"
onClick={() => {
setShowAdd(false);
setError(null);
@ -280,6 +288,7 @@ export function MemoryTab({ workspaceId }: Props) {
{entries.map((entry) => (
<div key={entry.key} className="bg-zinc-800 rounded border border-zinc-700">
<button
type="button"
onClick={() => setExpanded(expanded === entry.key ? null : entry.key)}
className="w-full flex items-center justify-between px-3 py-2 text-left"
aria-expanded={expanded === entry.key}
@ -307,6 +316,7 @@ export function MemoryTab({ workspaceId }: Props) {
Updated: {new Date(entry.updated_at).toLocaleString()}
</span>
<button
type="button"
onClick={() => handleDelete(entry.key)}
className="text-[10px] text-red-400 hover:text-red-300"
>
@ -328,6 +338,7 @@ export function MemoryTab({ workspaceId }: Props) {
</p>
</div>
<button
type="button"
onClick={() => setShowAdvanced(true)}
className="shrink-0 px-2 py-1 bg-blue-600 hover:bg-blue-500 text-[10px] rounded text-white"
>