Worker File Tree Sandbox
How this example is built
Section titled “How this example is built”This page shows a nested worker-based file tree without mocks. The docs page owns the host DOM and pointer hit-testing, while the remote Vue app keeps the tree structure, inline editing state, and drag logic inside a dedicated Web Worker.
The example has three parts:
- a host runtime that mounts
HostedTreeinto the preview area; - a remote worker app that renders folders, files, inline actions, and handles
RemoteSortableEvent; - a live snapshot below the tree that shows the current nested state after edits and drops.
How to use it
Section titled “How to use it”- Drag nodes by the grip handle to reorder siblings or move them into another folder.
- Rename items inline, add files or folders, and remove nodes from the current tree.
- Use
Reset treeto restore the initial workspace structure.
Sortable and editable file tree running in a dedicated Worker
Drag files and folders by their handle, move nodes across folders, rename items inline, create new entries, and reset the workspace whenever you want to start over.
Booting dedicated Worker runtime...
This tree is wired through the same host/worker transport model used by the library's public runtime APIs.
import type { Channel } from '@omnicajs/vue-remote/host'
import { createEndpoint, fromWebWorker } from '@remote-ui/rpc'import { createApp, h,} from 'vue'
import { HostedTree, createProvider, createReceiver,} from '@omnicajs/vue-remote/host'
interface SandboxWorkerApi { release (): void; run (channel: Channel, labels: SortableFileTreeSandboxLabels): Promise<void>;}
export interface SortableFileTreeSandboxLabels { addFileLabel: string; addFolderLabel: string; booting: string; cancelLabel: string; collapseLabel: string; deleteLabel: string; dragCanceledAction: string; draggingAction: string; emptyFolderLabel: string; expandLabel: string; failed: string; fileAddedAction: string; fileKindLabel: string; folderAddedAction: string; folderKindLabel: string; initialAction: string; movedAction: string; newFileName: string; newFolderName: string; ready: string; removedAction: string; renameLabel: string; renamedAction: string; resetLabel: string; rootEyebrow: string; saveLabel: string; snapshotLabel: string; snapshotUnavailable?: string; unsupported: string;}
export interface SortableFileTreeSandboxElements { rootElement: HTMLElement;}
export interface SortableFileTreeSandboxHandle { destroy (): Promise<void>;}
export const mountSortableFileTreeSandbox = async ( elements: SortableFileTreeSandboxElements, labels: SortableFileTreeSandboxLabels): Promise<SortableFileTreeSandboxHandle> => { const { rootElement } = elements
if (typeof Worker === 'undefined') { rootElement.textContent = labels.unsupported
return { async destroy () {}, } }
rootElement.textContent = labels.booting
const receiver = createReceiver() const provider = createProvider() const host = createApp({ render: () => h(HostedTree, { provider, receiver }), }) const worker = new Worker(new URL('./remote.worker.ts', import.meta.url), { type: 'module', }) const endpoint = createEndpoint<SandboxWorkerApi>(fromWebWorker(worker)) let destroyed = false
const destroy = async () => { if (destroyed) { return }
destroyed = true
try { await endpoint.call.release() } catch { // Worker teardown should not block page cleanup. } finally { endpoint.terminate() worker.terminate() host.unmount() rootElement.innerHTML = '' } }
try { host.mount(rootElement) await endpoint.call.run(receiver.receive, labels) } catch (error) { await destroy() rootElement.textContent = `${labels.failed}: ${error instanceof Error ? error.message : String(error)}` }
return { destroy, }}import type { MessageEndpoint } from '@remote-ui/rpc'import type { Channel } from '@omnicajs/vue-remote/host'import type { RemoteSortableEvent } from '@omnicajs/vue-remote/remote'
import { createEndpoint, release, retain,} from '@remote-ui/rpc'import { RemoteDragHandle, RemoteSortableContainer, RemoteSortableItem, createRemoteRenderer, createRemoteRoot,} from '@omnicajs/vue-remote/remote'import { defineComponent, h, ref,} from 'vue'
type FolderContainerId = `children:${string}`
interface FileNode { id: string; kind: 'file'; name: string;}
interface FolderNode { children: FileTreeNode[]; expanded: boolean; id: string; kind: 'folder'; name: string;}
type FileTreeNode = FileNode | FolderNode
interface FileTreeSnapshot { lastAction: string; tree: Array<{ children?: FileTreeSnapshot['tree']; id: string; kind: 'file' | 'folder'; name: string; }>;}
interface SandboxWorkerApi { release (): void; run (channel: Channel, labels: SandboxUiLabels): Promise<void>;}
interface SandboxUiLabels { addFileLabel: string; addFolderLabel: string; booting: string; cancelLabel: string; collapseLabel: string; deleteLabel: string; dragCanceledAction: string; draggingAction: string; emptyFolderLabel: string; expandLabel: string; failed: string; fileAddedAction: string; fileKindLabel: string; folderAddedAction: string; folderKindLabel: string; initialAction: string; movedAction: string; newFileName: string; newFolderName: string; ready: string; removedAction: string; renameLabel: string; renamedAction: string; resetLabel: string; rootEyebrow: string; saveLabel: string; snapshotLabel: string; snapshotUnavailable?: string; unsupported: string;}
const endpoint = createEndpoint<SandboxWorkerApi>(self as unknown as MessageEndpoint)
const ROOT_CONTAINER_ID = 'children:root' satisfies FolderContainerId
const initialTree = (): FileTreeNode[] => ([ { children: [ { children: [ { id: 'file-host-ts', kind: 'file', name: 'host.ts', }, { id: 'file-remote-ts', kind: 'file', name: 'remote.ts', }, ], expanded: true, id: 'folder-vue', kind: 'folder', name: 'vue', }, { children: [], expanded: true, id: 'folder-fixtures', kind: 'folder', name: 'fixtures', }, { id: 'file-dnd-ts', kind: 'file', name: 'dnd.ts', }, ], expanded: true, id: 'folder-src', kind: 'folder', name: 'src', }, { children: [ { id: 'file-introduction-mdx', kind: 'file', name: 'introduction.mdx', }, { id: 'file-sortable-mdx', kind: 'file', name: 'sortable-dnd.mdx', }, ], expanded: true, id: 'folder-docs', kind: 'folder', name: 'docs', }, { id: 'file-package-json', kind: 'file', name: 'package.json', },])
const isFolder = (node: FileTreeNode): node is FolderNode => node.kind === 'folder'
const cloneNode = (node: FileTreeNode): FileTreeNode => { if (!isFolder(node)) { return { ...node } }
return { ...node, children: node.children.map(cloneNode), }}
const cloneSnapshot = (nodes: FileTreeNode[], lastAction: string): FileTreeSnapshot => ({ lastAction, tree: nodes.map(node => { if (!isFolder(node)) { return { id: node.id, kind: node.kind, name: node.name, } }
return { children: cloneSnapshot(node.children, lastAction).tree, id: node.id, kind: node.kind, name: node.name, } }),})
const replaceNode = ( nodes: FileTreeNode[], id: string, updater: (node: FileTreeNode) => FileTreeNode): FileTreeNode[] => { return nodes.map(node => { if (node.id === id) { return updater(node) }
if (!isFolder(node)) { return node }
return { ...node, children: replaceNode(node.children, id, updater), } })}
const removeNode = ( nodes: FileTreeNode[], id: string): { nextNodes: FileTreeNode[]; removed: FileTreeNode | null;} => { let removed: FileTreeNode | null = null const nextNodes: FileTreeNode[] = []
nodes.forEach(node => { if (node.id === id) { removed = node return }
if (!isFolder(node)) { nextNodes.push(node) return }
const nested = removeNode(node.children, id)
if (nested.removed != null) { removed = nested.removed nextNodes.push({ ...node, children: nested.nextNodes, }) return }
nextNodes.push(node) })
return { nextNodes, removed, }}
const insertNode = ( nodes: FileTreeNode[], folderId: string, index: number, node: FileTreeNode): FileTreeNode[] => { if (folderId === 'root') { const nextNodes = [...nodes] nextNodes.splice(Math.min(Math.max(index, 0), nextNodes.length), 0, node) return nextNodes }
return nodes.map(entry => { if (!isFolder(entry)) { return entry }
if (entry.id === folderId) { const nextChildren = [...entry.children] nextChildren.splice(Math.min(Math.max(index, 0), nextChildren.length), 0, node)
return { ...entry, children: nextChildren, expanded: true, } }
return { ...entry, children: insertNode(entry.children, folderId, index, node), } })}
const containerFolderId = (containerId: string) => { return containerId === ROOT_CONTAINER_ID ? 'root' : containerId.replace(/^children:/, '')}
const nodeContainsFolder = (node: FileTreeNode, folderId: string): boolean => { if (!isFolder(node)) { return false }
if (node.id === folderId) { return true }
return node.children.some(child => nodeContainsFolder(child, folderId))}
const rows = ref<FileTreeNode[]>(initialTree())const editingId = ref<string | null>(null)const editingName = ref('')const lastAction = ref('Manage folders and files, then drag nodes to reorder or move them.')
let nextNodeIndex = 1let uiLabels: SandboxUiLabels = { addFileLabel: 'Add file', addFolderLabel: 'Add folder', booting: '', cancelLabel: 'Cancel', collapseLabel: 'Collapse', deleteLabel: 'Delete', dragCanceledAction: 'Drag canceled.', draggingAction: 'Dragging node.', emptyFolderLabel: 'Drop items here', expandLabel: 'Expand', failed: '', fileAddedAction: 'File created.', fileKindLabel: 'File', folderAddedAction: 'Folder created.', folderKindLabel: 'Folder', initialAction: 'Manage folders and files, then drag nodes to reorder or move them.', movedAction: 'Node moved.', newFileName: 'new-file.ts', newFolderName: 'new-folder', ready: '', removedAction: 'Node removed.', renameLabel: 'Rename', renamedAction: 'Name updated.', resetLabel: 'Reset tree', rootEyebrow: 'Workspace tree', saveLabel: 'Save', snapshotLabel: 'Snapshot', unsupported: '',}
let releaseRemote = () => {}
const createNodeId = (prefix: 'file' | 'folder') => { nextNodeIndex += 1 return `${prefix}-${nextNodeIndex}`}
const clearEditing = () => { editingId.value = null editingName.value = ''}
const setRows = (nextRows: FileTreeNode[]) => { rows.value = nextRows.map(cloneNode)}
const startRename = (node: FileTreeNode) => { editingId.value = node.id editingName.value = node.name}
const commitRename = (nodeId: string) => { const nextName = editingName.value.trim()
if (nextName.length === 0) { return }
setRows(replaceNode(rows.value, nodeId, node => ({ ...node, name: nextName, }))) clearEditing() lastAction.value = uiLabels.renamedAction}
const toggleFolder = (folderId: string) => { setRows(replaceNode(rows.value, folderId, node => { if (!isFolder(node)) { return node }
return { ...node, expanded: !node.expanded, } }))}
const addNode = (parentFolderId: string, kind: 'file' | 'folder') => { const nextNode: FileTreeNode = kind === 'file' ? { id: createNodeId('file'), kind: 'file', name: uiLabels.newFileName, } : { children: [], expanded: true, id: createNodeId('folder'), kind: 'folder', name: uiLabels.newFolderName, }
setRows(insertNode(rows.value, parentFolderId, Number.MAX_SAFE_INTEGER, nextNode)) startRename(nextNode) lastAction.value = kind === 'file' ? uiLabels.fileAddedAction : uiLabels.folderAddedAction}
const deleteNode = (nodeId: string) => { const removed = removeNode(rows.value, nodeId)
if (removed.removed == null) { return }
setRows(removed.nextNodes)
if (editingId.value === nodeId) { clearEditing() }
lastAction.value = uiLabels.removedAction}
const resetTree = () => { nextNodeIndex = 1 clearEditing() rows.value = initialTree() lastAction.value = uiLabels.initialAction}
const setDragStart = () => { lastAction.value = uiLabels.draggingAction}
const setDragCancel = () => { lastAction.value = uiLabels.dragCanceledAction}
const moveNode = (event: RemoteSortableEvent) => { if (!event.accepted || event.targetContainerId == null || event.targetIndex == null) { return }
const sourceRemoval = removeNode(rows.value, event.itemId) const removedNode = sourceRemoval.removed
if (removedNode == null) { return }
const targetFolderId = containerFolderId(event.targetContainerId)
if ( isFolder(removedNode) && targetFolderId !== 'root' && nodeContainsFolder(removedNode, targetFolderId) ) { lastAction.value = uiLabels.dragCanceledAction return }
setRows(insertNode(sourceRemoval.nextNodes, targetFolderId, event.targetIndex, removedNode)) lastAction.value = uiLabels.movedAction}
const gripIcon = () => h('svg', { 'aria-hidden': 'true', class: 'sandbox-tree__grip', fill: 'none', viewBox: '0 0 8 12',}, [ h('circle', { cx: '1.5', cy: '1.5', fill: 'currentColor', r: '1' }), h('circle', { cx: '6.5', cy: '1.5', fill: 'currentColor', r: '1' }), h('circle', { cx: '1.5', cy: '6', fill: 'currentColor', r: '1' }), h('circle', { cx: '6.5', cy: '6', fill: 'currentColor', r: '1' }), h('circle', { cx: '1.5', cy: '10.5', fill: 'currentColor', r: '1' }), h('circle', { cx: '6.5', cy: '10.5', fill: 'currentColor', r: '1' }),])
const icon = (path: string, viewBox = '0 0 16 16') => h('svg', { 'aria-hidden': 'true', class: 'sandbox-tree__action-icon', fill: 'none', viewBox,}, [ h('path', { d: path, 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '1.5', stroke: 'currentColor', }),])
const addFileIcon = () => icon('M8 3.25v9.5M3.25 8h9.5')const addFolderIcon = () => h('svg', { 'aria-hidden': 'true', class: 'sandbox-tree__action-icon', fill: 'none', viewBox: '0 0 16 16',}, [ h('path', { d: 'M2.75 5.25h3l1.1 1.5h6.4v5.5H2.75z', 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '1.4', stroke: 'currentColor', }), h('path', { d: 'M8 8.25v3.5M6.25 10h3.5', 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '1.4', stroke: 'currentColor', }),])const renameIcon = () => icon('M3 11.75l2.9-.65 5.55-5.55-2.25-2.25-5.55 5.55L3 11.75zM8.4 4.35l2.25 2.25')const deleteIcon = () => icon('M5.5 5.5v5M8 5.5v5M10.5 5.5v5M4.5 3.5h7M6 3.5v-1h4v1M4.75 3.5l.5 8h5.5l.5-8')const saveIcon = () => icon('M3.5 8.25l2.6 2.6 6.4-6.4')const cancelIcon = () => icon('M4.25 4.25l7.5 7.5M11.75 4.25l-7.5 7.5')const folderIcon = (title: string) => h('svg', { 'aria-label': title, class: 'sandbox-tree__node-icon', fill: 'none', role: 'img', title, viewBox: '0 0 16 16',}, [ h('path', { d: 'M2.5 5.25h3.1l1.2 1.55h6.7v4.95H2.5z', 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '1.5', stroke: 'currentColor', }),])const fileIcon = (title: string) => h('svg', { 'aria-label': title, class: 'sandbox-tree__node-icon', fill: 'none', role: 'img', title, viewBox: '0 0 16 16',}, [ h('path', { d: 'M5 2.75h4.5l2 2v8.5H5z', 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '1.5', stroke: 'currentColor', }), h('path', { d: 'M9.5 2.75v2h2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '1.5', stroke: 'currentColor', }),])
const TreeApp = defineComponent({ name: 'WorkerSortableFileTreeSandbox',
setup () { const renderRowActions = (node: FileTreeNode) => { if (editingId.value === node.id) { return [ h('button', { 'aria-label': uiLabels.saveLabel, class: 'sandbox-tree__action', 'data-testid': `sandbox-save-${node.id}`, onClick: () => commitRename(node.id), title: uiLabels.saveLabel, type: 'button', }, saveIcon()), h('button', { 'aria-label': uiLabels.cancelLabel, class: 'sandbox-tree__action', 'data-testid': `sandbox-cancel-${node.id}`, onClick: clearEditing, title: uiLabels.cancelLabel, type: 'button', }, cancelIcon()), ] }
return [ isFolder(node) ? h('button', { 'aria-label': uiLabels.addFileLabel, class: 'sandbox-tree__action', 'data-testid': `sandbox-add-file-${node.id}`, onClick: () => addNode(node.id, 'file'), title: uiLabels.addFileLabel, type: 'button', }, addFileIcon()) : null, isFolder(node) ? h('button', { 'aria-label': uiLabels.addFolderLabel, class: 'sandbox-tree__action', 'data-testid': `sandbox-add-folder-${node.id}`, onClick: () => addNode(node.id, 'folder'), title: uiLabels.addFolderLabel, type: 'button', }, addFolderIcon()) : null, h('button', { 'aria-label': uiLabels.renameLabel, class: 'sandbox-tree__action', 'data-testid': `sandbox-rename-${node.id}`, onClick: () => startRename(node), title: uiLabels.renameLabel, type: 'button', }, renameIcon()), h('button', { 'aria-label': uiLabels.deleteLabel, class: 'sandbox-tree__action sandbox-tree__action--danger', 'data-testid': `sandbox-delete-${node.id}`, onClick: () => deleteNode(node.id), title: uiLabels.deleteLabel, type: 'button', }, deleteIcon()), ].filter(Boolean) }
const renderNode = ( node: FileTreeNode, containerId: FolderContainerId, depth: number, index: number ): ReturnType<typeof h> => { const nodeClassName = `sandbox-tree__node sandbox-tree__node--${node.kind}` const rowClassName = `sandbox-tree__row sandbox-tree__row--${node.kind}` const childrenContainerId = `children:${node.id}` as FolderContainerId
return h(RemoteSortableItem, { as: 'div', class: nodeClassName, containerId, 'data-testid': `sandbox-node-${node.id}`, index, itemId: node.id, onDragcancel: setDragCancel, onDragstart: setDragStart, style: { '--tree-depth': String(depth), }, type: 'fs-node', }, { default: () => [ h('div', { class: rowClassName, 'data-testid': `sandbox-row-${node.id}`, }, [ h(RemoteDragHandle, { as: 'button', 'aria-label': `Drag ${node.name}`, class: 'sandbox-tree__handle', 'data-testid': `sandbox-handle-${node.id}`, type: 'button', }, { default: gripIcon, }), h('div', { class: 'sandbox-tree__row-main' }, [ isFolder(node) ? h('button', { 'aria-label': node.expanded ? uiLabels.collapseLabel : uiLabels.expandLabel, class: 'sandbox-tree__toggle', 'data-testid': `sandbox-toggle-${node.id}`, onClick: () => toggleFolder(node.id), type: 'button', }, node.expanded ? '−' : '+') : null, isFolder(node) ? folderIcon(uiLabels.folderKindLabel) : fileIcon(uiLabels.fileKindLabel), editingId.value === node.id ? h('input', { class: 'sandbox-tree__input', 'data-testid': `sandbox-input-${node.id}`, onInput: (event: Event) => { const target = event.target
if ( typeof target === 'object' && target != null && 'value' in target && typeof target.value === 'string' ) { editingName.value = target.value } }, onKeydown: (event: KeyboardEvent) => { if (event.key === 'Enter') { event.preventDefault() commitRename(node.id) }
if (event.key === 'Escape') { event.preventDefault() clearEditing() } }, value: editingName.value, }) : h('div', { class: 'sandbox-tree__meta' }, [ h('span', { class: 'sandbox-tree__name' }, node.name), ]), ]), h('div', { class: 'sandbox-tree__row-actions' }, renderRowActions(node)), ]), isFolder(node) && node.expanded ? h(RemoteSortableContainer, { as: 'div', accepts: ['fs-node'], class: node.children.length === 0 ? 'sandbox-tree__list sandbox-tree__children sandbox-tree__children--empty' : 'sandbox-tree__list sandbox-tree__children', containerId: childrenContainerId, 'data-testid': `sandbox-children-${node.id}`, onDrop: moveNode, orientation: 'vertical', }, { default: () => node.children.length > 0 ? node.children.map((child, childIndex) => renderNode(child, childrenContainerId, depth + 1, childIndex)) : [ h('div', { class: 'sandbox-tree__empty', 'data-testid': `sandbox-empty-${node.id}`, }, uiLabels.emptyFolderLabel), ], }) : null, ], }) }
return () => h('div', { class: 'worker-file-tree-sandbox__runtime' }, [ h('div', { class: 'worker-file-tree-sandbox__toolbar' }, [ h('span', { class: 'worker-file-tree-sandbox__status', 'data-sandbox-status': '', }, uiLabels.ready), h('button', { class: 'worker-file-tree-sandbox__reset', 'data-sandbox-reset': '', onClick: resetTree, type: 'button', }, uiLabels.resetLabel), ]), h('div', { class: 'worker-file-tree-sandbox__preview' }, [ h('div', { class: 'sandbox-tree' }, [ h('div', { class: 'sandbox-tree__toolbar' }, [ h('p', { class: 'sandbox-tree__eyebrow' }, uiLabels.rootEyebrow), h('p', { class: 'sandbox-tree__caption' }, lastAction.value), h('div', { class: 'sandbox-tree__actions' }, [ h('button', { 'aria-label': uiLabels.addFileLabel, class: 'sandbox-tree__toolbar-action', 'data-testid': 'sandbox-root-add-file', onClick: () => addNode('root', 'file'), title: uiLabels.addFileLabel, type: 'button', }, addFileIcon()), h('button', { 'aria-label': uiLabels.addFolderLabel, class: 'sandbox-tree__toolbar-action', 'data-testid': 'sandbox-root-add-folder', onClick: () => addNode('root', 'folder'), title: uiLabels.addFolderLabel, type: 'button', }, addFolderIcon()), ]), ]), h(RemoteSortableContainer, { as: 'div', accepts: ['fs-node'], class: 'sandbox-tree__list sandbox-tree__list--root', containerId: ROOT_CONTAINER_ID, 'data-testid': 'sandbox-tree-root', onDrop: moveNode, orientation: 'vertical', }, { default: () => rows.value.map((node, index) => renderNode(node, ROOT_CONTAINER_ID, 0, index)), }), ]), ]), h('div', { class: 'worker-file-tree-sandbox__snapshot', 'data-sandbox-snapshot-wrap': '', }, [ h('div', { class: 'worker-file-tree-sandbox__snapshot-title' }, uiLabels.snapshotLabel), h('pre', { class: 'worker-file-tree-sandbox__snapshot-code', 'data-sandbox-snapshot': '', }, JSON.stringify(cloneSnapshot(rows.value, lastAction.value), null, 2)), ]), ]) },})
endpoint.expose({ async run (channel: Channel, labels: SandboxUiLabels) { uiLabels = labels retain(channel)
const root = createRemoteRoot(channel) await root.mount()
const app = createRemoteRenderer(root).createApp(TreeApp) app.mount(root)
releaseRemote = () => { app.unmount() release(channel) } },
release () { releaseRemote() releaseRemote = () => {} },}).worker-file-tree-sandbox { --worker-sandbox-border: color-mix(in srgb, var(--sl-color-hairline-light) 78%, transparent); --worker-tree-branch: color-mix(in srgb, var(--sl-color-hairline-light) 84%, transparent); --worker-tree-surface-fill: color-mix(in srgb, var(--sl-color-bg) 92%, var(--sl-color-accent-low)); --worker-sandbox-surface: color-mix(in srgb, var(--sl-color-bg) 92%, var(--sl-color-accent-low)); margin-block: 2rem; width: 100%; max-width: 100%; min-width: 0;}
.worker-file-tree-sandbox__layout { display: grid; gap: 1.5rem; width: 100%; max-width: 100%; min-width: 0;}
.worker-file-tree-sandbox__surface { display: grid; gap: 1rem; width: 100%; max-width: 100%; min-width: 0;}
.worker-file-tree-sandbox__heading { display: grid; gap: 0.45rem; margin-bottom: 1rem;}
.worker-file-tree-sandbox__heading h2 { margin: 0; font-size: clamp(1.3rem, 1.15rem + 0.5vw, 1.7rem);}
.worker-file-tree-sandbox__heading p,.worker-file-tree-sandbox__note { margin: 0; color: var(--sl-color-gray-2);}
.worker-file-tree-sandbox__toolbar { display: flex; gap: 0.75rem; align-items: center; justify-content: space-between; margin-bottom: 1rem;}
.worker-file-tree-sandbox__status { color: var(--sl-color-gray-2); font-size: 0.92rem; font-weight: 600;}
.worker-file-tree-sandbox__reset { border: 1px solid var(--worker-sandbox-border); border-radius: 999px; background: color-mix(in srgb, var(--sl-color-white) 7%, transparent); color: var(--sl-color-text); padding: 0.55rem 0.95rem; font: inherit; font-weight: 700; cursor: pointer; transition: transform 140ms ease, border-color 140ms ease;}
.worker-file-tree-sandbox__reset:hover,.worker-file-tree-sandbox__reset:focus-visible { transform: translateY(-1px); border-color: color-mix(in srgb, var(--sl-color-accent-high) 50%, var(--worker-sandbox-border));}
.worker-file-tree-sandbox__runtime-host { min-height: 30rem; width: 100%; max-width: 100%; min-width: 0;}
.worker-file-tree-sandbox__runtime { display: grid; gap: 1rem; width: 100%; max-width: 100%; min-width: 0;}
.worker-file-tree-sandbox__preview { min-height: 30rem; width: 100%; max-width: 100%; min-width: 0;}
.worker-file-tree-sandbox__note { margin-top: 0.85rem; width: min(100%, var(--sl-content-width)); max-width: var(--sl-content-width); justify-self: start;}
.worker-file-tree-sandbox__snapshot { margin-top: 1rem; width: min(100%, var(--sl-content-width)); max-width: var(--sl-content-width); min-width: 0; justify-self: start;}
.worker-file-tree-sandbox__snapshot-title { margin-bottom: 0.45rem; font-size: 0.82rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--sl-color-gray-2);}
.worker-file-tree-sandbox__snapshot-code,.worker-file-tree-sandbox__code .expressive-code { margin: 0;}
.worker-file-tree-sandbox__code { display: grid; gap: 1rem; width: min(100%, var(--sl-content-width)); max-width: var(--sl-content-width); min-width: 0; justify-self: start;}
.worker-file-tree-sandbox__snapshot-code { overflow: auto; padding: 1rem; border-radius: 0.5rem; background: color-mix(in srgb, var(--sl-color-black) 12%, var(--sl-color-bg)); color: var(--sl-color-white); font-size: 0.82rem; line-height: 1.55;}
.worker-file-tree-sandbox__code .tablist-wrapper { margin-bottom: 1rem; max-width: 100%; min-width: 0; overflow-x: auto;}
.worker-file-tree-sandbox__code starlight-tabs,.worker-file-tree-sandbox__code [role='tablist'],.worker-file-tree-sandbox__code [role='tabpanel'] { min-width: 0; max-width: 100%;}
.worker-file-tree-sandbox__code starlight-tabs { display: grid; min-width: 0;}
.worker-file-tree-sandbox__code [role='tabpanel'] { margin-top: 0;}
.worker-file-tree-sandbox__code .expressive-code,.worker-file-tree-sandbox__code .expressive-code pre { max-width: 100%; min-width: 0;}
.worker-file-tree-sandbox .sandbox-tree { display: grid; gap: 1rem; padding: 1rem; margin: 0; border: 1px solid var(--worker-sandbox-border); border-radius: 0.5rem; background: radial-gradient(circle at top right, color-mix(in srgb, #34d399 10%, transparent), transparent 32%), linear-gradient(180deg, color-mix(in srgb, var(--worker-sandbox-surface) 96%, transparent), var(--sl-color-bg)); box-shadow: 0 1rem 2rem -1.5rem color-mix(in srgb, var(--sl-color-black) 25%, transparent);}
.worker-file-tree-sandbox .sandbox-tree__toolbar { display: grid; gap: 0.65rem; margin: 0;}
.worker-file-tree-sandbox .sandbox-tree__eyebrow { margin: 0; color: var(--sl-color-text-accent); font-size: 0.76rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase;}
.worker-file-tree-sandbox .sandbox-tree__caption { margin: 0; color: var(--sl-color-gray-2); font-size: 0.95rem; font-weight: 600;}
.worker-file-tree-sandbox .sandbox-tree__actions { display: flex; flex-wrap: nowrap; align-items: center; gap: 0.55rem; margin: 0; min-width: 0; overflow-x: auto;}
.worker-file-tree-sandbox .sandbox-tree__list { display: grid; gap: 0; margin: 0; min-width: 0;}
.worker-file-tree-sandbox .sandbox-tree__children { position: relative; margin-top: 0; margin-left: 1.15rem; padding-left: 0.85rem; padding-top: 0.15rem;}
.worker-file-tree-sandbox .sandbox-tree__children::before { content: ''; position: absolute; top: 0.15rem; bottom: 0.35rem; left: 0; width: 1px; background: var(--worker-tree-branch);}
.worker-file-tree-sandbox .sandbox-tree__children--empty { display: grid; gap: 0; margin: 0; min-height: 3rem; padding-block: 0.35rem;}
.worker-file-tree-sandbox .sandbox-tree__empty { display: grid; place-items: center start; min-height: 2.65rem; margin: 0; padding: 0.65rem 0.8rem; border: 1px dashed color-mix(in srgb, var(--sl-color-accent-high) 22%, var(--worker-sandbox-border)); border-radius: 0.5rem; color: var(--sl-color-gray-2); font-size: 0.84rem; font-weight: 600;}
.worker-file-tree-sandbox .sandbox-tree__children--empty[data-dnd-drag-over='true'][data-dnd-drop-allowed='true'] .sandbox-tree__empty { border-color: color-mix(in srgb, var(--sl-color-accent-high) 54%, var(--worker-sandbox-border)); background: color-mix(in srgb, var(--sl-color-accent-low) 18%, transparent); color: var(--sl-color-text-accent);}
.worker-file-tree-sandbox .sandbox-tree__children--empty [data-dnd-placeholder='true'] { visibility: hidden;}
.worker-file-tree-sandbox .sandbox-tree__node { display: grid; gap: 0; margin: 0; position: relative;}
.worker-file-tree-sandbox .sandbox-tree__children > .sandbox-tree__node::after { content: ''; position: absolute; top: 1.15rem; left: -0.85rem; width: 0.75rem; height: 0.7rem; border-left: 1px solid var(--worker-tree-branch); border-bottom: 1px solid var(--worker-tree-branch); border-bottom-left-radius: 0.4rem;}
.worker-file-tree-sandbox .sandbox-tree__children > .sandbox-tree__node:last-child::before { content: ''; position: absolute; top: calc(1.15rem + 0.7rem); bottom: -0.35rem; left: -0.85rem; width: 1px; background: var(--worker-tree-surface-fill);}
.worker-file-tree-sandbox .sandbox-tree__row,[data-dnd-overlay='true'] .sandbox-tree__row { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 0.55rem; align-items: center; margin: 0; padding: 0.42rem 0.4rem; border: 0; border-radius: 0.55rem; background: transparent; box-shadow: none; min-width: 0; transition: background-color 140ms ease;}
.worker-file-tree-sandbox .sandbox-tree__row:hover,.worker-file-tree-sandbox .sandbox-tree__row:focus-within { background: color-mix(in srgb, var(--sl-color-accent-high) 4%, transparent);}
.worker-file-tree-sandbox .sandbox-tree__row--folder .sandbox-tree__name,[data-dnd-overlay='true'] .sandbox-tree__row--folder .sandbox-tree__name { color: color-mix(in srgb, #0f766e 82%, var(--sl-color-text));}
.worker-file-tree-sandbox .sandbox-tree__row-main,[data-dnd-overlay='true'] .sandbox-tree__row-main { display: flex; align-items: center; gap: 0.6rem; margin: 0; min-width: 0;}
.worker-file-tree-sandbox .sandbox-tree__toggle,.worker-file-tree-sandbox .sandbox-tree__action,.worker-file-tree-sandbox .sandbox-tree__handle,.worker-file-tree-sandbox .sandbox-tree__toolbar-action,[data-dnd-overlay='true'] .sandbox-tree__handle { margin: 0; border: 0; border-radius: 0.55rem; background: transparent; color: var(--sl-color-text); font: inherit; cursor: pointer; transition: background-color 140ms ease, color 140ms ease, opacity 140ms ease; flex: none;}
.worker-file-tree-sandbox .sandbox-tree__toggle,.worker-file-tree-sandbox .sandbox-tree__handle,[data-dnd-overlay='true'] .sandbox-tree__handle,.worker-file-tree-sandbox .sandbox-tree__action,.worker-file-tree-sandbox .sandbox-tree__toolbar-action { display: inline-grid; place-items: center; width: 1.9rem; height: 1.9rem; padding: 0;}
.worker-file-tree-sandbox .sandbox-tree__toggle:hover,.worker-file-tree-sandbox .sandbox-tree__toggle:focus-visible,.worker-file-tree-sandbox .sandbox-tree__action:hover,.worker-file-tree-sandbox .sandbox-tree__action:focus-visible,.worker-file-tree-sandbox .sandbox-tree__handle:hover,.worker-file-tree-sandbox .sandbox-tree__handle:focus-visible,.worker-file-tree-sandbox .sandbox-tree__toolbar-action:hover,.worker-file-tree-sandbox .sandbox-tree__toolbar-action:focus-visible,[data-dnd-overlay='true'] .sandbox-tree__handle { background: color-mix(in srgb, var(--sl-color-accent-high) 8%, transparent); color: var(--sl-color-text-accent);}
.worker-file-tree-sandbox .sandbox-tree__handle,[data-dnd-overlay='true'] .sandbox-tree__handle { cursor: grab;}
.worker-file-tree-sandbox .sandbox-tree__grip,[data-dnd-overlay='true'] .sandbox-tree__grip { display: block; width: 0.72rem; height: 1.08rem; color: color-mix(in srgb, var(--sl-color-gray-3) 88%, var(--sl-color-text));}
.worker-file-tree-sandbox .sandbox-tree__node-icon,[data-dnd-overlay='true'] .sandbox-tree__node-icon { flex: none; width: 1rem; height: 1rem; margin: 0; color: color-mix(in srgb, var(--sl-color-gray-2) 85%, var(--sl-color-text));}
.worker-file-tree-sandbox .sandbox-tree__meta,[data-dnd-overlay='true'] .sandbox-tree__meta { display: grid; margin: 0; min-width: 0;}
.worker-file-tree-sandbox .sandbox-tree__name,[data-dnd-overlay='true'] .sandbox-tree__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 700;}
.worker-file-tree-sandbox .sandbox-tree__input { width: min(100%, 22rem); min-width: 0; margin: 0; padding: 0.36rem 0.55rem; border: 1px solid color-mix(in srgb, var(--sl-color-accent-high) 28%, var(--worker-sandbox-border)); border-radius: 0.7rem; background: color-mix(in srgb, var(--sl-color-white) 8%, transparent); color: var(--sl-color-text); font: inherit;}
.worker-file-tree-sandbox .sandbox-tree__row-actions,[data-dnd-overlay='true'] .sandbox-tree__row-actions { display: flex; align-items: center; justify-self: end; flex-wrap: nowrap; gap: 0.12rem; margin: 0; min-width: 0; white-space: nowrap;}
.worker-file-tree-sandbox .sandbox-tree__action--danger { color: color-mix(in srgb, #ef4444 78%, var(--sl-color-text));}
.worker-file-tree-sandbox .sandbox-tree__action-icon { width: 0.98rem; height: 0.98rem; margin: 0;}
.worker-file-tree-sandbox [data-dnd-dragging='true'] { visibility: hidden; opacity: 0; pointer-events: none;}
.worker-file-tree-sandbox [data-dnd-drop-forbidden='true'] { opacity: 0.55; filter: grayscale(0.22);}
[data-dnd-overlay='true'] { --worker-sandbox-border: color-mix(in srgb, var(--sl-color-hairline-light) 78%, transparent); position: fixed; top: 0; left: 0; z-index: 9999; width: var(--dnd-overlay-width); pointer-events: none; transform: translate3d(-9999px, -9999px, 0); will-change: transform; color: var(--sl-color-text); font: inherit;}
[data-dnd-overlay='true'] .sandbox-tree__row { position: relative; grid-template-columns: auto minmax(0, 1fr); width: fit-content; min-width: 0; max-width: min(36rem, calc(100vw - 2rem)); border: 1px solid color-mix(in srgb, var(--sl-color-accent-high) 40%, var(--worker-sandbox-border)); border-radius: 0.75rem; background: linear-gradient(180deg, color-mix(in srgb, var(--sl-color-white) 72%, transparent), color-mix(in srgb, var(--sl-color-bg) 98%, var(--sl-color-white))), color-mix(in srgb, var(--sl-color-accent-low) 18%, var(--sl-color-bg)); box-shadow: 0 1.1rem 2.4rem -1rem color-mix(in srgb, var(--sl-color-black) 38%, transparent), 0 0 0 1px color-mix(in srgb, var(--sl-color-white) 52%, transparent) inset; opacity: 1;}
[data-dnd-overlay='true'] .sandbox-tree__children { display: none;}
[data-dnd-overlay='true'] .sandbox-tree__row::after { content: var(--worker-dragging-label); position: absolute; top: -0.55rem; right: 0.55rem; display: inline-flex; align-items: center; padding: 0.18rem 0.55rem; border-radius: 999px; background: color-mix(in srgb, var(--sl-color-accent-high) 18%, var(--sl-color-bg)); border: 1px solid color-mix(in srgb, var(--sl-color-accent-high) 36%, var(--worker-sandbox-border)); color: var(--sl-color-text-accent); font-size: 0.68rem; font-weight: 800; letter-spacing: 0.05em; text-transform: uppercase;}
[data-dnd-overlay='true'] .sandbox-tree__toggle,[data-dnd-overlay='true'] .sandbox-tree__action,[data-dnd-overlay='true'] .sandbox-tree__toolbar-action { pointer-events: none;}
[data-dnd-overlay='true'] .sandbox-tree__row-main { min-width: 0;}
[data-dnd-overlay='true'] .sandbox-tree__grip,[data-dnd-overlay='true'] .sandbox-tree__node-icon,[data-dnd-overlay='true'] .sandbox-tree__action-icon,[data-dnd-overlay='true'] .sandbox-tree__name { opacity: 1; visibility: visible;}
[data-dnd-overlay='true'] .sandbox-tree__grip,[data-dnd-overlay='true'] .sandbox-tree__node-icon,[data-dnd-overlay='true'] .sandbox-tree__name { color: var(--sl-color-text);}
[data-dnd-overlay='true'] .sandbox-tree__handle { cursor: grabbing;}
[data-dnd-overlay='true'] .sandbox-tree__row-actions { display: none;}
.worker-file-tree-sandbox [data-dnd-placeholder='true'] { display: grid; gap: 0; margin: 0;}
.worker-file-tree-sandbox [data-dnd-placeholder='true'] > .sandbox-tree__row { border: 1px dashed color-mix(in srgb, var(--sl-color-accent-high) 46%, var(--worker-sandbox-border)); border-radius: 0.75rem; background: linear-gradient(180deg, color-mix(in srgb, var(--sl-color-accent-low) 18%, transparent), color-mix(in srgb, var(--sl-color-accent-high) 4%, transparent)); box-shadow: none;}
.worker-file-tree-sandbox [data-dnd-placeholder='true'] > .sandbox-tree__children { display: none;}
.worker-file-tree-sandbox [data-dnd-placeholder='true'] .sandbox-tree__row-actions { display: none;}
.worker-file-tree-sandbox [data-dnd-placeholder='true'] > * { opacity: 0.14;}
.worker-file-tree-sandbox [data-dnd-placeholder='true'] .sandbox-tree__grip,.worker-file-tree-sandbox [data-dnd-placeholder='true'] .sandbox-tree__node-icon,.worker-file-tree-sandbox [data-dnd-placeholder='true'] .sandbox-tree__name { opacity: 0.28;}
@media (max-width: 52rem) { .worker-file-tree-sandbox .sandbox-tree__row-actions, [data-dnd-overlay='true'] .sandbox-tree__row-actions { justify-self: end; padding-left: 0; }}