fix(web): keep browser autofill out of config forms; drop the closed panel's stray divider (#114)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yaowei Zheng
2026-07-29 16:13:13 +08:00
committed by GitHub
parent f865343c90
commit 5530b7ffad
8 changed files with 146 additions and 9 deletions
@@ -65,6 +65,7 @@ import { resolveContextWindow } from "../../lib/context";
import { useLocale } from "../../state/locale";
import { Dropdown } from "../../components/ui/dropdown";
import { GlyphIcon } from "../../components/ui/glyph-icon";
import { noAutofill } from "../../components/ui/input";
import { SkillIcon } from "../skills/skill-icon-view";
import { ZoomableImage } from "../../components/ui/image-zoom";
import { ProviderLogo } from "../../components/ui/provider-logo";
@@ -280,6 +281,7 @@ function ModelMenuList({
onChange={(e) => setQuery(e.target.value)}
placeholder={S.models.searchPlaceholder}
aria-label={S.models.searchPlaceholder}
{...noAutofill}
className="w-full rounded border border-transparent bg-transparent px-1 py-0.5 text-xs text-gray-700 placeholder:text-gray-400 focus:outline-none dark:text-gray-200 dark:placeholder:text-gray-500"
/>
</div>
@@ -680,6 +682,7 @@ function SkillSelect({
onChange={(e) => setQuery(e.target.value)}
placeholder={S.chat.skillsSearchPlaceholder}
aria-label={S.chat.skillsSearchPlaceholder}
{...noAutofill}
className="w-full rounded border border-transparent bg-transparent px-1 py-0.5 text-xs text-gray-700 placeholder:text-gray-400 focus:outline-none dark:text-gray-200 dark:placeholder:text-gray-500"
/>
</div>
@@ -2022,6 +2025,7 @@ export function ChatInput({
placeholder={S.chat.goalBudgetPlaceholder}
aria-invalid={goalBudgetDraftInvalid}
aria-describedby="goal-budget-hint"
{...noAutofill}
title={
goalBudgetDraftInvalid ? S.chat.goalBudgetInvalid : S.chat.goalBudgetHint
}
@@ -49,6 +49,7 @@ import { useSessions } from "../../state/sessions";
import { AgentAvatar } from "../../components/ui/agent-avatar";
import { Chevron } from "../../components/ui/chevron";
import { Dropdown } from "../../components/ui/dropdown";
import { noAutofill } from "../../components/ui/input";
import { PenguinLogo } from "../../components/ui/penguin-logo";
import { toastError } from "../../components/ui/toast";
import { useVersionInfo } from "../../lib/use-version-info";
@@ -967,6 +968,7 @@ function WorkspaceSelect({
value={pathDraft}
placeholder="…"
aria-label={S.chat.workspace}
{...noAutofill}
onChange={(e) => setPathDraft(e.target.value)}
onBlur={() => void commitPathEdit()}
onKeyDown={(e) => {
@@ -74,9 +74,15 @@ export function FilesPanel({ session, panel }: { session: SessionInfo; panel: Fi
// input) anchored to the nearest initial containing block instead, it would bypass this
// overflow-hidden and stretch the **document** wide, making a horizontal scrollbar
// appear out of nowhere.
className={`relative flex min-h-0 shrink-0 flex-col overflow-hidden border-l border-gray-200 dark:border-gray-800 ${
panel.resizing ? "pointer-events-none" : "transition-[width] duration-200"
}`}
//
// The divider belongs to the OPEN state only: with border-box sizing a closed panel's
// border still paints its 1px even at width 0, and since both docked panels stay
// mounted, the closed one left a stray line beside the open one — a hairline, a gap
// (the open panel's resize handle) and then the real divider, which reads as an extra
// empty panel wedged in. Both closed, the two leftover lines stacked at the window edge.
className={`relative flex min-h-0 shrink-0 flex-col overflow-hidden ${
panel.open ? "border-l border-gray-200 dark:border-gray-800" : ""
} ${panel.resizing ? "pointer-events-none" : "transition-[width] duration-200"}`}
>
{/* Content is fixed at the target width; the outer element is only a clipping window:
during the open/close animation the outer element passes through intermediate
@@ -80,11 +80,13 @@ export function SubagentsPanel({
<div
ref={panel.panelRef}
style={{ width: panel.open ? panel.width : 0 }}
// Same inert + clipping-window handling as the Files panel (see files-panel.tsx).
// Same inert + clipping-window handling as the Files panel, and the same open-only
// divider (a closed panel's 1px border would otherwise paint next to the open one —
// see files-panel.tsx).
inert={!panel.open}
className={`relative flex min-h-0 shrink-0 flex-col overflow-hidden border-l border-gray-200 dark:border-gray-800 ${
panel.resizing ? "pointer-events-none" : "transition-[width] duration-200"
}`}
className={`relative flex min-h-0 shrink-0 flex-col overflow-hidden ${
panel.open ? "border-l border-gray-200 dark:border-gray-800" : ""
} ${panel.resizing ? "pointer-events-none" : "transition-[width] duration-200"}`}
>
<div style={{ width: panel.width }} className="flex h-full min-h-0 flex-col">
<div className="flex shrink-0 items-center gap-1 px-3 pt-2">