Skip to content
HomePagesHomePages template kit

no-editor-globals

"No reads of the platform's internal `__tr*` canvas globals — `useEditor()` is the contract."

Venue: eslint — No reads of the platform’s internal __tr* canvas globals — useEditor() is the contract.

Section code may not read a property off the global object whose name begins with the platform’s reserved __tr prefix. The rule reports, in any .ts or .tsx file:

  • a dotted read — window.__trIslands;
  • the computed spelling — window["__trEditorBridge"];
  • a destructure off the global — const { __trIslands } = window;.

window, globalThis and self all count, and a write reports as well as a read. Every other global is fine — window.innerWidth, window.addEventListener — an island is browser code and is expected to use browser APIs. Only the reserved prefix reports.

The editor canvas does publish some state on the page’s window. Those properties are internals of the host, not an authoring API, and they carry no compatibility promise.

Your published island bundle is immutable — it is built once and served unchanged for as long as the page lives. The canvas host is not: it ships continuously. So a global your island reads today can be renamed tomorrow, and nothing anywhere fails. An absent global just reads as undefined, which the island interprets as “not in the canvas”, and it quietly behaves as though it were on a published page. That is not a hypothetical: it is how the same code got copied between islands with the name misspelled in one of them, and sat inert for months with no error, no warning, and nothing in the output to notice.

useEditor() is the supported channel and it cannot go stale that way: it is versioned with the kit you built against, it degrades to published-page values rather than to a wrong guess, and it is hydration-safe by construction.

Call useEditor().

"use client";
import { type IslandEditor } from "@homepages/template-kit";
import { useEffect, useState } from "react";
export const editor = { live: true } satisfies IslandEditor;
function isEditorCanvas() {
return typeof window !== "undefined" && window.__trEditorBridge !== undefined;
}
export default function Amenities({ rows }: { rows: { id: string }[] }) {
const [openId, setOpenId] = useState<string | null>(null);
useEffect(() => {
if (!isEditorCanvas()) return;
const selected = window.__trEditorBridge.selection;
setOpenId(rows[selected?.itemIndex ?? 0]?.id ?? null);
}, [rows]);
return <div data-open={openId} />;
}
"use client";
import { type IslandEditor, useEditor } from "@homepages/template-kit";
import { useEffect, useState } from "react";
export const editor = { live: true } satisfies IslandEditor;
export default function Amenities({ rows }: { rows: { id: string }[] }) {
const [openId, setOpenId] = useState<string | null>(null);
const { selection } = useEditor();
useEffect(() => {
if (selection?.kind !== "slot" || selection.itemIndex === undefined) return;
setOpenId(rows[selection.itemIndex]?.id ?? null);
}, [selection, rows]);
return <div data-open={openId} />;
}
  • Islands — what useEditor() returns, and the two patterns that use it.
  • require-editor-reason — the other half: every fork on inCanvas names what it protects.