2026-07-25 18:52:01 +00:00
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
|
<html lang="en">
|
|
|
|
|
|
<head>
|
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
2026-07-31 11:17:40 +00:00
|
|
|
|
<title>Friday, 24 July 2026 — Roject</title>
|
2026-07-25 18:52:01 +00:00
|
|
|
|
<link rel="stylesheet" href="../../../../_assets_/styles.css">
|
|
|
|
|
|
<link rel="stylesheet" href="../../../../_assets_/nav.css">
|
|
|
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
|
|
|
<div class="page">
|
|
|
|
|
|
|
|
|
|
|
|
<header>
|
2026-07-31 11:17:40 +00:00
|
|
|
|
<p class="date">Friday, 24 July 2026</p>
|
2026-07-25 18:52:01 +00:00
|
|
|
|
<h1>Session History</h1>
|
|
|
|
|
|
<p class="subtitle">Page editor panel: structured .page format, block registry, rich-text toolbar, default theme.</p>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<section>
|
|
|
|
|
|
<h2>What we built</h2>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<h3>Rename: <code>html-editor-panel</code> → <code>page-editor-panel</code></h3>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
The old HTML editor panel is replaced by <code>page-editor-panel</code>
|
|
|
|
|
|
(<code>PageEditorPanel</code>), handling <code>.page</code> files instead of
|
|
|
|
|
|
<code>.html</code>/<code>.htm</code>. All references updated across:
|
|
|
|
|
|
<code>FileEditorRegistry.ts</code>, <code>editor.html</code>,
|
|
|
|
|
|
<code>editor-shell.ts</code>, <code>tab-container.ts</code>,
|
|
|
|
|
|
<code>file-tree-panel.ts</code>. The old folder was deleted.
|
|
|
|
|
|
<code>editor-shell</code> no longer auto-opens <code>index.html</code> on startup
|
|
|
|
|
|
— the panel shows an empty state until the user opens a <code>.page</code> file
|
|
|
|
|
|
from the file tree.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<h3>Structured <code>.page</code> format</h3>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
A <code>.page</code> file is a full HTML document whose <code><body></code>
|
|
|
|
|
|
must contain exactly one <code><page-header></code>, one
|
|
|
|
|
|
<code><page-root></code>, and one <code><page-footer></code> as direct
|
|
|
|
|
|
children, in that order.
|
|
|
|
|
|
<code><page-root></code> holds any number of <code><page-block></code>
|
|
|
|
|
|
elements; each block contains one or more <code><page-area></code> elements
|
|
|
|
|
|
for rich-text content.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p style="margin-top:0.75rem">
|
|
|
|
|
|
Format validation is a placeholder function (<code>validatePageFormat</code>) that
|
|
|
|
|
|
always returns <code>true</code>, documented for future implementation. When real
|
|
|
|
|
|
validation is added, invalid files fall back to <code>code-panel</code>.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p style="margin-top:0.75rem">
|
|
|
|
|
|
<strong>Auto-template for empty files:</strong> when a <code>.page</code> 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.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<h3>Block registry</h3>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
Available block templates are defined in a static table
|
|
|
|
|
|
(<code>PAGE_BLOCK_REGISTRY</code>) in <code>page-editor-panel.ts</code>. Each
|
|
|
|
|
|
entry has a <code>name</code>, optional CSS-sketch <code>preview</code> markup
|
|
|
|
|
|
(using <code>.pbp-*</code> helper classes), and an <code>html</code> snippet
|
|
|
|
|
|
appended to <code><page-root></code> when selected. Two standard blocks ship:
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<ul style="line-height:1.9;margin-top:0.75rem">
|
|
|
|
|
|
<li><strong>Full Width</strong> — one <code><page-area></code> spanning the
|
|
|
|
|
|
full container width (<code>pep-block-full</code>).</li>
|
|
|
|
|
|
<li><strong>Two Columns</strong> — two equal <code><page-area></code> elements
|
|
|
|
|
|
side by side on landscape; stacked top-to-bottom on portrait via a CSS media
|
|
|
|
|
|
query (<code>pep-block-two-col</code>).</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
<p style="margin-top:0.75rem">
|
|
|
|
|
|
Block items without a <code>preview</code> entry show their name as a text label
|
|
|
|
|
|
inside the preview box.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<h3>Two-mode sidebar</h3>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
Two icon buttons on the left edge of the panel switch between editing modes:
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<ul style="line-height:1.9;margin-top:0.75rem">
|
|
|
|
|
|
<li><strong>Blocks mode (⊞)</strong> — 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 <code><page-root></code> in the iframe and makes
|
|
|
|
|
|
its new <code><page-area></code> elements contenteditable immediately,
|
|
|
|
|
|
without a full re-render.</li>
|
|
|
|
|
|
<li><strong>Areas mode (T)</strong> — shows the rich-text formatting toolbar.
|
|
|
|
|
|
Buttons: <code>B</code> (bold), <code>I</code> (italic), <code>U</code>
|
|
|
|
|
|
(underline), then a separator, then <code>H1</code>, <code>H2</code>,
|
|
|
|
|
|
<code>H3</code>.</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<h3>Rich-text: <code>wrapSelection</code></h3>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
Formatting is applied via <code>wrapSelection(doc, range, tagName, attributes?)</code>
|
|
|
|
|
|
— no <code>execCommand</code>. The helper uses <code>Range.extractContents()</code>
|
|
|
|
|
|
to pull the selected fragment out of the DOM, wraps it in the target element
|
|
|
|
|
|
(created in the iframe document), and re-inserts via <code>Range.insertNode()</code>.
|
|
|
|
|
|
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: <code><b></code>, <code><i></code>,
|
|
|
|
|
|
<code><u></code>, <code><h1></code>–<code><h3></code>.
|
|
|
|
|
|
Adjacent identical elements are not merged after wrapping (future work, documented).
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<h3>iframe sandbox + editor CSS injection</h3>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
The editor iframe carries <code>sandbox="allow-same-origin"</code>, blocking script
|
|
|
|
|
|
execution for user-authored <code><script></code> tags. The sandbox attribute
|
|
|
|
|
|
is documented as the single point to change if sandboxing needs adjustment.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p style="margin-top:0.75rem">
|
|
|
|
|
|
Editor-side layout styles (<code>PEP_EDITOR_STYLES</code>) are injected into the
|
|
|
|
|
|
live iframe <code><head></code> post-load as
|
|
|
|
|
|
<code><style id="pep-editor-injected"></code>. Before saving, the element is
|
|
|
|
|
|
temporarily removed, <code>outerHTML</code> is captured, then the element is
|
|
|
|
|
|
re-appended — so the injected styles never reach disk. The MutationObserver watches
|
|
|
|
|
|
only <code><page-root></code>, so head mutations do not trigger change events.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<h3>Default theme: <code>default-roject</code></h3>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
All theme CSS rules are scoped to <code>[data-theme="default-roject"]</code>.
|
|
|
|
|
|
The <code><body></code> and <code><page-root></code> in the standard
|
|
|
|
|
|
template both carry this attribute so the theme applies to the full document and
|
|
|
|
|
|
to all content inside blocks.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p style="margin-top:0.75rem">
|
|
|
|
|
|
The theme CSS (<code>DEFAULT_THEME_CSS</code>) is embedded as a
|
|
|
|
|
|
<code><style></code> block in the page <code><head></code> for new
|
|
|
|
|
|
files. Future plan: replace with a <code><link></code> to
|
|
|
|
|
|
<code>styles.rokojori.com</code> when themes are hosted there.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p style="margin-top:0.75rem">Styles defined:</p>
|
|
|
|
|
|
<ul style="line-height:1.9;margin-top:0.5rem">
|
|
|
|
|
|
<li>Base: dark background (<code>#0f1117</code>), whitish copy (<code>#c8cce0</code>),
|
|
|
|
|
|
Barlow font via <code>@import</code> from <code>styles.rokojori.com</code>,
|
|
|
|
|
|
<code>1rem / 1.7</code> line-height.</li>
|
|
|
|
|
|
<li><code>h1</code>: blue (<code>#7c8cff</code>), 2.5rem, 900 italic, uppercase.</li>
|
|
|
|
|
|
<li><code>h2</code>: blue (<code>#7c8cff</code>), 1.6rem, 700.</li>
|
|
|
|
|
|
<li><code>h3</code>: muted blue (<code>#9ba4c7</code>), 1.2rem, 700.</li>
|
|
|
|
|
|
<li><code>b</code> / <code>strong</code>: bright white (<code>#e2e4ed</code>).</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
2026-07-25 20:50:14 +00:00
|
|
|
|
<section>
|
|
|
|
|
|
<h2>Tab container updates</h2>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<h3>Split submenu + vertical split</h3>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
The single <em>Split</em> menu entry is replaced by a <em>Split ></em> submenu
|
|
|
|
|
|
with <em>↔ Horizontally</em> (adds a new section side by side in <code>.es-sections</code>)
|
|
|
|
|
|
and <em>↕ Vertically</em> (adds a second <code>tab-container</code> + horizontal
|
|
|
|
|
|
resize handle inside the same <code>.es-section</code>). The
|
|
|
|
|
|
<code>tab-container:split</code> event now carries a <code>direction</code> field.
|
|
|
|
|
|
<code>.es-section</code> was already <code>flex-direction: column</code>, so no CSS
|
|
|
|
|
|
changes were needed for vertical split.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<h3>Close Container</h3>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
A <em>Close Container</em> context menu entry fires <code>tab-container:close-container</code>,
|
|
|
|
|
|
handled by <code>editor-shell</code>: if the container is in a vertical split (multiple
|
|
|
|
|
|
<code>tab-container</code> elements in the section), only that container and its adjacent
|
|
|
|
|
|
handle are removed; otherwise the whole section and its adjacent <code>es-v-handle</code>
|
|
|
|
|
|
are removed. The entry is hidden when the container is the last one in its
|
|
|
|
|
|
<code>.es-panel</code> slot. If any tab has unsaved changes,
|
|
|
|
|
|
<code>showConfirmDialog</code> prompts with <em>Don't Close</em> /
|
|
|
|
|
|
<em>Close Without Saving</em> before dispatching the event.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<h3>Middle-mouse tab close</h3>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
Tabs now close on middle-mouse click: a <code>mousedown</code> listener with
|
|
|
|
|
|
<code>e.button === 1</code> calls <code>e.preventDefault()</code> (suppresses the
|
|
|
|
|
|
browser scroll cursor) and extracts the tab immediately.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<h3>EditorPanel / FileEditorPanel interface system</h3>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
New file <code>source/editor/editor-panel.ts</code> defines the panel contract.
|
|
|
|
|
|
<code>EditorPanel</code> requires <code>__interfaces__: string[]</code> and
|
|
|
|
|
|
<code>addContextMenuEntries()</code>. <code>FileEditorPanel extends EditorPanel</code>
|
|
|
|
|
|
adds <code>hasUnsavedChanges(): boolean</code>, replacing the old
|
|
|
|
|
|
<code>TabEntry.dirty</code> flag — <code>renderBar()</code> now queries the panel
|
|
|
|
|
|
live instead of caching a boolean.
|
|
|
|
|
|
Each interface has a companion Definition class with <code>static readonly type</code>;
|
|
|
|
|
|
<code>implementsInterface(el, Def)</code> is the single runtime check function.
|
|
|
|
|
|
All five panels (<code>page-editor-panel</code>, <code>code-panel</code>,
|
|
|
|
|
|
<code>file-tree-panel</code>, <code>rojo-settings-panel</code>,
|
|
|
|
|
|
<code>rojo-chat-panel</code>) updated with <code>__interfaces__</code>.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<h3>Bug fixes</h3>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
<strong>Section resize:</strong> <code>setupResizeHandler</code> previously observed
|
|
|
|
|
|
only the workspace element, so section redistribution never fired when a panel was
|
|
|
|
|
|
dragged (workspace size doesn't change on panel drag). Fixed by observing each
|
|
|
|
|
|
<code>.es-sections</code> element directly — when the panel narrows, its sections
|
|
|
|
|
|
container narrows too and the observer redistributes sections correctly.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p style="margin-top:0.75rem">
|
|
|
|
|
|
<strong>Portrait → landscape:</strong> <code>showPortraitPanel</code> set inline
|
|
|
|
|
|
<code>display: none</code> on panels and handles, but returning to landscape only
|
|
|
|
|
|
removed the <code>portrait</code> class — the inline styles persisted and kept panels
|
|
|
|
|
|
hidden. Fixed by clearing <code>style.display</code> on all panels and
|
|
|
|
|
|
<code>.es-v-handle</code> elements when <code>apply(false)</code> runs.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
2026-07-25 18:52:01 +00:00
|
|
|
|
<section>
|
|
|
|
|
|
<h2>Key decisions</h2>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<p>
|
|
|
|
|
|
<strong>Placeholder validation instead of gating immediately.</strong>
|
|
|
|
|
|
The real format check (exactly one page-header / page-root / page-footer in body,
|
|
|
|
|
|
no other elements) is documented but not enforced yet — <code>validatePageFormat</code>
|
|
|
|
|
|
always returns <code>true</code>. This keeps the editor usable while the format
|
|
|
|
|
|
stabilises, without hiding the validation contract.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<p>
|
|
|
|
|
|
<strong><code>wrapSelection</code> for headings, not block-level replacement.</strong>
|
|
|
|
|
|
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.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<p>
|
|
|
|
|
|
<strong>Theme attribute on both <code><body></code> and
|
|
|
|
|
|
<code><page-root></code>.</strong>
|
|
|
|
|
|
Putting <code>data-theme</code> on <code><body></code> lets the base styles
|
|
|
|
|
|
(background, font, colour) apply to the full document without a separate unscoped
|
|
|
|
|
|
<code>body { }</code> rule. <code><page-root></code> keeps its own attribute
|
|
|
|
|
|
for future per-block theme overrides.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<footer>
|
|
|
|
|
|
Roject — session history
|
|
|
|
|
|
</footer>
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<script>var NAV_ROOT = '../../../../';</script>
|
|
|
|
|
|
<script src="../../../../_assets_/nav-data.js"></script>
|
|
|
|
|
|
<script src="../../../../_assets_/nav.js"></script>
|
|
|
|
|
|
</body>
|
|
|
|
|
|
</html>
|