The Editor class is the central hub of the frontend editor.
This page documents its events, methods, and properties, and explains the
TypeScript compilation split between client and server code.
Editor lives at src/editor/Editor.ts and compiles
to public/editor/Editor.js. It is a singleton accessed everywhere
via Editor.get(). It owns all open document state, the
FileEditorRegistry, and the events that panels and the tab
container subscribe to. It is the only place where files are fetched from
the server and where saves are sent back.
Never import Editor in server-side code. It is a
browser-only module. The split between client and server code is described in
the section below.
import { Editor } from '../../editor/Editor.js';
const editor = Editor.get();
The .js extension is required in all client-side imports because
the TypeScript output is consumed directly by the browser as ES modules
(no bundler).
| Property | Type | Description |
|---|---|---|
projectId |
string |
UUID of the open project. Set by EditorShell on init from the URL query string. |
projectName |
string |
Display name of the open project. Set by EditorShell on init. |
openDocs |
Map<string, { content, dirty }> |
In-memory cache of all documents fetched this session. Keyed by file path. |
activeDoc |
string | null |
File path of the most recently opened document. |
fileEditorRegistry |
FileEditorRegistry |
Suffix-to-editor-tag resolver. Loaded lazily on first openDocument call. |
Editor.get(): Editor
Returns the singleton instance, creating it on first call.
openDocument( filePath: string ): Promise<void>
The main entry point for opening a file. Loads the registry (once), resolves
the editorTag for the file's suffix. If no tag is found, dispatches
onFileTypeUnknown and returns early. Otherwise fetches the file
content (cached after first fetch), then dispatches onDocumentOpened.
markDirty( filePath: string, content: string ): void
Called by a panel whenever the user edits content. Updates the in-memory cache
and dispatches onDocumentDirty. The tab container uses this to
show the dirty dot.
save( filePath: string ): Promise<void>
PUTs the cached content to /api/files/${projectId}/${filePath}
with Content-Type: text/plain. Clears the dirty flag and
dispatches onDocumentSaved.
All events use EventSlot from the shared library —
not DOM events and not a pub/sub bus. Add a listener with
Editor.get().onSomeEvent.addListener( e => ... ).
Listeners are called synchronously when the event is dispatched.
onDocumentOpened — EventSlot<DocumentOpenedEvent>interface DocumentOpenedEvent {
path: string; // file path relative to project root
content: string; // raw file content
editorTag: string; // custom element tag resolved by FileEditorRegistry
}
Fired after the file is fetched and the editor tag is resolved. Every editor
panel listens to this. Panels must check editorTag and
return early if it does not match their own tag — all panels receive
every event.
Editor.get().onDocumentOpened.addListener( ( e ) =>
{
if ( 'my-panel' !== e.editorTag ) return;
this._loadDocument( e.path, e.content );
} );
onDocumentDirty — EventSlot<DocumentPathEvent>interface DocumentPathEvent { path: string; }
Fired by markDirty. The TabContainer listens and
sets tab.dirty = true for the tab whose panel's
currentPath matches. Panels do not need to listen to this
themselves — they call markDirty and update their own Save button.
onDocumentSaved — EventSlot<DocumentPathEvent>
Fired by save after a successful PUT. The TabContainer
listens and clears the dirty dot for the matching tab.
onFilesChanged — EventSlot<void>
Fired with no payload when the file tree changes (file or folder created,
renamed, or deleted). FileTreePanel listens and re-fetches the
tree. Dispatch it after any operation that modifies the filesystem:
Editor.get().onFilesChanged.dispatch();
onFileTypeUnknown — EventSlot<DocumentPathEvent>
Fired when openDocument is called for a file whose suffix has no
entry in FileEditorRegistry. FileTreePanel listens
and shows a 3-second error banner. No file content is fetched when this fires.
Client and server TypeScript are compiled independently and must never import from each other's side.
| Side | Source | Output | How it runs |
|---|---|---|---|
| Client | src/ |
public/ |
npm run build → tsc --build tsconfig.client.json |
| Server | server/ |
none (in-process) | npm start → ts-node with tsconfig.ts-node.json |
| Library (browser) | src/library-ts/browser/ |
public/library-ts/browser/ |
Compiled via TypeScript project references as part of npm run build |
Component CSS is written directly in
public/components/<name>/<name>.css and is never
processed by TypeScript. Do not put CSS files in src/.
Edit the file in public/ directly; the browser picks it up
on next reload with no build step.
npm run build, then reload the browser.npm start). No build step.
Client TypeScript uses module: ESNext and
moduleResolution: bundler. There is no bundler — the browser
receives individual .js files as ES modules.
All imports in client code must include the .js
extension, even though the source files end in .ts:
import { Editor } from '../../editor/Editor.js';
import { EventSlot } from '../library-ts/browser/events/EventSlot.js';
TypeScript resolves these correctly during compilation because
moduleResolution: bundler allows importing .js
paths that correspond to .ts source files.