Skip to content

[codex] Add stable OpenWorlds agent UI hooks - #495

Merged
100yenadmin merged 3 commits into
mainfrom
codex/worldos-agent-ui-hooks
May 31, 2026
Merged

[codex] Add stable OpenWorlds agent UI hooks#495
100yenadmin merged 3 commits into
mainfrom
codex/worldos-agent-ui-hooks

Conversation

@100yenadmin

@100yenadmin 100yenadmin commented May 31, 2026

Copy link
Copy Markdown
Member

Summary

  • Add stable aria-label and data-worldos-testid hooks across the OpenWorlds launcher, chrome, table, settings, character, camp sidebar, and toast surfaces.
  • Make the active play surface easier for agents and harnesses to drive by exposing durable IDs for provider selection, hero/world selectors, action cards/buttons, move input, route tabs, app-status/session-surface links, and error/status banners.
  • Keep this as a read-only/driveability layer: no engine authority change, no state mutation outside the existing /move path.
  • Current-head cleanup makes first-turn hooks unambiguous and applies CodeRabbit accessibility fixes: chronicle-resume is unique to the primary banner, card resume uses chronicle-resume-detail, start-flow and create-submit are split, session-surface status no longer uses an app-status hook name, aria-pressed is explicit, alert live-region politeness is assertive, and action accessible names stay concise.

Evidence

  • Current head: 77722fb rebased onto main@9d36809.
  • Built-app product proof archive from the prior UI-hook head: /Volumes/LEXAR/Codex/worldos-built-app-playtest/ui-hooks-app-proof-20260601T054934.
  • Proof used built dist/WorldOS.app, provider scripted, private art root /Users/lume/ClawDnD-val/content/worlds/_private, live campaign camp_c44d76054d36, actor Abby, five enabled actions, writable /move, deterministic DM narration after a real move, and real image bytes for location/portrait/class probes.
  • The current-head delta after that proof is hook/accessibility semantics only; focused static validation below locks the new hook contract.

Tests

  • python3 -m pytest viewer/tests/test_openworlds_static.py -q -> 39 passed, 2 subtests passed
  • python3 -m pytest qa/test_macos_app_static.py -q -> 6 passed

@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds standardized data-worldos test IDs, ARIA attributes, and dialog semantics across OpenWorlds UI components (navigation, launcher, table, settings, character modals, toasts, and camp sidebar) and adds a static test that asserts these hooks remain present.

Changes

OpenWorlds UI Accessibility and Test Hooks

Layer / File(s) Summary
UI Component Props Foundation
viewer/openworlds/chrome.jsx
IconPlate and BrassButton signatures extended to accept testId, ariaLabel, and spread buttonProps, enabling downstream components to forward test and accessibility attributes onto rendered buttons.
Navigation UI Components
viewer/openworlds/chrome.jsx
NavRail and TabBar components now render with ARIA roles (tablist, tab), aria-selected, aria-label, data-worldos-testid, and item IDs for navigation and automated tests.
Dialog & Modal Overlays
viewer/openworlds/screen-launcher.jsx, viewer/openworlds/screen-character.jsx, viewer/openworlds/camp-sidebar.jsx
Launcher CTA, campaign modal, character rest/spellbook modals, and camp sidebar close button now include role="dialog", aria-modal, aria-label, and stable data-worldos-testid/testId modal-close hooks.
Game Table Core Instrumentation
viewer/openworlds/screen-table.jsx
Screen root, conditional session-surface status banner (aria-live, role switching), narration log, and action-palette now carry test IDs for status announcements and targeted automation.
Action & Move Instrumentation
viewer/openworlds/screen-table.jsx
EncounterButton extended with actionId; exploration/combat action buttons wired with action IDs and data-worldos-testid; move composer, dice buttons, move input, and submit button annotated with explicit type, test IDs, and ARIA labels.
Settings & Provider Cards
viewer/openworlds/screen-settings.jsx
Settings tabs marked with role="tablist"/role="tab" and aria-selected; provider status marked role="status"/aria-live="polite"; provider-controls wrapper and per-card data-worldos-testid/data-worldos-provider-id added.
Toast & Notification System
viewer/openworlds/toast.jsx
Toast region annotated with aria-live="polite", aria-label="Notifications", and data-worldos-testid="toast-region"; individual toasts set role conditionally (alert vs status) and conditional test IDs for error vs normal toasts.
Test Suite Validation
viewer/tests/test_openworlds_static.py
New test_openworlds_agent_driving_hooks_are_stable validates presence and stability of test IDs, ARIA roles, selected-state wiring, and modal close hooks across instrumented OpenWorlds screens via static file inspection.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

  • electricsheephq/WorldOS#298: Related to SpellbookBrowser modal changes; #298 introduced the component updated here with dialog/test/ARIA attributes.
  • electricsheephq/WorldOS#338: Overlaps on BrassButton and action/button prop changes that this PR extends for test/accessibility forwarding.
  • electricsheephq/WorldOS#470: Related work on TabBar/tab-button behavior; this PR adds ARIA/test attributes to the same navigation surface.

Poem

🐰 I hopped through code with nimble paws,

Adding labels, roles, and tiny claws.
Buttons now speak, modals softly chime,
Tests can find them—every time.
A rabbit's cheer for hooks well-timed.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding stable OpenWorlds agent UI hooks (aria-label and data-worldos-testid attributes) across multiple surfaces.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description provides a clear summary of changes, licensing confirmation, and validation evidence but is missing the validation checklist section.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands and usage tips.

@100yenadmin
100yenadmin force-pushed the codex/worldos-agent-ui-hooks branch from 9384cbc to 698ed65 Compare May 31, 2026 22:54
@100yenadmin

Copy link
Copy Markdown
Member Author

@coderabbitai review

@100yenadmin
100yenadmin marked this pull request as ready for review May 31, 2026 22:55
@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
viewer/openworlds/chrome.jsx (2)

269-284: ⚡ Quick win

Refactor aria-label handling to avoid redundancy.

The current pattern accesses buttonProps["aria-label"] explicitly (line 282) and then spreads buttonProps (line 284), which creates redundancy when aria-label is present in buttonProps. The spread will override the explicit attribute with the same value.

A clearer pattern would destructure aria-label from buttonProps before spreading:

♻️ Suggested refactor
-function IconPlate({ size = 56, label, framed = true, glyph, tone, children, onClick, active, style, testId, ...buttonProps }) {
+function IconPlate({ size = 56, label, framed = true, glyph, tone, children, onClick, active, style, testId, ...buttonProps }) {
+  const { "aria-label": ariaLabelProp, ...restButtonProps } = buttonProps;
   return (
     <button
       type="button"
       className={`icon-plate ${framed ? "framed" : ""} ${active ? "active" : ""}`}
       onClick={onClick}
       style={{
         width: size,
         height: size,
         ...(active ? { boxShadow: `inset 0 0 0 1px var(--b-500), inset 0 0 0 3px var(--p-100), inset 0 0 0 4px var(--b-400), 0 0 20px -2px var(--gold-glow)` } : {}),
         ...(style || {}),
       }}
       title={label}
-      aria-label={buttonProps["aria-label"] || label || undefined}
+      aria-label={ariaLabelProp || label || undefined}
       data-worldos-testid={testId || undefined}
-      {...buttonProps}
+      {...restButtonProps}
     >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@viewer/openworlds/chrome.jsx` around lines 269 - 284, In IconPlate, avoid
redundant aria-label by destructuring it from buttonProps (e.g., const {
"aria-label": ariaLabel, ...restButtonProps } = buttonProps) and then set
aria-label on the button to ariaLabel || label || undefined, and spread
restButtonProps instead of buttonProps; remove the explicit
buttonProps["aria-label"] usage so the spread cannot override the explicit
computed value and other props still pass through.

295-309: 💤 Low value

Note: Inconsistent aria-label prop pattern between IconPlate and BrassButton.

BrassButton uses a dedicated ariaLabel prop (line 295), while IconPlate allows aria-label to be passed via the spread buttonProps (line 282). This inconsistency could confuse developers about which pattern to use when calling these components.

Consider standardizing both components to use the same pattern—either a dedicated ariaLabel prop or allowing it via buttonProps. The dedicated prop approach (as used here in BrassButton) is clearer and more discoverable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@viewer/openworlds/chrome.jsx` around lines 295 - 309, BrassButton and
IconPlate are inconsistent: BrassButton declares an explicit ariaLabel prop
while IconPlate currently accepts aria-label via the spread buttonProps;
standardize to the explicit prop pattern by updating IconPlate to accept an
ariaLabel prop (in its function signature), explicitly forward aria-label using
aria-label={ariaLabel || undefined} on the rendered button, and ensure the
spread buttonProps does not override or duplicate aria-label (drop it from the
spread or prefer the explicit ariaLabel). This keeps the public API consistent
(use ariaLabel) and avoids duplicate/conflicting attributes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@viewer/openworlds/screen-launcher.jsx`:
- Line 480: The JSX uses a boolean variable selected for the aria-pressed
attribute (aria-pressed={selected}); change it to pass an explicit string per
ARIA spec by converting selected to "true"/"false" (or "mixed" when applicable)
before assigning to aria-pressed in the ScreenLauncher component (or the
component/element that renders this button), e.g., replace the boolean usage
with a string conversion using either a ternary based on selected or
String(selected) so the attribute value is always a string.

In `@viewer/openworlds/screen-table.jsx`:
- Around line 1180-1186: The button currently sets aria-label to the verbose
hint which overrides the concise visible label; change the button so the
accessible name is the visible label (use aria-label={label} or remove the
aria-label prop entirely) and keep title={hint} to supply the descriptive
tooltip; update the JSX around the button element that uses onClick, title,
hint, label, data-worldos-testid and data-worldos-action-id so screen readers
hear the concise label (e.g., "Say" or "Roll d20") while the hint remains only
as the title/tooltip.
- Around line 593-609: The banner currently sets role dynamically based on
surfaceStatus but always forces aria-live="polite", which downgrades alerts;
update the JSX that renders the status banner so aria-live matches the role:
when surfaceStatus === "loading" (role "status") keep aria-live="polite", and
when rendering the error path (role "alert" for surfaceStatus !== "loading")
either remove the explicit aria-live or set aria-live="assertive" so the alert
remains assertive; adjust the conditional that builds the div (referencing
surfaceStatus and its role) to choose the appropriate aria-live value rather
than hardcoding "polite".

---

Nitpick comments:
In `@viewer/openworlds/chrome.jsx`:
- Around line 269-284: In IconPlate, avoid redundant aria-label by destructuring
it from buttonProps (e.g., const { "aria-label": ariaLabel, ...restButtonProps }
= buttonProps) and then set aria-label on the button to ariaLabel || label ||
undefined, and spread restButtonProps instead of buttonProps; remove the
explicit buttonProps["aria-label"] usage so the spread cannot override the
explicit computed value and other props still pass through.
- Around line 295-309: BrassButton and IconPlate are inconsistent: BrassButton
declares an explicit ariaLabel prop while IconPlate currently accepts aria-label
via the spread buttonProps; standardize to the explicit prop pattern by updating
IconPlate to accept an ariaLabel prop (in its function signature), explicitly
forward aria-label using aria-label={ariaLabel || undefined} on the rendered
button, and ensure the spread buttonProps does not override or duplicate
aria-label (drop it from the spread or prefer the explicit ariaLabel). This
keeps the public API consistent (use ariaLabel) and avoids duplicate/conflicting
attributes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9d27166e-54f8-4e84-9e16-984844539602

📥 Commits

Reviewing files that changed from the base of the PR and between 9d36809 and 698ed65.

📒 Files selected for processing (8)
  • viewer/openworlds/camp-sidebar.jsx
  • viewer/openworlds/chrome.jsx
  • viewer/openworlds/screen-character.jsx
  • viewer/openworlds/screen-launcher.jsx
  • viewer/openworlds/screen-settings.jsx
  • viewer/openworlds/screen-table.jsx
  • viewer/openworlds/toast.jsx
  • viewer/tests/test_openworlds_static.py

Comment thread viewer/openworlds/screen-launcher.jsx Outdated
Comment thread viewer/openworlds/screen-table.jsx
Comment thread viewer/openworlds/screen-table.jsx
@100yenadmin

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@100yenadmin

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@100yenadmin
100yenadmin merged commit 1e148ab into main May 31, 2026
9 checks passed
@100yenadmin
100yenadmin deleted the codex/worldos-agent-ui-hooks branch May 31, 2026 23:27
100yenadmin added a commit that referenced this pull request May 31, 2026
Sync takeover truth docs to main@080497e after #475/#494/#495/#496. Records honest evidence split, issue hygiene, and transient harness-port guidance.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant