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.
Reason
Section titled “Reason”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().
Before
Section titled “Before”"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} />;}See also
Section titled “See also”- Islands — what
useEditor()returns, and the two patterns that use it. require-editor-reason— the other half: every fork oninCanvasnames what it protects.