Remove browser settings draft sync effects

This commit is contained in:
Neil
2026-05-29 06:02:27 -07:00
parent 031b9adea9
commit b497d0d8d9
2 changed files with 90 additions and 21 deletions
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'
import { useState } from 'react'
import { Plus } from 'lucide-react'
import { toast } from 'sonner'
import type { GlobalSettings } from '../../../../shared/types'
@@ -24,6 +24,29 @@ import { BrowserUseSetup } from './BrowserUsePane'
import { KagiSessionLinkForm } from './KagiSessionLinkForm'
export { BROWSER_PANE_SEARCH_ENTRIES }
type HomePageDraftState = {
sourceUrl: string
draft: string
}
function createHomePageDraftState(
browserDefaultUrl: string | null | undefined
): HomePageDraftState {
const sourceUrl = browserDefaultUrl ?? ''
return {
sourceUrl,
draft: sourceUrl
}
}
function resolveHomePageDraftState(
state: HomePageDraftState,
browserDefaultUrl: string | null | undefined
): HomePageDraftState {
const sourceUrl = browserDefaultUrl ?? ''
return state.sourceUrl === sourceUrl ? state : { sourceUrl, draft: sourceUrl }
}
type BrowserPaneProps = {
settings: GlobalSettings
updateSettings: (updates: Partial<GlobalSettings>) => void
@@ -47,17 +70,29 @@ export function BrowserPane({
const setDefaultBrowserSessionProfileId = useAppStore((s) => s.setDefaultBrowserSessionProfileId)
const defaultProfile = browserSessionProfiles.find((p) => p.id === 'default')
const nonDefaultProfiles = browserSessionProfiles.filter((p) => p.scope !== 'default')
const [homePageDraft, setHomePageDraft] = useState(browserDefaultUrl ?? '')
const [homePageDraftState, setHomePageDraftState] = useState(() =>
createHomePageDraftState(browserDefaultUrl)
)
const [newProfileDialogOpen, setNewProfileDialogOpen] = useState(false)
const [newProfileName, setNewProfileName] = useState('')
const [isCreatingProfile, setIsCreatingProfile] = useState(false)
// Why: sync draft with store value whenever it changes externally (e.g. the
// in-app browser tab's address bar saves a home page). Without this, the
// settings field would show stale text after another surface wrote the value.
useEffect(() => {
setHomePageDraft(browserDefaultUrl ?? '')
}, [browserDefaultUrl])
const resolvedHomePageDraftState = resolveHomePageDraftState(
homePageDraftState,
browserDefaultUrl
)
if (resolvedHomePageDraftState !== homePageDraftState) {
// Why: the address bar can save a new home page outside Settings; reconcile
// the draft before paint so the field never flashes stale text.
setHomePageDraftState(resolvedHomePageDraftState)
}
const homePageDraft = resolvedHomePageDraftState.draft
const updateHomePageDraft = (draft: string): void => {
setHomePageDraftState((current) => ({
...resolveHomePageDraftState(current, browserDefaultUrl),
draft
}))
}
const selectedSearchEngine = browserDefaultSearchEngine ?? 'google'
@@ -116,19 +151,20 @@ export function BrowserPane({
const trimmed = homePageDraft.trim()
if (!trimmed) {
setBrowserDefaultUrl(null)
setHomePageDraftState(createHomePageDraftState(null))
return
}
const normalized = normalizeBrowserNavigationUrl(trimmed)
if (normalized && normalized !== ORCA_BROWSER_BLANK_URL) {
setBrowserDefaultUrl(normalized)
setHomePageDraft(normalized)
setHomePageDraftState(createHomePageDraftState(normalized))
toast.success('Home page saved.')
}
}}
>
<Input
value={homePageDraft}
onChange={(e) => setHomePageDraft(e.target.value)}
onChange={(e) => updateHomePageDraft(e.target.value)}
placeholder="https://google.com"
spellCheck={false}
autoCapitalize="none"
@@ -1,26 +1,59 @@
import { useEffect, useState } from 'react'
import { useState } from 'react'
import { toast } from 'sonner'
import { normalizeKagiSessionLink } from '../../../../shared/browser-url'
import { useAppStore } from '../../store'
import { Button } from '../ui/button'
import { Input } from '../ui/input'
type KagiSessionLinkDraftState = {
sourceLink: string
draft: string
}
function createKagiSessionLinkDraftState(
browserKagiSessionLink: string | null | undefined
): KagiSessionLinkDraftState {
const sourceLink = browserKagiSessionLink ?? ''
return {
sourceLink,
draft: sourceLink
}
}
function resolveKagiSessionLinkDraftState(
state: KagiSessionLinkDraftState,
browserKagiSessionLink: string | null | undefined
): KagiSessionLinkDraftState {
const sourceLink = browserKagiSessionLink ?? ''
return state.sourceLink === sourceLink ? state : { sourceLink, draft: sourceLink }
}
export function KagiSessionLinkForm(): React.JSX.Element {
const browserKagiSessionLink = useAppStore((s) => s.browserKagiSessionLink)
const setBrowserKagiSessionLink = useAppStore((s) => s.setBrowserKagiSessionLink)
const [draft, setDraft] = useState(browserKagiSessionLink ?? '')
const [draftState, setDraftState] = useState(() =>
createKagiSessionLinkDraftState(browserKagiSessionLink)
)
// Why: the Kagi token is edited as a masked draft so accidental typing or
// external settings updates do not immediately overwrite the persisted secret.
useEffect(() => {
setDraft(browserKagiSessionLink ?? '')
}, [browserKagiSessionLink])
const resolvedDraftState = resolveKagiSessionLinkDraftState(draftState, browserKagiSessionLink)
if (resolvedDraftState !== draftState) {
// Why: the masked draft tracks persisted secret updates without committing
// accidental typing back to settings or waiting for an after-paint Effect.
setDraftState(resolvedDraftState)
}
const draft = resolvedDraftState.draft
const updateDraft = (nextDraft: string): void => {
setDraftState((current) => ({
...resolveKagiSessionLinkDraftState(current, browserKagiSessionLink),
draft: nextDraft
}))
}
const save = (): void => {
const trimmed = draft.trim()
if (!trimmed) {
setBrowserKagiSessionLink(null)
setDraft('')
setDraftState(createKagiSessionLinkDraftState(null))
toast.success('Kagi session link cleared.')
return
}
@@ -30,7 +63,7 @@ export function KagiSessionLinkForm(): React.JSX.Element {
return
}
setBrowserKagiSessionLink(normalized)
setDraft(normalized)
setDraftState(createKagiSessionLinkDraftState(normalized))
toast.success('Kagi session link saved.')
}
@@ -49,7 +82,7 @@ export function KagiSessionLinkForm(): React.JSX.Element {
<Input
type="password"
value={draft}
onChange={(e) => setDraft(e.target.value)}
onChange={(e) => updateDraft(e.target.value)}
placeholder="https://kagi.com/search?token=..."
spellCheck={false}
autoCapitalize="none"
@@ -69,7 +102,7 @@ export function KagiSessionLinkForm(): React.JSX.Element {
className="h-7 text-xs"
onClick={() => {
setBrowserKagiSessionLink(null)
setDraft('')
setDraftState(createKagiSessionLinkDraftState(null))
toast.success('Kagi session link cleared.')
}}
>