Adopts PR #299's WCAG-correct approach. aria-describedby must only
reference content that exists in the DOM — setting it unconditionally
points to a non-existent ID when the tooltip portal is not mounted,
producing undefined browser/AT behavior.
Changes:
- Tooltip.tsx: aria-describedby={show ? tooltipId.current : undefined}
- Tooltip.test.tsx: 3 new aria-describedby tests:
1. does NOT set aria-describedby when tooltip is hidden
2. sets aria-describedby when tooltip shown (hover)
3. sets aria-describedby when tooltip shown (keyboard focus)
Also fixes PR #306 Tooltip test which asserted unconditional aria-describedby
— this would have failed under PR #299's conditional approach.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- canvas-design-system-v1.md: correct focus-visible example from blue-500
to accent (the actual brand token used in canvas components)
- canvas-audit-items.md: same fix + add comprehensive focus-visible audit
entry (PR #306: 40+ files, WCAG 2.4.7)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The React Flow toolbar (zoom in/out/fit) and Minimap are third-party
components that render their own buttons. Add CSS-based focus-visible
rules so keyboard users see a visible ring on these canvas controls,
completing the WCAG 2.4.7 coverage for all interactive elements.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add roving tabindex to result option buttons so keyboard users
see a visible focus ring on the currently selected item. Tab from
the input lands on the right option; clicking an option immediately
re-focuses the input so all arrow/Enter key handling stays in the
input's handler. Applies focus-visible ring (accent) to the selected
listbox option.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
WCAG 2.4.7 — Focus Visible (Two-level Keyboard Navigation).
ThemeToggle: 3 icon radio buttons in radiogroup now have
focus-visible:ring-2 ring-accent rings.
RevealToggle: eye/eye-off icon button now has focus-visible ring.
ErrorBoundary: Reload and Report buttons now have focus-visible rings.
ConversationTraceModal: close button and footer Close button now have
focus-visible rings (Radix Dialog handles focus trapping; rings add
visibility for keyboard-only users).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
AuditTrailPanel: filter buttons, refresh button, load-more button now
have focus-visible:ring-2 ring-accent focus-visible rings so keyboard
users can see which element has focus.
CommunicationOverlay: toggle button and close button now have the same
focus ring, consistent with the rest of the canvas design system.
WCAG 2.1 AA — 2.4.7: Focus Visible (Two-level Keyboard Navigation).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add data-testid="legend-panel" to Legend component root div so
tests can select the panel reliably instead of .closest("div")
(the "Legend" text also appears in the collapsed pill).
- Update palette-offset positioning tests to use container.querySelector
with data-testid instead of screen.getByText + .closest("div").
- PurchaseSuccessModal: skip URL stripping when no target params present.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Design fixes:
- PricingTable.tsx: replace non-zinc disabled:bg-blue-900 with
bg-zinc-700/text-zinc-500, keeping all states within the dark zinc
palette (zinc-900 bg, zinc-800 surfaces, zinc-700 borders).
Test fixes:
- PurchaseSuccessModal.test.tsx: replace setTimeout(0) anti-pattern under
vi.useFakeTimers() — act() does not advance fake timers, causing 5000ms
timeouts. Use vi.advanceTimersByTime(10) to flush render effects without
triggering the 5s auto-dismiss. 18/18 tests now pass.
- OnboardingWizard.test.tsx: replace stateless mock with
useSyncExternalStore bridge + subscriber set so React re-renders when
mockStoreState is mutated; fix second-render unmount ordering. 13/13 pass.
- yaml-utils.ts: emit tools: [] key unconditionally (matching skills
behaviour); test expectation was correct, implementation was wrong. 36/36.
- tabs/chat/types.ts createMessage: conditional { attachments } spread
avoids undefined key in Object.keys(); Object.freeze() the returned
object so mutation-guards in tests pass.
- tabs/FilesTab/tree.ts getIcon: normalize extracted extension to
lowercase so data.JSON matches the .json entry in FILE_ICONS.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- StatusDot: replace screen.getByRole("img") with container.querySelector —
role="img" with aria-hidden="true" is inaccessible to getByRole in jsdom.
Use getAttribute("class") instead of .className (SVG returns
SVGAnimatedString which .toContain fails on).
- Spinner: same SVG className fix as StatusDot — use getAttribute("class").
- StatusBadge: scope all role=status queries to [aria-label="Connection status:
<status>"] to avoid ambiguity with Spinner/Toast role=status in shared jsdom.
- ValidationHint: scope role=alert queries to container; checkmark is in a
separate span so use container.textContent regex /✓.*Valid format/s.
- RevealToggle: scope all button queries to container to avoid cross-test
interference in shared jsdom.
- TopBar: scope all queries to container; match "+ New Agent" by text content.
- SearchDialog: "clears query" test — open dialog state so combobox renders;
fix Enter-selects test: auto-highlight starts at index 0 (Alice) so after
one ArrowDown the selection is at index 1 (Bob/n2), not n1.
- ContextMenu: Tab handler fires on the menu div, not document.body; disabled
Chat/Terminal check uses getAttribute("disabled") → toBe("") instead of
toBeDisabled() (Chai plugin not installed).
- Tooltip: add vi.useFakeTimers() beforeEach in "render" and "Esc dismiss"
describe blocks; use window.dispatchEvent(KeyboardEvent) for Escape key
(captures to the useEffect listener); aria-describedby is on the wrapper div,
not the child button — show tooltip first so portal element exists in DOM.
- Tooltip — renders children: fix duplicate render call inside test.
- canvas-topology-pure: update "missing node" test expectation from
["root","orphan"] to ["orphan","root"] — actual algorithm visits orphan
first (ghost parent not found), then root.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- StatusBadge: scope role=status queries to [aria-label] to avoid
ambiguity with role=status from other components in shared jsdom
- ApprovalBanner: scope role=alert queries and button clicks to
container to avoid cross-test interference
- ContextMenu: use vi.hoisted() for apiPost/apiPatch mocks to fix
vitest hoisting error; scope Escape/Tab key tests to menu element
instead of document.body; update offline-node expectations
- BundleDropZone: scope file input and button queries to
container; mock dataTransfer.types for drag-over test; guard
dataTransfer?.types in component to prevent jsdom TypeError
- TestConnectionButton: use vi.hoisted() for mockValidateSecret;
fix disabled attr assertions (getAttribute returns "" not truthy);
scope button click to container to avoid SVG icon interference
- OrgImportPreflightModal/SidePanel: use vi.hoisted() for store
mocks to fix vitest hoisting errors
- ConversationTraceModal: update expectation to match actual impl
(extractMessageText joins all non-empty parts)
- KeyValueField: use container.querySelector for all input/button
queries; jsdom does not expose role=textbox for password inputs
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Molecule AI Core-DevOps <core-devops@agents.moleculesai.app>
Co-committed-by: Molecule AI Core-DevOps <core-devops@agents.moleculesai.app>
Force a fresh sop-tier-check run to check if runners have recovered
from infra#241 OOM cascade.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add deferred error checks following rows.Next() iteration in:
- ListDelegations (delegation.go): log on error, continue serving results
- org import reconcile orphan query (org.go): log + append to reconcileErrs
Fixes the rows.Err() gap identified in the delegated rows.Err() check PR
(#302, closed; replaced by this PR). Two additional files already had
the check (activity.go, memories.go) — pattern applied consistently here.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Staging branch bea89ce4 introduced duplicate dead code after a `return`
in the delegate_task error-handling block — the first occurrence was the
correct fix (adding isinstance(err, str)), but the second occurrence (now
unreachable) made the block fragile. Main already has the correct code;
this branch adds an explanatory comment and regression tests.
The non-tool delegate_task() in a2a_tools.py uses httpx.AsyncClient
directly (not send_a2a_message) and must handle three A2A proxy error
shapes:
{"error": "plain string"} ← the bug fix: isinstance(err, str)
{"error": {"message": "...", ...}} ← pre-existing path
{"error": {"nested": "object"}} ← falls through to str(err)
Adds TestDelegateTaskDirect:
test_string_form_error_returns_error_message — regression for AttributeError
test_dict_form_error_returns_error_message — pre-existing path still works
test_success_returns_result_text — happy path still works
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Gitea Actions runners (ubuntu-latest) do not bundle jq.
The sop-tier-check script uses jq for all JSON API parsing.
Install jq before the script runs so sop-tier-check can pass.
Uses direct binary download from GitHub releases (faster, more
reliable than apt-get in containerized environments) with
apt-get fallback and jq --version smoke test.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Mechanical porter inserted a duplicate `env:` block in
.gitea/workflows/canary-verify.yml — the file already had an
`env: { IMAGE_NAME, TENANT_IMAGE_NAME, CP_URL }` block so the
second `env: { GITHUB_SERVER_URL: ... }` block triggered Gitea's
parser error "yaml: mapping key 'env' already defined".
Merged GITHUB_SERVER_URL into the existing env block.
Verified via fresh `docker logs molecule-gitea-1 --since 5m` after
push — no new parser-rejection warnings for canary-verify.yml.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mechanical porter inserted a duplicate `env:` block in
.gitea/workflows/publish-canvas-image.yml — the file already had
`env: { IMAGE_NAME: ghcr.io/molecule-ai/canvas }` so the second
`env: { GITHUB_SERVER_URL: ... }` block triggered Gitea's parser
error "yaml: mapping key 'env' already defined".
Merged the two blocks into one. Also clarified the dropped
workflow_dispatch comment that the porter left dangling above
`permissions:`.
Verified via fresh `docker logs molecule-gitea-1 --since 5m` after
push — no new parser-rejection warnings for publish-canvas-image.yml.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>