Subskill: Local Lit-HTML Renderer (Pass 2)
This subskill converts consolidated imperative DOM construction helper methods into reactive, declarative Lit-html templates, rendering them locally inside the existing element containers.
1. Declarative Technology Migration
Conserve Abstraction Topology:
- Maintain the existing class hierarchy, helper class boundaries, and logical groups.
- Do not aggressively "flatten" classes or collapse helper abstractions into the main view just to coerce a single master template. Pass 2 should only change the implementation details of rendering (imperative -> declarative) within existing compartments, not the public API or responsibilities of objects.
Determine Render Lifecycle per Layer:
- For UI.Widget classes: Define or update
performUpdate()to execute the main litrendercall, and usethis.requestUpdate()to queue updates. - For Delegate/Helper classes: Identify the custom update hook (e.g.,
update()orscheduleUpdate()) and invokerender()directly inside that method. Do not introduce Widget lifecycle methods (performUpdate) on classes that do not inherit them.
- For UI.Widget classes: Define or update
Import Modern Templating:
Import the
Litrendering system inside the view module:import {html, render, nothing} from '../../ui/lit/lit.js';
Modern Component Mapping:
- MANDATORY: You MUST read and load ui_engineering.md first to understand specific component mappings (e.g., Toolbar vs
devtools-toolbar). Do not assume standard Lit defaults. - If unsure, also consult the automatic migration code
- Prefer Component Built-Ins Over Legacy Boilerplate: Before porting legacy event handlers, comparators, or state variables, check if the modern component handles them natively. Drop host-level workarounds in favor of declarative component attributes.
- MANDATORY: You MUST read and load ui_engineering.md first to understand specific component mappings (e.g., Toolbar vs
Local Modular Renders:
- If the class contains multiple separate legacy container fields (e.g.,
fields generated by
appendFieldorcreateChildon a parent layout), prefer local modular renders (rendering templates individually into each container) to preserve the legacy layout framework. Do not force a monolithic template if doing so violates the existing topology.
- If the class contains multiple separate legacy container fields (e.g.,
fields generated by
Resolve TypeScript Type Friction:
- Legacy DevTools elements may be typed as
Element. Lit'srender()function expectsHTMLElement | DocumentFragment. - When rendering into legacy containers, cast the container using
as HTMLElementor update its class property type definition fromElementtoHTMLElementto satisfy the TypeScript compiler. - Example:
render(this.template(), this.containerField as HTMLElement);
- Legacy DevTools elements may be typed as
Delegate to Unmigrated Rendering Engines:
- Do not attempt to rewrite complex historical subsystems (like Linkifiers or specialized UI utilities) to be "pure Lit" during this pass.
- Capitalize on Lit's ability to interpolate standard
HTMLElementorElementinstances directly. Treat unmigrated utilities as "black boxes" that generate DOM, and embed their output in standard template expressions:html${this.legacyElement}``. - CRITICAL: Before treating any component as "unmigrated," you MUST follow steps in Section 4. You are only allowed to use this escape hatch if no declarative migration instructions exist.
Handle Asynchronous DOM Updates:
- For asynchronous callback-driven updates (e.g., elements updated in a
.then()callback), render the parent container or a placeholder template first. Once the data is retrieved, execute a localrender()inside the callback targeting the specific sub-container.
- For asynchronous callback-driven updates (e.g., elements updated in a
Template Factorization Strategies:
- Prefer
nothing: Use Lit'snothingsentinel instead of empty strings''or empty templates for conditional rendering. - Parameterized Fragment Factories: For repeated UI patterns or
intermediate layout branches, extract them into parameterized helper
functions inside the class scope returning
LitTemplate. This avoids monoliths, preserves readability, and reuses template cache strategies.
- Prefer
Visual Parity, CSS Adaptation, and Accessibility:
- Zero-Tolerance Regression: Screenshot tests are the ground truth for this phase. Any visual diff (above 0%) in the generated screenshots is unacceptable and must be resolved before proceeding.
- CSS Selector Migration: When replacing legacy imperative components/widgets with modern web components (e.g., replacing legacy
DataGridwith<devtools-data-grid>), inspect and update the associated.cssfile. Replace legacy class selectors (.data-grid) with tag selectors (devtools-data-grid). - Strict Tag/Class Parity: Do not change tag types (e.g.,
spantodiv) or drop class names during template translation, as CSS may depend on them. - Explicit Component Variants: Modern Custom Element (e.g.
<devtools-button>) defaults may not match legacy appearance. Explicitly bind.data=${{variant: Buttons.Button.Variant.OUTLINED}}(or appropriate variant) rather than relying on defaults. - Accessibility Preservation: Convert custom accessibility
instrumentation (like
ARIAUtils.markAsAlert()) to native ARIA attributes in the template (e.g.,role="alert",aria-live="polite").
Render Templates Syntax & Code Movement:
- Surround the expressions containing lit template literals with
// clang-format offand// clang-format onto prevent clang-format from corrupting template indentation. - Minimize Code Movement: Avoid moving existing code to a different location in the file. Put the function signature around the existing code to minimize the diff.
- The Gerrit Code Review UI cannot identify moved blocks of unchanged code.
- If blocks of code need to be reordered, create a "prefactoring" change that first extracts the code that needs to be reordered into a named helper functions and pause the migration, waiting user confirmation.
- Surround the expressions containing lit template literals with
Micro-Commit Strategy:
- Do not attempt to convert every helper method and UI component in a single monolithic commit. Break the template extraction into small, self-contained micro-commits (e.g., migrating one helper method, toolbar button, or list item render at a time).
- Verify with build and run tests after each micro-commit to guarantee zero regressions and 100% visual parity before moving to the next compartment.
- Provide the exact list of files and commit message when presenting progress so the change history remains atomic and easy to review.
Wait for confirmation:
- Wait for an explicit confirmation from the user (or Parent Orchestrator Agent) before proceeding to the next step.
🔍 Mental Audit (Internal Self-Correction)
Before reporting back or committing, re-read the instructions and verify:
- ❓ Tag Parity: Did I accidentally change any
spantodiv(or vice versa)? - ❓ Attribute Drop: Did I drop any
aria-*,role, or custom data attributes? - ❓ Clang-Off Boundary: Is the template wrapped in
// clang-format off? - ❓ Build: Does the code compile and do visual tests pass with 0% diff?