Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions docs/current/PACKAGE_SURFACE.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,10 @@ unaffected: their `tagName` export remains the registration tag.

### v0.44 experimental additions (unfrozen)

| Export (element root) | Class | Purpose | Removal/move condition |
| ----------------------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| `isDangerousKey`, `injectPropsSafe`, `DANGEROUS_KEYS` | experimental (#1214) | The one canonical prototype-pollution guard shared by page projection (SPA bootstrap, `projectPageProps`) and adapter codegen, which serializes `DANGEROUS_KEYS` into generated server runtimes at build time | May move to a dedicated security subpath at the B1.2 surface freeze |
| Export (element root) | Class | Purpose | Removal/move condition |
| ----------------------------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `isDangerousKey`, `injectPropsSafe`, `DANGEROUS_KEYS` | experimental (#1214) | The one canonical prototype-pollution guard shared by page projection (SPA bootstrap, `projectPageProps`) and adapter codegen, which serializes `DANGEROUS_KEYS` into generated server runtimes at build time | May move to a dedicated security subpath at the B1.2 surface freeze |
| `element`, `property` | experimental (#1209) | Compile-time-only decorator intrinsics: the compiler admits them by binding provenance (a runtime named import from `@openelement/element`) and erases them from generated code; evaluated without the compiler (unit tests, config evaluation) they are inert no-ops, carrying no runtime semantics and acting as no second recognizer | May move to a dedicated authoring subpath at the B1.2 surface freeze |

## Removed from current graph

Expand Down
30 changes: 10 additions & 20 deletions docs/current/v0.44.0-MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,7 @@ The canonical route module is a thin wrapper around the compiled page element

```tsx
// app/components/page-form.tsx — compiled by the open:compiled-element transform
import { OpenElement } from '@openelement/element';

declare function element(
tag: string,
options?: { root: 'light' | 'shadow-open' | 'shadow-closed' },
): ClassDecorator;
declare function property(options: { reflect: boolean; attribute?: false }): PropertyDecorator;
import { element, OpenElement, property } from '@openelement/element';

@element('form-page', { root: 'shadow-open' })
export default class FormPage extends OpenElement {
Expand Down Expand Up @@ -113,15 +107,9 @@ Islands are single-module compiled classes; the delivery policy stays in the
statically scanned `defineIslandConfig` export:

```tsx
import { OpenElement } from '@openelement/element';
import { element, OpenElement, property } from '@openelement/element';
import { defineIslandConfig } from '@openelement/app';

declare function element(
tag: string,
options?: { root: 'light' | 'shadow-open' | 'shadow-closed' },
): ClassDecorator;
declare function property(options: { reflect: boolean; attribute?: false }): PropertyDecorator;

export const openElement = defineIslandConfig({ hydrate: 'visible', ssr: true, dsd: true });

@element('live-counter', { root: 'shadow-open' })
Expand Down Expand Up @@ -205,12 +193,14 @@ The automatic subset is deliberately narrow:
or a zero-argument render function.

The generated class uses the canonical `element` decorator and `OpenElement`
import. If the source does not already declare the decorator, the codemod adds
`declare function element(tag: string): ClassDecorator;`. This is compiler-only
input and is erased with the decorator; it is not runtime registration
machinery. Existing class method bodies are retained as source text; the
compiler remains responsible for lowering supported render syntax to the Part
Program.
import. If the source does not already import the decorator, the codemod adds
`element` to the canonical named import from `@openelement/element` (#1209:
the compiler admits intrinsics by binding provenance, never by spelling). The
binding is compile-time-only — the compiler erases the decorator application
and strips the binding from generated imports; evaluated without the compiler
it is an inert no-op, not runtime registration machinery. Existing class
method bodies are retained as source text; the compiler remains responsible
for lowering supported render syntax to the Part Program.

## Manual repair cases

Expand Down
4 changes: 3 additions & 1 deletion docs/release/public-interface-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"declarations": {
".": {
"publicShapeSha256": "3334cfeb124b5ad4d7f22fc6647ed8170fd087fc65412a46852a1d26b670b1bb",
"publicShapeSha256": "91ae10b67582b58198720342b1d5e7065af882d94215f04bac7f1adea002c671",
"publicSymbols": [
"ACTION_FETCH_HEADER=value:\"x-openelement-action\"",
"Action=type:{call:(ctx:{env:Env;formData:FormData;params:Record<string, string>;platform:union(Platform|undefined);request:Request;responseHeaders:Headers;route:Route})=>union(Promise<T>|T)}",
Expand Down Expand Up @@ -72,6 +72,7 @@
"createLogger=value:{call:(tag:string)=>{debug:{call:(msg:string,...args:array(unknown))=>void};error:{call:(msg:string,...args:array(unknown))=>void};info:{call:(msg:string,...args:array(unknown))=>void};warn:{call:(msg:string,...args:array(unknown))=>void}}}",
"deepGetElementById=value:{call:(id:string,root:union(Document|ShadowRoot))=>union(HTMLElement|null)}",
"effect=value:{call:(fn:{call:()=>union(void|{call:()=>void})})=>{call:()=>void}}",
"element=value:{call:(_tag:string,_options:union(undefined|{delegatesFocus?:union(false|true|undefined);formAssociated?:union(false|true|undefined);root?:union(\"light\"|\"shadow-closed\"|\"shadow-open\"|undefined)}))=>{call:(target:unknown,context:unknown)=>void}}",
"ensureDeepFragmentNavigation=value:{call:(options:{enabled?:union(false|true|undefined)})=>void}",
"ensurePreHydrationClickCapture=value:{call:(root:union(EventTarget|undefined))=>void}",
"escapeAttr=value:{call:(value:string)=>string}",
Expand All @@ -81,6 +82,7 @@
"isDangerousKey=value:{call:(key:string)=>union(false|true)}",
"isSafeAttributeName=value:{call:(name:string)=>union(false|true)}",
"isValidTagName=value:{call:(tagName:string)=>union(false|true)}",
"property=value:{call:(_options:{attribute?:union(false|string|undefined);converter?:unknown;reflect:union(false|true);type?:unknown})=>{call:(target:unknown,context:unknown)=>void}}",
"provideContext=value:{call:<T>(host:HTMLElement,context:{readonly defaultValue:T;readonly key:symbol},value:T)=>{call:()=>void}}",
"renderDsd=value:{call:(input:union(CustomElementConstructor|string),options:{componentClass?:union(CustomElementConstructor|undefined);projectedChildren?:union(ReadonlyMap<string, TrustedHtml>|undefined);props?:union(Record<string, unknown>|undefined);sourceInfo?:union(undefined|{route?:union(string|undefined);source?:union(string|undefined)});ssrRenderableTags?:union(array(string)|undefined)})=>{errors:array({code:string;message:string;phase:string;recoverable:union(false|true);severity:union(\"error\"|\"warning\");tagName:string});html:string;hydrationHints:array({hydrate?:union(\"idle\"|\"load\"|\"only\"|\"visible\"|undefined);layer:union(\"dsd-interactive\"|\"dsd-static\"|\"light-dom\"|\"pure-island\");tagName:string});metrics:{hasError:union(false|true);layer:union(\"dsd-interactive\"|\"dsd-static\"|\"light-dom\"|\"pure-island\");nestingDepth:number;renderTimeMs:number;tagName:string;templateSize:number}}}",
"reportError=value:{call:(error:{readonly code:string;readonly phase:union(\"build\"|\"csr\"|\"navigation\"|\"render\"|\"ssr\"|\"unknown\"|\"validation\");readonly recoverable:union(false|true);readonly severity:union(\"error\"|\"warning\");readonly statusCode?:union(number|undefined);toJSON:{call:()=>Record<string, unknown>}})=>void}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,7 @@
* shown in each row through the section-level forms (grammar v1 list Regions
* carry no per-row forms).
*/
import { OpenElement } from '@openelement/element';

declare function element(
tag: string,
options?: { root: 'light' | 'shadow-open' | 'shadow-closed' },
): ClassDecorator;
declare function property(
options: { reflect: boolean; attribute?: false },
): (target: undefined, context: ClassFieldDecoratorContext) => void;
import { element, OpenElement, property } from '@openelement/element';

@element('admin-page', { root: 'shadow-open' })
export default class AdminPage extends OpenElement {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
/** /auth/callback page element (v0.44 compiled). The PKCE exchange runs in the loader; the page renders the outcome. */
import { OpenElement } from '@openelement/element';

declare function element(
tag: string,
options?: { root: 'light' | 'shadow-open' | 'shadow-closed' },
): ClassDecorator;
declare function property(
options: { reflect: boolean; attribute?: false },
): (target: undefined, context: ClassFieldDecoratorContext) => void;
import { element, OpenElement, property } from '@openelement/element';

@element('auth-callback', { root: 'shadow-open' })
export default class AuthCallbackPage extends OpenElement {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,7 @@
* fully static conditional Regions; the attempt id rides a property Part on
* the hidden input (the form stays no-JS capable).
*/
import { OpenElement } from '@openelement/element';

declare function element(
tag: string,
options?: { root: 'light' | 'shadow-open' | 'shadow-closed' },
): ClassDecorator;
declare function property(
options: { reflect: boolean; attribute?: false },
): (target: undefined, context: ClassFieldDecoratorContext) => void;
import { element, OpenElement, property } from '@openelement/element';

@element('checkout-page', { root: 'shadow-open' })
export default class CheckoutPage extends OpenElement {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
/** Home page element — public, prerendered at build time (default static intent). */
import { OpenElement } from '@openelement/element';

declare function element(
tag: string,
options?: { root: 'light' | 'shadow-open' | 'shadow-closed' },
): ClassDecorator;
import { element, OpenElement } from '@openelement/element';

@element('index-page', { root: 'shadow-open' })
export default class HomePage extends OpenElement {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,7 @@
* placeholder keeps "no provider configured" an explicit page state that
* Tier-2 evidence can assert on.
*/
import { OpenElement } from '@openelement/element';

declare function element(
tag: string,
options?: { root: 'light' | 'shadow-open' | 'shadow-closed' },
): ClassDecorator;
declare function property(
options: { reflect: boolean; attribute?: false },
): (target: undefined, context: ClassFieldDecoratorContext) => void;
import { element, OpenElement, property } from '@openelement/element';

@element('login-page', { root: 'shadow-open' })
export default class LoginPage extends OpenElement {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,7 @@
* /magic-link page element (v0.44 compiled). Request-time rendered; the props
* projector lives in app/route-logic/magic-link.ts.
*/
import { OpenElement } from '@openelement/element';

declare function element(
tag: string,
options?: { root: 'light' | 'shadow-open' | 'shadow-closed' },
): ClassDecorator;
declare function property(
options: { reflect: boolean; attribute?: false },
): (target: undefined, context: ClassFieldDecoratorContext) => void;
import { element, OpenElement, property } from '@openelement/element';

@element('magic-link-page', { root: 'shadow-open' })
export default class MagicLinkPage extends OpenElement {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,7 @@
* dashed dynamic host attributes); the island's attribute-backed @property
* fields receive them through the SSR expansion and the client claim.
*/
import { OpenElement } from '@openelement/element';

declare function element(
tag: string,
options?: { root: 'light' | 'shadow-open' | 'shadow-closed' },
): ClassDecorator;
declare function property(
options: { reflect: boolean; attribute?: false },
): (target: undefined, context: ClassFieldDecoratorContext) => void;
import { element, OpenElement, property } from '@openelement/element';

@element('notes-page', { root: 'shadow-open' })
export default class NotesPage extends OpenElement {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,7 @@
* onto the compiled properties. The sent confirmation is a fully static
* conditional Region branch; the action error rides the text Part.
*/
import { OpenElement } from '@openelement/element';

declare function element(
tag: string,
options?: { root: 'light' | 'shadow-open' | 'shadow-closed' },
): ClassDecorator;
declare function property(
options: { reflect: boolean; attribute?: false },
): (target: undefined, context: ClassFieldDecoratorContext) => void;
import { element, OpenElement, property } from '@openelement/element';

@element('recover-page', { root: 'shadow-open' })
export default class RecoverPage extends OpenElement {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
/** /reset-password page element (v0.44 compiled). */
import { OpenElement } from '@openelement/element';

declare function element(
tag: string,
options?: { root: 'light' | 'shadow-open' | 'shadow-closed' },
): ClassDecorator;
declare function property(
options: { reflect: boolean; attribute?: false },
): (target: undefined, context: ClassFieldDecoratorContext) => void;
import { element, OpenElement, property } from '@openelement/element';

@element('reset-password-page', { root: 'shadow-open' })
export default class ResetPasswordPage extends OpenElement {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,7 @@
* fully static conditional Region branch; the action error and the email echo
* ride text/property Parts.
*/
import { OpenElement } from '@openelement/element';

declare function element(
tag: string,
options?: { root: 'light' | 'shadow-open' | 'shadow-closed' },
): ClassDecorator;
declare function property(
options: { reflect: boolean; attribute?: false },
): (target: undefined, context: ClassFieldDecoratorContext) => void;
import { element, OpenElement, property } from '@openelement/element';

@element('signup-page', { root: 'shadow-open' })
export default class SignupPage extends OpenElement {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,7 @@
* per-item attributes, so the 0.43 per-row download links/delete forms are
* outside v1); deletion posts the object key from the section-level form.
*/
import { OpenElement } from '@openelement/element';

declare function element(
tag: string,
options?: { root: 'light' | 'shadow-open' | 'shadow-closed' },
): ClassDecorator;
declare function property(
options: { reflect: boolean; attribute?: false },
): (target: undefined, context: ClassFieldDecoratorContext) => void;
import { element, OpenElement, property } from '@openelement/element';

@element('upload-page', { root: 'shadow-open' })
export default class UploadPage extends OpenElement {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,7 @@
* inputs ride property Parts (dynamic intrinsic attributes are outside the
* SSR part schema in grammar v1) so the active filters survive paging.
*/
import { OpenElement } from '@openelement/element';

declare function element(
tag: string,
options?: { root: 'light' | 'shadow-open' | 'shadow-closed' },
): ClassDecorator;
declare function property(
options: { reflect: boolean; attribute?: false },
): (target: undefined, context: ClassFieldDecoratorContext) => void;
import { element, OpenElement, property } from '@openelement/element';

@element('workspace-records-page', { root: 'shadow-open' })
export default class WorkspaceRecordsPage extends OpenElement {
Expand Down
10 changes: 1 addition & 9 deletions examples/supabase-cloudflare-starter/app/islands/notes-live.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,13 @@
* connectedCallback.
*/
import { defineIslandConfig } from '@openelement/app';
import { OpenElement } from '@openelement/element';
import { element, OpenElement, property } from '@openelement/element';
import {
connectNotesLive,
disconnectNotesLive,
reconnectNotesLiveNow,
} from '../components/notes-live-shared.ts';

declare function element(
tag: string,
options?: { root: 'light' | 'shadow-open' | 'shadow-closed' },
): ClassDecorator;
declare function property(
options: { reflect: boolean; attribute?: false },
): (target: undefined, context: ClassFieldDecoratorContext) => void;

export const openElement = defineIslandConfig({
hydrate: 'load',
ssr: true,
Expand Down
7 changes: 1 addition & 6 deletions examples/supabase-cloudflare-starter/app/shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@
* server entry registers and renders the class; the static <slot> projects
* the page host. No `static styles` — the shell never hydrates client-side.
*/
import { OpenElement } from '@openelement/element';

declare function element(
tag: string,
options?: { root: 'light' | 'shadow-open' | 'shadow-closed' },
): ClassDecorator;
import { element, OpenElement } from '@openelement/element';

@element('ref-layout', { root: 'shadow-open' })
export default class RefLayout extends OpenElement {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@
*
* This is the exact TSX grammar recognized by the open:compiled-element v1
* transform. The file is transformed through the Vite plugin hook; it is never
* executed directly. The local decorator declarations exist only so the fixture
* is self-contained - the canonical decorator contract is owned by #1162.
* executed directly. The intrinsics are canonical named imports - the
* compile-time-only element/property bindings are stripped from generated
* output (the canonical decorator contract is owned by #1162).
*/
import { OpenElement } from '@openelement/element';

declare function element(tag: string): ClassDecorator;
declare function property(options: { reflect: boolean }): PropertyDecorator;
import { element, OpenElement, property } from '@openelement/element';

@element('oe-program-counter')
export class ProgramCounter extends OpenElement {
Expand Down
Loading
Loading