The Manage Backends modal previously rendered pencil and trash icons on
every backend row even when the deployment was started with
--lock-to-cloud. In locked mode the user should not be able to mutate
the locked backend's host/key/name or remove it outright.
backend-row.tsx now consults getLockedCloudHost() and skips rendering
both action buttons when a locked cloud host is configured. The row
identity (name + host) still renders so the user can see which backend
is locked.
Adds three focused tests to describe("BackendRow", ...):
* renders edit and remove buttons when not locked to a cloud host
* hides edit and remove buttons when locked via VITE_LOCK_TO_CLOUD
* hides edit and remove buttons when locked via the
window.__AGENT_CANVAS_LOCK_TO_CLOUD__ runtime global
Also extends afterEach with vi.unstubAllEnvs + window-global cleanup
(consistent with backend-form-modal.test.tsx) and isolates three
pre-existing edit-form tests from a local .env that sets
VITE_LOCK_TO_CLOUD, so they keep exercising edit-form behavior rather
than lock behavior.
Co-authored-by: openhands <openhands@all-hands.dev>
* Use libraries for local proxy and static serving
* Fix CI for proxy library refactor
* Fix static server CI failures
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: openhands <openhands@all-hands.dev>
* fix(conversation): fast-poll active conversation until title is set (#1508)
The header conversation title only refreshed on the slow 30s interval
because useActiveConversation dropped to 30s as soon as conversation_url
was present — which happens before the agent asynchronously generates the
title. Add a fast-poll (3s) trigger while the title is unset and the agent
is actively executing, bounded by isExecutionActive so terminal/paused
titleless conversations don't poll forever.
* docs(conversation): trim verbose comments in title-poll change
---------
Co-authored-by: VascoSch92 <vasco@openhands.dev>
* fix(examples): inherit acp-docker image from config/defaults.json
examples/acp-docker/docker-compose.yml hardcoded the agent-server image at
`1.25.0-python`. Canvas enforces `compatibility.minimumAgentServer` (1.28.0)
from the repo's single source of truth, so the example default fell below the
floor and rendered "Disconnected — requires 1.28.0 or newer" — a reviewer
following the quickstart as written never reached the feature.
examples/acp-docker was the lone in-repo file hardcoding a version instead of
inheriting from config/defaults.json (14 other files read it; check-sdk-version
-sync only validates the released PyPI package, not in-repo files).
- scripts/gen-acp-docker-env.mjs: read defaults.json, pin AGENT_SERVER_IMAGE to
`${images.agentServer}:${versions.agentServer}-python` in examples/acp-docker
/.env (idempotent upsert; mirrors scripts/docker-build.mjs).
- package.json: `npm run example:acp-docker:env`.
- docker-compose.yml: no-config fallback `1.25.0-python` -> `latest-python`,
always >= the compatibility floor, so zero-config `docker compose up` never
shows "Disconnected"; the generated .env overrides with the pinned SoT
version for the reproducible path.
- .env.example / README.md: document both paths; correct the version narrative
(floor is the defaults.json compatibility pin; #3510 is the deeper functional
floor at/below it).
- __tests__/scripts/acp-docker-env-sync.test.ts: assert the generator's tag
matches defaults.json, the pin satisfies the floor, and the compose fallback
stays `latest-python`. Mirrors docs-version-sync.test.ts — the guard that
makes "can't silently drift" true.
* test(examples): harden acp-docker env-sync per review
Addresses the cli-review-panel findings worth acting on (the rest were
cosmetic or matched the no-validation idiom of scripts/docker-build.mjs):
- gte() in the test guarded with parseSemver — a non-numeric pin (sha /
pre-release) now fails the floor check loudly instead of silently
comparing NaN. The floor check is a CI gate; its one piece of logic
shouldn't mis-compare in silence.
- compose-fallback assertion derives the registry from config.images
.agentServer instead of hardcoding ghcr.io/openhands/... — a registry
change no longer false-fails a test that only cares about the latest-python
tag.
- upsertEnvLine now has unit tests (append / replace-in-place+preserve /
idempotent / commented-template-line / keyless-line guard), making the
"idempotent upsert" claim defensible. It was the one untested piece of real
logic.
- upsertEnvLine guards a keyless line (no "=") with a clear throw, instead of
an empty key matching every line and rewriting the whole file.
* fix(examples): guard acp-docker env-sync entrypoint against undefined argv[1]
The CLI entrypoint guard called pathToFileURL(process.argv[1]) unconditionally.
process.argv[1] is undefined in some ESM contexts (e.g. importing the module for
its exports via `node --input-type=module -e "import(...)"`), so the guard threw
ERR_INVALID_ARG_TYPE at import, before any exported helper was reachable.
Short-circuit on process.argv[1] before pathToFileURL so importing the module is
side-effect-free while the CLI path is unchanged. Add a regression test that
reproduces the bare-import context and asserts a clean exit.
Addresses the review finding on #1434.
* docs(acp-docker): trim verbose comments per review
Address all-hands-bot's review suggestions on #1434:
- test header describes the current invariant, not the prior-state history
(that narration belonged in the PR description)
- docker-compose.yml: condense the image-pin comment to the how-to-override;
the compatibility-floor / #3510 rationale already lives in README §1 + the test
- .env.example: 7-line pin explainer down to 2
Comment-only; env-sync test still 10/10 green, prettier clean.
* Clarify ACP Docker image version guidance
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: enyst <engel.nyst@gmail.com>
Co-authored-by: openhands <openhands@all-hands.dev>
* fix(settings): show ACP-disabled tooltip on desktop (drop pointer-events-none)
When an ACP agent is active, the desktop settings sidebar greys out the
LLM/Condenser/Verification items and wraps them in a hover StyledTooltip
("Disabled while {agent} is active"). The tooltip never opened, so users
saw a greyed item with no explanation (reported on macOS desktop).
Root cause: the disabled link also got `pointer-events-none`, but
StyledTooltip is HeroUI's pointer-driven Tooltip — with pointer events
suppressed, onPointerEnter never fires and the tooltip can't open.
pointer-events-none wasn't needed for correctness either: onClick already
preventDefaults navigation, and tabIndex=-1 + aria-disabled cover
keyboard/AT.
Fix: keep the item visually greyed (opacity-50) but only apply
pointer-events-none when there's no disabledReason tooltip to show.
Fixes#1498
Co-authored-by: smolpaws <engel@enyst.org>
* chore: Remove PR-only artifacts
* Address ACP tooltip review comment
* Restore sidebar disabled aria comment
---------
Co-authored-by: smolpaws <engel@enyst.org>
Co-authored-by: openhands <openhands@all-hands.dev>
* feat(settings): add Cloud link to settings sidebar for cloud backends
Add a "Cloud" external link at the bottom of the Settings sidebar that
appears only when the active backend is a Cloud backend. It links to
`{cloudHost}/settings` (opens in a new tab) with an external-link icon,
giving users a quick path to their hosted account/settings page. Local
backends and the no-backend state render nothing.
- New `CloudSettingsLink` component reads the active backend and renders
only for cloud backends, normalizing the host (trailing slash) before
appending `/settings`.
- Added to the desktop sidebar, mobile hub, and mobile drawer (next to
the existing backend-synced badge).
- New i18n key `SETTINGS$CLOUD_SETTINGS_LINK` (allowlisted as a brand
name since "Cloud" is identical across locales).
- Added unit tests covering cloud/local/no-backend cases and URL building.
Screenshot of the new Cloud button in the Settings window: .pr/settings-cloud-button.png
Co-authored-by: openhands <openhands@all-hands.dev>
* docs(pr): replace screenshot with correct Settings sidebar view
The previous screenshot captured the manage-backends overlay that
appears for a logged-out cloud backend, not the Settings sidebar.
Re-captured against a connected cloud backend so the Cloud link is
visible at the bottom of the Settings sidebar alongside the nav items.
Co-authored-by: openhands <openhands@all-hands.dev>
* feat(settings): add cloud glyph to Cloud settings sidebar link
Render the Cloud settings link with a leading cloud icon (lucide `Cloud`)
next to the "Cloud" label, keeping the trailing external-link icon so
users can tell it opens the hosted page in a new tab. Matches the other
iconified rows in the Settings sidebar.
Update the PR screenshot to show the new two-icon layout.
* feat(settings): place Cloud link below Secrets with nav-row styling
Move the Cloud settings link out of the sidebar footer into the nav
list, directly below the Secrets entry and above the synced-settings
badge, so it sits where users expect a settings sub-page to be.
Restyle the link to match the other sidebar nav rows: reuse the shared
sidebar-layout classes (sidebarNavRowClassName + SIDEBAR_ROW_INTERACTIVE
idle, SIDEBAR_ICON_SLOT_CLASS, sidebarNavLabelClassName) so it has the
same height, padding, border-radius, transparent idle background, and
hover background as Secrets/LLM/etc. Drop the bespoke bordered card.
Still renders a leading cloud glyph, the "Cloud" label, and a trailing
external-link icon.
Apply the same placement in the mobile hub and mobile drawer.
* chore: Remove PR-only artifacts
* docs(settings): simplify CloudSettingsLink JSDoc; add PR screenshots
- Apply reviewer suggestion to trim verbose JSDoc (keep only the
non-obvious note about why local backends are excluded).
- Add the three evidence screenshots referenced in the PR description
to .pr/ so the Video/Screenshots links resolve on the branch.
---------
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: neubig <398875+neubig@users.noreply.github.com>
Co-authored-by: allhands-bot <allhands-bot@users.noreply.github.com>
* fix: recognize subscription llm profiles as configured
Co-authored-by: openhands <openhands@all-hands.dev>
* Add APP-2495 before/after subscription profile screenshots
Evidence for PR #1458 (issue #1488, Linear APP-2495):
- before: main blocks new chat with an active ChatGPT subscription profile (no API key)
- after: PR branch recognizes subscription auth as configured and allows new chat
Co-authored-by: openhands <openhands@all-hands.dev>
* Update APP-2495 screenshots: dismiss onboarding/telemetry modals
Re-captured the before/after home screens with the welcome onboarding
modal (localStorage openhands-onboarded=1) and the @openhands/telemetry
privacy/telemetry consent dialogs dismissed, so the new-chat readiness
state (LLM-not-configured banner vs. ready launcher) is unobstructed.
Co-authored-by: openhands <openhands@all-hands.dev>
* test: cover api_key fast path and no-key non-subscription profile
Address review feedback on PR #1458: add two regression cases to
use-llm-configured.test.tsx:
- API-key profile (api_key_set: true) is configured without fetching
profile detail, proving the fast path stays intact.
- No-key, non-subscription local profile is not configured, guarding
against isSubscriptionLlmConfig returning true for undefined configs.
Co-authored-by: openhands <openhands@all-hands.dev>
* chore: Remove PR-only artifacts
---------
Co-authored-by: neubig <398875+neubig@users.noreply.github.com>
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: allhands-bot <allhands-bot@users.noreply.github.com>
* chore(deps): bump @openhands/typescript-client to 1.27.0
* test: update ACP provider/model fixtures for typescript-client 1.27.0
1.27.0 refreshed the claude-code/codex ACP registry data: provider command
versions (claude-agent-acp 0.30.0->0.44.0, codex-acp 0.15.0->0.16.0),
claude-code model ids (claude-opus-4-8->opus[1m], claude-sonnet-4-6->sonnet,
claude-haiku-4-5->haiku) plus a new well-labeled "default" option, and the
codex default (gpt-5.5/medium->gpt-5.5).
Canvas sources these lists from the client registry (closes#740), so the
source was already correct -- only the hardcoded test expectations were
stale. Also relaxed the acp-providers placeholder guard to accept the SDK's
intentional "Default (recommended)" entry.
* chore(deps): bump agent-server/openhands-sdk to 1.29.0
Align the spawned agent-server SDK release train (openhands-sdk,
openhands-tools, openhands-workspace, openhands-agent-server) with the
version @openhands/typescript-client 1.27.0 is validated against
(agent-server 1.29.0-python). Bump the coupled openhands-automation pin
to 1.0.0a12, whose SDK deps resolve to 1.29.0, to satisfy the
check-sdk-version-sync gate. minimumAgentServer compat floor unchanged.
Doc/JSDoc/test references updated to keep docs-version-sync green.
* feat: stream OpenHands agent responses
Bring the OpenHands agent to feature parity with the ACP agents by
enabling token streaming. The agent-server only emits StreamingDeltaEvents
when an LLM has stream=True, so set it on both the conversation-start
payload and the mid-conversation switch_llm payload.
Split inline <think> reasoning out of streamed and persisted assistant
content into the existing collapsible thinking section so reasoning is no
longer rendered as visible message text.
* fix: only strip a leading, closed <think> reasoning block
User-typed messages were rendered through MarkdownRenderer with raw-HTML
parsing enabled (allowHtml defaults to true), so a message like
`<something>` was parsed as an unknown HTML tag and dropped by
rehype-sanitize — leaving an empty bubble that looked like nothing was sent.
Disable raw-HTML parsing for user messages only: UserMessageBody (settled
messages) and the pending/error branch in ChatMessage. Agent output keeps
allowHtml=true so badges, <details>, <mark>, etc. still render. Adds
regression tests for settled and sending user messages plus an agent-HTML
scoping guard.
* fix(onboarding): remove duplicate bottom padding in modal scroll area
Step footers already apply pb-7, so the scroll area's matching padding
stacked and left too much empty space below the action buttons.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(chat): improve wide markdown tables with horizontal scroll fades
Let tables grow to their natural column width inside a custom-scrollbar
container, round the table itself, and show animated edge gradients when
more content is off-screen. Add a dev/mock table-demo conversation for QA.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(home): restyle LLM setup banner for theme and small screens
Use standard surface/border tokens and rounded-xl so the warning matches
other alerts. Stack content on narrow viewports, keep the CTA on one line,
and restore home-screen side inset at the md breakpoint where parent padding
drops to zero.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(mcp): restore logo badges for extensions 0.6.0 catalog types
IntegrationCatalogEntry no longer exposes logoUrl; render marketplace logos
from INTEGRATION_LOGOS again and align synthetic MCP test fixtures with the
required kind field.
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor(mock): serve table-demo fixture only through MSW
Remove dev-only hook branches that duplicated MSW data and behaved
differently in dev:mock vs build:mock. Static demo events now honor
sort_order in the mock events/search handler like pagination fixtures.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(mcp): use catalog logoUrl instead of unpublished logos export
@openhands/extensions@0.6.0 exposes logoUrl on IntegrationCatalogEntry but
does not publish integrations/logos. Restore the main-branch badge rendering
and drop invalid synthetic kind fields from MCP install modal tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: hieptl <hieptl.developer@gmail.com>
The mock-LLM files-and-git step 3 asserted the control bar shows the
workspace folder basename ("my-app"). But once the local
`git remote get-url origin` probe detects the remote that step 2 adds,
the bar shows the repo slug instead — the folder pill is only the
pre-detection fallback (GitControlBarRepoButton: selectedRepository ||
workspaceName). The assertion raced that probe and failed intermittently.
Accept either the folder basename or the configured remote slug, and
share the slug via a single EXPECTED_REPO_SLUG constant used by both the
trajectory's git remote add and the assertion.
* Show onboarding before public backend auth gate
Co-authored-by: openhands <openhands@all-hands.dev>
* Make backend setup the first onboarding step
Co-authored-by: openhands <openhands@all-hands.dev>
* Restore Cloud backend option in onboarding
Co-authored-by: openhands <openhands@all-hands.dev>
* Make first-run backend onboarding calmer
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: update public onboarding e2e expectation
* fix: cover onboarding-first public auth e2e
* test: keep ProgressEvent polyfill through teardown
* chore: refresh PR checks after QA
* Add lock-to-cloud backend setup mode
Co-authored-by: openhands <openhands@all-hands.dev>
* Hide skip on locked Cloud backend onboarding
Co-authored-by: openhands <openhands@all-hands.dev>
* Remove add-backend onboarding subtitle
Co-authored-by: openhands <openhands@all-hands.dev>
* Skip healthy backend onboarding step
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: support skipped backend step in onboarding e2e
* chore: Remove PR-only artifacts
* fix: address onboarding review nits
* fix: show onboarding for locked cloud first run
* ci: support stacked mock llm runs
* test: assert scoped shell background
* fix: resolve merge conflicts with main (fix-public-onboarding stacking)
- Remove duplicate handleConnected/actionRowClassName/titleKey declarations
in check-backend-step.tsx that resulted from merging the parent PR's
changes on top of our lock-to-cloud additions
- Remove erroneous waitFor(onboarding-backend-connected) steps from the
'shows a connection error' test which uses a no-backend context where
the connection banner is never shown
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: remove unused isLockedToCloud export
All callsites use getLockedCloudHost() !== null directly.
Remove the redundant helper to keep the public API intentional.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: show onboarding first in locked-cloud mode when a session key is present
On PR #1389 Hiep reported that `static-server.mjs --lock-to-cloud ...`
landed on the Manage Backends recovery modal ("Add Backend") instead of
first-run onboarding after a fresh `~/.openhands`.
Root cause: when the build had a baked-in `VITE_SESSION_API_KEY` (or one
was injected via `--session-api-key`), `makeDefaultLocalBackend()` seeded
a Local backend even in locked-to-Cloud mode. That made `isNoBackend()`
false, so `lockedNoBackend` was false and first-run onboarding was
skipped; the subsequent `/server_info` probe failed and `root.tsx`
rendered `MissingAgentServerScreen` (Manage Backends recovery modal).
Fix:
- `makeDefaultLocalBackend()` returns null when `getLockedCloudHost()` is
set, so locked mode never auto-seeds a Local backend.
- `root.tsx` broadens the gate to `lockedNeedsOnboarding`: locked + (no
backend OR active backend is not Cloud) triggers onboarding, covering a
stale persisted Local backend from a previous non-locked session too.
Verified by building with a baked `VITE_SESSION_API_KEY` and serving with
`--lock-to-cloud`: the app now shows the first-run onboarding Cloud-login
screen instead of the recovery modal, and no Local backend is seeded.
Non-locked mode still seeds the Local backend as before.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: locked-cloud onboarding layout + restore CI test mock
CI fix:
- `use-create-conversation-metadata.test.ts` mocks the whole
`agent-server-config` module but was missing `getLockedCloudHost`, which
`makeDefaultLocalBackend()` now imports. Add it (returning null) so the
default local backend seeds and the create-conversation mutation
succeeds again.
Onboarding layout (locked-to-Cloud first-run step):
- Drop the `max-w-sm` cap on the locked CloudLoginColumn so the "Skip the
setup — connect instantly with your OpenHands Cloud account." text fills
the modal content width instead of wrapping in a narrow centered column.
- Add `pb-7` to the onboarding scroll area so the "Login with OpenHands
Cloud" button is no longer flush with / cut off by the modal bottom.
Widening the text (fewer lines) plus the bottom padding together give
the button breathing room.
Co-authored-by: openhands <openhands@all-hands.dev>
* test: add getLockedCloudHost to agent-server-config test mocks
`makeDefaultLocalBackend()` now imports `getLockedCloudHost` from
`agent-server-config`. Two tests that fully mock that module were missing
the export, so the default local backend never seeded and every create-/
read-conversation path threw `NoBackendAvailableError`:
- `agent-server-conversation-service.test.ts` (23 failures on ubuntu CI)
- `use-create-conversation-metadata.test.ts` (already fixed in prev commit)
Add `getLockedCloudHost: vi.fn(() => null)` to both mocks so the non-locked
default-backend seeding path works again.
Co-authored-by: openhands <openhands@all-hands.dev>
* Enhance conversation sidebar with pinned section and grouped organization (#1144)
* Add pinned conversations and reorderable workspace folders to the sidebar.
Persist pins per backend with a capped pinned section, pin-on-hover cards that keep the icon aligned with hover actions via an invisible ellipsis spacer, and drag-and-drop folder ordering stored in panel preferences.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Simplify grouped folder rows for drag and expand.
Drop the grip and chevron controls, remove selection highlight and layout animation, and drag or click the folder label directly while keeping row hover feedback.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Polish folder drag-and-drop and pinned section visuals.
Drag the whole folder (and contents) as the drag image, show an accent drop
line between folders with position-aware reordering, and animate sibling
folders into place only around a reorder. Swap the folder icon to its open or
closed counterpart on hover, add a chronological-view divider plus an outline
pin icon to the pinned section header, and render that header in normal weight.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Add hover metadata popover for sidebar conversations.
Show a modal-styled popover on conversation hover with the full title, status
dot, and repo/branch-or-directory, model, and created-date rows. Reserve the
action overlay width so titles truncate instead of colliding with the pin,
drop the small status tooltip, and gate the popover behind a new "Hover
metadata" toggle in the filter dropdown (persisted, on by default).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Improve folder drag preview and placeholder.
Show a rounded, surfaced drag image anchored to the grab point and blank the
original row (preserving its height) via opacity so Chrome does not cancel the
native drag.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Harden sidebar "Load more" pagination
Dedupe loaded conversations by id and keep fetching pages until the
visible list actually grows, so a single "Load more" click reliably
surfaces new rows despite the 10s background refetch dropping in-flight
fetchNextPage calls or pages yielding zero visible rows. Show the
skeleton throughout. Also drop the native title tooltip on card titles
and record the still-intermittent double-click symptom as a KNOWN ISSUE.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Keep pinned conversations exclusive to the pinned section.
Filter pinned threads out of grouped/chronological lists to prevent duplicates, add regression coverage for both list modes, and add the missing upgrade-button translation key with typed i18n usage.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: failing tests
* fix: lint
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: hieptl <hieptl.developer@gmail.com>
* Fix locked cloud onboarding follow-ups
Co-authored-by: openhands <openhands@all-hands.dev>
* Slow down onboarding follow-up GIFs
Co-authored-by: openhands <openhands@all-hands.dev>
* Skip onboarding when active backend already has a configured LLM
Detect returning users via flat `llm_api_key_set` + `agent_settings.llm.model` (or subscription auth), regardless of backend kind. Locked-Cloud-not-logged-in and stale local backend still fall through to the modal so the existing recovery paths kick in.
Co-authored-by: openhands <openhands@all-hands.dev>
* Scope onboarding skip rule to Cloud backends only
Local agent-servers can be started with an env-injected `LLM_API_KEY`, which makes `llm_api_key_set` an unreliable returning-user signal — Mock-LLM E2E fresh-install tests were tripping on the SDK default model + env key combo. For Local backends the skip stays driven by the existing `openhands-onboarded` localStorage flag; Cloud backends continue to use the settings-based rule.
Co-authored-by: openhands <openhands@all-hands.dev>
* Trigger CI re-run (empty commit)
Workflows didn't fire on 80ea575a — pushing empty commit to nudge the webhook.
Co-authored-by: openhands <openhands@all-hands.dev>
* Always pre-fill onboarding LLM step with OpenAI GPT-5.5 default
The returning-Cloud-user case is now handled at the host level (OnboardingHost skips the whole modal). Users who actually reach the LLM step are first-time installs who want the default pre-filled — restoring the pre-PR-1389 behavior that the onboarding-regressions E2E asserts. Also drops the now-empty unit test that mirrored the old step-level preservation.
Co-authored-by: openhands <openhands@all-hands.dev>
* chore: Update PR QA artifacts
* Generalize onboarding-skip to Local backends with configured LLMs
Hiep flagged that the onboarding modal still walks users through Set Up
your LLM after they connect to a pre-configured backend. Investigation:
* On Cloud, the fast-path keyed off settings.llm_api_key_set + a
non-empty llm.model. That worked.
* On Local, the fast-path bailed early on backend.kind !== 'cloud'.
But the local agent-server reports the exact same readiness signal
via llm_api_key_is_set (and the local settings-service mapper
already remaps that to llm_api_key_set on the way through). The
only reason the skip didn't fire was the explicit kind gate.
Drop the gate, accept either field name, and rename the predicate to
reflect what it actually checks (isBackendLlmReady). A truly fresh
agent-server reports both flags as false, so the modal still shows for
genuine first-run setup.
Tests:
* Updated 'does not skip onboarding for a Local backend' to its
inverse: 'skips for a Local backend with an LLM already configured'.
* Added 'still shows the modal for a fresh Local agent-server with no
API key set' to lock in the fresh-install case.
* All 3328 vitest tests pass; typecheck clean.
Refs Hiep's review comment on PR #1389.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix(onboarding): address Hiep's review on PR #1389 (#1389)
Resolves the three issues Hiep reported on PR #1389:
1. **Choose Agent step gets skipped after Cloud login.** When the
backend slide finished via Cloud login and `skipBackendStep` flipped
true, the slide indices renumbered (agent: 1→0, setup: 2→1). The
user's numeric `currentStep` of 1 — pointing at Choose Agent before
the flip — now pointed at Set Up LLM, and the corrective effect that
decremented it ran a render too late. Track the user's *phase*
("backend" | "agent" | "setup" | "hello") instead of a numeric
step. The visible slide index is derived from phase + slideOrder, so
renumbering can never move the user onto a different logical step.
The previous `wasSkippingBackendStep` ref + decrement effect is
replaced by a single effect that snaps phase forward only when the
current phase is no longer in slideOrder (e.g. "backend" right
after the slide collapsed).
2. **Existing Cloud LLM settings not shown to returning users.** The
skip-onboarding fix from commit 78254e1b already routes returning
users with a configured LLM around the onboarding modal entirely,
so they never hit the Set Up LLM step in the first place. The new
phase-based flow preserves that behavior; no further change needed.
3. **Redundant 'Or' divider** between manual and Cloud columns in
BackendConnectionOptions. Both columns have prominent titles
("OpenHands Cloud" with logo on the right) and a generous gap
already; the explicit divider added visual noise without
information. Remove the divider markup.
Also gitignores local static-server runtime artifacts (workspace/,
build-fresh/) that were getting picked up by 'git add -A'.
Two regression tests cover the standard (non-locked-cloud) flow: one
verifies the user stays on Choose Agent after completing Cloud login
from the side-by-side picker, and one verifies the 'Or' divider is
gone. All 3,241 unit tests pass; lint and typecheck are clean.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: suppress Add Backend modal in locked-to-cloud mode
Resolves hieptl's review feedback on PR #1389: when the static server is
launched with --lock-to-cloud, navigating to the app showed the Manage
Backends recovery modal ("Add Backend") instead of going straight to
Cloud onboarding/login.
Root cause: the `openhands-onboarded` localStorage flag is origin-scoped
and persists across deployments. A user who previously completed
onboarding in a non-locked session on the same origin carries that flag
into a locked-to-Cloud session. The stale flag suppressed
first-run onboarding (`shouldShowFirstRunOnboarding` was gated on
`!onboardingCompleted`), so the app fell through to the
`/server_info` probe. With no usable local backend in locked mode the
probe throws `AgentServerUnavailableError`, and root.tsx renders the
`MissingAgentServerScreen` / `ManageBackendsModal` recovery modal.
Fix: when `lockedNeedsOnboarding` is true, ignore the completion flag
and force first-run onboarding (which owns the Cloud login). The
non-locked path is unchanged — `onboardingCompleted` still suppresses
the modal for returning users with a configured backend.
Also confirms the minor cleanup from the bot review: `isLockedToCloud()`
was already removed in commit addda40e; no remaining references.
Adds a regression test reproducing hieptl's exact scenario (stale
`openhands-onboarded` flag + locked-to-Cloud + no backend) and asserting
the onboarding modal renders instead of the Manage Backends modal.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix(onboarding): don't skip onboarding modal for launcher-seeded backend
PR #1389 generalized the OnboardingHost "returning user with a
configured LLM" skip from Cloud-only to all backends (commit 78254e1b).
That broke the mock-LLM E2E fresh-install / onboarding-happy-path /
onboarding-regressions specs:
tests/e2e/mock-llm/backends/mock-llm-auth-modes.spec.ts:57
"auth mode: fresh install with runtime-injected key ›
reaches the onboarding modal without pre-seeded localStorage"
The mock-LLM E2E stack runs every spec serially against a single
shared agent-server. Earlier specs configure an LLM profile that
persists in the server's settings, so by the time the fresh-install
spec runs (with a clean browser context, no `openhands-onboarded`
flag, and a launcher-seeded default-local backend), the server
reports `llm_api_key_is_set: true` + a non-empty model.
`OnboardingHost.isBackendLlmReady` then returned true, so the host
marked onboarding complete and returned null — the first-run modal
never mounted and the test timed out waiting for
`onboarding-step-choose-agent`. Main is green on the same test
because main's skip was Cloud-only.
The settings-based LLM-ready signal is unreliable for the
launcher-seeded default-local backend: the agent-server can be
started with an env-injected LLM key, and shared-server deployments
retain configured LLMs across browser sessions. Keying first-run
onboarding off the server's LLM state would suppress the modal for
a genuinely fresh browser install.
Fix: keep the skip for Cloud backends and for Local backends the
user explicitly added via "Add Backend" (which carry a non-default
id), but suppress it for the launcher-seeded default-local backend
(`SEEDED_DEFAULT_BACKEND_ID`). First-run detection for that backend
stays driven by the `openhands-onboarded` localStorage flag, matching
main's behavior and restoring the E2E fresh-install contract. The
PR's core intent (suppress the Add Backend recovery modal in
locked-to-Cloud mode, commit 47619f11) is unchanged.
Tests:
* Updated "skips the modal for a Local backend..." to seed a
user-added Local backend (non-default id) so the skip still
fires for the Add-Backend scenario.
* Added "still shows the modal for a launcher-seeded default-local
backend even when the agent-server reports a configured LLM" to
lock in the fresh-install regression.
* All 3332 vitest tests pass; typecheck + lint + build clean.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix(onboarding): don't auto-complete onboarding for launcher-seeded backend
Commit 9029e036 fixed OnboardingHost so the first-run onboarding modal
shows for the launcher-seeded default-local backend even when the
shared mock-LLM agent-server reports a configured LLM. But the same
over-suppression existed in src/root.tsx: a separate
`isBackendLlmReady` check (no default-local exclusion) fed a
`markCompleted()` effect that persisted `openhands-onboarded=1`
whenever the active backend reported a ready LLM — including the
launcher-seeded default-local backend.
That root-level effect was the remaining cause of the
mock-llm-onboarding-regressions.spec.ts:16 failure
("keeps the modal open on backdrop click and Escape"):
* The OnboardingModal already renders with no `onClose` on its
ModalBackdrop, so backdrop clicks and Escape are no-ops — the
modal itself was never closeable that way.
* The test failure was actually the `expect.poll` asserting
`openhands-onboarded` stays null: root.tsx's `markCompleted`
effect fired (agent-server had a configured LLM from earlier
serial specs) and persisted completion, even though the modal
stayed mounted.
Fix: apply the same `SEEDED_DEFAULT_BACKEND_ID` exclusion to
root.tsx's `isBackendLlmReady` that OnboardingHost already uses.
The settings-based LLM-ready signal is unreliable for the
launcher-seeded default backend (env-injected keys, shared-server
LLM persistence across browser sessions), so first-run detection
there stays driven by the `openhands-onboarded` localStorage flag.
The skip still fires for Cloud backends and for Local backends the
user explicitly added via "Add Backend" (non-default id).
The OnboardingModal's non-dismissible backdrop/Escape behavior is
unchanged and already correct (ModalBackdrop receives no `onClose`,
so `closeOnEscape`/`closeOnBackdropClick` default-true handlers
call `onClose?.()` which is a no-op).
Tests:
* Added root.test.tsx case "does not mark onboarding complete for
the launcher-seeded default-local backend even when the
agent-server reports a configured LLM" — verified it fails
without the root.tsx fix and passes with it.
* All 3333 vitest tests pass; typecheck + lint + build clean.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: force Cloud replacement for stale Local backend in locked mode
Critical fixes for the locked-to-Cloud flow (PR #1389 review):
1. root.tsx: the ready-backend fast-path in locked mode now requires the
active backend to match the locked Cloud host (normalized via the new
isSameCloudHost helper), not just . A reachable stale
Local backend (or a Cloud backend on a different host) that reports a
configured LLM no longer bypasses the Cloud login/replacement flow.
The markCompleted effect is also guarded so it only persists completion
for the legitimate locked Cloud host.
2. onboarding-modal.tsx: in locked mode, CheckBackendStep is only skipped
when the active backend IS the locked Cloud host. A reachable stale
Local backend keeps the backend slide visible so Cloud login can
replace it.
Also addresses minor review suggestions:
- LOCK_TO_CLOUD_WINDOW_KEY is now module-private (only getLockedCloudHost
reads it; static-server.mjs/tests use the literal string).
- Extract shared isBackendLlmReady helper into its own module
(is-backend-llm-ready.ts) so root.tsx and OnboardingHost stay in sync
without duplicating the rule and without pulling the onboarding modal
graph into root's eager bundle.
- Inline the no-op initialValueOverrides intermediate in setup-llm-step.
Adds regression tests for the stale-Local-backend and other-Cloud-host
scenarios in both root.test.tsx and onboarding-modal.test.tsx.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix(onboarding): close stale-backend lock-to-Cloud bypass in CheckBackendStep (#1389)
PR-review bot pointed out (HEAD 55d382be) that keeping the backend
slide visible for a non-matching backend in locked mode is insufficient:
CheckBackendStep itself still hits its connected-backend shortcut for
a reachable stale Local backend, hiding the Cloud login UI and showing
a Next button that lets the user continue as Local.
Apply the same host-match guard inside CheckBackendStep. A new local
`treatAsNoBackend` (= noBackendSelected || lockedCloudHostMismatch)
drives:
- title: ONBOARDING$LOGIN_TO_CLOUD_TITLE (not BACKEND_TITLE)
- render: BackendConnectionOptions (Cloud login UI), no ConnectionBanner
- no "Show configuration" toggle and no Next-shortcut action row
`noBackendSelected` still governs whether handleConnected calls
`addBackend` or `updateBackend`, so the stale backend is replaced
rather than duplicated.
Strengthen the regression test the bot flagged: it now asserts the
Cloud login title and login button are visible, and that the
`onboarding-backend-show-configuration` toggle, `onboarding-backend-next`
button, and the (misleading) Connected subtitle are all absent.
All 3,249 unit tests pass; lint and typecheck are clean.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix(onboarding): clear stale active org_id when replacing a Cloud backend host (#1389)
PR-review bot raised one remaining state carry-over: replacing a
mismatched Cloud backend updates its host/apiKey via `updateBackend`,
but the persisted `active.orgId` (X-Org-Id) is keyed to the OLD
host's org list. The newly-locked Cloud backend would keep sending an
invalid `X-Org-Id` until the user manually re-picked an org.
Fix in CheckBackendStep.handleConnected: when the submitted payload's
host differs from the previously-active backend's host, call
`setActive(backend.id, null)` to drop the now-invalid org selection.
The user re-picks an org on the new host via the usual org switcher.
Local-only edits are unaffected because Local backends always carry
`active.orgId === null`, so the conditional is a no-op there.
New regression test seeds a Cloud backend at other-cloud.example.com
with `orgId="stale-org-from-other-host"`, drives the Cloud login
button, and asserts `getActiveSelection().orgId === null` while the
backend row is updated in place (same id).
All 3,250 unit tests pass; lint and typecheck are clean.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix(onboarding): dismiss modal immediately after Cloud login in locked mode (#1389)
Resolves the flicker hieptl reported on PR #1389: after logging into
OpenHands Cloud in locked-to-Cloud mode, the onboarding modal advanced
to the Choose Agent slide (the "next window"), then got torn down by
the root first-run gate, then briefly remounted via OnboardingHost —
appearing to flash in and out.
Cloud login IS the onboarding completion in locked mode, so:
- CheckBackendStep now calls onClose (dismiss) instead of onNext when
a Cloud login succeeds in locked-to-Cloud mode, so the next slide
never shows. Standard (non-locked) mode still walks the user through
agent/LLM setup via onNext.
- root.tsx's locked-mode first-run gate now treats onboardingCompleted
as authoritative once the active backend IS the locked Cloud host,
so the first-run screen hides immediately on login (without waiting
for the Cloud settings probe to confirm a configured LLM). The flag
is still ignored when the active backend is not the locked Cloud
host, preserving the stale-flag bypass protection.
Added failing tests (now passing) reproducing both halves of the flicker:
- onboarding-modal: Cloud login in locked mode calls onClose, not onNext.
- root: the first-run screen hides immediately after Cloud login
completes (post-login state with no configured LLM), instead of
reopening via OnboardingHost.
Co-authored-by: openhands <openhands@all-hands.dev>
* chore: Remove PR-only artifacts
* ci: revert docker.yml pull_request branch filter change
Reverts the removal of `branches: [main]` from the `pull_request`
trigger in .github/workflows/docker.yml (introduced in 5bb8049f). That
change is unrelated to the locked-to-Cloud onboarding work on this PR
and is out of scope. Restores the file to match main exactly so the
Docker workflow again only runs on PRs targeting `main`.
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: Graham Neubig <gneubig@users.noreply.github.com>
Co-authored-by: neubig <398875+neubig@users.noreply.github.com>
Co-authored-by: allhands-bot <allhands-bot@users.noreply.github.com>
Co-authored-by: hieptl <hieptl.developer@gmail.com>
Co-authored-by: FraterCCCLXIII <panentheum@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix for console warning
* test: make UserMessageBody truncation regression test detect the bug
The previous test could not fail against the buggy implementation:
the render-phase update warning is never emitted under the no-op
ResizeObserver mock, and the matcher checked the already-substituted
string while React passes a %s format string with the component names
as separate args.
Reproduce the warning deterministically by firing the ResizeObserver
callback and changing the measured height between measurements, which
defeats React's eager-state bailout so the render-phase parent update
runs. Resolve console.error args with util.format before matching, and
guard that truncation is measured more than once.
---------
Co-authored-by: Devin <devinvinson@gmail.com>
Co-authored-by: hieptl <hieptl.developer@gmail.com>
* Preserve encrypted MCP credentials for ACP
* Cover OpenHands encrypted MCP start payload
* chore: address PR review feedback (#1426)
Resolve stored stdio MCP credentials by the editor-assigned positional id
(`stdio-<i>`) instead of the display name, so renaming a stdio server no
longer loses its stored encrypted env. Previously the rename changed the
dict-key lookup, missed the stored entry, and saved the literal `<redacted>`
placeholder over the encrypted secret.
Add regression tests for the rename case (direct unit test + save-flow test
asserting the saved SDK config contains the encrypted value, not the
placeholder).
---------
Co-authored-by: neubig <398875+neubig@users.noreply.github.com>
Co-authored-by: openhands <openhands@all-hands.dev>
* Add pinned conversations and reorderable workspace folders to the sidebar.
Persist pins per backend with a capped pinned section, pin-on-hover cards that keep the icon aligned with hover actions via an invisible ellipsis spacer, and drag-and-drop folder ordering stored in panel preferences.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Simplify grouped folder rows for drag and expand.
Drop the grip and chevron controls, remove selection highlight and layout animation, and drag or click the folder label directly while keeping row hover feedback.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Polish folder drag-and-drop and pinned section visuals.
Drag the whole folder (and contents) as the drag image, show an accent drop
line between folders with position-aware reordering, and animate sibling
folders into place only around a reorder. Swap the folder icon to its open or
closed counterpart on hover, add a chronological-view divider plus an outline
pin icon to the pinned section header, and render that header in normal weight.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Add hover metadata popover for sidebar conversations.
Show a modal-styled popover on conversation hover with the full title, status
dot, and repo/branch-or-directory, model, and created-date rows. Reserve the
action overlay width so titles truncate instead of colliding with the pin,
drop the small status tooltip, and gate the popover behind a new "Hover
metadata" toggle in the filter dropdown (persisted, on by default).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Improve folder drag preview and placeholder.
Show a rounded, surfaced drag image anchored to the grab point and blank the
original row (preserving its height) via opacity so Chrome does not cancel the
native drag.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Harden sidebar "Load more" pagination
Dedupe loaded conversations by id and keep fetching pages until the
visible list actually grows, so a single "Load more" click reliably
surfaces new rows despite the 10s background refetch dropping in-flight
fetchNextPage calls or pages yielding zero visible rows. Show the
skeleton throughout. Also drop the native title tooltip on card titles
and record the still-intermittent double-click symptom as a KNOWN ISSUE.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Keep pinned conversations exclusive to the pinned section.
Filter pinned threads out of grouped/chronological lists to prevent duplicates, add regression coverage for both list modes, and add the missing upgrade-button translation key with typed i18n usage.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: failing tests
* fix: lint
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: hieptl <hieptl.developer@gmail.com>
The SDK now emits ConversationErrorEvents with a meaningful `code`
(ACPAuthRequired / ACPSpawnError / ACPInitError / ACPPromptError /
UsagePolicyRefusal) and a rich `detail`, but the canvas banner discarded the
code (analytics only) and showed `detail` alone. So a credential failure looked
the same as any other error, with no recovery path.
Thread `code` through the error-message store to the banner:
- Show a localized header for known ACP codes (auth failures get
"Authentication required"; the rest reuse the generic "Agent error" title).
The rich detail still renders verbatim below it.
- For credential failures (ACPAuthRequired), show an "Update credentials" action
that deep-links to Settings → Agent (/settings/agent).
Pairs with software-agent-sdk #3756 (which produces the code + detail). Adds a
small `acp-error-codes` mapping util, two i18n keys (all 15 locales), and tests
for the util, store, and banner.
Co-authored-by: Debug Agent <simon@openhands.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(acp): surface the real claude-agent-acp "default" model
claude-agent-acp 0.44+ exposes `default` ("Default (recommended)") as a
real, selectable model in its configOptions select — the server reports it
as `currentModelId` and lists it in `availableModels`. But realAcpModel()
treated "default"/"default (recommended)" as SDK placeholders and returned
null, so the conversation chip showed no model for a session genuinely
running on `default`.
Drop the ACP_DEFAULT_PLACEHOLDERS set (keep the legacy `acp-managed`
sentinel) and add a regression test for resolveEffectiveAcpModel.
Verified against a live agent-server: current_model_id="default" and
available_models includes `default`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(acp): align toAppConversation tests with new "default" model behavior
The PR removes the ACP_DEFAULT_PLACEHOLDERS filter so that claude-agent-acp
0.44+'s real "default" / "Default (recommended)" model id surfaces like
any other model. Update three toAppConversation tests that still asserted
the old null-filtering behavior.
Co-authored-by: openhands <openhands@all-hands.dev>
* docs(acp): fix stale chip-path comment about `default`
`default` is now a real model, so the old "would lie about what's running"
note no longer applies. Clarify that omitting providerDefault lets the chip
fall back to the provider display name when no concrete model resolves.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Debug Agent <simon@openhands.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: openhands <openhands@all-hands.dev>
Enter-to-send was gated on isMobileDevice(), which returns true for any
touch device whose primary pointer is coarse. On Windows touchscreen
laptops / 2-in-1s the browser reports the touchscreen as the primary
pointer ((pointer: fine) is false), so Enter inserted a newline instead
of sending.
Add isMobileUserAgent() (user-agent only) and gate Enter-to-send on it,
so any desktop OS with a touchscreen submits on Enter. isMobileDevice()
is unchanged for its touch-aware callers.
* fix: seed AUTOMATION_KV_SECRET for local dev
The KV store (automation PR#69) requires AUTOMATION_KV_SECRET to be set
or every KV endpoint returns 503. In a local dev stack the secret is never
configured, so the store is silently unavailable.
Fall back to sessionApiKey when the env var is not set explicitly — the
same zero-config pattern already used for AUTOMATION_AGENT_SERVER_URL,
AUTOMATION_BASE_URL, and AUTOMATION_WORKSPACE_BASE.
Co-authored-by: openhands <openhands@all-hands.dev>
* chore: bump openhands-automation to 1.0.0a10
Update to the latest released version of openhands-automation on PyPI.
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: openhands <openhands@all-hands.dev>
* feat(mcp): give SSE/shttp MCP servers a user-given name
SSE/shttp servers were serialized under auto-generated dict keys
("sse", "shttp", "shttp_1"), making them unreferenceable by name in
AgentProfile mcp_server_refs. Surface an optional "Server name" field
so they get a stable, user-meaningful key — consistent with stdio.
- Add name? to MCPSSEServer / MCPSHTTPServer
- toSdkMcpConfig: reserve(entry.name || "sse"/"shttp") so the name
becomes the dict key; reserve() still de-dups collisions
- parseMcpConfig: round-trip user-given names, but treat auto-generated
keys as nameless so existing configs re-serialize unchanged
- Plumb name through the add/update mutations and flattenMcpConfig
- Add optional "Server name" input to the SSE/shttp editor form
Part of #3726 (AgentProfile epic #3713).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(mcp): name marketplace remote installs after the catalog slug
The marketplace install path builds its own payload, separate from the
custom editor. Stdio entries already carry serverName, but remote
(sse/shttp) installs set no name — so GitHub, Linear, etc. landed under
the auto-generated "sse"/"shttp" key and were unreferenceable in
mcp_server_refs, the same gap the custom-editor fix addressed.
Set name: entry.id on the remote-install payload so e.g. GitHub keys as
"github". reserve() still de-dups repeat installs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(e2e): expect GitHub MCP install under the "github" key
The marketplace remote-install path now names servers after the catalog
slug, so a GitHub install persists under mcpServers.github (referenceable
in mcp_server_refs) rather than the auto-generated "shttp" fallback.
Update the install/delete e2e assertions accordingly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(mcp): validate the optional SSE/shttp server name as a safe key
The Server name we added for SSE/shttp becomes the mcp_config dict key
(and the mcp_server_refs reference), but the form only validated stdio
names. An sse/shttp name with spaces/special chars would produce a
malformed key. Apply the same ^[a-zA-Z0-9_-]+$ rule stdio uses, while
keeping the field optional.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Debug Agent <simon@openhands.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Increase secrets table max-height to show up to 12 rows
Raises the scrollable container cap from min(40vh,22rem) to
min(70vh,39rem). At 48px per row (h-12) + 48px sticky header,
39rem (624px) fits exactly 12 data rows before the scrollbar
appears. The max-h constraint means the container still
shrinks naturally when there are fewer than 12 rows.
Co-authored-by: openhands <openhands@all-hands.dev>
* Remove scroll cap and sticky header from secrets table
The secrets table had a max-h constraint (overflow-auto) that triggered
a scrollbar after 12 rows, plus a sticky thead to compensate. Both were
specific to this file. Since the server API returns all secrets in one
response and the columns are simple (name, description, actions), a
plain full-height table is clearer.
- Replace settingsListScrollContainerClassName with
settingsListContainerClassName on the outer div
- Inline border-b on <thead> instead of the sticky-specific class
- Remove onScroll handler, tableContainerRef, handleScroll callback,
and isFetchingNextPage spinner (all scroll-only code)
The shared scroll/sticky classes in settings-list-classes.ts are
untouched for use by other tables.
Co-authored-by: openhands <openhands@all-hands.dev>
* Revert secrets-settings.tsx and settings-list-classes.ts to main
* Increase secrets table max-height to show up to 12 rows
Raises the scrollable container cap from min(40vh,22rem) to
min(70vh,39rem). At 48px per row (h-12) + 48px sticky header,
39rem (624px) fits exactly 12 data rows before the scrollbar
appears. The max-h constraint means the container still
shrinks naturally when there are fewer than 12 rows.
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: openhands <openhands@all-hands.dev>