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.
This commit is contained in:
Menghuan1918
2026-08-29 15:49:19 +08:00
parent 2088ef6891
commit a9353ce627
44 changed files with 391 additions and 38 deletions
+10 -3
View File
@@ -91,9 +91,11 @@ export function builtinTabs(ctx: Context, options: BuiltinTabOptions = {}): read
dedupeKey: (tab) => tab.path,
// Declarative settings: the file-open behavior picker (in-place switch
// vs per-path windows) renders as an iconed select row under the
// editor card's gear in the Side card settings page; the "open with"
// configuration (SSH host + custom editors) is the custom panel BELOW
// those rows — the settings seam renders rows first, custom panel after.
// editor card's gear in the Side card settings page, followed by the
// workspace fence switch (the host's containment guard over every
// sidebar filesystem route); the "open with" configuration (SSH host +
// custom editors) is the custom panel BELOW those rows — the settings
// seam renders rows first, custom panel after.
settings: {
toggles: [{
key: 'editorExplorer',
@@ -114,6 +116,10 @@ export function builtinTabs(ctx: Context, options: BuiltinTabOptions = {}): read
desc: () => t('editorExplorerSplitDesc'),
},
],
}, {
key: 'workspaceFence',
title: () => t('settingsFenceTitle'),
desc: () => t('settingsFenceDesc'),
}],
render: ({ pluginSettings, updatePluginSetting }) => (
<OpenWithSettings pluginSettings={pluginSettings} updatePluginSetting={updatePluginSetting} />
@@ -141,6 +147,7 @@ export function builtinTabs(ctx: Context, options: BuiltinTabOptions = {}): read
component: ({ ctx, store, scope, visible, onOpenDiff }) => (
<GitView
scope={scope}
store={store}
visible={visible}
onOpenFile={(path) => { openSidebarFile(ctx, store, scope.sessionId, path) }}
onOpenDiff={onOpenDiff ?? (() => { /* no-op */ })}