--- name: aidd-lit description: Enforces Lit element authoring best practices. Use when creating Lit elements, binding elements, presentations, DatabaseElement, useObservableValues, or when the user asks about Lit UI patterns, reactive binding, or action callbacks. --- # Lit element authoring Lit elements live in the `components/` layer per [structure](../aidd-structure/SKILL.md). Consume Observe and void actions from plugins per [service](../aidd-service/SKILL.md). --- ## Extending DatabaseElement Binding elements extend `DatabaseElement` — either directly or via an intermediate base class. The required plugin must be specified in the generic. This ensures the plugin's services are extended onto the main database **before** the element renders, so `this.service` exposes the correct Observe APIs and actions. ```ts // Direct export class HelloWorldElement extends DatabaseElement { get plugin() { return helloWorldPlugin; } } // Indirect — base extends DatabaseElement, leaf specifies plugin export class LayoutElement extends CoreApplicationElement { } export class ToolbarElement extends LayoutElement { } ``` --- ## Binding element vs presentation ```sudolang BindingElement { injects: "observed values via useObservableValues" triggers: "re-render when those values change" binds: "action callbacks to the presentation" } Presentation { type: "pure function (no hooks)" receives: "data and action callbacks as props" returns: "TemplateResult" constraint: "Keep reactive logic in binding element; presentation stays pure" } ``` --- ## Testing ```sudolang Testing { presentation: "add *.test.ts when appropriate; unit test the presentation" bindingElement: "not unit tested — no business logic; uses Database service (already unit tested)" } ``` --- ## useObservableValues *Most* binding elements use a **single** `useObservableValues` call. Collect all observed values in one object. **Observe only what you need** — the minimal values required for rendering. For values that may resolve slowly, wrap with `Observe.withDefault` so you can render a skeleton (or placeholder) immediately while waiting. See [observe](../aidd-observe/SKILL.md). ```ts render() { const values = useObservableValues(() => ({ visible: this.service.actions.isViewVisible(name), userProfile: this.service.services.authentication.userProfile, })); if (!values) return; return presentation.render({ ...values, toggleView: () => this.toggleView(name) }); } ``` --- ## Presentation exports ```sudolang Constraints { Presentation files ONLY export render (and unlocalized bundles where appropriate) Nothing else ever exported from a presentation file For render args type externally: use Parameters[0] } ``` --- ## Action callbacks (not events) ```sudolang PresentationCallbacks { are: "action calls, not events" semantics: "verbNoun — not onClick/onToggle/onSignOut style" bindingElement: "passes action callbacks (e.g. toggleView, signOut) as props" presentation: "invokes them when user intent occurs" callback: "calls the service/transaction directly" } ``` ```ts // Binding element: binds the action (verbNoun) toggleView: () => this.toggleToolbarChild(name) // Presentation: receives and invokes when user acts item.toggleView() ``` --- ## Lit properties ```sudolang PropertyRules { default: "Almost never use @property on binding elements" exception: "Use properties ONLY when needed to bind to the correct entity in the database (e.g. entity for table rows, layer for view hosts)" multipleInstances: "need a property to identify which entity they represent" singleInstanceElements: "have no properties; observe values directly; re-render only when those values change" } ``` --- ## Execute ```sudolang fn whenCreatingOrModifyingLitElement() { Constraints { Extend DatabaseElement (directly or indirectly) with required plugin specified Split into binding element (reactive) and presentation (pure) Use single useObservableValues in binding element; Observe only minimal values; use Observe.withDefault for slow-resolving values Pass observed values and action callbacks to presentation Keep presentation pure — no hooks Add @property only when entity binding requires it (multiple instances) Presentation exports only render (and localization bundles where appropriate) Add *-presentation.test.ts for presentation when appropriate; do not unit test binding elements Never include business logic within binding elements — move into computed values or action handlers Good binding elements should be extremely small } } ```