import { html } from "lit"; import { BaseElement } from "./base-element.ts"; /** * Retired elements: inert passthrough stubs for components we have removed. * * Why this exists: a pattern's source is DURABLE. Pieces keep running the * source they were stored with, for as long as that piece lives — so the set * of element names patterns may emit is not "whatever the current palette * defines", it is the union of every palette that has ever shipped. Deleting a * component therefore does not retire it; it only removes the definition out * from under source that still names it. * * Removing `cf-cell-context` (#5132) demonstrated the cost. Stored home * sections still emitted it, and with both the component and its `jsx.d.ts` * declaration gone, rows that mapped over durable data stopped instantiating — * silently, because the failure surfaced as cell requests that never settled * rather than as an error. Blank Favorites and Profile tabs, nothing in the * console, for weeks. * * So a retired element keeps a definition that renders its children and does * nothing else. It stays a known element, its props keep their declared * meaning, and the subtree underneath it goes on working. It is loud rather * than fatal: one console warning per element per session at runtime. It has * no JSX declaration, because declaring a retired element in * `JSX.IntrinsicElements` stops spaces with stored source that names it from * rendering; new source that names it therefore fails to type-check. Patterns * get rewritten off it at their own pace; the stub leaves only when nothing * durable names it any more. */ const warned = new Set(); /** Warn once per element name per session that durable source still uses it. */ export function warnRetiredElementUsed( tag: string, replacement?: string, ): void { if (warned.has(tag)) return; warned.add(tag); console.warn( `[retired-element] <${tag}> is retired and now renders as an inert ` + `passthrough. Durable pattern source still names it` + (replacement ? `; use ${replacement} instead.` : ".") + ` Update the pattern's source to stop relying on it.`, ); } /** Reset the warn-once ledger. Test seam only. */ export function resetRetiredElementWarnings(): void { warned.clear(); } /** * Base class for a retired element: renders a default slot, nothing else. * * Subclasses declare their own tag name and any props the retired component * carried, so source that binds those props keeps working. * * They must also KEEP THAT COMPONENT'S HOST LAYOUT. This class deliberately * imposes none: an inert element is not a layout-neutral one. Stored source * was authored against the retired component's box — `display: block` with * `flex: 1`, an inline variant, whatever it had — and collapsing every stub to * `display: contents` would silently reflow the pages this exists to keep * working. Copy the retired component's `:host` rules into the stub; the point * is that nothing changes for source that still names it. */ export abstract class RetiredElement extends BaseElement { static override styles = [BaseElement.baseStyles]; /** The tag this stub stands in for. */ protected abstract retiredTag: string; /** What to use instead, when there is a successor. */ protected retiredReplacement?: string; /** * Emit this element's retirement warning. * * Idempotent by way of the warn-once ledger, which is what lets `render()` * call it directly. Warning from render rather than `connectedCallback` * keeps the signal on the path that proves the element was actually used, * and keeps this class free of anything needing a live document — so the * behavior is unit-testable instead of reachable only from a browser lane. */ notifyRetiredUsage(): void { warnRetiredElementUsed(this.retiredTag, this.retiredReplacement); } override render() { this.notifyRetiredUsage(); return html` `; } }