Files
apes-sidebar/src/client/FenceErrorNotice.tsx
T
Menghuan1918 a9353ce627 feat: workspace fence toggle (workspaceFence pref + one-click off in error surfaces)
- New persisted built-in pref workspaceFence (default true) under the
  files card's secondary settings modal; host reads it per request via
  the settings seam (fenceEnabledOf — absent/malformed stays ON).
- ensureWorkspacePath/ensureWorkspaceWritePath gain a fence param:
  disarmed skips the containment assert but keeps realpath resolution;
  wired through fs.tree/fs.read/fs.write, the media + html routes, and
  the upload path (writeWorkspaceUpload fence input).
- Editor load failures and file-tree level errors show the localized
  reason plus a one-click global off (FenceErrorNotice) that flips the
  pref, adopts it into the store, and auto-retries the failed operation
  (reloadSeq / retryDir) — the raw wire text is never shown.
- GitView's two client-side pre-checks (hidden open action + no-op
  defense) respect the pref; store threaded through TreePanel/FileTree.
- i18n: 4 new keys across all 21 dictionaries; tests: fence-off route
  coverage, prefs parsing, schema default, editor toggles list;
  docs: guide §8/§8.1 + plans/2026-08-29 design record.
2026-08-29 15:49:19 +08:00

41 lines
1.6 KiB
TypeScript

/**
* The workspace-fence refusal surface. The raw wire text (`path "..." is
* outside workspace`) is never shown as-is: the editor / file-tree error
* slots render the localized reason plus a one-click global off — the click
* flips the `workspaceFence` pref through the settings route, adopts the
* returned document into the store (so every prefs reader — GitView's open
* guard, the settings page — flips with it), and calls `onDisabled` so the
* caller retries the failed operation immediately.
*/
import { useState } from 'react'
import { Button } from '@deepseek-ai/dsh-client-ui-primitives'
import { api } from './api.ts'
import { t } from './locales.ts'
import { parsePrefs } from './prefs.ts'
import css from './sidebar.module.css'
import type { SidebarStore } from './state.ts'
export function FenceErrorNotice(props: { store: SidebarStore; onDisabled: () => void }) {
const { store, onDisabled } = props
const [busy, setBusy] = useState(false)
const disable = (): void => {
if (busy) return
setBusy(true)
api.settingsUpdate({ workspaceFence: false }).then((view) => {
store.setPrefs(parsePrefs(view.value))
onDisabled()
}).catch((error: unknown) => {
// The fence stays armed on failure (the notice remains, button
// re-enabled); the console carries the cause.
console.error('workspace fence disable failed', error)
setBusy(false)
})
}
return (
<div className={css.fenceError}>
<span>{t('fenceErrorReason')}</span>
<Button variant="outline" disabled={busy} onClick={disable}>{t('fenceDisableAction')}</Button>
</div>
)
}