EN / field notes OpenHands feature map

OpenHands / F02

App shell, sidebar and command menu

Every page inside the root layout shares one shell. On desktop it is a left rail with the logo, Search commands, New Chat, Customize, Automate (when the automation interface is present), the conversation list, the Getting started checklist, an update tile (only when a newer release exists) and the backend selector with a settings gear. The rail collapses to icons and remembers that choice. Below 768 px the rail turns into a drawer that opens from a top bar or the chat header. A user can pin Customize or Automate as the page / opens. A Ctrl/Cmd+K command menu searches pages, settings and actions. Failed requests show error toasts.

26 mapped behaviors · 26 recipes and supporting checks · source snapshot 9 October 2026
From upstream main at 8793c111. Read the maintained source.

How to get to it

  • Any page in the app: the left rail (desktop) or the hamburger (phone, browser viewport phone).
  • Direct URLs: / (the default home, or a redirect to the pinned page), /conversations, /customize, /automations, /settings.
  • Command menu: press Control+k or Meta+k anywhere, or click Search commands (command-menu-trigger) in the rail or drawer. Type a page or setting name, then press Enter or click the row.
  • Hover a sidebar row to reveal its pin. Hover the collapsed rail's logo to reveal the expand chevron.
  • Settings → Application (/settings/app): the Show Getting Started checklist switch.
  • The Getting started card: click an item row to go to its page, or hover it for the preview.
  • Keyboard: Control+k also works while the composer has focus; Tab moves from the search input into the option list.

Before you start

Start with the common launch and health checks, then follow this family’s preconditions in order. Recipes share the fixtures and state named below.

Preconditions:

  • Baseline state (launched, doctored, onboard --skip done), desktop viewport, sidebar expanded, no home pin, checklist shown.
  • F02.checklist-progress (LLM and chat items) needs control-openhands llm preset deepseek and one conversation (control-openhands conversation start --prompt "Reply with only the word: ok" --wait --timeout 180). Run both before the F02.conversation-list bullet. The preset writes through the API, so control-openhands browser reload before you read the LLM item. The conversation-header hamburger check in F02.mobile-drawer reuses that conversation.
  • F02.update-tile (the sidebar tile) is blocked unless the installed version is older than npm latest and the browser can reach registry.npmjs.org. The Settings → Application check runs either way.
  • F02.alert-banner is blocked on the local backend, whose config always reports no alerts; it needs a Cloud backend that publishes them.
  • F02.settings-404-modal is blocked: it needs a backend whose settings endpoint returns 404.
  • F02.checklist-all-complete needs an automation and an MCP server on top of the four items above. Both are added through the UI from two fixtures (see that bullet) and deleted again afterwards.

Behavior inventory

26 stable behavior IDs and their expected behavior
  • F02.pin-as-home hovering Customize or Automate reveals a pin. Pinning makes / redirect to that page, pinning the other one moves the pin, and unpinning restores the default home. The pin is stored per backend and org. Read recipe ↓
  • F02.sidebar-collapse the chevron collapses the rail to a 60 px icon rail with hover tooltips, a Settings icon and a backend icon with a status dot that opens a backend popover. The checklist is hidden while collapsed. The state survives a reload. To expand, click the chevron that appears when you hover the logo, or click empty rail space. Read recipe ↓
  • F02.conversation-list the expanded rail embeds the conversation list (conversation-panel). The list itself belongs to the conversations family. Read recipe ↓
  • F02.command-menu-open-close Control+k, Meta+k and the sidebar Search commands row (also the search icon on the collapsed rail) open a modal Command menu dialog with the input focused. Escape or the backdrop closes it, and the query is cleared on close. Escape returns focus to the element that had it before the menu opened (the Search commands row when it opened the menu). The row and the menu header show the shortcut as Ctrl+K off Apple platforms and ⌘K on macOS and iOS. Read recipe ↓
  • F02.command-menu-focus the shortcut works while typing in another field (the composer) and moves focus to the search input. Tab moves focus to the first option. Escape closes the menu from any focused element inside it and returns focus to the composer. Read recipe ↓
  • F02.command-menu-items the menu shows Navigation (New chat, Customize, Automations, MCP servers), Settings (Settings, Agent settings, LLM profiles, Model Router, Condenser, Agent Context, Verification, Application, Secrets) and Actions (Toggle sidebar). Each Go item navigates to its route. Read recipe ↓
  • F02.command-menu-toggle-sidebar the Toggle sidebar action collapses or expands the desktop rail. Read recipe ↓
  • F02.command-menu-new-tab a middle-click or modifier-click on a Go item opens the route in a new tab and leaves the menu open. Read recipe ↓
  • F02.command-menu-phone at 390 px the menu fits the screen and opens from the drawer's Search row. Escape closes the menu and leaves the drawer open. Running an item closes the drawer. Read recipe ↓
  • F02.mobile-drawer at phone width the rail is hidden and a hamburger (sidebar-mobile-menu-toggle) opens a 300 px drawer. The hamburger is in the top bar, or in the chat header on conversation pages. The drawer closes on its chevron, Escape, a backdrop click or any route change. Read recipe ↓
  • F02.mobile-top-bar at phone width, settings sub-pages and Customize detail pages (/skills, /mcp, /plugins, /apps) show a Back chevron that returns to /settings or /customize. Read recipe ↓
  • F02.checklist after onboarding, the expanded rail shows a Getting started card with N complete and six items. They link to /settings/llm, /conversations, /automations, /settings/agents, /mcp and the external Slack invite. Read recipe ↓
  • F02.checklist-progress items get a check and a strikethrough when done. The LLM item completes when a profile is configured, Start your first chat when a conversation exists, Customize your agent after a visit to /settings/agents, and Join Slack on click. Read recipe ↓
  • F02.checklist-preview hovering an item shows a preview with a description, a Documentation link and an action button that navigates. Read recipe ↓
  • F02.checklist-minimize the card header minimizes and expands the card, and the state survives a reload. Read recipe ↓
  • F02.checklist-hide the Show Getting Started checklist switch in Settings → Application hides the card (it stays hidden after a reload) and can show it again. Read recipe ↓
  • F02.checklist-all-complete the card disappears once all six items are complete. Completion is recomputed from current data, so deleting the automation or the MCP server brings the card back. Read recipe ↓
  • F02.super-admin-setup-guide on an OpenHands Enterprise (cloud) backend with ENABLE_SUPER_ADMIN, the first Super Admin (/me permissions include manage_super_admins) sees a floating Setup guide panel in the lower-right corner with n/4 progress, the steps Add an LLM, Choose an automation template, Add an integration, Invite users and Optional: SAML / instance, a Next: <step> status and a Start link for that step (the positive Enterprise path remains unverified here). Close collapses it to a pill that reopens it. While the guide loads or shows, the Getting started card is hidden. Local backends never show it and make no /api/admin/setup-state request (#17969). Read recipe ↓
  • F02.update-tile when npm latest is newer than the running version, a New version tile (agent-canvas-version-tile) opens an update modal with the npm and Docker commands. The tile is never shown in locked-Cloud mode. Read recipe ↓
  • F02.alert-banner when the server config reports maintenance, faulty models or an error message, a dismissible banner (alert-banner) appears at the top of the content area.
  • F02.settings-404-modal if the backend's settings return 404, the LLM settings modal opens by itself. Other settings fetch errors show a toast.
  • F02.error-toasts failed requests show a top-right error toast with the product or server message, and the toast closes by itself. Read recipe ↓

Readable recipes

Read each script from top to bottom. Code is copied from the map; prose gives the action, expected observation, and conditions. <id>, <run> and similar placeholders stand for values from your own run. Short forms such as browser count continue the same control-openhands invocation; they are kept as documented.

Expected observations describe the recipe’s contract. Captures below selected recipes show representative real states from this snapshot; they do not mark every mapped behavior as passed. Follow cleanup before moving to another family.

Sidebar links #

  1. Do
    control-openhands browser goto /conversations
  2. Do
    control-openhands browser click 'testid=sidebar-skills-link'
  3. Wait
    control-openhands browser wait-url '/mcp$'
  4. Expect
    The URL ends in /mcp because desktop /customize redirects there.
  5. Check
    control-openhands browser attr 'testid=sidebar-skills-link' class
  6. Note
    the class contains bg-tertiary (the active style), and
  7. Check
    control-openhands browser attr 'testid=sidebar-skills-link' aria-current

    is page.

  8. Note
    Repeat browser goto with /skills, /plugins and /apps; each time the class still contains bg-tertiary and aria-current is still page.
  9. Do
    control-openhands browser click 'testid=sidebar-automations-link'
  10. Check
    control-openhands browser url

    (ends in /automations) and

  11. Check
    control-openhands browser attr 'testid=sidebar-automations-link' aria-current

    (page);

  12. Check
    control-openhands browser attr 'testid=sidebar-skills-link' aria-current

    is now null.

  13. Do
    control-openhands browser click 'testid=sidebar-conversations-link'
  14. Do
    control-openhands browser click 'aside[data-collapsed] >> role=link[name="OpenHands Logo"]'
  15. Note
    both leave the URL at /conversations.

Pin as home #

  1. Note
    From /conversations run
  2. Check
    control-openhands browser visible 'testid=sidebar-pin-home-toggle-customize'

    (false),

  3. Do
    control-openhands browser hover 'testid=sidebar-skills-link'
  4. Note
    the same visible (true), then
  5. Do
    control-openhands browser click 'testid=sidebar-pin-home-toggle-customize'
  6. Check
    control-openhands browser attr 'testid=sidebar-pin-home-toggle-customize' aria-pressed

    (true).

  7. Do
    control-openhands browser goto /
  8. Wait
    control-openhands browser wait-url '/mcp$'
  9. Note
    the URL ends in /mcp.
  10. Do
    control-openhands browser screenshot 'aside[data-collapsed]' --feature F02.pin-as-home --name customize-pinned
  11. Note
    the screenshot shows a filled pin on the Customize row with no hover.
  12. Note
    Move the pin: run
  13. Do
    control-openhands browser hover 'testid=sidebar-automations-link'
  14. Do
    control-openhands browser click 'testid=sidebar-pin-home-toggle-automations'
  15. Expect
    The Customize pin's aria-pressed becomes false, and
  16. Do
    control-openhands browser goto /
  17. Note
    lands on /automations.
  18. Note
    Unpin with
  19. Do
    control-openhands browser click 'testid=sidebar-pin-home-toggle-automations'
  20. Do
    control-openhands browser goto /
  21. Check
    control-openhands browser count 'testid=home-screen'
  22. Expect
    The URL stays / and the count is 1.
  23. Do
    control-openhands browser storage --values

    shows oh:pinned-home-route:<backend>:<org> as null.

Collapse and expand #

  1. Do
    control-openhands browser click 'testid=sidebar-collapse-toggle'
  2. Check
    control-openhands browser attr 'aside[data-collapsed]' data-collapsed

    (true).

  3. Do
    control-openhands browser reload
  4. Note
    the attribute is still true, browser storage --values shows openhands-sidebar with "collapsed":true, and
  5. Check
    control-openhands browser bbox 'aside[data-collapsed]'
  6. Note
    reports width 60.
  7. Check
    control-openhands browser count 'testid=sidebar-onboarding-checklist'

    is 0.

  8. Note
    For the tooltip, hover one icon, then another:
  9. Do
    control-openhands browser hover 'testid=sidebar-automations-link'
  10. Do
    control-openhands browser hover 'testid=sidebar-skills-link'
  11. Wait
    control-openhands browser wait 'role=tooltip'
  12. Check
    control-openhands browser text 'role=tooltip'

    (Customize).

  13. Note
    For the backend popover, run
  14. Do
    control-openhands browser hover 'testid=collapsed-backend-selector-link'
  15. Note
    its aria-expanded is true, browser testids --filter backend lists add-backend-menu-item and manage-backends-menu-item, and
  16. Do
    control-openhands browser screenshot --feature F02.sidebar-collapse --name backend-popover

    shows the popover.

  17. Note
    Expand by clicking empty rail space with
  18. Do
    control-openhands browser click 'aside[data-collapsed]'

    (data-collapsed becomes false).

  19. Note
    Collapse again, then run
  20. Do
    control-openhands browser hover 'aside[data-collapsed] >> role=link[name="OpenHands Logo"]'
  21. Note
    the toggle's class has opacity-100 and its aria-label is Expand sidebar.
  22. Do
    control-openhands browser click 'testid=sidebar-collapse-toggle'
  23. Note
    expands the rail.

Settings gear #

  1. Expect
    The previous bullet expanded the rail: collapse it with
  2. Do
    control-openhands browser click 'testid=sidebar-collapse-toggle'
  3. Do
    control-openhands browser click 'testid=collapsed-settings-link'
  4. Wait
    control-openhands browser wait-url '/settings'
  5. Note
    Expand it again with
  6. Do
    control-openhands browser click 'testid=sidebar-collapse-toggle'
  7. Do
    control-openhands browser goto /conversations
  8. Do
    control-openhands browser click 'testid=backend-selector-settings-link'
  9. Check
    control-openhands browser url
  10. Note
    the URL ends in /settings/agents.
  11. Check
    control-openhands browser attr 'testid=backend-selector-settings-link' target

    is null (same tab).

Conversation list #

  1. Expect
    After conversation start (preconditions), run
  2. Do
    control-openhands browser goto /conversations
  3. Check
    control-openhands browser testids 'testid=conversation-panel'
  4. Expect
    The list includes conversation-card.

Open and close the menu #

  1. Note
    From /conversations run
  2. Check
    control-openhands browser text 'testid=command-menu-trigger'
  3. Expect
    It reads Search commands and CTRL+K on Linux and Windows (the hint is Ctrl+K, drawn in capitals; macOS and iOS show ⌘K).
  4. Do
    control-openhands browser press Control+k
  5. Check
    control-openhands browser visible 'testid=command-menu'

    (true),

  6. Do
    control-openhands browser eval "document.activeElement && document.activeElement.id"

    (command-menu-search) and

  7. Check
    control-openhands browser text 'testid=command-menu >> css=kbd'

    (CTRL+K, the header hint).

  8. Do
    control-openhands browser screenshot --feature F02.command-menu-open-close --name open

    shows the dimmed, blurred backdrop.

  9. Do
    control-openhands browser press Escape
  10. Note
    browser count 'testid=command-menu' is 0.
  11. Do
    control-openhands browser press Meta+k

    (count 1),

  12. Do
    control-openhands browser type 'testid=command-menu >> role=combobox' secr
  13. Do
    control-openhands browser click 'testid=command-menu >> role=button[name="Close command menu"]'

    (the backdrop; count 0).

  14. Do
    control-openhands browser click 'testid=command-menu-trigger'
  15. Check
    control-openhands browser value 'testid=command-menu >> role=combobox'
  16. Note
    the value is "" because the query was cleared on close.
  17. Note
    Close it with
  18. Do
    control-openhands browser press Escape
  19. Note
    focus is back on the row (control-openhands browser eval "document.activeElement.dataset.testid" is command-menu-trigger).
  20. Note
    Collapsed rail: run
  21. Do
    control-openhands browser click 'testid=sidebar-collapse-toggle'
  22. Do
    control-openhands browser click 'testid=command-menu-trigger'

    (the search icon; count 1),

  23. Do
    control-openhands browser press Escape
  24. Note
    then expand with
  25. Do
    control-openhands browser click 'aside[data-collapsed]'
The command menu lists navigation, settings and actions over the dimmed application.
Ctrl+K opens the searchable command menu; Escape closes it. CLI capture · 1440 × 1000 · 9 October 2026 · Canvas 8793c111

The command menu lists navigation, settings and actions over the dimmed application.

Representative documentation capture, not a complete run of this recipe or family. Captured on an isolated local backend at desktop viewport using the current main checkout.

How this screenshot was taken

canvas: 1.26.0 · agent server: 1.53.0 · sdk: 1.53.0 · automation: 1.19.0

OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser goto /
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser wait testid=home-screen
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser click testid=open-workspace-button
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser wait role=dialog
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser press Escape
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser press Control+k
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser wait testid=command-menu
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser screenshot testid=command-menu --feature F02.command-menu-open-close --name command-menu

Every item navigates #

  1. Expect
    The previous bullet closed the menu: run
  2. Do
    control-openhands browser press Control+k
  3. Check
    control-openhands browser snapshot 'testid=command-menu' --feature F02.command-menu-items --name all-items
  4. Note
    it lists 14 options under Navigation, Settings and Actions, with Model Router after LLM profiles and Agent Context after Condenser settings.
  5. Note
    Close it with
  6. Do
    control-openhands browser press Escape
  7. Note
    and rest the pointer off the list with
  8. Do
    control-openhands browser hover 'testid=command-menu-trigger'

    (see Gotchas).

  9. Note
    For each item run
  10. Do
    control-openhands browser goto /conversations
  11. Do
    control-openhands browser press Control+k
  12. Do
    control-openhands browser type 'testid=command-menu >> role=combobox' '<term>'
  13. Do
    control-openhands browser press Enter
  14. Wait
    control-openhands browser wait-url '<route>$'
  15. Note
    Expected routes: New chat → /conversations, Customize → /mcp (via /customize), Automations → /automations, MCP servers → /mcp, Settings overview → /settings/agents (via /settings), Agent settings → /settings/agents, LLM profiles → /settings/llm, Model Router → /settings/meta-llm, Condenser → /settings/condenser, Agent Context → /settings/agent-context, Verification → /settings/verification, Application → /settings/app, Secrets → /settings/secrets. browser count 'testid=command-menu' is 0 after each run.
  16. Note
    Click path:
  17. Do
    control-openhands browser goto /settings/app
  18. Note
    open the menu, type new chat, then
  19. Do
    control-openhands browser click 'testid=command-menu >> role=option[name=/New chat/]'
  20. Note
    the URL becomes /conversations.

Toggle sidebar #

  1. Do
    control-openhands browser press Control+k
  2. Do
    control-openhands browser type 'testid=command-menu >> role=combobox' toggle
  3. Do
    control-openhands browser press Enter
  4. Check
    control-openhands browser attr 'aside[data-collapsed]' data-collapsed

    (true).

  5. Note
    Repeat with the term sidebar; the value returns to false.

Search, empty state, keyboard #

  1. Expect
    The Toggle sidebar action closed the menu: open it with
  2. Do
    control-openhands browser press Control+k
  3. Do
    control-openhands browser fill 'testid=command-menu >> role=combobox' theme
  4. Check
    control-openhands browser count 'testid=command-menu >> role=option'

    (1, Application, matched by its keywords).

  5. Expect
    The term settings secret leaves 1 option, secret automations leaves 0 (all terms must match) and model leaves 3 (MCP servers, LLM profiles, Model Router).
  6. Note
    Fill qa-zzz-nothing, then run
  7. Check
    control-openhands browser text 'testid=command-menu >> role=listbox'

    (No commands found / Try a page, action, or setting name.) and

  8. Do
    control-openhands browser screenshot --feature F02.command-menu-search --name no-results
  9. Do
    control-openhands browser press Enter
  10. Note
    the menu stays open and the URL does not change.
  11. Do
    control-openhands browser click 'testid=command-menu >> role=button[name="Clear search"]'
  12. Note
    the value is "" and the count is back to 14.
  13. Note
    Keyboard:
  14. Check
    control-openhands browser attr 'testid=command-menu >> role=combobox' aria-activedescendant
  15. Note
    starts at command-menu-option-new-chat.
  16. Do
    control-openhands browser press ArrowUp
  17. Note
    wraps to command-menu-option-toggle-sidebar, and ArrowDown returns to new-chat, then moves to customize.
  18. Do
    control-openhands browser hover 'testid=command-menu >> role=option[name=/Secrets settings/]'
  19. Note
    sets the value to command-menu-option-secrets-settings.

Open in a new tab #

  1. Note
    With the menu open and secrets typed,
  2. Check
    control-openhands browser attr 'testid=command-menu >> role=option[name=/Secrets settings/]' href

    is /settings/secrets.

  3. Do
    control-openhands browser click 'testid=command-menu >> role=option[name=/Secrets settings/]' --button middle
  4. Do
    control-openhands browser tabs
  5. Note
    tab 1 is /settings/secrets, tab 0 keeps its URL, and browser count 'testid=command-menu' is still 1.
  6. Do
    control-openhands browser close-tab 1
  7. Expect
    The modifier path works the same:
  8. Do
    control-openhands browser click 'testid=command-menu >> role=option[name=/Secrets settings/]' --modifiers Control
  9. Note
    opens tab 1 with the menu still open; close that tab too.
  10. Expect
    The click leaves focus on the option;
  11. Do
    control-openhands browser press Escape
  12. Note
    still closes the menu (count 0).

Keyboard focus #

  1. Note
    From /conversations run
  2. Do
    control-openhands browser click 'testid=chat-input'
  3. Do
    control-openhands browser press Control+k
  4. Note
    browser count 'testid=command-menu' (1) and
  5. Do
    control-openhands browser eval "document.activeElement && document.activeElement.id"

    (command-menu-search): the shortcut opens the menu from inside the composer.

  6. Do
    control-openhands browser press Tab
  7. Note
    the same eval returns command-menu-option-new-chat.
  8. Do
    control-openhands browser press Escape
  9. Note
    closes the menu from the option (count 0), and
  10. Do
    control-openhands browser eval "document.activeElement.dataset.testid"

    returns chat-input: focus is back in the composer.

Phone drawer #

  1. Do
    control-openhands browser viewport phone
  2. Do
    control-openhands browser goto /conversations
  3. Check
    control-openhands browser visible 'aside[data-collapsed]'

    is false.

  4. Do
    control-openhands browser click 'testid=sidebar-mobile-menu-toggle'
  5. Wait
    control-openhands browser wait 'testid=sidebar-mobile-drawer'
  6. Check
    control-openhands browser bbox 'testid=sidebar-mobile-drawer'
  7. Note
    gives width 300, insideViewport true and pageHorizontalOverflow false, and
  8. Do
    control-openhands browser screenshot --feature F02.mobile-drawer --name open

    shows the full rail.

  9. Note
    Close paths, each followed by
  10. Wait
    control-openhands browser wait 'testid=sidebar-mobile-drawer' --state detached
  11. Do
    control-openhands browser click 'testid=sidebar-mobile-drawer-close'
  12. Do
    control-openhands browser press Escape
  13. Do
    control-openhands browser click 'testid=sidebar-mobile-drawer >> testid=sidebar-automations-link'

    (route change; the URL becomes /automations); and a backdrop click to the right of the drawer,

  14. Do
    control-openhands browser mouse-click 350 400

    (on /conversations, control-openhands browser click 'testid=submit-button' --force also lands on the backdrop, because the empty composer's send button sits at x≈326 under it).

  15. Note
    On a conversation page (browser goto the URL from conversation start),
  16. Check
    control-openhands browser bbox 'testid=sidebar-mobile-menu-toggle'

    is at the top-left of the chat header.

  17. Do
    control-openhands browser screenshot --feature F02.mobile-drawer --name conversation-header

    shows it beside the title, and clicking it opens the drawer.

  18. Note
    Return with
  19. Do
    control-openhands browser viewport desktop

Back chevron #

  1. Expect
    The Phone drawer bullet returned to desktop: run
  2. Do
    control-openhands browser viewport phone
  3. Do
    control-openhands browser goto /settings/llm
  4. Check
    control-openhands browser attr 'testid=sidebar-mobile-back-button' aria-label

    (Settings).

  5. Do
    control-openhands browser click 'testid=sidebar-mobile-back-button'
  6. Check
    control-openhands browser url
  7. Note
    the URL is /settings, and on that page browser count 'testid=sidebar-mobile-back-button' is 0.
  8. Do
    control-openhands browser screenshot --feature F02.mobile-top-bar --name settings-root

    shows only the hamburger above the settings menu.

  9. Note
    Do the same from /skills: the label is Customize and the click lands on /customize, which on phone is the hub (extensions-mobile-hub).

Phone command menu #

  1. Note
    At phone width open the drawer, then run
  2. Do
    control-openhands browser click 'testid=sidebar-mobile-drawer >> testid=command-menu-trigger'
  3. Check
    control-openhands browser bbox 'testid=command-menu >> role=listbox'
  4. Note
    reports insideViewport true and pageHorizontalOverflow false, and
  5. Do
    control-openhands browser screenshot --feature F02.command-menu-phone --name open

    shows it.

  6. Do
    control-openhands browser press Escape
  7. Note
    closes only the menu: browser count 'testid=command-menu' is 0 and
  8. Check
    control-openhands browser visible 'testid=sidebar-mobile-drawer'

    is still true.

  9. Note
    Reopen it with
  10. Do
    control-openhands browser click 'testid=sidebar-mobile-drawer >> testid=command-menu-trigger'
  11. Note
    type secrets and press Enter;
  12. Wait
    control-openhands browser wait-url 'secrets$'
  13. Note
    passes and
  14. Wait
    control-openhands browser wait 'testid=sidebar-mobile-drawer' --state detached
  15. Note
    passes.

Checklist card #

  1. Expect
    The phone bullets above leave the phone viewport on /settings/secrets: run
  2. Do
    control-openhands browser viewport desktop
  3. Do
    control-openhands browser goto /conversations
  4. Check
    control-openhands browser count 'testid=sidebar-onboarding-checklist >> role=listitem'

    (6) and

  5. Check
    control-openhands browser text 'testid=sidebar-onboarding-checklist'
  6. Expect
    The first lines are Getting started and N complete.
  7. Check
    control-openhands browser snapshot 'aside[data-collapsed]'

    shows the six links: /settings/llm, /conversations, /automations, /settings/agents, /mcp and https://openhands.dev/joinslack.

Item links #

  1. Check
    control-openhands browser click 'testid=sidebar-onboarding-checklist-item-schedule-task' --expect-url '/automations(\?|$)'
  2. Check
    control-openhands browser click 'testid=sidebar-onboarding-checklist-item-configure-llm' --expect-url '/settings/llm(\?|$)'
  3. Note
    both pass, browser tabs still lists one tab, and browser count 'testid=sidebar-onboarding-checklist-item-schedule-task >> css=span.line-through' stays 0 (visiting is not completing).

Progress #

  1. Note
    Each completed item renders a struck-through label, so
  2. Check
    control-openhands browser count 'testid=sidebar-onboarding-checklist-item-<id> >> css=span.line-through'

    is 1.

  3. Do
    control-openhands browser goto /settings/agents
  4. Note
    then come back; customize-agent is done.
  5. Do
    control-openhands browser click 'testid=sidebar-onboarding-checklist-item-join-slack'
  6. Note
    join-slack is done and browser tabs shows an external Slack tab, which you close with
  7. Do
    control-openhands browser close-tab 1
  8. Expect
    After llm preset deepseek, run
  9. Do
    control-openhands browser reload

    (the open page does not refetch settings written through the API), then

  10. Wait
    control-openhands browser wait 'testid=sidebar-onboarding-checklist-item-configure-llm >> css=span.line-through' --timeout 15000
  11. Expect
    After conversation start, start-conversation is done.
  12. Expect
    The header count rises each time (4 complete after these four).

Item preview #

  1. Do
    control-openhands browser hover 'testid=sidebar-onboarding-checklist-item-join-slack'
  2. Do
    control-openhands browser hover 'testid=sidebar-onboarding-checklist-item-connect-mcp'
  3. Wait
    control-openhands browser wait 'testid=sidebar-onboarding-checklist-preview-connect-mcp' --timeout 5000
  4. Check
    control-openhands browser text 'testid=sidebar-onboarding-checklist-preview-connect-mcp'
  5. Note
    it reads the title, the description, Documentation and Connect.
  6. Check
    control-openhands browser attr 'testid=sidebar-onboarding-checklist-preview-docs-connect-mcp' href

    is https://docs.openhands.dev/overview/model-context-protocol.

  7. Note
    Take
  8. Do
    control-openhands browser screenshot --feature F02.checklist-preview --name connect-mcp
  9. Do
    control-openhands browser click 'testid=sidebar-onboarding-checklist-preview-action-connect-mcp'
  10. Wait
    control-openhands browser wait-url '/mcp$'

Minimize #

  1. Do
    control-openhands browser click 'testid=sidebar-onboarding-checklist-toggle'
  2. Check
    control-openhands browser attr 'testid=sidebar-onboarding-checklist' data-minimized

    (true) and browser count 'testid=sidebar-onboarding-checklist >> role=listitem' (0).

  3. Note
    After
  4. Do
    control-openhands browser reload
  5. Note
    the attribute is still true and the toggle's aria-label is Expand checklist.
  6. Note
    Click the toggle again to restore false.

Hide from settings #

  1. Do
    control-openhands browser goto /settings/app
  2. Check
    control-openhands browser click 'text=Show Getting Started checklist'
  3. Note
    browser count 'testid=sidebar-onboarding-checklist' is 0.
  4. Note
    After
  5. Do
    control-openhands browser reload
  6. Note
    the count is still 0, and
  7. Check
    control-openhands browser eval "document.querySelector('[data-testid=show-getting-started-checklist-switch]').checked"

    is false.

  8. Note
    Click the same text again; the count is 1.

All complete #

  1. Note
    Start with the four items above done (5 complete would mean one fixture is left over).
  2. Note
    Arrange two fixtures: a minimal stdio MCP server,
  3. Arrange
    control-openhands fixture file --name qa-f02-mcp.py --content "$(printf '%s\n' 'import sys,json' 'for line in sys.stdin:' ' m=json.loads(line)' ' if "id" not in m: continue' ' r={}' ' if m.get("method")=="initialize": r={"protocolVersion":m["params"].get("protocolVersion","2024-11-05"),"capabilities":{"tools":{}},"serverInfo":{"name":"qa","version":"1"}}' ' elif m.get("method")=="tools/list": r={"tools":[]}' ' sys.stdout.write(json.dumps({"jsonrpc":"2.0","id":m["id"],"result":r})+"\n"); sys.stdout.flush()')"
  4. Arrange
    control-openhands fixture file --name qa-f02.automation.json --content '{"version":1,"kind":"automation","spec":{"name":"QA_F02 Done","trigger":{"type":"cron","schedule":"0 0 1 1 *","timezone":"UTC"},"enabled":false,"prompt":"Reply with the single word: pong. Do not run any tools."}}'
  5. Note
    Add the server in the UI:
  6. Do
    control-openhands browser goto /mcp
  7. Do
    control-openhands browser click 'testid=mcp-add-custom-server'
  8. Do
    control-openhands browser click 'testid=server-type-dropdown'
  9. Do
    control-openhands browser click 'role=option[name="STDIO"]'
  10. Do
    control-openhands browser fill 'testid=name-input' qa_f02_mcp
  11. Do
    control-openhands browser fill 'testid=command-input' python3
  12. Do
    control-openhands browser fill 'testid=args-input' "$OH_VERIFY_RUN/evidence/_fixtures/qa-f02-mcp.py"
  13. Do
    control-openhands browser click 'testid=mcp-custom-editor >> testid=submit-button'
  14. Wait
    control-openhands browser wait 'testid=mcp-custom-editor' --state detached
  15. Expect
    The header reads 5 complete.
  16. Note
    Import the automation:
  17. Do
    control-openhands browser goto /automations
  18. Do
    control-openhands browser click 'testid=automations-add-automation'
  19. Do
    control-openhands browser click 'testid=automations-import-automation'
  20. Do
    control-openhands browser upload 'testid=automations-import-file' "$OH_VERIFY_RUN/evidence/_fixtures/qa-f02.automation.json"
  21. Do
    control-openhands browser click 'testid=import-automation-confirm'
  22. Wait
    control-openhands browser wait 'testid=import-automation-modal' --state detached
  23. Note
    Now browser count 'testid=sidebar-onboarding-checklist' is 0, still 0 after
  24. Do
    control-openhands browser reload
  25. Do
    control-openhands browser screenshot 'aside[data-collapsed]' --feature F02.checklist-all-complete --name rail

    shows the rail without the card.

  26. Note
    Restore and prove it comes back: open the card's kebab with
  27. Do
    control-openhands browser click '[data-testid^="automation-card-"] >> has-text=QA_F02 Done >> role=button[name="Automation actions"]'
  28. Do
    control-openhands browser click 'role=list >> has-text=Export >> role=button[name="Delete"]'
  29. Do
    control-openhands browser click 'role=heading[name="Delete automation"] >> xpath=.. >> role=button[name="Delete"]'
  30. Do
    control-openhands browser reload
  31. Note
    the card count is 1 again (5 complete).
  32. Do
    control-openhands browser goto /mcp
  33. Do
    control-openhands browser click 'testid=mcp-server-detail-qa_f02_mcp'
  34. Do
    control-openhands browser click 'testid=mcp-custom-editor-delete'
  35. Do
    control-openhands browser click 'testid=confirmation-modal >> testid=confirm-button'
  36. Do
    control-openhands browser reload
  37. Note
    testid=mcp-installed-empty is back and the card reads 4 complete.

Setup guide #

  1. Note
    Local:
  2. Check
    control-openhands browser count 'testid=super-admin-setup-guide'

    is 0 at desktop and phone, browser count 'testid=sidebar-onboarding-checklist' is 1, and

  3. Check
    control-openhands browser network --filter setup-state
  4. Note
    reports total 0.
  5. Expect
    The positive path is blocked locally: it needs an OHE backend with ENABLE_SUPER_ADMIN whose /api/admin/setup-state returns a guide_org_id, guide_dismissed false and at least one unfinished required step, signed in as the first Super Admin.

Update tile #

  1. Expect
    The tile is blocked in an up-to-date checkout or without registry access.
  2. Note
    Check with
  3. Check
    control-openhands browser count 'testid=agent-canvas-version-tile'
  4. Note
    When the count is 1, click it; the modal has agent-canvas-update-command-npm, agent-canvas-update-command-docker and agent-canvas-update-check-button.
  5. Expect
    The manual check runs either way: on /settings/app (control-openhands browser goto /settings/app; the previous bullet ends on /mcp), testid=agent-canvas-update-badge reads Up to date (or New version),
  6. Do
    control-openhands browser click 'testid=agent-canvas-update-toggle'
  7. Note
    opens agent-canvas-update-modal, and
  8. Do
    control-openhands browser click 'testid=agent-canvas-update-check-button'
  9. Note
    reports the result in agent-canvas-update-status (You're running the latest version. What's new when current).
  10. Note
    Close it with
  11. Do
    control-openhands browser click 'testid=close-agent-canvas-update-modal'

Error toast #

  1. Do
    control-openhands browser goto /conversations/00000000-0000-0000-0000-000000000001
  2. Wait
    control-openhands browser wait-text 'This conversation does not exist'
  3. Expect
    The URL becomes /conversations, and
  4. Check
    control-openhands browser screenshot --feature F02.error-toasts --name missing-conversation

    shows a top-right error toast.

  5. Expect
    It is the only toast:
  6. Check
    control-openhands browser toasts

    lists just the "This conversation does not exist, or you do not have permission to access it…" text, and

  7. Check
    control-openhands browser toasts --history

    has no entry containing this UI does not understand.

  8. Wait
    control-openhands browser wait 'text=This conversation does not exist' --state hidden
  9. Note
    passes within 30 s.

Check for errors #

  1. Check
    control-openhands browser errors --app-only
  2. Note
    after each group; it reports pageErrors 0 and appErrors 0.
  3. Note
    External registry.npmjs.org and cdn.simpleicons.org failures are sandbox noise.

Restore #

  1. Note
    Leave the sidebar expanded, unpinned, with the checklist shown and expanded, at browser viewport desktop.

Gotchas and known limits

  • The rail animates its width for 200 ms. A bbox taken right after the collapse click reads an intermediate width (about 70); measure after browser reload.
  • browser url straight after clicking Customize can still read /customize; desktop redirects to /mcp a moment later. Use browser wait-url '/mcp$'.
  • /settings redirects to /settings/agents on desktop but shows the settings menu on phone. That redirect also marks the checklist's Customize your agent item done, so visiting Settings by any route completes it. The sidebar Customize link (/customize) does not.
  • The pin toggles are hidden until hover (visible is false), except a pinned row's toggle, which stays visible. They exist only on the expanded rail.
  • The two desktop elements share the label Sidebar navigation, and settings pages add a second complementary landmark, so role=complementary is ambiguous. Use aside[data-collapsed] for the desktop rail and testid=sidebar-mobile-drawer for the drawer.
  • HeroUI tooltips (the collapsed rail and checklist previews) often stay closed on the first browser hover after a navigation. Hover a neighbor first, then the target, and browser wait for the tooltip. Never count it right away.
  • When collapsed, the expand chevron is opacity-0 pointer-events-none until the rail is hovered. A forced click on it lands on the logo link and does nothing; hover the logo first.
  • With the full list open, the command-menu panel covers the viewport center, so clicking the backdrop button by selector only works after a filter shortens the list (type secr first). control-openhands browser mouse-click 1300 500 hits the desktop backdrop with any list length.
  • The new-tab path works with browser click ... --button middle and with --modifiers Control (or Meta). The phone drawer's backdrop has no handle; browser mouse-click 350 400 clicks it.
  • Escape closes one layer at a time (#18044): with the command menu open over the phone drawer, the first Escape closes the menu and the second closes the drawer. Focus goes back to whatever had it when the menu opened: right after browser goto /conversations that is the composer (chat-input takes focus on load), and on /settings/app it is <body>.
  • Toggle sidebar at phone width flips the stored desktop state without any visible change. Expand the rail again after returning to desktop.
  • Typing does not reset the command menu's selection. The active option keeps its index, cut down to the shorter list, from the last ArrowUp/ArrowDown or hover, and a pointer resting where the list opens hovers the option under it. A term with several matches can then run a later one on Enter: after two ArrowDown presses, typing LLM profiles (LLM profiles and Model Router) selects Model Router. Before opening the menu for an Enter check, rest the pointer with browser hover 'testid=command-menu-trigger', and read aria-activedescendant before Enter when a term matches several items. Repro candidate, not yet filed.
  • The command menu's LLM profiles item uses a magnifier icon (lucide-search, the same icon as the menu's search field).
  • The checklist card has no dismiss button. Hide it with the App settings switch or by completing every item. Clicking Join the OpenHands Slack counts as joining and opens an external tab you must close.
  • Registry access depends on the sandbox. Where the browser cannot reach registry.npmjs.org (ERR_CERT_AUTHORITY_INVALID), the update check reports Couldn't check for updates; that is the environment, not the product. Where it can, the badge reads Up to date and the check says You're running the latest version.
  • Adding a custom MCP server tests the connection first and refuses an unreachable one (Could not reach the server..., or Connection failed: McpError: Connection closed for uvx mcp-server-time here), so the all-complete recipe uses the local stdio fixture.

Source paths: src/components/features/sidebar/, src/components/features/command-menu/, src/stores/sidebar-store.ts, src/stores/command-menu-store.ts, src/hooks/use-pinned-home-route.ts, src/routes/index-home.tsx, src/utils/mobile-section-nav.ts, src/components/features/settings/agent-canvas-version-tile.tsx, src/components/features/alerts/alert-banner.tsx, src/utils/custom-toast-handlers.tsx, src/components/features/setup-guide/, src/api/cloud/setup-state-service.api.ts.