* feat(editor): merged explorer mode — path input, docked file tree, global name search
New editorExplorer pref (default on, editor card gear): the editor tab gains
a path-input header and a toggleable right-docked tree panel (per-tab state
in tab.meta.treeOpen; file tabs default closed, the seeded path-less home
tab defaults open). Fresh sessions seed that empty editor tab instead of
the explorer tab when merged mode is on; the standalone explorer tab stays
in the + menu unchanged.
- Extract FileTree.tsx from ExplorerView (shared by both hosts)
- Host fs.search route: recursive case-insensitive name search rooted at
the session cwd, budget-capped (200 matches / 100k visited), skips .git,
never descends symlink dirs
- EditorHost reads the pref reactively (boolean snapshot) and keeps the
pre-merge layout verbatim when off
* feat(settings): select row type for declarative settings; editorExplorer as iconed select
- SidebarSettingToggle gains type 'select' with options {value,title,desc,icon}
and multi (default false; stores the picked values as an array in options
order). Any option carrying an icon renders big-icon option cards in the
dropdown and the icon in the closed anchor; icon-less rows are one-line text.
- SelectRow in SideCardSection rides the primitives Menu (portal); single-pick
commits and closes, multi toggles and stays open. Wired for both host-prefs
toggles and plugin-owned pluginToggles via the new onSelectValue seam.
- The editorExplorer merged-mode pref is now an iconed select (merged vs
separate) instead of a switch; the stored value stays boolean.
- Advertise the capability as 'settingSelect' in SIDEBAR_FEATURES.
* feat(editor): unify explorer into the editor surface, resizable tree dock, hoisted toolbar
- One tree implementation, two presentations: the shared TreePanel (global
name search + refresh + FileTree) docks as the editor tab's right panel
and — in merged mode — fills the explorer tab's window; the pref-off
explorer keeps its legacy header shell.
- The docked panel drag-resizes from its left edge (160-480px clamp),
persisted as tab.meta.treeWidth alongside treeOpen.
- The text editor's preview/edit/save toolbar moves into the path-input
header row: FileViewerProps gains internal toolbar fields ('host' mode
skips the viewer's own row, reports EditorToolbarState and registers
EditorToolbarControls); non-merged mode is byte-identical to before.
* feat(editor): single files window with in-place switching; explorer descriptor removed
- editor tab replaces explorer on the + menu (order 10, Files); persisted
explorer tabs migrate to the editor home tab via sanitizeState
- editorExplorer now only controls open behavior: merged = in-place
updateTab, independent = openSidebarFile; both modes share the merged
chrome (path input + hoisted preview/edit/save toolbar + tree toggle)
- file tree context menu gains 'open in new tab' / 'open to the side'
(mintTabId avoids the id safety net; insertLeafAt splits right)
- tree dock resize uses pointer capture (touch-action none, 6px handle)
- defaults: openByDefault off, sidebar width 35%
- file tree clips horizontal overflow (no sideways scroll)
- e2e: mount lane arms the editor-chunk wait before goto and clicks the
tree row's left edge (hover @-reference button sits mid-row); drag lane
expands the panel first (collapsed push writes 0px), waits for the
slide-in to settle, and picks the leftmost col-resize strip; 'Files'
title assertions require draggable to skip the bottom panel's welcome
cards
* feat(editor): split mode turns the path-less window into the standalone explorer
In independent (split) mode a path-less files window renders ONLY the
tree panel (search + FileTree, full-window form) — no path input, no
hoisted toolbar, no tree toggle. File clicks open per-path editor tabs
(full chrome, tree docked closed by default) through openSidebarFile,
unchanged. Merged mode is untouched (chrome + docked tree, in-place
switching). TreePanel's previously reserved 'full' prop now has its
consumer.
82 lines
4.3 KiB
TypeScript
82 lines
4.3 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import Loader from '@cordisjs/plugin-loader'
|
|
import * as sidebar from '../src/index.ts'
|
|
|
|
/**
|
|
* Run the real namespace export through `Loader.unwrapExports`; a stray
|
|
* default would discard `name`, `inject`, `Config`, and `apply`. Same guard
|
|
* the official plugin repos ship (dsh-external/turtle-ui,
|
|
* packages/ui/jsonrpc).
|
|
*/
|
|
describe('dsh-better-sidebar plugin export shape', () => {
|
|
it('has the namespace-plugin export shape (no stray default) so the Loader keeps name/inject/Config/apply', () => {
|
|
expect('default' in sidebar).toBe(false)
|
|
expect(typeof sidebar.apply).toBe('function')
|
|
|
|
const loader = Object.create(Loader.prototype) as Loader
|
|
const unwrapped = loader.unwrapExports(sidebar) as Record<string, unknown>
|
|
expect(unwrapped).toBe(sidebar)
|
|
expect(unwrapped.name).toBe('dsh-better-sidebar')
|
|
expect(unwrapped.inject).toEqual(['webServer', 'sessions', 'webRuntime', 'tools'])
|
|
expect(unwrapped.Config).toBeDefined()
|
|
expect(typeof unwrapped.apply).toBe('function')
|
|
})
|
|
|
|
it('exports the schemastery Config with the documented tunable fields', () => {
|
|
const schema = sidebar.Config
|
|
expect(schema).toBeDefined()
|
|
// The resolved defaults mirror the pre-config constants.
|
|
const resolved = (schema as unknown as {
|
|
(input: Record<string, unknown> | undefined): Record<string, unknown>
|
|
})(undefined)
|
|
expect(resolved.readLimit).toBe(512 * 1024)
|
|
expect(resolved.mediaLimit).toBe(20 * 1024 * 1024)
|
|
expect(resolved.listLimit).toBe(1000)
|
|
expect(resolved.terminalsPerSession).toBe(3)
|
|
expect(resolved.reconnectGraceMs).toBe(30_000)
|
|
// The terminal shell config defaults to auto-resolution (empty shell =
|
|
// the platform chain in defaultShell()).
|
|
expect(resolved.shell).toBe('')
|
|
const configured = (schema as unknown as {
|
|
(input: Record<string, unknown> | undefined): Record<string, unknown>
|
|
})({ shell: 'pwsh.exe' })
|
|
expect(configured.shell).toBe('pwsh.exe')
|
|
})
|
|
|
|
it('registers the side card preferences schema with the documented defaults', async () => {
|
|
const { PrefsSchema, SIDEBAR_PREFS_NS } = await import('../src/config.ts')
|
|
expect(SIDEBAR_PREFS_NS).toBe('dsh-better-sidebar')
|
|
const resolved = (PrefsSchema as unknown as {
|
|
(input: Record<string, unknown> | undefined): Record<string, unknown>
|
|
})(undefined)
|
|
expect(resolved.openByDefault).toBe(false)
|
|
expect(resolved.defaultWidthPercent).toBe(35)
|
|
expect(resolved.autoOpenSubagent).toBe(true)
|
|
// A new background job auto-opens the Jobs page too.
|
|
expect(resolved.autoOpenJobs).toBe(true)
|
|
// The terminal tools default OFF (the feature is dormant until the user
|
|
// enables it in the side card settings).
|
|
expect(resolved.agentTerminalTools).toBe(false)
|
|
// The terminal font customizations default to the theme (empty family)
|
|
// and 13px.
|
|
expect(resolved.terminalFontFamily).toBe('')
|
|
expect(resolved.terminalFontSize).toBe(13)
|
|
// The position-compat mode defaults OFF (the normal layout is default),
|
|
// with the strip defaulting to 40px.
|
|
expect(resolved.titleBarCompat).toBe(false)
|
|
expect(resolved.titleBarStripPx).toBe(40)
|
|
// The enable-switch maps resolve to {} (everything on) for old documents.
|
|
expect(resolved.tabsEnabled).toEqual({})
|
|
expect(resolved.viewersEnabled).toEqual({})
|
|
// The merged editor-explorer mode defaults ON.
|
|
expect(resolved.editorExplorer).toBe(true)
|
|
// A stored overridden value resolves through (the range contract is
|
|
// enforced by the settings service on write); the new pref keeps its
|
|
// default when the stored document predates it.
|
|
const overridden = (PrefsSchema as unknown as {
|
|
(input: Record<string, unknown> | undefined): Record<string, unknown>
|
|
})({ openByDefault: false, defaultWidthPercent: 45 })
|
|
expect(overridden).toEqual({ openByDefault: false, defaultWidthPercent: 45, autoOpenSubagent: true, autoOpenJobs: true, agentTerminalTools: false, bottomPanelAutoTerminal: true, terminalFontFamily: '', terminalFontSize: 13, interceptOpenPath: true, editorExplorer: true, titleBarCompat: false, titleBarStripPx: 40, htmlViewerNoSandbox: false, htmlViewerDefaultUnsafe: false, browserNoSandbox: false, browserInterceptLinks: true, browserInterceptHttp: true, browserInterceptHttps: false, tabsEnabled: {}, viewersEnabled: {}, pluginSettings: {} })
|
|
})
|
|
})
|