Friday, 25 July 2026

Session History

Page editor panel: structured .page format, block registry, rich-text toolbar, default theme.

What we built

Rename: html-editor-panelpage-editor-panel

The old HTML editor panel is replaced by page-editor-panel (PageEditorPanel), handling .page files instead of .html/.htm. All references updated across: FileEditorRegistry.ts, editor.html, editor-shell.ts, tab-container.ts, file-tree-panel.ts. The old folder was deleted. editor-shell no longer auto-opens index.html on startup — the panel shows an empty state until the user opens a .page file from the file tree.

Structured .page format

A .page file is a full HTML document whose <body> must contain exactly one <page-header>, one <page-root>, and one <page-footer> as direct children, in that order. <page-root> holds any number of <page-block> elements; each block contains one or more <page-area> elements for rich-text content.

Format validation is a placeholder function (validatePageFormat) that always returns true, documented for future implementation. When real validation is added, invalid files fall back to code-panel.

Auto-template for empty files: when a .page file is opened with empty content, validation is bypassed and the standard template (full-width block, default theme) is injected automatically. The document is marked dirty — the user must save to persist the structure.

Block registry

Available block templates are defined in a static table (PAGE_BLOCK_REGISTRY) in page-editor-panel.ts. Each entry has a name, optional CSS-sketch preview markup (using .pbp-* helper classes), and an html snippet appended to <page-root> when selected. Two standard blocks ship:

  • Full Width — one <page-area> spanning the full container width (pep-block-full).
  • Two Columns — two equal <page-area> elements side by side on landscape; stacked top-to-bottom on portrait via a CSS media query (pep-block-two-col).

Block items without a preview entry show their name as a text label inside the preview box.

Two-mode sidebar

Two icon buttons on the left edge of the panel switch between editing modes:

  • Blocks mode (⊞) — shows a horizontal scrollable list of block templates. Each entry has a CSS layout sketch preview above its name. Clicking a block appends it to <page-root> in the iframe and makes its new <page-area> elements contenteditable immediately, without a full re-render.
  • Areas mode (T) — shows the rich-text formatting toolbar. Buttons: B (bold), I (italic), U (underline), then a separator, then H1, H2, H3.

Rich-text: wrapSelection

Formatting is applied via wrapSelection(doc, range, tagName, attributes?) — no execCommand. The helper uses Range.extractContents() to pull the selected fragment out of the DOM, wraps it in the target element (created in the iframe document), and re-inserts via Range.insertNode(). The Range API automatically splits text nodes and element boundaries, so both fully-contained nodes and boundary intersections are handled correctly. Semantic tags are preferred: <b>, <i>, <u>, <h1><h3>. Adjacent identical elements are not merged after wrapping (future work, documented).

iframe sandbox + editor CSS injection

The editor iframe carries sandbox="allow-same-origin", blocking script execution for user-authored <script> tags. The sandbox attribute is documented as the single point to change if sandboxing needs adjustment.

Editor-side layout styles (PEP_EDITOR_STYLES) are injected into the live iframe <head> post-load as <style id="pep-editor-injected">. Before saving, the element is temporarily removed, outerHTML is captured, then the element is re-appended — so the injected styles never reach disk. The MutationObserver watches only <page-root>, so head mutations do not trigger change events.

Default theme: default-roject

All theme CSS rules are scoped to [data-theme="default-roject"]. The <body> and <page-root> in the standard template both carry this attribute so the theme applies to the full document and to all content inside blocks.

The theme CSS (DEFAULT_THEME_CSS) is embedded as a <style> block in the page <head> for new files. Future plan: replace with a <link> to styles.rokojori.com when themes are hosted there.

Styles defined:

  • Base: dark background (#0f1117), whitish copy (#c8cce0), Barlow font via @import from styles.rokojori.com, 1rem / 1.7 line-height.
  • h1: blue (#7c8cff), 2.5rem, 900 italic, uppercase.
  • h2: blue (#7c8cff), 1.6rem, 700.
  • h3: muted blue (#9ba4c7), 1.2rem, 700.
  • b / strong: bright white (#e2e4ed).

Key decisions

Placeholder validation instead of gating immediately. The real format check (exactly one page-header / page-root / page-footer in body, no other elements) is documented but not enforced yet — validatePageFormat always returns true. This keeps the editor usable while the format stabilises, without hiding the validation contract.

wrapSelection for headings, not block-level replacement. H1/H2/H3 buttons wrap the selection the same way as B/I/U rather than replacing the parent block element. This is consistent with the existing system and avoids the complexity of block-level conversion for now. The limitation is documented.

Theme attribute on both <body> and <page-root>. Putting data-theme on <body> lets the base styles (background, font, colour) apply to the full document without a separate unscoped body { } rule. <page-root> keeps its own attribute for future per-block theme overrides.