Friday, 31 July 2026

Session History

File tree UX improvements, page editor toolbar, EditorConsole message system, console-panel tab.

What we built

File tree: open-state preservation on refresh

Before this fix, calling refresh() on file-tree-panel collapsed all open directories. The fix records which ftp-dir elements carry the open class (via data-path on their ftp-dir-label) before rebuilding the HTML, then re-adds open to the matching labels after the new tree renders.

File tree: "Mark As Root Directory" moved to context menu

The double-click listener on ftp-dir-label was removed. "As Root Directory" is now a context menu entry that appears when right-clicking a directory, inside the existing showItemMenu() method. isDir detection uses targetPath.endsWith('/').

File tree: "Open >" submenu for alternate editors

When right-clicking a file that has a known editor, the context menu shows an Open > submenu listing the default editor plus all registered alternatives. Selecting an entry calls _openFileIn(path, editorTag), which focuses an existing tab of that type or creates a new one.

Two static maps drive this: _panelTypeMap (editorTag → panelType + label) and _editorAlternatives (defaultEditorTag → alternativeEditorTags[]). _openFileDefault (single click) focuses the file in any open editor regardless of type; _openFileIn (context menu) focuses only in the specified editor type.

File tree: context menu label truncation

The context menu title now shows only the filename (not the full path), truncated to a configurable menuLabelMaxChars (20). Names longer than the limit are shown as ...last-20-chars.

Page editor: mode buttons moved into toolbar

The left sidebar (.pep-sidebar) and its wrapper (.pep-main) were removed. The Blocks (⊞) and Areas (T) mode buttons were moved directly into .pep-toolbar, separated from the left-side toolbar items by a .pep-toolbar-sep spacer (flex: 1) that pushes them to the right. page-editor-panel now uses flex-direction: column with three direct children: toolbar, mode panel, iframe.

EditorConsole — centralised message system

New singleton EditorConsole (source/editor/EditorConsole.ts) holds a capped ring of 500 ConsoleMessage objects (text, type: 'info'|'error'|'hint', timestamp) and dispatches them via onMessage: EventSlot. showHover / hideHover dispatch a secondary onHover: EventSlot<string | null> for future tooltip use.

editor-shell subscribes to onMessage and shows incoming messages in a new .es-info element in the header — fades in for 5 s then fades out. Portrait mode hides the inline element and would show a fixed bottom bar instead. Editor.get().onFileTypeUnknown is now handled here, logging to EditorConsole rather than as an inline error in file-tree-panel.

console-panel — new editor tab

console-panel is a new tab that renders all messages from EditorConsole. Custom elements: conp-header, conp-title, conp-list, conp-entry, conp-time, conp-text. On connect it pre-populates from EditorConsole.get().messages, then appends new entries as they arrive. Time format: HH:MM:SS. Entry classes (conp-entry-error, conp-entry-hint) colour conp-text accordingly. Implements EditorPanelDefinition.type via __interfaces__.

Bug fix: openDocumentIn ignored the target panel type

Editor.openDocumentIn(filePath, panelElement) was re-resolving editorTag from the FileEditorRegistry, which always returned the default editor for the file type (e.g. page-editor-panel for .page files). The code-panel listener checked if ('code-panel' !== e.editorTag) return and bailed, so the file appeared to open but showed no content.

Fix: derive editorTag from panelElement.tagName.toLowerCase() directly, removing the registry lookup entirely from openDocumentIn. The caller already decided which panel to target; the dispatch should respect that decision.

Key decisions

EditorConsole as a standalone singleton, not part of Editor. Keeping it separate means any component (including future non-editor panels) can import just EditorConsole without pulling in the full editor state. Editor.onFileTypeUnknown is bridged to it in editor-shell, the single component that knows about both.

Two open-file methods instead of one. _openFileDefault (single click) focuses the file in any editor; _openFileIn (context menu "Open >") focuses only in the specified editor type and forces that type when creating a new tab. This makes the intent explicit without adding a flag parameter.