fix(app): make Add Server form fields editable and persist credentials
Some checks are pending
deploy / deploy (push) Waiting to run
generate / generate (push) Waiting to run
nix-eval / nix-eval (push) Waiting to run
publish / version (push) Waiting to run
publish / build-cli (push) Blocked by required conditions
publish / sign-cli-windows (push) Blocked by required conditions
publish / build-electron (map[bun_install_flags:--os=darwin --cpu=arm64 host:macos-26 platform_flag:--mac --arm64 target:aarch64-apple-darwin]) (push) Blocked by required conditions
publish / build-electron (map[bun_install_flags:--os=darwin --cpu=x64 host:macos-26-intel platform_flag:--mac --x64 target:x86_64-apple-darwin]) (push) Blocked by required conditions
publish / build-electron (map[host:blacksmith-4vcpu-ubuntu-2404 platform_flag:--linux target:x86_64-unknown-linux-gnu]) (push) Blocked by required conditions
publish / build-electron (map[host:blacksmith-4vcpu-ubuntu-2404-arm platform_flag:--linux --arm64 target:aarch64-unknown-linux-gnu]) (push) Blocked by required conditions
publish / build-electron (map[host:blacksmith-4vcpu-windows-2025 platform_flag:--win target:x86_64-pc-windows-msvc]) (push) Blocked by required conditions
publish / build-electron (map[host:windows-2025 platform_flag:--win --arm64 target:aarch64-pc-windows-msvc]) (push) Blocked by required conditions
publish / publish (push) Blocked by required conditions
test / unit (linux) (push) Waiting to run
test / unit (windows) (push) Waiting to run
test / e2e (linux) (push) Waiting to run
test / e2e (windows) (push) Waiting to run
typecheck / typecheck (push) Waiting to run
Some checks are pending
deploy / deploy (push) Waiting to run
generate / generate (push) Waiting to run
nix-eval / nix-eval (push) Waiting to run
publish / version (push) Waiting to run
publish / build-cli (push) Blocked by required conditions
publish / sign-cli-windows (push) Blocked by required conditions
publish / build-electron (map[bun_install_flags:--os=darwin --cpu=arm64 host:macos-26 platform_flag:--mac --arm64 target:aarch64-apple-darwin]) (push) Blocked by required conditions
publish / build-electron (map[bun_install_flags:--os=darwin --cpu=x64 host:macos-26-intel platform_flag:--mac --x64 target:x86_64-apple-darwin]) (push) Blocked by required conditions
publish / build-electron (map[host:blacksmith-4vcpu-ubuntu-2404 platform_flag:--linux target:x86_64-unknown-linux-gnu]) (push) Blocked by required conditions
publish / build-electron (map[host:blacksmith-4vcpu-ubuntu-2404-arm platform_flag:--linux --arm64 target:aarch64-unknown-linux-gnu]) (push) Blocked by required conditions
publish / build-electron (map[host:blacksmith-4vcpu-windows-2025 platform_flag:--win target:x86_64-pc-windows-msvc]) (push) Blocked by required conditions
publish / build-electron (map[host:windows-2025 platform_flag:--win --arm64 target:aarch64-pc-windows-msvc]) (push) Blocked by required conditions
publish / publish (push) Blocked by required conditions
test / unit (linux) (push) Waiting to run
test / unit (windows) (push) Waiting to run
test / e2e (linux) (push) Waiting to run
test / e2e (windows) (push) Waiting to run
typecheck / typecheck (push) Waiting to run
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
74372f6840
commit
c7e53a1958
3 changed files with 42 additions and 34 deletions
24
CHANGELOGS/2026-08-25-add-server-form.md
Normal file
24
CHANGELOGS/2026-08-25-add-server-form.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# 2026-08-25 — Fix Add Server form field entry and save
|
||||
|
||||
## Summary
|
||||
|
||||
Fixed the Settings Add/Edit Server form so name, username, and password can be typed and persisted on save.
|
||||
|
||||
## Changes
|
||||
|
||||
### `packages/app/src/components/settings-v2/dialog-server-v2.tsx`
|
||||
|
||||
- Initialize add/edit form state during setup (before first paint) so uncontrolled defaults apply correctly.
|
||||
- Keep URL controlled for validation/preview; make name, username, and password write-once initial values (plain strings, not reactive) so typed characters are not rolled back on Windows/Electron.
|
||||
- Stop disabling text fields while the health check runs; only the submit button stays disabled.
|
||||
- Allow Cancel during a pending check.
|
||||
|
||||
### `packages/app/src/components/dialog-select-server.tsx`
|
||||
|
||||
- Persist username independently of password on add (aligned with edit), so credentials are not dropped when only one is set.
|
||||
- Remove `isPending` early-returns from field change handlers so input continues updating the store during health checks.
|
||||
- Keep optional legacy `ServerForm` fields enabled while checking; only the submit button is disabled.
|
||||
|
||||
## Verification
|
||||
|
||||
- `bun typecheck` in `packages/app`
|
||||
|
|
@ -29,7 +29,6 @@ interface ServerFormProps {
|
|||
username: string
|
||||
password: string
|
||||
placeholder: string
|
||||
busy: boolean
|
||||
error: string
|
||||
status: boolean | undefined
|
||||
onChange: (value: string) => void
|
||||
|
|
@ -136,7 +135,6 @@ function ServerForm(props: ServerFormProps) {
|
|||
autofocus
|
||||
validationState={props.error ? "invalid" : "valid"}
|
||||
error={props.error}
|
||||
disabled={props.busy}
|
||||
onChange={props.onChange}
|
||||
onKeyDown={keyDown}
|
||||
/>
|
||||
|
|
@ -146,7 +144,6 @@ function ServerForm(props: ServerFormProps) {
|
|||
label={language.t("dialog.server.add.name")}
|
||||
placeholder={language.t("dialog.server.add.namePlaceholder")}
|
||||
defaultValue={props.name}
|
||||
disabled={props.busy}
|
||||
onChange={props.onNameChange}
|
||||
onKeyDown={keyDown}
|
||||
/>
|
||||
|
|
@ -156,7 +153,6 @@ function ServerForm(props: ServerFormProps) {
|
|||
label={language.t("dialog.server.add.username")}
|
||||
placeholder={language.t("dialog.server.add.usernamePlaceholder")}
|
||||
defaultValue={props.username}
|
||||
disabled={props.busy}
|
||||
onChange={props.onUsernameChange}
|
||||
onKeyDown={keyDown}
|
||||
/>
|
||||
|
|
@ -165,7 +161,6 @@ function ServerForm(props: ServerFormProps) {
|
|||
label={language.t("dialog.server.add.password")}
|
||||
placeholder={language.t("dialog.server.add.passwordPlaceholder")}
|
||||
defaultValue={props.password}
|
||||
disabled={props.busy}
|
||||
onChange={props.onPasswordChange}
|
||||
onKeyDown={keyDown}
|
||||
/>
|
||||
|
|
@ -252,13 +247,14 @@ export function useServerManagementController(options: { onSelect?: () => void;
|
|||
return
|
||||
}
|
||||
|
||||
const name = store.addServer.name.trim() || undefined
|
||||
const username = store.addServer.username || undefined
|
||||
const password = store.addServer.password || undefined
|
||||
const conn: ServerConnection.Http = {
|
||||
type: "http",
|
||||
http: { url: normalized },
|
||||
displayName: name,
|
||||
http: { url: normalized, username, password },
|
||||
}
|
||||
if (store.addServer.name.trim()) conn.displayName = store.addServer.name.trim()
|
||||
if (store.addServer.password) conn.http.password = store.addServer.password
|
||||
if (store.addServer.password && store.addServer.username) conn.http.username = store.addServer.username
|
||||
const result = await checkServerHealth(conn.http)
|
||||
if (!result.healthy) {
|
||||
setStore("addServer", { error: language.t("dialog.server.add.error") })
|
||||
|
|
@ -394,7 +390,6 @@ export function useServerManagementController(options: { onSelect?: () => void;
|
|||
}
|
||||
|
||||
const handleAddChange = (value: string) => {
|
||||
if (addMutation.isPending) return
|
||||
setStore("addServer", { url: value, error: "" })
|
||||
void previewStatus(value, store.addServer.username, store.addServer.password, (next) =>
|
||||
setStore("addServer", { status: next }),
|
||||
|
|
@ -402,12 +397,10 @@ export function useServerManagementController(options: { onSelect?: () => void;
|
|||
}
|
||||
|
||||
const handleAddNameChange = (value: string) => {
|
||||
if (addMutation.isPending) return
|
||||
setStore("addServer", { name: value, error: "" })
|
||||
}
|
||||
|
||||
const handleAddUsernameChange = (value: string) => {
|
||||
if (addMutation.isPending) return
|
||||
setStore("addServer", { username: value, error: "" })
|
||||
void previewStatus(store.addServer.url, value, store.addServer.password, (next) =>
|
||||
setStore("addServer", { status: next }),
|
||||
|
|
@ -415,7 +408,6 @@ export function useServerManagementController(options: { onSelect?: () => void;
|
|||
}
|
||||
|
||||
const handleAddPasswordChange = (value: string) => {
|
||||
if (addMutation.isPending) return
|
||||
setStore("addServer", { password: value, error: "" })
|
||||
void previewStatus(store.addServer.url, store.addServer.username, value, (next) =>
|
||||
setStore("addServer", { status: next }),
|
||||
|
|
@ -423,7 +415,6 @@ export function useServerManagementController(options: { onSelect?: () => void;
|
|||
}
|
||||
|
||||
const handleEditChange = (value: string) => {
|
||||
if (editMutation.isPending) return
|
||||
setStore("editServer", { value, error: "" })
|
||||
void previewStatus(value, store.editServer.username, store.editServer.password, (next) =>
|
||||
setStore("editServer", { status: next }),
|
||||
|
|
@ -431,12 +422,10 @@ export function useServerManagementController(options: { onSelect?: () => void;
|
|||
}
|
||||
|
||||
const handleEditNameChange = (value: string) => {
|
||||
if (editMutation.isPending) return
|
||||
setStore("editServer", { name: value, error: "" })
|
||||
}
|
||||
|
||||
const handleEditUsernameChange = (value: string) => {
|
||||
if (editMutation.isPending) return
|
||||
setStore("editServer", { username: value, error: "" })
|
||||
void previewStatus(store.editServer.value, value, store.editServer.password, (next) =>
|
||||
setStore("editServer", { status: next }),
|
||||
|
|
@ -444,7 +433,6 @@ export function useServerManagementController(options: { onSelect?: () => void;
|
|||
}
|
||||
|
||||
const handleEditPasswordChange = (value: string) => {
|
||||
if (editMutation.isPending) return
|
||||
setStore("editServer", { password: value, error: "" })
|
||||
void previewStatus(store.editServer.value, store.editServer.username, value, (next) =>
|
||||
setStore("editServer", { status: next }),
|
||||
|
|
@ -692,7 +680,6 @@ export function ServerConnectionForm(props: { controller: ReturnType<typeof useS
|
|||
username={props.controller.formUsername()}
|
||||
password={props.controller.formPassword()}
|
||||
placeholder={language.t("dialog.server.add.placeholder")}
|
||||
busy={props.controller.formBusy()}
|
||||
error={props.controller.formError()}
|
||||
status={props.controller.formStatus()}
|
||||
onChange={props.controller.handleFormChange()}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Dialog, DialogBody, DialogFooter, DialogHeader, DialogTitle } from "@op
|
|||
import { DividerV2 } from "@opencode-ai/ui/v2/divider-v2"
|
||||
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { type Component, Show, createEffect, createSignal, onCleanup, onMount } from "solid-js"
|
||||
import { type Component, Show, createEffect, onCleanup } from "solid-js"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { type ServerConnection } from "@/context/server"
|
||||
import { useServerManagementController } from "../dialog-select-server"
|
||||
|
|
@ -19,20 +19,21 @@ export const DialogServerV2: Component<{
|
|||
onSelect: () => dialog.close(),
|
||||
navigateOnAdd: false,
|
||||
})
|
||||
const [opened, setOpened] = createSignal(false)
|
||||
|
||||
onMount(() => {
|
||||
if (props.mode === "add") controller.startAdd()
|
||||
if (props.mode === "edit" && props.server) controller.startEdit(props.server)
|
||||
setOpened(true)
|
||||
})
|
||||
// Initialize before first paint so optional-field initial values stick.
|
||||
if (props.mode === "add") controller.startAdd()
|
||||
if (props.mode === "edit" && props.server) controller.startEdit(props.server)
|
||||
|
||||
// Plain strings (not accessors) so Solid treats these as initial values, not controlled.
|
||||
const initialName = controller.formName()
|
||||
const initialUsername = controller.formUsername()
|
||||
const initialPassword = controller.formPassword()
|
||||
|
||||
onCleanup(() => {
|
||||
controller.resetForm()
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (!opened()) return
|
||||
if (controller.isFormMode()) return
|
||||
dialog.close()
|
||||
})
|
||||
|
|
@ -69,7 +70,6 @@ export const DialogServerV2: Component<{
|
|||
value={controller.formValue()}
|
||||
placeholder={language.t("dialog.server.add.placeholder")}
|
||||
invalid={!!controller.formError()}
|
||||
disabled={controller.formBusy()}
|
||||
autofocus
|
||||
onInput={(event) => controller.handleFormChange()(event.currentTarget.value)}
|
||||
onKeyDown={keyDown}
|
||||
|
|
@ -84,9 +84,8 @@ export const DialogServerV2: Component<{
|
|||
type="text"
|
||||
appearance="large"
|
||||
class="!w-full self-stretch"
|
||||
value={controller.formName()}
|
||||
value={initialName}
|
||||
placeholder={language.t("dialog.server.add.namePlaceholder")}
|
||||
disabled={controller.formBusy()}
|
||||
onInput={(event) => controller.handleFormNameChange()(event.currentTarget.value)}
|
||||
onKeyDown={keyDown}
|
||||
/>
|
||||
|
|
@ -98,9 +97,8 @@ export const DialogServerV2: Component<{
|
|||
type="text"
|
||||
appearance="large"
|
||||
class="!w-full self-stretch"
|
||||
value={controller.formUsername()}
|
||||
value={initialUsername}
|
||||
placeholder={language.t("dialog.server.add.usernamePlaceholder")}
|
||||
disabled={controller.formBusy()}
|
||||
onInput={(event) => controller.handleFormUsernameChange()(event.currentTarget.value)}
|
||||
onKeyDown={keyDown}
|
||||
/>
|
||||
|
|
@ -111,9 +109,8 @@ export const DialogServerV2: Component<{
|
|||
type="password"
|
||||
appearance="large"
|
||||
class="!w-full self-stretch"
|
||||
value={controller.formPassword()}
|
||||
value={initialPassword}
|
||||
placeholder={language.t("dialog.server.add.passwordPlaceholder")}
|
||||
disabled={controller.formBusy()}
|
||||
onInput={(event) => controller.handleFormPasswordChange()(event.currentTarget.value)}
|
||||
onKeyDown={keyDown}
|
||||
/>
|
||||
|
|
@ -122,7 +119,7 @@ export const DialogServerV2: Component<{
|
|||
</div>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<ButtonV2 variant="neutral" disabled={controller.formBusy()} onClick={() => dialog.close()}>
|
||||
<ButtonV2 variant="neutral" onClick={() => dialog.close()}>
|
||||
{language.t("common.cancel")}
|
||||
</ButtonV2>
|
||||
<ButtonV2 variant="contrast" disabled={controller.formBusy()} onClick={controller.submitForm}>
|
||||
|
|
|
|||
Loading…
Reference in a new issue