- name
- gerrit-frontend-engineering
- description
- Provides guidance and best practices on Polygerrit UI development, frontend architecture, and TypeScript/JS coding standards in Gerrit.
# Frontend Engineering & UI Development Engineering Guide
## Executive Summary
Welcome to the Frontend Engineering & UI Development guide. This repository
serves as the authoritative source of tribal knowledge for our UI architecture,
born from historical refactoring efforts, critical performance optimizations,
and the ongoing necessity to prevent recurrent regression of known failure
modes. By codifying these engineering standards, we ensure that incoming
engineers can confidently navigate the complexities of our frontend ecosystem
without falling into legacy traps, introducing unverified UI states, or
triggering silent runtime failures.
This guide enforces strict architectural boundaries across the entire UI
development lifecycle. It mandates rigorous state encapsulation within the Lit
framework, uncompromising TypeScript type safety, and highly resilient
client-server integrations. It further standardizes hermetic UI testing
methodologies, unifies our CSS design systems, and outlines strict strategies
for client-side performance profiling.
Adherence to these principles guarantees structural consistency and system
reliability. Whether you are building dynamic web components, integrating
complex REST API endpoints, or optimizing data payloads for AI contexts and
telemetry, this documentation establishes the foundational constraints required
to ship a performant, predictable, and scalable user interface.
## Summary
| Chapter Theme / Title | Scope & Objective |
| :----------------------------------- | :------------------------------------ |
| **Lit Framework Idioms & State | Enforce strict Lit framework idioms |
: Encapsulation** : by leveraging declarative rendering, :
: : reactive property encapsulation, and :
: : native lifecycle hooks. Avoid :
: : imperative DOM manipulation and :
: : properly isolate transient UI status :
: : from the core data models. :
| **TypeScript Strictness & Type | This domain governs the strict |
: Safety** : enforcement of TypeScript type safety :
: : by explicitly forbidding unsafe :
: : casting and blanket compiler :
: : suppressions. It mandates precise :
: : component modeling, strict interface :
: : adherence, and proper access :
: : modifiers to eliminate silent runtime :
: : failures and unverified states. :
| **Hermetic Testing & Visual | This chapter mandates the strict |
: Regression** : isolation of unit tests, :
: : comprehensive visual validation using :
: : full shadow DOM snapshots, and the :
: : centralization of test data :
: : generation to guarantee hermetic :
: : execution and prevent false-positive :
: : assertions. :
| **Client-Side Performance & | This theme governs the optimization |
: Telemetry** : of client-side performance by :
: : enforcing strict telemetry on :
: : synchronous CPU-bound operations and :
: : minimizing network latency. It :
: : mandates the caching of asynchronous :
: : API promises, the derivation of state :
: : from existing payloads, and the rigid :
: : bounding of background data requests. :
| **CSS Architecture & Design System | This domain governs the consistent |
: Consistency** : application of styles across the UI, :
: : mandating the use of content-driven :
: : layout techniques, externalized :
: : custom property configurations for :
: : visual assets, and declarative Lit :
: : directives over imperative inline :
: : styling. :
| **API Integration & Error Handling** | This chapter governs the resilient |
: : integration of frontend logic with :
: : REST APIs. It establishes strict :
: : constraints for maintaining backend :
: : payload parity, explicitly modeling :
: : structural variances, and enforcing :
: : centralized error handling over :
: : localized `try...catch` blocks. :
| **AI Context & Telemetry Payload | This domain governs the client-side |
: Optimization** : lifecycle and optimization of data :
: : structures sent to AI agents and :
: : telemetry pipelines. It strictly :
: : dictates payload deduplication :
: : strategies, transparent AI token :
: : constraint surfacing, and rigorous :
: : parsing validation to prevent silent :
: : context loss. :
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
## Chapter: Lit Framework Idioms & State Encapsulation
**Context:** Enforce strict Lit framework idioms by leveraging declarative
rendering, reactive property encapsulation, and native lifecycle hooks. Avoid
imperative DOM manipulation and properly isolate transient UI status from the
core data models.
### Summary
| Rule ID | Principle / | Priority | Primary Symptom / Trap |
: : Constraint : : :
| :-------- | :----------------- | :------- | :------------------------------- |
| **T1-01** | Separation of Data | High | Reusing a data property to hold |
: : and UI State : : UI loading or error text, which :
: : Variables : : corrupts the data being passed :
: : : : to child components or the :
: : : : clipboard. :
| **T1-02** | Explicit Boolean | High | Inspecting the DOM via |
: : Properties over : : `this.children` in Lit element :
: : Dynamic Slot : : methods to determine if a named :
: : Detection : : slot element was provided by the :
: : : : parent. :
| **T1-03** | Declarative Event | Medium | Attaching DOM event listeners |
: : Listeners in Lit : : imperatively within the :
: : Templates : : component constructor. :
| **T1-04** | Declarative | Medium | Chaining ternary operators |
: : Conditional CSS : : inside a template string to :
: : via classMap : : build a class list. :
| **T1-05** | Centralized | Medium | Defining a custom manager object |
: : Periodic : : or singleton strictly tied to :
: : LitElement Updates : : one specific UI component type :
: : via Generic : : for periodic updates. :
: : Utility : : :
| **T1-06** | Declarative | Medium | Manually invoking setAttribute |
: : Attribute : : or removeAttribute within a Lit :
: : Reflection in Lit : : lifecycle method to sync DOM :
: : Components : : properties. :
| **T1-07** | Idempotent | Medium | Firing a tracking event inside |
: : Telemetry : : `updated` purely based on :
: : Reporting in Lit : : conditional presence, without a :
: : Component : : state flag acknowledging it :
: : Lifecycle Hooks : : fired. :
| **T1-08** | Strict Truthiness | High | Relying on optional chaining |
: : Checks for : : length checks to represent empty :
: : Asynchronous Array : : or missing data. :
: : Data : : :
| **T1-09** | Pre-computing | Medium | A helper method called in |
: : Derived State in : : `render()` that parses raw :
: : Lit Lifecycle : : strings into arrays on every :
: : Methods : : invocation. :
| **T1-10** | Returning | Medium | Using an empty template literal |
: : `nothing` for : : or omitting a return when a :
: : Empty Templates in : : condition fails in the template. :
: : Lit : : :
| **T1-11** | Single-Pass | Medium | Assigning values to `@state()` |
: : Initialization of : : or `@property()` fields inside :
: : Reactive : : the `firstUpdated` hook. :
: : Properties : : :
| **T1-12** | Guarding | High | Executing timeout handlers or |
: : Asynchronous State : : asynchronous DOM updates without :
: : Updates : : confirming the component remains :
: : Post-Disconnection : : in the DOM structure. :
| **T1-13** | Use Lit classMap | Medium | Concatenating ternary operators |
: : Directive for : : inside the `class` attribute :
: : Conditional CSS : : string of a Lit HTML template. :
: : Classes : : :
| **T1-14** | Strict Lit State | High | Using the `@property` decorator |
: : Encapsulation : : for variables that represent :
: : : : internal component state (like :
: : : : data lists or loading spinners). :
| **T1-15** | Declarative | Medium | Controlling element visibility |
: : Component : : using CSS class conditionals. :
: : Visibility : : :
: : Toggling : : :
| **T1-16** | Immutable Lit Form | High | Mutating form state fields |
: : State Resets : : directly without triggering a :
: : : : reference change for Lit to :
: : : : detect. :
--------------------------------------------------------------------------------
### Rules
#### T1-01: Separation of Data and UI State Variables
> **Rule:** Never overload core data variables with transient UI status strings.
> You must use dedicated status properties to manage UI state separately from
> the underlying data model.
>
> **What:** Do not overload core data variables with transient UI status strings
> (e.g., 'Loading...', 'Error'). Use dedicated status properties to manage UI
> state separately from the underlying data model.
>
> **Applies To:** Lit components managing asynchronous operations and displaying
> resulting data alongside loading or error states.
>
> **Why:** A `generatedPassword` property was overloaded to temporarily hold
> 'Generating...' or 'Failed to generate'. This caused the associated 'Copy to
> clipboard' component to copy the error or status text instead of a valid
> password. Failing to adhere to this typically results in **Invalid Data
> Copied**.
**Trap 1: Reusing a data property to hold UI loading or error text, which
corrupts the data being passed to child components or the clipboard.**
**Don't:**
```typescript
this._generatedPassword = 'Generating...';
this.restApiService.generatePassword().then(newPassword => {
this._generatedPassword = newPassword ?? 'Failed to generate';
});
```
**Do:**
```typescript
this.status = 'Generating...';
this.restApiService.generatePassword().then(newPassword => {
if (newPassword) {
this.generatedPassword = newPassword;
this.status = undefined;
} else {
this.status = 'Failed to generate';
}
});
```
#### T1-02: Explicit Boolean Properties over Dynamic Slot Detection
> **Rule:** Always control conditional layout wrappers using explicit boolean
> properties mapped to the component API. Never dynamically query the DOM for
> the presence of assigned slots.
>
> **What:** Control the rendering of conditional layout wrappers using explicit
> boolean properties mapped to the component's API, rather than dynamically
> querying the DOM for the presence of assigned slots.
>
> **Applies To:** Lit components featuring conditional layout containers that
> wrap `<slot>` elements (e.g., search bars with optional leading icons).
>
> **Why:** Dynamically checking `hasNamedSlot` by iterating over `this.children`
> was computationally brittle and caused rendering bugs where structural
> elements (like search icons) were accidentally removed or created unintended
> 'ghost spacing'. Failing to adhere to this typically results in **Layout
> Regression / Missing Elements**.
**Trap 1: Inspecting the DOM via `this.children` in Lit element methods to
determine if a named slot element was provided by the parent.**
**Don't:**
```typescript
private hasNamedSlot(name: string): boolean {
return Array.from(this.children).some(
el => el.getAttribute('slot') === name
);
}
render() {
return this.hasNamedSlot('leading-icon') ? html`<div><slot name="leading-icon"></slot></div>` : nothing;
}
```
**Do:**
```typescript
@property({type: Boolean})
showLeadingIcon = false;
render() {
return this.showLeadingIcon ? html`<div><slot name="leading-icon"></slot></div>` : nothing;
}
```
#### T1-03: Declarative Event Listeners in Lit Templates
> **Rule:** Always bind event listeners declaratively directly within the
> component's `render()` template. Never imperatively attach listeners using
> `this.addEventListener` in the constructor.
>
> **What:** Bind event listeners declaratively directly within the component's
> `render()` template using `@event` syntax, rather than imperatively attaching
> them using `this.addEventListener` in the constructor.
>
> **Applies To:** LitElement initialization and user interaction event handling.
>
> **Why:** Imperatively adding listeners via the constructor disconnects the
> logic from the declarative template, risks memory leaks if not cleaned up,
> leaves inline documentation orphaned, and triggers automated code health
> warnings. Failing to adhere to this typically results in **Structural
> Anti-Pattern / Orphaned Context**.
**Trap 1: Attaching DOM event listeners imperatively within the component
constructor.**
**Don't:**
```typescript
constructor() {
super();
// BAD: Imperative binding
this.addEventListener('mousedown', e => this.handleMouseDown(e));
}
```
**Do:**
```html
override render() {
// GOOD: Declarative binding directly in the Lit template
return html`
<div class="menu" @mousedown=${this.handleMenuMouseDown}>
<div class="menu-item">${this.hoverCardText}</div>
</div>
`;
}
View on GitHub